Skip to content

Commit 384a40d

Browse files
yasicaryasicar
authored andcommitted
feature-aws-resource-metadata
Signed-off-by: yasicar <[email protected]>
1 parent 6be5f5d commit 384a40d

File tree

7 files changed

+98
-10
lines changed

7 files changed

+98
-10
lines changed

pkg/clients/tagging/v1/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ func (c client) GetResources(ctx context.Context, job model.DiscoveryJob, region
115115
Namespace: job.Namespace,
116116
Region: region,
117117
Tags: make([]model.Tag, 0, len(resourceTagMapping.Tags)),
118+
Metadata: make(map[string]string),
118119
}
119120

120121
for _, t := range resourceTagMapping.Tags {

pkg/clients/tagging/v1/filters.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ var ServiceFilters = map[string]ServiceFilter{
109109
ARN: aws.StringValue(asg.AutoScalingGroupARN),
110110
Namespace: job.Namespace,
111111
Region: region,
112+
Metadata: make(map[string]string),
112113
}
113114

114115
for _, t := range asg.Tags {
@@ -196,6 +197,7 @@ var ServiceFilters = map[string]ServiceFilter{
196197
ARN: aws.StringValue(ec2Spot.SpotFleetRequestId),
197198
Namespace: job.Namespace,
198199
Region: region,
200+
Metadata: make(map[string]string),
199201
}
200202

201203
for _, t := range ec2Spot.Tags {
@@ -229,6 +231,7 @@ var ServiceFilters = map[string]ServiceFilter{
229231
ARN: aws.StringValue(ws.Arn),
230232
Namespace: job.Namespace,
231233
Region: region,
234+
Metadata: make(map[string]string),
232235
}
233236

234237
for key, value := range ws.Tags {
@@ -262,6 +265,7 @@ var ServiceFilters = map[string]ServiceFilter{
262265
ARN: fmt.Sprintf("%s/%s", *gwa.GatewayId, *gwa.GatewayName),
263266
Namespace: job.Namespace,
264267
Region: region,
268+
Metadata: make(map[string]string),
265269
}
266270

267271
tagsRequest := &storagegateway.ListTagsForResourceInput{
@@ -302,6 +306,7 @@ var ServiceFilters = map[string]ServiceFilter{
302306
ARN: fmt.Sprintf("%s/%s", *tgwa.TransitGatewayId, *tgwa.TransitGatewayAttachmentId),
303307
Namespace: job.Namespace,
304308
Region: region,
309+
Metadata: make(map[string]string),
305310
}
306311

307312
for _, t := range tgwa.Tags {
@@ -352,12 +357,13 @@ var ServiceFilters = map[string]ServiceFilter{
352357
// these land in us-east-1 so any protected resource without a region should be added when the job
353358
// is for us-east-1
354359
if protectedResource.Region == region || (protectedResource.Region == "" && region == "us-east-1") {
355-
taggedResource := &model.TaggedResource{
356-
ARN: protectedResourceArn,
357-
Namespace: job.Namespace,
358-
Region: region,
359-
Tags: []model.Tag{{Key: "ProtectionArn", Value: protectionArn}},
360-
}
360+
taggedResource := &model.TaggedResource{
361+
ARN: protectedResourceArn,
362+
Namespace: job.Namespace,
363+
Region: region,
364+
Tags: []model.Tag{{Key: "ProtectionArn", Value: protectionArn}},
365+
Metadata: make(map[string]string),
366+
}
361367
output = append(output, taggedResource)
362368
}
363369
}
@@ -369,4 +375,5 @@ var ServiceFilters = map[string]ServiceFilter{
369375
return output, nil
370376
},
371377
},
378+
372379
}

pkg/clients/tagging/v2/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ func (c client) GetResources(ctx context.Context, job model.DiscoveryJob, region
122122
Namespace: job.Namespace,
123123
Region: region,
124124
Tags: make([]model.Tag, 0, len(resourceTagMapping.Tags)),
125+
Metadata: make(map[string]string),
125126
}
126127

127128
for _, t := range resourceTagMapping.Tags {

pkg/clients/tagging/v2/filters.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"strings"
1919

2020
"github.com/aws/aws-sdk-go-v2/aws/arn"
21+
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
2122
"github.com/aws/aws-sdk-go-v2/service/amp"
2223
"github.com/aws/aws-sdk-go-v2/service/apigateway"
2324
"github.com/aws/aws-sdk-go-v2/service/apigatewayv2"
@@ -382,6 +383,7 @@ var ServiceFilters = map[string]ServiceFilter{
382383
Namespace: job.Namespace,
383384
Region: region,
384385
Tags: []model.Tag{{Key: "ProtectionArn", Value: protectionArn}},
386+
Metadata: make(map[string]string),
385387
}
386388
output = append(output, taggedResource)
387389
}
@@ -391,4 +393,40 @@ var ServiceFilters = map[string]ServiceFilter{
391393
return output, nil
392394
},
393395
},
396+
"AWS/DynamoDB": {
397+
// Fetch service-specific metadata for DynamoDB tables
398+
FilterFunc: func(ctx context.Context, client client, inputResources []*model.TaggedResource) ([]*model.TaggedResource, error) {
399+
// Create DynamoDB client
400+
dynamoClient := dynamodb.NewFromConfig(client.config)
401+
402+
// Process each DynamoDB table resource
403+
for _, resource := range inputResources {
404+
// Extract table name from ARN: arn:aws:dynamodb:region:account:table/TableName
405+
arnParts := strings.Split(resource.ARN, "/")
406+
if len(arnParts) < 2 {
407+
continue
408+
}
409+
tableName := arnParts[len(arnParts)-1]
410+
411+
// Fetch table description to get service-specific metadata
412+
tableResult, err := dynamoClient.DescribeTable(ctx, &dynamodb.DescribeTableInput{
413+
TableName: &tableName,
414+
})
415+
if err != nil {
416+
// Log warning but don't fail the entire operation
417+
continue
418+
}
419+
420+
// Extract and store service-specific metadata
421+
if tableResult.Table != nil {
422+
// Extract Table Class if available
423+
if tableResult.Table.TableClassSummary != nil {
424+
resource.Metadata["table_class"] = string(tableResult.Table.TableClassSummary.TableClass)
425+
}
426+
}
427+
}
428+
429+
return inputResources, nil
430+
},
431+
},
394432
}

pkg/model/model.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,10 @@ type TaggedResource struct {
219219

220220
// Tags is a set of tags associated to the resource
221221
Tags []Tag
222+
223+
// Metadata contains service-specific additional information about the resource
224+
// For example: TableClass, BillingMode, InstanceType, State, etc.
225+
Metadata map[string]string
222226
}
223227

224228
// FilterThroughTags returns true if all filterTags match

pkg/model/model_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ func Test_FilterThroughTags(t *testing.T) {
177177
Namespace: "AWS/Service",
178178
Region: "us-east-1",
179179
Tags: tc.resourceTags,
180+
Metadata: make(map[string]string),
180181
}
181182
require.Equal(t, tc.result, res.FilterThroughTags(tc.filterTags))
182183
})
@@ -257,6 +258,7 @@ func Test_MetricTags(t *testing.T) {
257258
Namespace: "AWS/Service",
258259
Region: "us-east-1",
259260
Tags: tc.resourceTags,
261+
Metadata: make(map[string]string),
260262
}
261263

