@ -42,6 +42,8 @@ func (e *AzureMonitorDatasource) ResourceRequest(rw http.ResponseWriter, req *ht
e . Proxy . Do ( rw , req , cli )
}
var subscriptions = map [ string ] string { }
// executeTimeSeriesQuery does the following:
// 1. build the AzureMonitor url and querystring for each query
// 2. executes each query by calling the Azure Monitor API
@ -112,6 +114,7 @@ func (e *AzureMonitorDatasource) buildQueries(logger log.Logger, queries []backe
params . Add ( "region" , azJSONModel . Region )
}
resourceIDs := [ ] string { }
resourceMap := map [ string ] types . AzureMonitorResource { }
if hasOne , resourceGroup , resourceName := hasOneResource ( queryJSONModel ) ; hasOne {
ub := urlBuilder {
ResourceURI : azJSONModel . ResourceURI ,
@ -125,6 +128,7 @@ func (e *AzureMonitorDatasource) buildQueries(logger log.Logger, queries []backe
azureURL = ub . BuildMetricsURL ( )
// POST requests are only supported at the subscription level
filterInBody = false
resourceMap [ ub . buildResourceURI ( ) ] = types . AzureMonitorResource { ResourceGroup : resourceGroup , ResourceName : resourceName }
} else {
for _ , r := range azJSONModel . Resources {
ub := urlBuilder {
@ -134,7 +138,9 @@ func (e *AzureMonitorDatasource) buildQueries(logger log.Logger, queries []backe
MetricNamespace : azJSONModel . MetricNamespace ,
ResourceName : r . ResourceName ,
}
resourceIDs = append ( resourceIDs , fmt . Sprintf ( "Microsoft.ResourceId eq '%s'" , ub . buildResourceURI ( ) ) )
resourceUri := ub . buildResourceURI ( )
resourceMap [ resourceUri ] = r
resourceIDs = append ( resourceIDs , fmt . Sprintf ( "Microsoft.ResourceId eq '%s'" , resourceUri ) )
}
}
@ -180,13 +186,15 @@ func (e *AzureMonitorDatasource) buildQueries(logger log.Logger, queries []backe
}
query := & types . AzureMonitorQuery {
URL : azureURL ,
Target : target ,
Params : params ,
RefID : query . RefID ,
Alias : alias ,
TimeRange : query . TimeRange ,
Dimensions : azJSONModel . DimensionFilters ,
URL : azureURL ,
Target : target ,
Params : params ,
RefID : query . RefID ,
Alias : alias ,
TimeRange : query . TimeRange ,
Dimensions : azJSONModel . DimensionFilters ,
Resources : resourceMap ,
Subscription : queryJSONModel . Subscription ,
}
if filterString != "" {
if filterInBody {
@ -201,6 +209,51 @@ func (e *AzureMonitorDatasource) buildQueries(logger log.Logger, queries []backe
return azureMonitorQueries , nil
}
func retrieveSubscriptionDetails ( e * AzureMonitorDatasource , cli * http . Client , ctx context . Context , logger log . Logger , tracer tracing . Tracer , subscriptionId string , baseUrl string , dsId int64 , orgId int64 ) {
req , err := e . createRequest ( ctx , logger , fmt . Sprintf ( "%s/subscriptions/%s" , baseUrl , subscriptionId ) )
if err != nil {
logger . Error ( "failed to retrieve subscription details for subscription %s: %s" , subscriptionId , err )
}
values := req . URL . Query ( )
values . Add ( "api-version" , "2022-12-01" )
req . URL . RawQuery = values . Encode ( )
ctx , span := tracer . Start ( ctx , "azuremonitor query" )
span . SetAttributes ( "subscription" , subscriptionId , attribute . Key ( "subscription" ) . String ( subscriptionId ) )
span . SetAttributes ( "datasource_id" , dsId , attribute . Key ( "datasource_id" ) . Int64 ( dsId ) )
span . SetAttributes ( "org_id" , orgId , attribute . Key ( "org_id" ) . Int64 ( orgId ) )
defer span . End ( )
tracer . Inject ( ctx , req . Header , span )
logger . Debug ( "AzureMonitor" , "Subscription Details Req" )
res , err := cli . Do ( req )
if err != nil {
logger . Warn ( "failed to request subscription details: %s" , err )
}
defer func ( ) {
if err := res . Body . Close ( ) ; err != nil {
logger . Warn ( "Failed to close response body" , "err" , err )
}
} ( )
body , err := io . ReadAll ( res . Body )
if err != nil {
logger . Warn ( "failed to read response body: %s" , err )
}
if res . StatusCode / 100 != 2 {
logger . Warn ( "request failed, status: %s, error: %s" , res . Status , string ( body ) )
}
var data types . SubscriptionsResponse
err = json . Unmarshal ( body , & data )
if err != nil {
logger . Warn ( "Failed to unmarshal subscription detail response" , "error" , err , "status" , res . Status , "body" , string ( body ) )
}
subscriptions [ data . SubscriptionID ] = data . DisplayName
}
func ( e * AzureMonitorDatasource ) executeQuery ( ctx context . Context , logger log . Logger , query * types . AzureMonitorQuery , dsInfo types . DatasourceInfo , cli * http . Client ,
url string , tracer tracing . Tracer ) backend . DataResponse {
dataResponse := backend . DataResponse { }
@ -253,6 +306,10 @@ func (e *AzureMonitorDatasource) executeQuery(ctx context.Context, logger log.Lo
return dataResponse
}
if _ , ok := subscriptions [ query . Subscription ] ; ! ok {
retrieveSubscriptionDetails ( e , cli , ctx , logger , tracer , query . Subscription , dsInfo . Routes [ "Azure Monitor" ] . URL , dsInfo . DatasourceID , dsInfo . OrgID )
}
dataResponse . Frames , err = e . parseResponse ( data , query , azurePortalUrl )
if err != nil {
dataResponse . Error = err
@ -340,8 +397,9 @@ func (e *AzureMonitorDatasource) parseResponse(amr types.AzureMonitorResponse, q
labels [ "resourceName" ] = resourceName
}
currentResource := query . Resources [ resourceID ]
if query . Alias != "" {
displayName := formatAzureMonitorLegendKey ( query . Alias , resourceNam e ,
displayName := formatAzureMonitorLegendKey ( query . Alias , que ry . Subscription , currentR esource,
amr . Value [ 0 ] . Name . LocalizedValue , "" , "" , amr . Namespace , amr . Value [ 0 ] . ID , labels )
if dataField . Config != nil {
@ -379,7 +437,6 @@ func (e *AzureMonitorDatasource) parseResponse(amr types.AzureMonitorResponse, q
if err != nil {
return nil , err
}
frameWithLink := loganalytics . AddConfigLinks ( * frame , queryUrl , nil )
frames = append ( frames , & frameWithLink )
}
@ -495,12 +552,8 @@ func getQueryUrl(query *types.AzureMonitorQuery, azurePortalUrl, resourceID, res
// formatAzureMonitorLegendKey builds the legend key or timeseries name
// Alias patterns like {{resourcename}} are replaced with the appropriate data values.
func formatAzureMonitorLegendKey ( alias string , resourceName string , metricName string , metadataName string ,
func formatAzureMonitorLegendKey ( alias string , subscriptionId string , resource types . AzureMonitorResource , metricName string , metadataName string ,
metadataValue string , namespace string , seriesID string , labels data . Labels ) string {
startIndex := strings . Index ( seriesID , "/resourceGroups/" ) + 16
endIndex := strings . Index ( seriesID , "/providers" )
resourceGroup := seriesID [ startIndex : endIndex ]
// Could be a collision problem if there were two keys that varied only in case, but I don't think that would happen in azure.
lowerLabels := data . Labels { }
for k , v := range labels {
@ -517,8 +570,20 @@ func formatAzureMonitorLegendKey(alias string, resourceName string, metricName s
metaPartName = strings . Replace ( metaPartName , "}}" , "" , 1 )
metaPartName = strings . ToLower ( strings . TrimSpace ( metaPartName ) )
if metaPartName == "subscriptionid" {
return [ ] byte ( subscriptionId )
}
if metaPartName == "subscription" {
if subscription , ok := subscriptions [ subscriptionId ] ; ok {
return [ ] byte ( subscription )
} else {
return [ ] byte { }
}
}
if metaPartName == "resourcegroup" {
return [ ] byte ( resourceGroup )
return [ ] byte ( resource . Resource Group)
}
if metaPartName == "namespace" {
@ -526,7 +591,7 @@ func formatAzureMonitorLegendKey(alias string, resourceName string, metricName s
}
if metaPartName == "resourcename" {
return [ ] byte ( resourceName )
return [ ] byte ( resource . Resource Name)
}
if metaPartName == "metric" {