Skip to content

Commit 3e1f7a1

Browse files
committed
[processor/resourcedetectionprocessor] Support Azure Tags
* Mirrors EC2 regex logic
1 parent 8fd3a47 commit 3e1f7a1

File tree

6 files changed

+126
-16
lines changed

6 files changed

+126
-16
lines changed

internal/metadataproviders/azure/metadata.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,21 @@ func NewProvider() Provider {
3737
}
3838
}
3939

40+
type ComputeTagsListMetadata struct {
41+
Name string `json:"name"`
42+
Value string `json:"value"`
43+
}
44+
4045
// ComputeMetadata is the Azure IMDS compute metadata response format
4146
type ComputeMetadata struct {
42-
Location string `json:"location"`
43-
Name string `json:"name"`
44-
VMID string `json:"vmID"`
45-
VMSize string `json:"vmSize"`
46-
SubscriptionID string `json:"subscriptionID"`
47-
ResourceGroupName string `json:"resourceGroupName"`
48-
VMScaleSetName string `json:"vmScaleSetName"`
47+
Location string `json:"location"`
48+
Name string `json:"name"`
49+
VMID string `json:"vmID"`
50+
VMSize string `json:"vmSize"`
51+
SubscriptionID string `json:"subscriptionID"`
52+
ResourceGroupName string `json:"resourceGroupName"`
53+
VMScaleSetName string `json:"vmScaleSetName"`
54+
TagsList []ComputeTagsListMetadata `json:"tagsList"`
4955
}
5056

5157
// Metadata queries a given endpoint and parses the output to the Azure IMDS format

internal/metadataproviders/azure/metadata_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ func TestQueryEndpointCorrect(t *testing.T) {
5757
VMSize: "vmSize",
5858
SubscriptionID: "subscriptionID",
5959
ResourceGroupName: "resourceGroup",
60+
TagsList: []ComputeTagsListMetadata{
61+
{
62+
Name: "tag1",
63+
Value: "value1",
64+
},
65+
},
6066
}
6167
marshalledMetadata, err := json.Marshal(sentMetadata)
6268
require.NoError(t, err)

