Skip to content

Commit

Permalink
Release/list by tag (#53)
Browse files Browse the repository at this point in the history
Co-Authored-By: vincentcreusotfx <34456730+vincentcreusotfx@users.noreply.github.com>
  • Loading branch information
vincentcreusotfx authored and brian-brazil committed Apr 15, 2019
1 parent 5d60d33 commit 4411b47
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 17 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ resource_groups:
metrics:
- name: "CPU Credits Consumed"
resource_tags:
- resource_tag_name: "group"
resource_tag_value: "tomonitor"
metrics:
- name: "CPU Credits Consumed"
```

By default, all aggregations are returned (`Total`, `Maximum`, `Average`, `Minimum`). It can be overridden per resource.
Expand All @@ -111,6 +117,16 @@ List of regexps that is matched against the resource name.
Metrics of all matched resources are ignored (defaults to exclude none)
Excludes take precedence over the include filter.

### Resource tag filtering

Resources having a specific tag name and tag value can be filtered:

`resource_tag_name`:
Name of the tag to be filtered against.

`resource_tag_value`:
Value of the tag to be filtered against.

## Prometheus configuration

### Example config
Expand All @@ -122,4 +138,4 @@ scrape_configs:
- job_name: azure
static_configs:
- targets: ['localhost:9276']
```
```
83 changes: 71 additions & 12 deletions azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,15 @@ func (ac *AzureClient) filteredListFromResourceGroup(resourceGroup config.Resour
return filteredResources, nil
}

// Returns resource list filtered by tag name and tag value
func (ac *AzureClient) filteredListByTag(resourceTag config.ResourceTag) ([]string, error) {
resources, err := ac.listByTag(resourceTag.ResourceTagName, resourceTag.ResourceTagValue)
if err != nil {
return nil, err
}
return resources, nil
}

// Returns all resources for given resource group and types
func (ac *AzureClient) listFromResourceGroup(resourceGroup string, resourceTypes []string) ([]string, error) {
apiVersion := "2018-02-01"
Expand All @@ -266,9 +275,61 @@ func (ac *AzureClient) listFromResourceGroup(resourceGroup string, resourceTypes

subscription := fmt.Sprintf("subscriptions/%s", sc.C.Credentials.SubscriptionID)

metricValueEndpoint := fmt.Sprintf("%s/%s/resourceGroups/%s/resources?api-version=%s&$filter=%s", sc.C.ResourceManagerURL, subscription, resourceGroup, apiVersion, filterTypes)
resourcesEndpoint := fmt.Sprintf("%s/%s/resourceGroups/%s/resources?api-version=%s&$filter=%s", sc.C.ResourceManagerURL, subscription, resourceGroup, apiVersion, filterTypes)

req, err := http.NewRequest("GET", metricValueEndpoint, nil)
body, err := getAzureMonitorResponse(resourcesEndpoint)

if err != nil {
return nil, err
}

var data AzureResourceListResponse
err = json.Unmarshal(body, &data)
if err != nil {
return nil, fmt.Errorf("Error unmarshalling response body: %v", err)
}

resources := extractResourceNames(data, subscription)

return resources, nil
}

// Returns all resource with the given couple tagname, tagvalue
func (ac *AzureClient) listByTag(tagName string, tagValue string) ([]string, error) {
apiVersion := "2018-05-01"
securedTagName := secureString(tagName)
securedTagValue := secureString(tagValue)
filterTypes := url.QueryEscape(fmt.Sprintf("tagName eq '%s' and tagValue eq '%s'", securedTagName, securedTagValue))

subscription := fmt.Sprintf("subscriptions/%s", sc.C.Credentials.SubscriptionID)

resourcesEndpoint := fmt.Sprintf("%s/%s/resources?api-version=%s&$filter=%s", sc.C.ResourceManagerURL,subscription, apiVersion, filterTypes)

body, err := getAzureMonitorResponse(resourcesEndpoint)

if err != nil {
return nil, err
}

var data AzureResourceListResponse
err = json.Unmarshal(body, &data)
if err != nil {
return nil, fmt.Errorf("Error unmarshalling response body: %v", err)
}

resources := extractResourceNames(data, subscription)

return resources, nil
}

func secureString(value string) string {
securedValue := strings.Replace(value, "'", "\\'", -1)
return securedValue
}


func getAzureMonitorResponse(azureManagementEndpoint string) ([]byte, error) {
req, err := http.NewRequest("GET", azureManagementEndpoint, nil)
if err != nil {
return nil, fmt.Errorf("Error creating HTTP request: %v", err)
}
Expand All @@ -279,32 +340,30 @@ func (ac *AzureClient) listFromResourceGroup(resourceGroup string, resourceTypes
return nil, fmt.Errorf("Error: %v", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)

if resp.StatusCode != 200 {
return nil, fmt.Errorf("Unable to query resource group API with status code: %d", resp.StatusCode)
return nil, fmt.Errorf("Unable to query API with status code: %d and with body: %s", resp.StatusCode, body)
}

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("Error reading body of response: %v", err)
}
return body, err

var data AzureResourceListResponse
err = json.Unmarshal(body, &data)
if err != nil {
return nil, fmt.Errorf("Error unmarshalling response body: %v", err)
}
}

// Extract resource names from the AzureResourceListResponse
func extractResourceNames(data AzureResourceListResponse, subscription string) []string {
var resources []string

for _, result := range data.Value {
// subscription + leading '/'
subscriptionPrefixLen := len(subscription) + 1

// remove subscription from path to match manually specified ones
resources = append(resources, result.Id[subscriptionPrefixLen:])
}

return resources, nil
return resources
}

// Returns a filtered resource list based on a given resource list and regular expressions from the configuration
Expand Down
18 changes: 14 additions & 4 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import (

// Config - Azure exporter configuration
type Config struct {
// Config - Azure exporter configuration
ActiveDirectoryAuthorityURL string `yaml:"active_directory_authority_url"`
ResourceManagerURL string `yaml:"resource_manager_url"`
Credentials Credentials `yaml:"credentials"`
Targets []Target `yaml:"targets"`
ResourceGroups []ResourceGroup `yaml:"resource_groups"`
Credentials Credentials `yaml:"credentials"`
Targets []Target `yaml:"targets"`
ResourceGroups []ResourceGroup `yaml:"resource_groups"`
ResourceTags []ResourceTag `yaml:"resource_tags"`

// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
Expand Down Expand Up @@ -146,6 +146,16 @@ type ResourceGroup struct {
XXX map[string]interface{} `yaml:",inline"`
}

// ResourceTag selects resources with tag name and tag value
type ResourceTag struct {
ResourceTagName string `yaml:"resource_tag_name"`
ResourceTagValue string `yaml:"resource_tag_value"`
Metrics []Metric `yaml:"metrics"`
Aggregations []string `yaml:"aggregations"`

XXX map[string]interface{} `yaml:",inline"`
}

// Metric defines metric name
type Metric struct {
Name string `yaml:"name"`
Expand Down
21 changes: 21 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"

"github.com/RobustPerception/azure_metrics_exporter/config"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/common/version"
Expand Down Expand Up @@ -136,6 +137,26 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) {
c.collectResource(ch, resource, metricsStr, resourceGroup.Aggregations)
}
}

for _, resourceTag := range sc.C.ResourceTags {
metrics := []string{}
for _, metric := range resourceTag.Metrics {
metrics = append(metrics, metric.Name)
}
metricsStr := strings.Join(metrics, ",")

filteredResources, err := ac.filteredListByTag(resourceTag)
if err != nil {
log.Printf("Failed to get resources for tag name %s, tag value %s: %v",
resourceTag.ResourceTagName, resourceTag.ResourceTagValue, err)
ch <- prometheus.NewInvalidMetric(azureErrorDesc, err)
return
}

for _, resource := range filteredResources {
c.collectResource(ch, resource, metricsStr, resourceTag.Aggregations)
}
}
}

func handler(w http.ResponseWriter, r *http.Request) {
Expand Down

0 comments on commit 4411b47

Please sign in to comment.