[release-12.0.2] Azure: Fix Application Insights metadata requests (#105838)
Azure: Fix Application Insights metadata requests (#105614)
* Fix App insights metadata requests
- Correctly handle error responses
* Fix linting and always log the error message
* More lint
(cherry picked from commit 844c854d75)
Co-authored-by: Andreas Christou <andreas.christou@grafana.com>
This commit is contained in:
co-authored by
Andreas Christou
parent
62c8b3e771
commit
40ee9e10e2
@@ -40,6 +40,71 @@ func filterTablesWithData(tables []types.MetadataTable) []types.MetadataTable {
|
|||||||
return filtered
|
return filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeErrorResponse(rw http.ResponseWriter, statusCode int, message string) error {
|
||||||
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
rw.WriteHeader(statusCode)
|
||||||
|
|
||||||
|
// Log the raw error message
|
||||||
|
backend.Logger.Error(message)
|
||||||
|
|
||||||
|
// Set error response to initial error message
|
||||||
|
errorBody := map[string]string{"error": message}
|
||||||
|
|
||||||
|
// Attempt to locate JSON portion in error message
|
||||||
|
re := regexp.MustCompile(`\{.*\}`)
|
||||||
|
jsonPart := re.FindString(message)
|
||||||
|
if jsonPart != "" {
|
||||||
|
var jsonData map[string]interface{}
|
||||||
|
if unmarshalErr := json.Unmarshal([]byte(jsonPart), &jsonData); unmarshalErr != nil {
|
||||||
|
errorBody["error"] = fmt.Sprintf("Invalid JSON format in error message. Raw error: %s", message)
|
||||||
|
backend.Logger.Error("failed to unmarshal JSON error message", "error", unmarshalErr)
|
||||||
|
} else {
|
||||||
|
// Extract relevant fields for a formatted error message
|
||||||
|
errorType, ok := jsonData["error"].(string)
|
||||||
|
if ok {
|
||||||
|
errorDescription, ok := jsonData["error_description"].(string)
|
||||||
|
if !ok {
|
||||||
|
backend.Logger.Error("unable to convert error_description to string", "rawError", jsonData["error_description"])
|
||||||
|
// Attempt to just format the error as a string
|
||||||
|
errorDescription = fmt.Sprintf("%v", jsonData["error_description"])
|
||||||
|
}
|
||||||
|
if errorType == "" {
|
||||||
|
errorType = "UnknownError"
|
||||||
|
}
|
||||||
|
|
||||||
|
errorBody["error"] = fmt.Sprintf("%s: %s", errorType, errorDescription)
|
||||||
|
} else {
|
||||||
|
nestedError, ok := jsonData["error"].(map[string]interface{})
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
errorBody["error"] = fmt.Sprintf("Invalid JSON format in error message. Raw error: %s", message)
|
||||||
|
backend.Logger.Error("failed to unmarshal JSON error message", "error", unmarshalErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
errorType := nestedError["code"].(string)
|
||||||
|
errorDescription, ok := nestedError["message"].(string)
|
||||||
|
if !ok {
|
||||||
|
backend.Logger.Error("unable to convert error_description to string", "rawError", jsonData["error_description"])
|
||||||
|
// Attempt to just format the error as a string
|
||||||
|
errorDescription = fmt.Sprintf("%v", nestedError["message"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if errorType == "" {
|
||||||
|
errorType = "UnknownError"
|
||||||
|
}
|
||||||
|
errorBody["error"] = fmt.Sprintf("%s: %s", errorType, errorDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonRes, _ := json.Marshal(errorBody)
|
||||||
|
_, err := rw.Write(jsonRes)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to write HTTP response: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *AzureLogAnalyticsDatasource) ResourceRequest(rw http.ResponseWriter, req *http.Request, cli *http.Client) (http.ResponseWriter, error) {
|
func (e *AzureLogAnalyticsDatasource) ResourceRequest(rw http.ResponseWriter, req *http.Request, cli *http.Client) (http.ResponseWriter, error) {
|
||||||
if req.URL.Path == "/usage/basiclogs" {
|
if req.URL.Path == "/usage/basiclogs" {
|
||||||
newUrl := &url.URL{
|
newUrl := &url.URL{
|
||||||
@@ -49,15 +114,21 @@ func (e *AzureLogAnalyticsDatasource) ResourceRequest(rw http.ResponseWriter, re
|
|||||||
}
|
}
|
||||||
return e.GetBasicLogsUsage(req.Context(), newUrl.String(), cli, rw, req.Body)
|
return e.GetBasicLogsUsage(req.Context(), newUrl.String(), cli, rw, req.Body)
|
||||||
} else if strings.Contains(req.URL.Path, "/metadata") {
|
} else if strings.Contains(req.URL.Path, "/metadata") {
|
||||||
|
isAppInsights := strings.Contains(req.URL.Path, "Microsoft.Insights/components")
|
||||||
// Add necessary headers
|
// Add necessary headers
|
||||||
req.Header.Set("Prefer", "metadata-format-v4,exclude-resourcetypes,exclude-customfunctions")
|
if isAppInsights {
|
||||||
|
// metadata-format-v4 is not supported for AppInsights resources
|
||||||
|
req.Header.Set("Prefer", "metadata-format-v3,exclude-resourcetypes,exclude-customfunctions")
|
||||||
|
} else {
|
||||||
|
req.Header.Set("Prefer", "metadata-format-v4,exclude-resourcetypes,exclude-customfunctions")
|
||||||
|
}
|
||||||
queryParams := req.URL.Query()
|
queryParams := req.URL.Query()
|
||||||
// Add necessary query params
|
// Add necessary query params
|
||||||
queryParams.Add("select", "categories,solutions,tables,workspaces")
|
queryParams.Add("select", "categories,solutions,tables,workspaces")
|
||||||
req.URL.RawQuery = queryParams.Encode()
|
req.URL.RawQuery = queryParams.Encode()
|
||||||
resp, err := cli.Do(req)
|
resp, err := cli.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to fetch metadata: %w", err)
|
return nil, writeErrorResponse(rw, resp.StatusCode, fmt.Sprintf("failed to fetch metadata: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -69,23 +140,28 @@ func (e *AzureLogAnalyticsDatasource) ResourceRequest(rw http.ResponseWriter, re
|
|||||||
encoding := resp.Header.Get("Content-Encoding")
|
encoding := resp.Header.Get("Content-Encoding")
|
||||||
body, err := decode(encoding, resp.Body)
|
body, err := decode(encoding, resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read metadata response: %w", err)
|
return nil, writeErrorResponse(rw, resp.StatusCode, fmt.Sprintf("failed to read metadata response: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, fmt.Errorf("metadata API error: %s", string(body))
|
return nil, writeErrorResponse(rw, resp.StatusCode, fmt.Sprintf("metadata API error: %s", string(body)))
|
||||||
}
|
}
|
||||||
|
|
||||||
var metadata types.AzureLogAnalyticsMetadata
|
var metadata types.AzureLogAnalyticsMetadata
|
||||||
err = json.Unmarshal(body, &metadata)
|
err = json.Unmarshal(body, &metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to unmarshal metadata response: %w", err)
|
return nil, writeErrorResponse(rw, http.StatusInternalServerError, fmt.Sprintf("failed to unmarshal metadata response: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppInsights metadata requests do not return the HasData field
|
||||||
|
// So we return all tables
|
||||||
|
if !isAppInsights {
|
||||||
|
metadata.Tables = filterTablesWithData(metadata.Tables)
|
||||||
}
|
}
|
||||||
metadata.Tables = filterTablesWithData(metadata.Tables)
|
|
||||||
|
|
||||||
responseBody, err := json.Marshal(metadata)
|
responseBody, err := json.Marshal(metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to marshal metadata response: %w", err)
|
return nil, writeErrorResponse(rw, http.StatusInternalServerError, fmt.Sprintf("failed to marshal metadata response: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
|||||||
Reference in New Issue
Block a user