262264
require.Equal(t, tc.result, res.MetricTags(tc.exportedTags))

pkg/promutil/migrate.go

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,30 +68,65 @@ func BuildMetricName(namespace, metricName, statistic string) string {
6868
}
6969

7070
func BuildNamespaceInfoMetrics(tagData []model.TaggedResourceResult, metrics []*PrometheusMetric, observedMetricLabels map[string]model.LabelSet, labelsSnakeCase bool, logger *slog.Logger) ([]*PrometheusMetric, map[string]model.LabelSet) {
71+
// Loop through each AWS service result (e.g., discovery results from any service)
7172
for _, tagResult := range tagData {
73+
// Extract context labels (region, account_id, account_alias, custom_tags) from the scrape context
7274
contextLabels := contextToLabels(tagResult.Context, labelsSnakeCase, logger)
75+
76+
// Loop through each discovered resource (e.g., each table, instance, bucket, etc.)
7377
for _, d := range tagResult.Data {
78+
// Build the metric name: AWS/ServiceName + "info" + "" → "aws_servicename_info"
7479
metricName := BuildMetricName(d.Namespace, "info", "")
7580

76-
promLabels := make(map[string]string, len(d.Tags)+len(contextLabels)+1)
81+
// Pre-allocate map for all labels: resource tags + context labels + metadata + 1 for "name" label
82+
promLabels := make(map[string]string, len(d.Tags)+len(contextLabels)+len(d.Metadata)+1)
83+
84+
// Copy all context labels (region, account_id, etc.) into the prometheus labels map
7785
maps.Copy(promLabels, contextLabels)
86+
87+
// Add the "name" label containing the full ARN of the resource
88+
// Example: "arn:aws:service:region:account:resource/ResourceName"
7889
promLabels["name"] = d.ARN
90+
91+
// Loop through all AWS tags attached to this resource
7992
for _, tag := range d.Tags {
93+
// Convert AWS tag key to valid Prometheus label name (handles special chars, snake_case)
8094
ok, promTag := PromStringTag(tag.Key, labelsSnakeCase)
8195
if !ok {
96+
// Skip invalid tag names that can't be converted to Prometheus labels
8297
logger.Warn("tag name is an invalid prometheus label name", "tag", tag.Key)
8398
continue
8499
}
85100

101+
// Create label with "tag_" prefix: "Environment" → "tag_Environment"
86102
labelName := "tag_" + promTag
103+
// Set the label value to the AWS tag value
87104
promLabels[labelName] = tag.Value
88105
}
89106

107+
// Loop through all metadata attached to this resource (e.g., service-specific attributes)
108+
for metadataKey, metadataValue := range d.Metadata {
109+
// Convert metadata key to valid Prometheus label name (handles special chars, snake_case)
110+
ok, promKey := PromStringTag(metadataKey, labelsSnakeCase)
111+
if !ok {
112+
// Skip invalid metadata keys that can't be converted to Prometheus labels
113+
logger.Warn("metadata key is an invalid prometheus label name", "key", metadataKey)
114+
continue
115+
}
116+
117+
// Add metadata as labels directly (no prefix needed since they're service-specific)
118+
// Examples: table_class="Standard", instance_type="t3.micro", billing_mode="PAY_PER_REQUEST"
119+
promLabels[promKey] = metadataValue
120+
}
121+
122+
// Track all label names used by this metric for consistency checking later
90123
observedMetricLabels = recordLabelsForMetric(metricName, promLabels, observedMetricLabels)
124+
125+
// Create the final info metric with all labels and value=0 (info metrics are label carriers)
91126
metrics = append(metrics, &PrometheusMetric{
92-
Name: metricName,
93-
Labels: promLabels,
94-
Value: 0,
127+
Name: metricName, // e.g., "aws_dynamodb_info", "aws_ec2_info", etc.
128+
Labels: promLabels, // All the labels we built above
129+
Value: 0, // Info metrics always have value 0
95130
})
96131
}
97132
}

0 commit comments

Comments
 (0)