processor/resourcedetectionprocessor/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,26 @@ processors:
397397
override: false
398398
```
399399

400+
It also can optionally gather tags from the azure instance that the collector is running on.
401+
402+
Azure custom configuration example:
403+
404+
```yaml
405+
processors:
406+
resourcedetection/azure:
407+
detectors: ["azure"]
408+
azure:
409+
# A list of regex's to match tag keys to add as resource attributes can be specified
410+
tags:
411+
- ^tag1$
412+
- ^tag2$
413+
- ^label.*$
414+
```
415+
416+
Matched tags are added as:
417+
418+
* azure.tags.<tag name>
419+
400420
### Azure AKS
401421

402422
* cloud.provider ("azure")

processor/resourcedetectionprocessor/internal/azure/azure.go

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package azure // import "github.com/open-telemetry/opentelemetry-collector-contr
55

66
import (
77
"context"
8+
"regexp"
89

910
"go.opentelemetry.io/collector/pdata/pcommon"
1011
"go.opentelemetry.io/collector/processor"
@@ -18,25 +19,34 @@ import (
1819

1920
const (
2021
// TypeStr is type of detector.
21-
TypeStr = "azure"
22+
TypeStr = "azure"
23+
tagPrefix = "azure.tag."
2224
)
2325

2426
var _ internal.Detector = (*Detector)(nil)
2527

2628
// Detector is an Azure metadata detector
2729
type Detector struct {
28-
provider azure.Provider
29-
logger *zap.Logger
30-
rb *metadata.ResourceBuilder
30+
provider azure.Provider
31+
tagKeyRegexes []*regexp.Regexp
32+
logger *zap.Logger
33+
rb *metadata.ResourceBuilder
3134
}
3235

3336
// NewDetector creates a new Azure metadata detector
3437
func NewDetector(p processor.CreateSettings, dcfg internal.DetectorConfig) (internal.Detector, error) {
3538
cfg := dcfg.(Config)
39+
40+
tagKeyRegexes, err := compileRegexes(cfg)
41+
if err != nil {
42+
return nil, err
43+
}
44+
3645
return &Detector{
37-
provider: azure.NewProvider(),
38-
logger: p.Logger,
39-
rb: metadata.NewResourceBuilder(cfg.ResourceAttributes),
46+
provider: azure.NewProvider(),
47+
tagKeyRegexes: tagKeyRegexes,
48+
logger: p.Logger,
49+
rb: metadata.NewResourceBuilder(cfg.ResourceAttributes),
4050
}, nil
4151
}
4252

@@ -62,6 +72,47 @@ func (d *Detector) Detect(ctx context.Context) (resource pcommon.Resource, schem
6272
d.rb.SetAzureVMSize(compute.VMSize)
6373
d.rb.SetAzureVMScalesetName(compute.VMScaleSetName)
6474
d.rb.SetAzureResourcegroupName(compute.ResourceGroupName)
75+
res := d.rb.Emit()
76+
77+
if len(d.tagKeyRegexes) != 0 {
78+
tags := matchAzureTags(compute.TagsList, d.tagKeyRegexes)
79+
for key, val := range tags {
80+
res.Attributes().PutStr(tagPrefix+key, val)
81+
}
82+
}
83+
84+
return res, conventions.SchemaURL, nil
85+
}
6586

66-
return d.rb.Emit(), conventions.SchemaURL, nil
87+
func matchAzureTags(azureTags []azure.ComputeTagsListMetadata, tagKeyRegexes []*regexp.Regexp) map[string]string {
88+
tags := make(map[string]string)
89+
for _, tag := range azureTags {
90+
matched := regexArrayMatch(tagKeyRegexes, tag.Name)
91+
if matched {
92+
tags[tag.Name] = tag.Value
93+
}
94+
}
95+
return tags
96+
}
97+
98+
func compileRegexes(cfg Config) ([]*regexp.Regexp, error) {
99+
tagRegexes := make([]*regexp.Regexp, len(cfg.Tags))
100+
for i, elem := range cfg.Tags {
101+
regex, err := regexp.Compile(elem)
102+
if err != nil {
103+
return nil, err
104+
}
105+
tagRegexes[i] = regex
106+
}
107+
return tagRegexes, nil
108+
}
109+
110+
func regexArrayMatch(arr []*regexp.Regexp, val string) bool {
111+
for _, elem := range arr {
112+
matched := elem.MatchString(val)
113+
if matched {
114+
return true
115+
}
116+
}
117+
return false
67118
}

processor/resourcedetectionprocessor/internal/azure/azure_test.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package azure
66
import (
77
"context"
88
"fmt"
9+
"regexp"
910
"testing"
1011

1112
"github.com/stretchr/testify/assert"
@@ -36,9 +37,25 @@ func TestDetectAzureAvailable(t *testing.T) {
3637
SubscriptionID: "subscriptionID",
3738
ResourceGroupName: "resourceGroup",
3839
VMScaleSetName: "myScaleset",
40+
TagsList: []azure.ComputeTagsListMetadata{
41+
{
42+
Name: "tag1key",
43+
Value: "value1",
44+
},
45+
{
46+
Name: "tag2key",
47+
Value: "value2",
48+
},
49+
},
3950
}, nil)
4051

41-
detector := &Detector{provider: mp, rb: metadata.NewResourceBuilder(metadata.DefaultResourceAttributesConfig())}
52+
detector := &Detector{
53+
provider: mp,
54+
tagKeyRegexes: []*regexp.Regexp{
55+
regexp.MustCompile("^tag1key$"),
56+
},
57+
rb: metadata.NewResourceBuilder(metadata.DefaultResourceAttributesConfig()),
58+
}
4259
res, schemaURL, err := detector.Detect(context.Background())
4360
require.NoError(t, err)
4461
assert.Equal(t, conventions.SchemaURL, schemaURL)
@@ -55,9 +72,15 @@ func TestDetectAzureAvailable(t *testing.T) {
5572
"azure.vm.size": "vmSize",
5673
"azure.resourcegroup.name": "resourceGroup",
5774
"azure.vm.scaleset.name": "myScaleset",
75+
"azure.tag.tag1key": "value1",
76+
}
77+
78+
notExpected := map[string]any{
79+
"azure.tag.tag2key": "value2",
5880
}
5981

6082
assert.Equal(t, expected, res.Attributes().AsRaw())
83+
assert.NotEqual(t, notExpected, res.Attributes().AsRaw())
6184
}
6285

6386
func TestDetectError(t *testing.T) {

processor/resourcedetectionprocessor/internal/azure/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ import (
88
)
99

1010
type Config struct {
11+
// Tags is a list of regex's to match ec2 instance tag keys that users want
12+
// to add as resource attributes to processed data
13+
Tags []string `mapstructure:"tags"`
1114
ResourceAttributes metadata.ResourceAttributesConfig `mapstructure:"resource_attributes"`
1215
}
1316

1417
func CreateDefaultConfig() Config {
1518
return Config{
19+
Tags: []string{},
1620
ResourceAttributes: metadata.DefaultResourceAttributesConfig(),
1721
}
1822
}

0 commit comments

Comments
 (0)