Skip to content

Commit

Permalink
Case insensitive service provider (#1478)
Browse files Browse the repository at this point in the history
  • Loading branch information
chadpatel authored Dec 23, 2024
1 parent 43d475d commit 03cbd6b
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 24 deletions.
11 changes: 4 additions & 7 deletions extension/entitystore/extension_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down
35 changes: 23 additions & 12 deletions extension/entitystore/serviceprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
Expand All @@ -277,6 +279,7 @@ func (s *serviceprovider) scrapeImdsServiceNameAndASG() error {
s.mutex.Unlock()
}
}

if s.GetIMDSServiceName() == "" {
s.logger.Debug("Service name not found through IMDS")
}
Expand All @@ -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,
Expand Down
32 changes: 32 additions & 0 deletions extension/entitystore/serviceprovider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"}},
Expand Down Expand Up @@ -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}},
Expand Down
11 changes: 8 additions & 3 deletions internal/ec2metadataprovider/ec2metadataprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
}
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions plugins/processors/ec2tagger/ec2tagger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 03cbd6b

Please sign in to comment.