Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/clients/tagging/v1/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ func (c client) GetResources(ctx context.Context, job model.DiscoveryJob, region
Namespace: job.Namespace,
Region: region,
Tags: make([]model.Tag, 0, len(resourceTagMapping.Tags)),
Metadata: make(map[string]string),
}

for _, t := range resourceTagMapping.Tags {
Expand Down
19 changes: 13 additions & 6 deletions pkg/clients/tagging/v1/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ var ServiceFilters = map[string]ServiceFilter{
ARN: aws.StringValue(asg.AutoScalingGroupARN),
Namespace: job.Namespace,
Region: region,
Metadata: make(map[string]string),
}

for _, t := range asg.Tags {
Expand Down Expand Up @@ -196,6 +197,7 @@ var ServiceFilters = map[string]ServiceFilter{
ARN: aws.StringValue(ec2Spot.SpotFleetRequestId),
Namespace: job.Namespace,
Region: region,
Metadata: make(map[string]string),
}

for _, t := range ec2Spot.Tags {
Expand Down Expand Up @@ -229,6 +231,7 @@ var ServiceFilters = map[string]ServiceFilter{
ARN: aws.StringValue(ws.Arn),
Namespace: job.Namespace,
Region: region,
Metadata: make(map[string]string),
}

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

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

for _, t := range tgwa.Tags {
Expand Down Expand Up @@ -352,12 +357,13 @@ var ServiceFilters = map[string]ServiceFilter{
// these land in us-east-1 so any protected resource without a region should be added when the job
// is for us-east-1
if protectedResource.Region == region || (protectedResource.Region == "" && region == "us-east-1") {
taggedResource := &model.TaggedResource{
ARN: protectedResourceArn,
Namespace: job.Namespace,
Region: region,
Tags: []model.Tag{{Key: "ProtectionArn", Value: protectionArn}},
}
taggedResource := &model.TaggedResource{
ARN: protectedResourceArn,
Namespace: job.Namespace,
Region: region,
Tags: []model.Tag{{Key: "ProtectionArn", Value: protectionArn}},
Metadata: make(map[string]string),
}
output = append(output, taggedResource)
}
}
Expand All @@ -369,4 +375,5 @@ var ServiceFilters = map[string]ServiceFilter{
return output, nil
},
},

}
1 change: 1 addition & 0 deletions pkg/clients/tagging/v2/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ func (c client) GetResources(ctx context.Context, job model.DiscoveryJob, region
Namespace: job.Namespace,
Region: region,
Tags: make([]model.Tag, 0, len(resourceTagMapping.Tags)),
Metadata: make(map[string]string),
}

for _, t := range resourceTagMapping.Tags {
Expand Down
38 changes: 38 additions & 0 deletions pkg/clients/tagging/v2/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"strings"

"github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/amp"
"github.com/aws/aws-sdk-go-v2/service/apigateway"
"github.com/aws/aws-sdk-go-v2/service/apigatewayv2"
Expand Down Expand Up @@ -382,6 +383,7 @@
Namespace: job.Namespace,
Region: region,
Tags: []model.Tag{{Key: "ProtectionArn", Value: protectionArn}},
Metadata: make(map[string]string),
}
output = append(output, taggedResource)
}
Expand All @@ -391,4 +393,40 @@
return output, nil
},
},
"AWS/DynamoDB": {
// Fetch service-specific metadata for DynamoDB tables
FilterFunc: func(ctx context.Context, client client, inputResources []*model.TaggedResource) ([]*model.TaggedResource, error) {
// Create DynamoDB client
dynamoClient := dynamodb.NewFromConfig(client.config)

Check failure on line 400 in pkg/clients/tagging/v2/filters.go

View workflow job for this annotation

GitHub Actions / lint

client.config undefined (type client has no field or method config) (typecheck)

// Process each DynamoDB table resource
for _, resource := range inputResources {
// Extract table name from ARN: arn:aws:dynamodb:region:account:table/TableName
arnParts := strings.Split(resource.ARN, "/")
if len(arnParts) < 2 {
continue
}
tableName := arnParts[len(arnParts)-1]

// Fetch table description to get service-specific metadata
tableResult, err := dynamoClient.DescribeTable(ctx, &dynamodb.DescribeTableInput{
TableName: &tableName,
})
if err != nil {
// Log warning but don't fail the entire operation
continue
}

// Extract and store service-specific metadata
if tableResult.Table != nil {
// Extract Table Class if available
if tableResult.Table.TableClassSummary != nil {
resource.Metadata["table_class"] = string(tableResult.Table.TableClassSummary.TableClass)
}
}
}

return inputResources, nil
},
},
}
4 changes: 4 additions & 0 deletions pkg/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,10 @@ type TaggedResource struct {

// Tags is a set of tags associated to the resource
Tags []Tag

// Metadata contains service-specific additional information about the resource
// For example: TableClass, BillingMode, InstanceType, State, etc.
Metadata map[string]string
}

