Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Case insensitive service provider #1478

Merged
merged 3 commits into from
Dec 23, 2024
Merged
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
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
Loading