diff --git a/extension/entitystore/extension_test.go b/extension/entitystore/extension_test.go index f1571e2c8d..982be1ee47 100644 --- a/extension/entitystore/extension_test.go +++ b/extension/entitystore/extension_test.go @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/mock" "go.uber.org/zap" "go.uber.org/zap/zapcore" + "golang.org/x/exp/maps" "github.com/aws/amazon-cloudwatch-agent/internal/ec2metadataprovider" "github.com/aws/amazon-cloudwatch-agent/plugins/processors/awsentity/entityattributes" @@ -108,15 +109,11 @@ func (m *mockMetadataProvider) InstanceID(ctx context.Context) (string, error) { return "MockInstanceID", nil } -func (m *mockMetadataProvider) InstanceTags(ctx context.Context) (string, error) { +func (m *mockMetadataProvider) InstanceTags(_ context.Context) ([]string, error) { if m.InstanceTagError { - return "", errors.New("an error occurred for instance tag retrieval") + return nil, errors.New("an error occurred for instance tag retrieval") } - var tagsString string - for key, val := range m.Tags { - tagsString += key + "=" + val + "," - } - return tagsString, nil + return maps.Keys(m.Tags), nil } func (m *mockMetadataProvider) ClientIAMRole(ctx context.Context) (string, error) { diff --git a/extension/entitystore/serviceprovider.go b/extension/entitystore/serviceprovider.go index d644f7b6cd..b43fceeea5 100644 --- a/extension/entitystore/serviceprovider.go +++ b/extension/entitystore/serviceprovider.go @@ -40,8 +40,8 @@ const ( ) var ( - //priorityMap is ranking in how we prioritize which IMDS tag determines the service name - priorityMap = []string{SERVICE, APPLICATION, APP} + //serviceProviderPriorities is ranking in how we prioritize which IMDS tag determines the service name + serviceProviderPriorities = []string{SERVICE, APPLICATION, APP} ) type ServiceAttribute struct { @@ -245,26 +245,28 @@ func (s *serviceprovider) scrapeIAMRole() error { return nil } func (s *serviceprovider) scrapeImdsServiceNameAndASG() error { - tags, err := s.metadataProvider.InstanceTags(context.Background()) + tagKeys, err := s.metadataProvider.InstanceTags(context.Background()) if err != nil { s.logger.Debug("Failed to get service name from instance tags. This is likely because instance tag is not enabled for IMDS but will not affect agent functionality.") return err } - // This will check whether the tags contains SERVICE, APPLICATION, APP, in that order. - for _, value := range priorityMap { - if strings.Contains(tags, value) { - serviceName, err := s.metadataProvider.InstanceTagValue(context.Background(), value) + + // This will check whether the tags contains SERVICE, APPLICATION, APP, in that order (case insensitive) + lowerTagKeys := toLowerKeyMap(tagKeys) + for _, potentialServiceProviderKey := range serviceProviderPriorities { + if originalCaseKey, exists := lowerTagKeys[potentialServiceProviderKey]; exists { + serviceName, err := s.metadataProvider.InstanceTagValue(context.Background(), originalCaseKey) if err != nil { continue - } else { - s.mutex.Lock() - s.imdsServiceName = serviceName - s.mutex.Unlock() } + s.mutex.Lock() + s.imdsServiceName = serviceName + s.mutex.Unlock() break } } - if strings.Contains(tags, ec2tagger.Ec2InstanceTagKeyASG) { + // case sensitive + if originalCaseKey := lowerTagKeys[strings.ToLower(ec2tagger.Ec2InstanceTagKeyASG)]; originalCaseKey == ec2tagger.Ec2InstanceTagKeyASG { asg, err := s.metadataProvider.InstanceTagValue(context.Background(), ec2tagger.Ec2InstanceTagKeyASG) if err == nil { s.logger.Debug("AutoScalingGroup retrieved through IMDS") @@ -277,6 +279,7 @@ func (s *serviceprovider) scrapeImdsServiceNameAndASG() error { s.mutex.Unlock() } } + if s.GetIMDSServiceName() == "" { s.logger.Debug("Service name not found through IMDS") } @@ -286,6 +289,14 @@ func (s *serviceprovider) scrapeImdsServiceNameAndASG() error { return nil } +func toLowerKeyMap(values []string) map[string]string { + set := make(map[string]string, len(values)) + for _, v := range values { + set[strings.ToLower(v)] = v + } + return set +} + func newServiceProvider(mode string, region string, ec2Info *EC2Info, metadataProvider ec2metadataprovider.MetadataProvider, providerType ec2ProviderType, ec2Credential *configaws.CredentialConfig, done chan struct{}, logger *zap.Logger) serviceProviderInterface { return &serviceprovider{ mode: mode, diff --git a/extension/entitystore/serviceprovider_test.go b/extension/entitystore/serviceprovider_test.go index 486a73689a..e2c5bcad08 100644 --- a/extension/entitystore/serviceprovider_test.go +++ b/extension/entitystore/serviceprovider_test.go @@ -306,6 +306,16 @@ func Test_serviceprovider_scrapeAndgetImdsServiceNameAndASG(t *testing.T) { metadataProvider: &mockMetadataProvider{InstanceIdentityDocument: mockedInstanceIdentityDoc, Tags: map[string]string{"service": "test-service"}}, wantTagServiceName: "test-service", }, + { + name: "HappyPath_ServiceExistsCaseInsensitive", + metadataProvider: &mockMetadataProvider{InstanceIdentityDocument: mockedInstanceIdentityDoc, Tags: map[string]string{"ServicE": "test-service"}}, + wantTagServiceName: "test-service", + }, + { + name: "ServiceExistsRequiresExactMatch", + metadataProvider: &mockMetadataProvider{InstanceIdentityDocument: mockedInstanceIdentityDoc, Tags: map[string]string{"sservicee": "test-service"}}, + wantTagServiceName: "", + }, { name: "HappyPath_ApplicationExists", metadataProvider: &mockMetadataProvider{InstanceIdentityDocument: mockedInstanceIdentityDoc, Tags: map[string]string{"application": "test-application"}}, @@ -358,6 +368,28 @@ func Test_serviceprovider_scrapeAndgetImdsServiceNameAndASG(t *testing.T) { }}, wantASGName: "", }, + { + name: "AutoScalingGroup case sensitive", + metadataProvider: &mockMetadataProvider{ + InstanceIdentityDocument: mockedInstanceIdentityDoc, + Tags: map[string]string{ + "aws:autoscaling:groupname": tagVal3, + "env": "test-env", + "name": "test-name", + }}, + wantASGName: "", + }, + { + name: "AutoScalingGroup exact match", + metadataProvider: &mockMetadataProvider{ + InstanceIdentityDocument: mockedInstanceIdentityDoc, + Tags: map[string]string{ + "aws:autoscaling:groupnamee": tagVal3, + "env": "test-env", + "name": "test-name", + }}, + wantASGName: "", + }, { name: "Success IMDS tags call with no ASG", metadataProvider: &mockMetadataProvider{InstanceIdentityDocument: mockedInstanceIdentityDoc, Tags: map[string]string{"name": tagVal3}}, diff --git a/internal/ec2metadataprovider/ec2metadataprovider.go b/internal/ec2metadataprovider/ec2metadataprovider.go index 23ece5ad46..5134c1ce5a 100644 --- a/internal/ec2metadataprovider/ec2metadataprovider.go +++ b/internal/ec2metadataprovider/ec2metadataprovider.go @@ -6,6 +6,7 @@ package ec2metadataprovider import ( "context" "log" + "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/client" @@ -20,7 +21,7 @@ type MetadataProvider interface { Get(ctx context.Context) (ec2metadata.EC2InstanceIdentityDocument, error) Hostname(ctx context.Context) (string, error) InstanceID(ctx context.Context) (string, error) - InstanceTags(ctx context.Context) (string, error) + InstanceTags(ctx context.Context) ([]string, error) ClientIAMRole(ctx context.Context) (string, error) InstanceTagValue(ctx context.Context, tagKey string) (string, error) } @@ -67,10 +68,14 @@ func (c *metadataClient) ClientIAMRole(ctx context.Context) (string, error) { }) } -func (c *metadataClient) InstanceTags(ctx context.Context) (string, error) { - return withMetadataFallbackRetry(ctx, c, func(metadataClient *ec2metadata.EC2Metadata) (string, error) { +func (c *metadataClient) InstanceTags(ctx context.Context) ([]string, error) { + tags, err := withMetadataFallbackRetry(ctx, c, func(metadataClient *ec2metadata.EC2Metadata) (string, error) { return metadataClient.GetMetadataWithContext(ctx, "tags/instance") }) + if err != nil { + return nil, err + } + return strings.Fields(tags), nil } func (c *metadataClient) InstanceTagValue(ctx context.Context, tagKey string) (string, error) { diff --git a/plugins/processors/ec2tagger/ec2tagger_test.go b/plugins/processors/ec2tagger/ec2tagger_test.go index f550a75ebc..93642e3c6e 100644 --- a/plugins/processors/ec2tagger/ec2tagger_test.go +++ b/plugins/processors/ec2tagger/ec2tagger_test.go @@ -144,8 +144,8 @@ func (m *mockMetadataProvider) InstanceID(ctx context.Context) (string, error) { return "MockInstanceID", nil } -func (m *mockMetadataProvider) InstanceTags(ctx context.Context) (string, error) { - return "MockInstanceTag", nil +func (m *mockMetadataProvider) InstanceTags(_ context.Context) ([]string, error) { + return []string{"MockInstanceTag"}, nil } func (m *mockMetadataProvider) InstanceTagValue(ctx context.Context, tagKey string) (string, error) {