// FilterThroughTags returns true if all filterTags match
Expand Down
2 changes: 2 additions & 0 deletions pkg/model/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ func Test_FilterThroughTags(t *testing.T) {
Namespace: "AWS/Service",
Region: "us-east-1",
Tags: tc.resourceTags,
Metadata: make(map[string]string),
}
require.Equal(t, tc.result, res.FilterThroughTags(tc.filterTags))
})
Expand Down Expand Up @@ -257,6 +258,7 @@ func Test_MetricTags(t *testing.T) {
Namespace: "AWS/Service",
Region: "us-east-1",
Tags: tc.resourceTags,
Metadata: make(map[string]string),
}

require.Equal(t, tc.result, res.MetricTags(tc.exportedTags))
Expand Down
43 changes: 39 additions & 4 deletions pkg/promutil/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,30 +68,65 @@ func BuildMetricName(namespace, metricName, statistic string) string {
}

func BuildNamespaceInfoMetrics(tagData []model.TaggedResourceResult, metrics []*PrometheusMetric, observedMetricLabels map[string]model.LabelSet, labelsSnakeCase bool, logger *slog.Logger) ([]*PrometheusMetric, map[string]model.LabelSet) {
// Loop through each AWS service result (e.g., discovery results from any service)
for _, tagResult := range tagData {
// Extract context labels (region, account_id, account_alias, custom_tags) from the scrape context
contextLabels := contextToLabels(tagResult.Context, labelsSnakeCase, logger)

// Loop through each discovered resource (e.g., each table, instance, bucket, etc.)
for _, d := range tagResult.Data {
// Build the metric name: AWS/ServiceName + "info" + "" → "aws_servicename_info"
metricName := BuildMetricName(d.Namespace, "info", "")

promLabels := make(map[string]string, len(d.Tags)+len(contextLabels)+1)
// Pre-allocate map for all labels: resource tags + context labels + metadata + 1 for "name" label
promLabels := make(map[string]string, len(d.Tags)+len(contextLabels)+len(d.Metadata)+1)

// Copy all context labels (region, account_id, etc.) into the prometheus labels map
maps.Copy(promLabels, contextLabels)

// Add the "name" label containing the full ARN of the resource
// Example: "arn:aws:service:region:account:resource/ResourceName"
promLabels["name"] = d.ARN

// Loop through all AWS tags attached to this resource
for _, tag := range d.Tags {
// Convert AWS tag key to valid Prometheus label name (handles special chars, snake_case)
ok, promTag := PromStringTag(tag.Key, labelsSnakeCase)
if !ok {
// Skip invalid tag names that can't be converted to Prometheus labels
logger.Warn("tag name is an invalid prometheus label name", "tag", tag.Key)
continue
}

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

// Loop through all metadata attached to this resource (e.g., service-specific attributes)
for metadataKey, metadataValue := range d.Metadata {
// Convert metadata key to valid Prometheus label name (handles special chars, snake_case)
ok, promKey := PromStringTag(metadataKey, labelsSnakeCase)
if !ok {
// Skip invalid metadata keys that can't be converted to Prometheus labels
logger.Warn("metadata key is an invalid prometheus label name", "key", metadataKey)
continue
}

// Add metadata as labels directly (no prefix needed since they're service-specific)
// Examples: table_class="Standard", instance_type="t3.micro", billing_mode="PAY_PER_REQUEST"
promLabels[promKey] = metadataValue
}

// Track all label names used by this metric for consistency checking later
observedMetricLabels = recordLabelsForMetric(metricName, promLabels, observedMetricLabels)

// Create the final info metric with all labels and value=0 (info metrics are label carriers)
metrics = append(metrics, &PrometheusMetric{
Name: metricName,
Labels: promLabels,
Value: 0,
Name: metricName, // e.g., "aws_dynamodb_info", "aws_ec2_info", etc.
Labels: promLabels, // All the labels we built above
Value: 0, // Info metrics always have value 0
})
}
}
Expand Down
Loading