diff --git a/plugins/processors/awsapplicationsignals/common/types.go b/plugins/processors/awsapplicationsignals/common/types.go index c110f7f519..941d559a93 100644 --- a/plugins/processors/awsapplicationsignals/common/types.go +++ b/plugins/processors/awsapplicationsignals/common/types.go @@ -28,6 +28,14 @@ const ( AttributeHost = "Host" ) +// Platform attribute used as CloudWatch EMF log field. +const ( + MetricAttributeECSCluster = "ECS.Cluster" + MetricAttributeECSTaskId = "ECS.TaskId" + MetricAttributeECSTaskDefinitionFamily = "ECS.TaskDefinitionFamily" + MetricAttributeECSTaskDefinitionRevision = "ECS.TaskDefinitionRevision" +) + // Telemetry attributes used as CloudWatch EMF log fields. const ( MetricAttributeTelemetrySDK = "Telemetry.SDK" diff --git a/plugins/processors/awsapplicationsignals/config/config.go b/plugins/processors/awsapplicationsignals/config/config.go index 19d5faeddf..e79a0a4960 100644 --- a/plugins/processors/awsapplicationsignals/config/config.go +++ b/plugins/processors/awsapplicationsignals/config/config.go @@ -62,9 +62,7 @@ func (cfg *Config) Validate() error { if resolver.Name == "" { return errors.New("name must not be empty for k8s resolver") } - case PlatformEC2, PlatformGeneric: - case PlatformECS: - return errors.New("ecs resolver is not supported") + case PlatformEC2, PlatformECS, PlatformGeneric: default: return errors.New("unknown resolver") } diff --git a/plugins/processors/awsapplicationsignals/config/config_test.go b/plugins/processors/awsapplicationsignals/config/config_test.go index 26a2119350..75762e2145 100644 --- a/plugins/processors/awsapplicationsignals/config/config_test.go +++ b/plugins/processors/awsapplicationsignals/config/config_test.go @@ -10,23 +10,41 @@ import ( ) func TestValidatePassed(t *testing.T) { - config := Config{ - Resolvers: []Resolver{NewEKSResolver("test"), NewGenericResolver("")}, - Rules: nil, - } - assert.Nil(t, config.Validate()) - - config = Config{ - Resolvers: []Resolver{NewK8sResolver("test"), NewGenericResolver("")}, - Rules: nil, + tests := []struct { + name string + resolver Resolver + }{ + { + "testEKS", + NewEKSResolver("test"), + }, + { + "testK8S", + NewK8sResolver("test"), + }, + { + "testEC2", + NewEC2Resolver("test"), + }, + { + "testECS", + NewECSResolver("test"), + }, + { + "testGeneric", + NewGenericResolver("test"), + }, } - assert.Nil(t, config.Validate()) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config := Config{ + Resolvers: []Resolver{tt.resolver}, + Rules: nil, + } + assert.Nil(t, config.Validate()) - config = Config{ - Resolvers: []Resolver{NewEC2Resolver("test"), NewGenericResolver("")}, - Rules: nil, + }) } - assert.Nil(t, config.Validate()) } func TestValidateFailedOnEmptyResolver(t *testing.T) { @@ -38,15 +56,27 @@ func TestValidateFailedOnEmptyResolver(t *testing.T) { } func TestValidateFailedOnEmptyResolverName(t *testing.T) { - config := Config{ - Resolvers: []Resolver{NewEKSResolver("")}, - Rules: nil, + tests := []struct { + name string + resolver Resolver + }{ + { + "testEKS", + NewEKSResolver(""), + }, + { + "testK8S", + NewK8sResolver(""), + }, } - assert.NotNil(t, config.Validate()) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config := Config{ + Resolvers: []Resolver{tt.resolver}, + Rules: nil, + } + assert.NotNil(t, config.Validate()) - config = Config{ - Resolvers: []Resolver{NewK8sResolver("")}, - Rules: nil, + }) } - assert.NotNil(t, config.Validate()) } diff --git a/plugins/processors/awsapplicationsignals/config/resolvers.go b/plugins/processors/awsapplicationsignals/config/resolvers.go index 2ad6e25bf4..b61d76a7d6 100644 --- a/plugins/processors/awsapplicationsignals/config/resolvers.go +++ b/plugins/processors/awsapplicationsignals/config/resolvers.go @@ -42,6 +42,13 @@ func NewEC2Resolver(name string) Resolver { } } +func NewECSResolver(name string) Resolver { + return Resolver{ + Name: name, + Platform: PlatformECS, + } +} + func NewGenericResolver(name string) Resolver { return Resolver{ Name: name, diff --git a/plugins/processors/awsapplicationsignals/internal/attributes/attributes.go b/plugins/processors/awsapplicationsignals/internal/attributes/attributes.go index ffcefee0f2..4bc191eba7 100644 --- a/plugins/processors/awsapplicationsignals/internal/attributes/attributes.go +++ b/plugins/processors/awsapplicationsignals/internal/attributes/attributes.go @@ -19,6 +19,9 @@ const ( AWSRemoteDbUser = "aws.remote.db.user" AWSRemoteResourceCfnPrimaryIdentifier = "aws.remote.resource.cfn.primary.identifier" + AWSECSClusterName = "aws.ecs.cluster.name" + AWSECSTaskID = "aws.ecs.task.id" + // resource detection processor attributes ResourceDetectionHostId = "host.id" ResourceDetectionHostName = "host.name" diff --git a/plugins/processors/awsapplicationsignals/internal/normalizer/attributesnormalizer.go b/plugins/processors/awsapplicationsignals/internal/normalizer/attributesnormalizer.go index 52fade864f..2e5cf826ed 100644 --- a/plugins/processors/awsapplicationsignals/internal/normalizer/attributesnormalizer.go +++ b/plugins/processors/awsapplicationsignals/internal/normalizer/attributesnormalizer.go @@ -42,10 +42,12 @@ var attributesRenamingForMetric = map[string]string{ attr.AWSRemoteResourceType: common.CWMetricAttributeRemoteResourceType, attr.AWSRemoteDbUser: common.MetricAttributeRemoteDbUser, attr.AWSRemoteResourceCfnPrimaryIdentifier: common.MetricAttributeRemoteResourceCfnPrimaryIdentifier, + attr.AWSECSClusterName: common.MetricAttributeECSCluster, + attr.AWSECSTaskID: common.MetricAttributeECSTaskId, } var resourceAttributesRenamingForTrace = map[string]string{ - // these kubernetes resource attributes are set by the openTelemetry operator + // these kubernetes resource attributes are set by the OpenTelemetry operator // see the code references from upstream: // * https://github.com/open-telemetry/opentelemetry-operator/blob/0e39ee77693146e0924da3ca474a0fe14dc30b3a/pkg/instrumentation/sdk.go#L245 // * https://github.com/open-telemetry/opentelemetry-operator/blob/0e39ee77693146e0924da3ca474a0fe14dc30b3a/pkg/instrumentation/sdk.go#L305C43-L305C43 @@ -61,9 +63,9 @@ var attributesRenamingForTrace = map[string]string{ attr.AWSRemoteTarget: attr.AWSRemoteResourceIdentifier, } -var copyMapForMetric = map[string]string{ - // these kubernetes resource attributes are set by the openTelemtry operator - // see the code referecnes from upstream: +var resourceToMetricAttributes = map[string]string{ + // these kubernetes resource attributes are set by the OpenTelemetry operator + // see the code references from upstream: // * https://github.com/open-telemetry/opentelemetry-operator/blob/0e39ee77693146e0924da3ca474a0fe14dc30b3a/pkg/instrumentation/sdk.go#L245 // * https://github.com/open-telemetry/opentelemetry-operator/blob/0e39ee77693146e0924da3ca474a0fe14dc30b3a/pkg/instrumentation/sdk.go#L305C43-L305C43 semconv.AttributeK8SDeploymentName: common.AttributeK8SWorkload, @@ -73,6 +75,8 @@ var copyMapForMetric = map[string]string{ semconv.AttributeK8SCronJobName: common.AttributeK8SWorkload, semconv.AttributeK8SPodName: common.AttributeK8SPod, semconv.AttributeAWSLogGroupNames: "aws.log.group.names", + semconv.AttributeAWSECSTaskRevision: common.MetricAttributeECSTaskDefinitionRevision, + semconv.AttributeAWSECSTaskFamily: common.MetricAttributeECSTaskDefinitionFamily, } const ( @@ -107,7 +111,7 @@ func (n *attributesNormalizer) copyResourceAttributesToAttributes(attributes, re if isTrace { return } - for k, v := range copyMapForMetric { + for k, v := range resourceToMetricAttributes { if resourceAttrValue, ok := resourceAttributes.Get(k); ok { // print some debug info when an attribute value is overwritten if originalAttrValue, ok := attributes.Get(k); ok { diff --git a/plugins/processors/awsapplicationsignals/internal/normalizer/attributesnormalizer_test.go b/plugins/processors/awsapplicationsignals/internal/normalizer/attributesnormalizer_test.go index 017e91cc86..01cb5e2b33 100644 --- a/plugins/processors/awsapplicationsignals/internal/normalizer/attributesnormalizer_test.go +++ b/plugins/processors/awsapplicationsignals/internal/normalizer/attributesnormalizer_test.go @@ -86,7 +86,7 @@ func TestCopyResourceAttributesToAttributes(t *testing.T) { // Create a pcommon.Map for resourceAttributes with some attributes resourceAttributes := pcommon.NewMap() - for resourceAttrKey, attrKey := range copyMapForMetric { + for resourceAttrKey, attrKey := range resourceToMetricAttributes { resourceAttributes.PutStr(resourceAttrKey, attrKey+"-value") } resourceAttributes.PutStr("host.id", "i-01ef7d37f42caa168") @@ -98,7 +98,7 @@ func TestCopyResourceAttributesToAttributes(t *testing.T) { normalizer.copyResourceAttributesToAttributes(attributes, resourceAttributes, false) // Check that the attribute has been copied correctly - for _, attrKey := range copyMapForMetric { + for _, attrKey := range resourceToMetricAttributes { if value, ok := attributes.Get(attrKey); !ok || value.AsString() != attrKey+"-value" { t.Errorf("Attribute was not copied correctly: got %v, want %v", value.AsString(), attrKey+"-value") } diff --git a/plugins/processors/awsapplicationsignals/internal/resolver/attributesresolver.go b/plugins/processors/awsapplicationsignals/internal/resolver/attributesresolver.go index cab4106676..7e57a2c388 100644 --- a/plugins/processors/awsapplicationsignals/internal/resolver/attributesresolver.go +++ b/plugins/processors/awsapplicationsignals/internal/resolver/attributesresolver.go @@ -7,7 +7,6 @@ import ( "context" "errors" "fmt" - "strings" "go.opentelemetry.io/collector/pdata/pcommon" semconv "go.opentelemetry.io/collector/semconv/v1.22.0" @@ -16,7 +15,6 @@ import ( "github.com/aws/amazon-cloudwatch-agent/plugins/processors/awsapplicationsignals/common" appsignalsconfig "github.com/aws/amazon-cloudwatch-agent/plugins/processors/awsapplicationsignals/config" attr "github.com/aws/amazon-cloudwatch-agent/plugins/processors/awsapplicationsignals/internal/attributes" - "github.com/aws/amazon-cloudwatch-agent/translator/util/ecsutil" ) const ( @@ -25,6 +23,7 @@ const ( AttributePlatformGeneric = "Generic" AttributePlatformEC2 = "AWS::EC2" AttributePlatformEKS = "AWS::EKS" + AttributePlatformECS = "AWS::ECS" AttributePlatformK8S = "K8s" ) @@ -59,12 +58,10 @@ func NewAttributesResolver(resolvers []appsignalsconfig.Resolver, logger *zap.Lo subResolvers = append(subResolvers, getKubernetesResolver(resolver.Platform, resolver.Name, logger), newKubernetesResourceAttributesResolver(resolver.Platform, resolver.Name)) case appsignalsconfig.PlatformEC2: subResolvers = append(subResolvers, newResourceAttributesResolver(resolver.Platform, AttributePlatformEC2, DefaultInheritedAttributes)) + case appsignalsconfig.PlatformECS: + subResolvers = append(subResolvers, newECSResourceAttributesResolver(resolver.Platform, resolver.Name)) default: - if ecsutil.GetECSUtilSingleton().IsECS() { - subResolvers = append(subResolvers, newResourceAttributesResolver(appsignalsconfig.PlatformECS, AttributePlatformGeneric, DefaultInheritedAttributes)) - } else { - subResolvers = append(subResolvers, newResourceAttributesResolver(resolver.Platform, AttributePlatformGeneric, GenericInheritedAttributes)) - } + subResolvers = append(subResolvers, newResourceAttributesResolver(resolver.Platform, AttributePlatformGeneric, GenericInheritedAttributes)) } } return &attributesResolver{ @@ -121,40 +118,15 @@ func getLocalEnvironment(attributes, resourceAttributes pcommon.Map, defaultEnvP if val, found := resourceAttributes.Get(attr.AWSHostedInEnvironment); found { return val.Str() } - if defaultEnvPrefix == appsignalsconfig.PlatformECS { - if clusterName, _ := getECSClusterName(resourceAttributes); clusterName != "" { - return getDefaultEnvironment(defaultEnvPrefix, clusterName) - } - if clusterName := ecsutil.GetECSUtilSingleton().Cluster; clusterName != "" { - return getDefaultEnvironment(defaultEnvPrefix, clusterName) - } - } else if defaultEnvPrefix == appsignalsconfig.PlatformEC2 { + if defaultEnvPrefix == appsignalsconfig.PlatformEC2 { if asgAttr, found := resourceAttributes.Get(attr.ResourceDetectionASG); found { - return getDefaultEnvironment(defaultEnvPrefix, asgAttr.Str()) - } - } - return getDefaultEnvironment(defaultEnvPrefix, AttributeEnvironmentDefault) -} - -func getECSClusterName(resourceAttributes pcommon.Map) (string, bool) { - if clusterAttr, ok := resourceAttributes.Get(semconv.AttributeAWSECSClusterARN); ok { - parts := strings.Split(clusterAttr.Str(), "/") - clusterName := parts[len(parts)-1] - return clusterName, true - } else if taskAttr, ok := resourceAttributes.Get(semconv.AttributeAWSECSTaskARN); ok { - parts := strings.SplitAfterN(taskAttr.Str(), ":task/", 2) - if len(parts) == 2 { - taskParts := strings.Split(parts[1], "/") - // cluster name in ARN - if len(taskParts) == 2 { - return taskParts[0], true - } + return generateLocalEnvironment(defaultEnvPrefix, asgAttr.Str()) } } - return "", false + return generateLocalEnvironment(defaultEnvPrefix, AttributeEnvironmentDefault) } -func getDefaultEnvironment(platformCode, val string) string { +func generateLocalEnvironment(platformCode, val string) string { return fmt.Sprintf("%s:%s", platformCode, val) } diff --git a/plugins/processors/awsapplicationsignals/internal/resolver/attributesresolver_test.go b/plugins/processors/awsapplicationsignals/internal/resolver/attributesresolver_test.go index cf8fd05f57..fd055f1489 100644 --- a/plugins/processors/awsapplicationsignals/internal/resolver/attributesresolver_test.go +++ b/plugins/processors/awsapplicationsignals/internal/resolver/attributesresolver_test.go @@ -75,28 +75,6 @@ func TestResourceAttributesResolverWithNoConfiguredName(t *testing.T) { } } -func TestResourceAttributesResolverWithECSClusterName(t *testing.T) { - resolver := resourceAttributesResolver{ - defaultEnvPrefix: "ecs", - platformType: "Generic", - attributeMap: DefaultInheritedAttributes, - } - - attributes := pcommon.NewMap() - resourceAttributes := pcommon.NewMap() - resourceAttributes.PutStr(semconv.AttributeAWSECSTaskARN, "arn:aws:ecs:us-west-1:123456789123:task/my-cluster/10838bed-421f-43ef-870a-f43feacbbb5b") - - resolver.Process(attributes, resourceAttributes) - - attribute, ok := attributes.Get(common.AttributePlatformType) - assert.True(t, ok) - assert.Equal(t, "Generic", attribute.Str()) - - attribute, ok = attributes.Get(attr.AWSLocalEnvironment) - assert.True(t, ok) - assert.Equal(t, "ecs:my-cluster", attribute.Str()) -} - func TestResourceAttributesResolverWithOnEC2WithASG(t *testing.T) { logger, _ := zap.NewDevelopment() attributesResolver := NewAttributesResolver([]config.Resolver{config.NewEC2Resolver("")}, logger) @@ -226,22 +204,3 @@ func TestAttributesResolver_Stop(t *testing.T) { mockSubResolver1.AssertExpectations(t) mockSubResolver2.AssertExpectations(t) } - -func TestGetClusterName(t *testing.T) { - resourceAttributes := pcommon.NewMap() - resourceAttributes.PutStr(semconv.AttributeAWSECSClusterARN, "arn:aws:ecs:us-west-2:123456789123:cluster/my-cluster") - clusterName, ok := getECSClusterName(resourceAttributes) - assert.True(t, ok) - assert.Equal(t, "my-cluster", clusterName) - - resourceAttributes = pcommon.NewMap() - resourceAttributes.PutStr(semconv.AttributeAWSECSTaskARN, "arn:aws:ecs:us-west-1:123456789123:task/10838bed-421f-43ef-870a-f43feacbbb5b") - _, ok = getECSClusterName(resourceAttributes) - assert.False(t, ok) - - resourceAttributes = pcommon.NewMap() - resourceAttributes.PutStr(semconv.AttributeAWSECSTaskARN, "arn:aws:ecs:us-west-1:123456789123:task/my-cluster/10838bed-421f-43ef-870a-f43feacbbb5b") - clusterName, ok = getECSClusterName(resourceAttributes) - assert.True(t, ok) - assert.Equal(t, "my-cluster", clusterName) -} diff --git a/plugins/processors/awsapplicationsignals/internal/resolver/ecs.go b/plugins/processors/awsapplicationsignals/internal/resolver/ecs.go new file mode 100644 index 0000000000..845d7ca408 --- /dev/null +++ b/plugins/processors/awsapplicationsignals/internal/resolver/ecs.go @@ -0,0 +1,105 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +package resolver + +import ( + "context" + "strings" + + "go.opentelemetry.io/collector/pdata/pcommon" + semconv "go.opentelemetry.io/collector/semconv/v1.22.0" + + "github.com/aws/amazon-cloudwatch-agent/plugins/processors/awsapplicationsignals/common" + attr "github.com/aws/amazon-cloudwatch-agent/plugins/processors/awsapplicationsignals/internal/attributes" + "github.com/aws/amazon-cloudwatch-agent/translator/util/ecsutil" +) + +type ecsResourceAttributesResolver struct { + resourceAttributesResolver + hostIn string +} + +func (e *ecsResourceAttributesResolver) Process(attributes, resourceAttributes pcommon.Map) error { + for attrKey, mappingKey := range e.attributeMap { + if val, ok := resourceAttributes.Get(attrKey); ok { + attributes.PutStr(mappingKey, val.Str()) + } + } + + clusterName, taskId := getECSResourcesFromResourceAttributes(resourceAttributes) + if clusterName == "" { + clusterName = ecsutil.GetECSUtilSingleton().Cluster + } + + attributes.PutStr(common.AttributePlatformType, e.platformType) + attributes.PutStr(attr.AWSLocalEnvironment, e.getLocalEnvironment(attributes, resourceAttributes, clusterName)) + attributes.PutStr(attr.AWSECSClusterName, clusterName) + if taskId != "" { + attributes.PutStr(attr.AWSECSTaskID, taskId) + } + return nil +} + +// getLocalEnvironment determines the environment based on the following priority: +// 1. aws.local.environment (from deployment.environment) +// 2. aws.hostedin.environment (deprecated soon) +// 3. hosted_in (user-specified) +// 4. aws.ecs.cluster.arn (auto-detected) +// 5. aws.ecs.task.arn (auto-detected) +// 6. Cluster name from CWA (auto-detected) +// 7. Hardcoded `default` +func (e *ecsResourceAttributesResolver) getLocalEnvironment(attributes pcommon.Map, resourceAttributes pcommon.Map, clusterName string) string { + if val, ok := attributes.Get(attr.AWSLocalEnvironment); ok { + return val.Str() + } + if val, found := resourceAttributes.Get(attr.AWSHostedInEnvironment); found { + return val.Str() + } + if e.hostIn != "" { + return generateLocalEnvironment(e.defaultEnvPrefix, e.hostIn) + } + if clusterName != "" { + return generateLocalEnvironment(e.defaultEnvPrefix, clusterName) + } + return generateLocalEnvironment(e.defaultEnvPrefix, AttributeEnvironmentDefault) +} + +func (e *ecsResourceAttributesResolver) Stop(ctx context.Context) error { + return nil +} + +func newECSResourceAttributesResolver(defaultEnvPrefix string, hostIn string) *ecsResourceAttributesResolver { + return &ecsResourceAttributesResolver{ + resourceAttributesResolver: resourceAttributesResolver{ + defaultEnvPrefix: defaultEnvPrefix, + platformType: AttributePlatformECS, + attributeMap: DefaultInheritedAttributes, + }, + hostIn: hostIn, + } +} + +func getECSResourcesFromResourceAttributes(resourceAttributes pcommon.Map) (clusterName, taskId string) { + if clusterAttr, ok := resourceAttributes.Get(semconv.AttributeAWSECSClusterARN); ok { + parts := strings.Split(clusterAttr.Str(), "/") + clusterName = parts[len(parts)-1] + } + if taskAttr, ok := resourceAttributes.Get(semconv.AttributeAWSECSTaskARN); ok { + parts := strings.SplitAfterN(taskAttr.Str(), ":task/", 2) + if len(parts) == 2 { + taskParts := strings.Split(parts[1], "/") + // New Task ARN format "task/cluster-name/task-id". + if len(taskParts) == 2 { + taskId = taskParts[1] + if clusterName == "" { + clusterName = taskParts[0] + } + } else if len(taskParts) == 1 { + // Legacy Task ARN format "task/task-id". + taskId = taskParts[0] + } + } + } + return +} diff --git a/plugins/processors/awsapplicationsignals/internal/resolver/ecs_test.go b/plugins/processors/awsapplicationsignals/internal/resolver/ecs_test.go new file mode 100644 index 0000000000..6deac1ded2 --- /dev/null +++ b/plugins/processors/awsapplicationsignals/internal/resolver/ecs_test.go @@ -0,0 +1,105 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT + +package resolver + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/pdata/pcommon" + semconv "go.opentelemetry.io/collector/semconv/v1.22.0" + + "github.com/aws/amazon-cloudwatch-agent/plugins/processors/awsapplicationsignals/common" + appsignalsconfig "github.com/aws/amazon-cloudwatch-agent/plugins/processors/awsapplicationsignals/config" + attr "github.com/aws/amazon-cloudwatch-agent/plugins/processors/awsapplicationsignals/internal/attributes" + "github.com/aws/amazon-cloudwatch-agent/translator/util/ecsutil" +) + +func TestResourceAttributesResolverWithECSClusterName(t *testing.T) { + testCases := []struct { + name string + hostIn string + ecsTaskArn string + autoDetectedClusterName string + expectedClusterName string + expectedEnvironmentName string + }{ + { + name: "testECSClusterFromTaskArn", + hostIn: "", + ecsTaskArn: "arn:aws:ecs:us-west-1:123456789123:task/my-cluster/10838bed-421f-43ef-870a-f43feacbbb5b", + expectedClusterName: "my-cluster", + expectedEnvironmentName: "ecs:my-cluster", + }, + { + name: "testECSClusterFromHostIn", + hostIn: "host-in", + ecsTaskArn: "arn:aws:ecs:us-west-1:123456789123:task/my-cluster/10838bed-421f-43ef-870a-f43feacbbb5b", + expectedClusterName: "my-cluster", + expectedEnvironmentName: "ecs:host-in", + }, + { + name: "testECSClusterFromECSUtil", + hostIn: "", + ecsTaskArn: "", + autoDetectedClusterName: "my-cluster", + expectedClusterName: "my-cluster", + expectedEnvironmentName: "ecs:my-cluster", + }, + { + name: "testECSClusterDefault", + hostIn: "", + ecsTaskArn: "", + autoDetectedClusterName: "", + expectedClusterName: "", + expectedEnvironmentName: "ecs:default", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ecsutil.GetECSUtilSingleton().Cluster = tc.autoDetectedClusterName + resolver := newECSResourceAttributesResolver(appsignalsconfig.PlatformECS, tc.hostIn) + + attributes := pcommon.NewMap() + resourceAttributes := pcommon.NewMap() + resourceAttributes.PutStr(semconv.AttributeAWSECSTaskARN, tc.ecsTaskArn) + + resolver.Process(attributes, resourceAttributes) + + attribute, ok := attributes.Get(common.AttributePlatformType) + assert.True(t, ok) + assert.Equal(t, AttributePlatformECS, attribute.Str()) + + attribute, ok = attributes.Get(attr.AWSECSClusterName) + assert.True(t, ok) + assert.Equal(t, tc.expectedClusterName, attribute.Str()) + + attribute, ok = attributes.Get(attr.AWSLocalEnvironment) + assert.True(t, ok) + assert.Equal(t, tc.expectedEnvironmentName, attribute.Str()) + }) + } + ecsutil.GetECSUtilSingleton().Cluster = "" +} + +func TestGetClusterName(t *testing.T) { + resourceAttributes := pcommon.NewMap() + resourceAttributes.PutStr(semconv.AttributeAWSECSClusterARN, "arn:aws:ecs:us-west-2:123456789123:cluster/my-cluster") + clusterName, taskId := getECSResourcesFromResourceAttributes(resourceAttributes) + assert.Equal(t, "my-cluster", clusterName) + assert.Equal(t, "", taskId) + + resourceAttributes = pcommon.NewMap() + resourceAttributes.PutStr(semconv.AttributeAWSECSTaskARN, "arn:aws:ecs:us-west-1:123456789123:task/10838bedacbbb5b") + clusterName, taskId = getECSResourcesFromResourceAttributes(resourceAttributes) + assert.Equal(t, "", clusterName) + assert.Equal(t, "10838bedacbbb5b", taskId) + + resourceAttributes = pcommon.NewMap() + resourceAttributes.PutStr(semconv.AttributeAWSECSTaskARN, "arn:aws:ecs:us-west-1:123456789123:task/my-cluster/10838bedacbbb5b") + clusterName, taskId = getECSResourcesFromResourceAttributes(resourceAttributes) + assert.Equal(t, "my-cluster", clusterName) + assert.Equal(t, "10838bedacbbb5b", taskId) +} diff --git a/plugins/processors/awsapplicationsignals/internal/resolver/kubernetes.go b/plugins/processors/awsapplicationsignals/internal/resolver/kubernetes.go index b3c4c44fd2..753af4e60e 100644 --- a/plugins/processors/awsapplicationsignals/internal/resolver/kubernetes.go +++ b/plugins/processors/awsapplicationsignals/internal/resolver/kubernetes.go @@ -614,7 +614,7 @@ func (h *kubernetesResourceAttributesResolver) Process(attributes, resourceAttri } if val, ok := attributes.Get(attr.AWSLocalEnvironment); !ok { - env := getDefaultEnvironment(h.platformCode, h.clusterName+"/"+namespace) + env := generateLocalEnvironment(h.platformCode, h.clusterName+"/"+namespace) attributes.PutStr(attr.AWSLocalEnvironment, env) } else { attributes.PutStr(attr.AWSLocalEnvironment, val.Str()) diff --git a/translator/tocwconfig/sampleConfig/appsignals_and_ecs_config.conf b/translator/tocwconfig/sampleConfig/appsignals_and_ecs_config.conf new file mode 100644 index 0000000000..7c47acbc9e --- /dev/null +++ b/translator/tocwconfig/sampleConfig/appsignals_and_ecs_config.conf @@ -0,0 +1,27 @@ +[agent] + collection_jitter = "0s" + debug = false + flush_interval = "1s" + flush_jitter = "0s" + hostname = "host_name_from_env" + interval = "60s" + logfile = "" + logtarget = "lumberjack" + metric_batch_size = 1000 + metric_buffer_limit = 10000 + omit_hostname = false + precision = "" + quiet = false + round_interval = false + +[inputs] + +[outputs] + + [[outputs.cloudwatchlogs]] + endpoint_override = "https://fake_endpoint" + force_flush_interval = "5s" + log_stream_name = "arn_aws_ecs_us-east-1_account_id_task/task_id" + region = "us-east-1" + +[processors] diff --git a/translator/tocwconfig/sampleConfig/appsignals_and_ecs_config.json b/translator/tocwconfig/sampleConfig/appsignals_and_ecs_config.json new file mode 100644 index 0000000000..1979d4bf77 --- /dev/null +++ b/translator/tocwconfig/sampleConfig/appsignals_and_ecs_config.json @@ -0,0 +1,26 @@ +{ + "agent": { + "region": "us-east-1" + }, + "logs": { + "metrics_collected": { + "application_signals": { + "tls": { + "cert_file": "path/to/cert.crt", + "key_file": "path/to/key.key" + }, + "limiter": { + "log_dropped_metrics": true, + "rotation_interval": "10m" + } + } + }, + "force_flush_interval": 5, + "endpoint_override":"https://fake_endpoint" + }, + "traces": { + "traces_collected": { + "application_signals": {} + } + } +} \ No newline at end of file diff --git a/translator/tocwconfig/sampleConfig/appsignals_and_ecs_config.yaml b/translator/tocwconfig/sampleConfig/appsignals_and_ecs_config.yaml new file mode 100644 index 0000000000..aee08171cc --- /dev/null +++ b/translator/tocwconfig/sampleConfig/appsignals_and_ecs_config.yaml @@ -0,0 +1,523 @@ +exporters: + awsemf/application_signals: + certificate_file_path: '' + detailed_metrics: false + dimension_rollup_option: NoDimensionRollup + disable_metric_extraction: false + eks_fargate_container_insights_enabled: false + endpoint: 'https://fake_endpoint' + enhanced_container_insights: false + imds_retries: 1 + local_mode: false + log_group_name: /aws/application-signals/data + log_retention: 0 + log_stream_name: '' + max_retries: 2 + metric_declarations: + - dimensions: + - - Environment + - Operation + - Service + - - Environment + - Service + label_matchers: + - label_names: + - Telemetry.Source + regex: ^(ServerSpan|LocalRootSpan)$ + separator: ; + metric_name_selectors: + - Latency + - Fault + - Error + - dimensions: + - - Environment + - Operation + - RemoteOperation + - RemoteResourceIdentifier + - RemoteResourceType + - RemoteService + - Service + - - Environment + - Operation + - RemoteOperation + - RemoteService + - Service + - - Environment + - RemoteService + - Service + - - Environment + - RemoteOperation + - RemoteResourceIdentifier + - RemoteResourceType + - RemoteService + - Service + - - Environment + - RemoteOperation + - RemoteService + - Service + - - Environment + - RemoteResourceIdentifier + - RemoteResourceType + - RemoteService + - Service + - - RemoteResourceIdentifier + - RemoteResourceType + - RemoteService + - - RemoteService + label_matchers: + - label_names: + - Telemetry.Source + regex: ^(ClientSpan|ProducerSpan|ConsumerSpan)$ + separator: ; + metric_name_selectors: + - Latency + - Fault + - Error + middleware: agenthealth/logs + namespace: ApplicationSignals + no_verify_ssl: false + num_workers: 8 + output_destination: cloudwatch + profile: '' + proxy_address: '' + region: us-east-1 + request_timeout_seconds: 30 + resource_arn: '' + resource_to_telemetry_conversion: + enabled: false + retain_initial_value_of_delta_metric: false + role_arn: '' + version: '1' + awsxray/application_signals: + certificate_file_path: '' + endpoint: '' + imds_retries: 1 + index_all_attributes: false + indexed_attributes: + - aws.local.service + - aws.local.operation + - aws.local.environment + - aws.remote.service + - aws.remote.operation + - aws.remote.environment + - aws.remote.resource.identifier + - aws.remote.resource.type + local_mode: false + max_retries: 2 + middleware: agenthealth/traces + no_verify_ssl: false + num_workers: 8 + profile: '' + proxy_address: '' + region: us-east-1 + request_timeout_seconds: 30 + resource_arn: '' + role_arn: '' + telemetry: + enabled: true + include_metadata: true +extensions: + agenthealth/logs: + is_usage_data_enabled: true + stats: + operations: + - PutLogEvents + usage_flags: + mode: EC2 + region_type: ACJ + agenthealth/traces: + is_usage_data_enabled: true + stats: + operations: + - PutTraceSegments + usage_flags: + mode: EC2 + region_type: ACJ + awsproxy/application_signals: + aws_endpoint: '' + dialer: + timeout: 0s + certificate_file_path: '' + endpoint: '0.0.0.0:2000' + imds_retries: 1 + local_mode: false + profile: '' + proxy_address: '' + region: us-east-1 + service_name: '' + role_arn: '' +processors: + awsapplicationsignals: + limiter: + disabled: false + drop_threshold: 500 + garbage_collection_interval: 10m0s + log_dropped_metrics: true + rotation_interval: 10m0s + resolvers: + - name: '' + platform: ecs + resourcedetection: + aks: + resource_attributes: + cloud.platform: + enabled: true + cloud.provider: + enabled: true + k8s.cluster.name: + enabled: false + azure: + resource_attributes: + azure.resourcegroup.name: + enabled: true + azure.vm.name: + enabled: true + azure.vm.scaleset.name: + enabled: true + azure.vm.size: + enabled: true + cloud.account.id: + enabled: true + cloud.platform: + enabled: true + cloud.provider: + enabled: true + cloud.region: + enabled: true + host.id: + enabled: true + host.name: + enabled: true + tags: [] + compression: '' + consul: + address: '' + datacenter: '' + namespace: '' + resource_attributes: + cloud.region: + enabled: true + host.id: + enabled: true + host.name: + enabled: true + token_file: '' + detectors: + - env + - ecs + - ec2 + disable_keep_alives: false + docker: + resource_attributes: + host.name: + enabled: true + os.type: + enabled: true + ec2: + resource_attributes: + cloud.account.id: + enabled: true + cloud.availability_zone: + enabled: true + cloud.platform: + enabled: true + cloud.provider: + enabled: true + cloud.region: + enabled: true + host.id: + enabled: true + host.image.id: + enabled: true + host.name: + enabled: true + host.type: + enabled: true + tags: + - '^aws:autoscaling:groupName' + ecs: + resource_attributes: + aws.ecs.cluster.arn: + enabled: true + aws.ecs.launchtype: + enabled: true + aws.ecs.task.arn: + enabled: false + aws.ecs.task.family: + enabled: false + aws.ecs.task.revision: + enabled: false + aws.ecs.task.id: + enabled: false + aws.log.group.arns: + enabled: false + aws.log.group.names: + enabled: false + aws.log.stream.arns: + enabled: false + aws.log.stream.names: + enabled: false + cloud.account.id: + enabled: true + cloud.availability_zone: + enabled: true + cloud.platform: + enabled: true + cloud.provider: + enabled: true + cloud.region: + enabled: true + eks: + resource_attributes: + cloud.platform: + enabled: true + cloud.provider: + enabled: true + k8s.cluster.name: + enabled: false + elasticbeanstalk: + resource_attributes: + cloud.platform: + enabled: true + cloud.provider: + enabled: true + deployment.environment: + enabled: true + service.instance.id: + enabled: true + service.version: + enabled: true + endpoint: '' + gcp: + resource_attributes: + cloud.account.id: + enabled: true + cloud.availability_zone: + enabled: true + cloud.platform: + enabled: true + cloud.provider: + enabled: true + cloud.region: + enabled: true + faas.id: + enabled: true + faas.instance: + enabled: true + faas.name: + enabled: true + faas.version: + enabled: true + gcp.cloud_run.job.execution: + enabled: true + gcp.cloud_run.job.task_index: + enabled: true + gcp.gce.instance.hostname: + enabled: false + gcp.gce.instance.name: + enabled: false + host.id: + enabled: true + host.name: + enabled: true + host.type: + enabled: true + k8s.cluster.name: + enabled: true + heroku: + resource_attributes: + cloud.provider: + enabled: true + heroku.app.id: + enabled: true + heroku.dyno.id: + enabled: true + heroku.release.commit: + enabled: true + heroku.release.creation_timestamp: + enabled: true + service.instance.id: + enabled: true + service.name: + enabled: true + service.version: + enabled: true + http2_ping_timeout: 0s + http2_read_idle_timeout: 0s + idle_conn_timeout: 1m30s + k8snode: + auth_type: serviceAccount + context: '' + kube_config_path: '' + node_from_env_var: '' + resource_attributes: + k8s.node.name: + enabled: true + k8s.node.uid: + enabled: true + lambda: + resource_attributes: + aws.log.group.names: + enabled: true + aws.log.stream.names: + enabled: true + cloud.platform: + enabled: true + cloud.provider: + enabled: true + cloud.region: + enabled: true + faas.instance: + enabled: true + faas.max_memory: + enabled: true + faas.name: + enabled: true + faas.version: + enabled: true + max_idle_conns: 100 + openshift: + address: '' + resource_attributes: + cloud.platform: + enabled: true + cloud.provider: + enabled: true + cloud.region: + enabled: true + k8s.cluster.name: + enabled: true + tls: + ca_file: '' + cert_file: '' + include_system_ca_certs_pool: false + insecure: false + insecure_skip_verify: false + key_file: '' + max_version: '' + min_version: '' + reload_interval: 0s + server_name_override: '' + token: '' + override: true + proxy_url: '' + read_buffer_size: 0 + system: + resource_attributes: + host.arch: + enabled: false + host.cpu.cache.l2.size: + enabled: false + host.cpu.family: + enabled: false + host.cpu.model.id: + enabled: false + host.cpu.model.name: + enabled: false + host.cpu.stepping: + enabled: false + host.cpu.vendor.id: + enabled: false + host.id: + enabled: false + host.ip: + enabled: false + host.mac: + enabled: false + host.name: + enabled: true + os.description: + enabled: false + os.type: + enabled: true + timeout: 2s + tls: + ca_file: '' + cert_file: '' + include_system_ca_certs_pool: false + insecure: false + insecure_skip_verify: false + key_file: '' + max_version: '' + min_version: '' + reload_interval: 0s + server_name_override: '' + write_buffer_size: 0 +receivers: + otlp/application_signals: + protocols: + grpc: + endpoint: '0.0.0.0:4315' + dialer: + timeout: 0s + include_metadata: false + max_concurrent_streams: 0 + max_recv_msg_size_mib: 0 + read_buffer_size: 524288 + tls: + ca_file: '' + cert_file: path/to/cert.crt + client_ca_file: '' + client_ca_file_reload: false + include_system_ca_certs_pool: false + key_file: path/to/key.key + max_version: '' + min_version: '' + reload_interval: 0s + transport: tcp + write_buffer_size: 0 + http: + endpoint: '0.0.0.0:4316' + include_metadata: false + logs_url_path: /v1/logs + max_request_body_size: 0 + metrics_url_path: /v1/metrics + tls: + ca_file: '' + cert_file: path/to/cert.crt + client_ca_file: '' + client_ca_file_reload: false + include_system_ca_certs_pool: false + key_file: path/to/key.key + max_version: '' + min_version: '' + reload_interval: 0s + traces_url_path: /v1/traces +service: + extensions: + - awsproxy/application_signals + - agenthealth/traces + - agenthealth/logs + pipelines: + metrics/application_signals: + exporters: + - awsemf/application_signals + processors: + - resourcedetection + - awsapplicationsignals + receivers: + - otlp/application_signals + traces/application_signals: + exporters: + - awsxray/application_signals + processors: + - resourcedetection + - awsapplicationsignals + receivers: + - otlp/application_signals + telemetry: + logs: + development: false + disable_caller: false + disable_stacktrace: false + encoding: console + level: info + sampling: + enabled: true + initial: 2 + thereafter: 500 + tick: 10s + metrics: + address: '' + level: None + traces: {} diff --git a/translator/tocwconfig/tocwconfig_test.go b/translator/tocwconfig/tocwconfig_test.go index 4fd9049521..ab59deafd8 100644 --- a/translator/tocwconfig/tocwconfig_test.go +++ b/translator/tocwconfig/tocwconfig_test.go @@ -38,6 +38,7 @@ import ( "github.com/aws/amazon-cloudwatch-agent/translator/translate/agent" "github.com/aws/amazon-cloudwatch-agent/translator/translate/otel/common" "github.com/aws/amazon-cloudwatch-agent/translator/util" + "github.com/aws/amazon-cloudwatch-agent/translator/util/ecsutil" "github.com/aws/amazon-cloudwatch-agent/translator/util/eksdetector" ) @@ -122,6 +123,20 @@ func TestAppSignalsFallbackAndEKSConfig(t *testing.T) { checkTranslation(t, "appsignals_fallback_and_eks_config", "windows", expectedEnvVars, "") } +func TestAppSignalsAndECSConfig(t *testing.T) { + resetContext(t) + context.CurrentContext().SetRunInContainer(true) + t.Setenv(config.HOST_NAME, "host_name_from_env") + t.Setenv(config.HOST_IP, "127.0.0.1") + context.CurrentContext().SetMode(config.ModeEC2) + ecsutil.GetECSUtilSingleton().Region = "test-region" + ecsutil.GetECSUtilSingleton().TaskARN = "arn:aws:ecs:us-east-1:account_id:task/task_id" + + expectedEnvVars := map[string]string{} + checkTranslation(t, "appsignals_and_ecs_config", "linux", expectedEnvVars, "") + checkTranslation(t, "appsignals_and_ecs_config", "windows", expectedEnvVars, "") +} + func TestAppSignalsFavorOverFallbackConfig(t *testing.T) { resetContext(t) context.CurrentContext().SetRunInContainer(true) @@ -618,6 +633,7 @@ func resetContext(t *testing.T) { util.DetectCredentialsPath = func() string { return "fake-path" } + ecsutil.GetECSUtilSingleton().Region = "" context.ResetContext() t.Setenv("ProgramData", "c:\\ProgramData") diff --git a/translator/translate/otel/processor/awsapplicationsignals/translator.go b/translator/translate/otel/processor/awsapplicationsignals/translator.go index 49c669efc9..4e68af9e5a 100644 --- a/translator/translate/otel/processor/awsapplicationsignals/translator.go +++ b/translator/translate/otel/processor/awsapplicationsignals/translator.go @@ -100,7 +100,7 @@ func (t *translator) Translate(conf *confmap.Conf) (component.Config, error) { } case config.ModeECS: cfg.Resolvers = []appsignalsconfig.Resolver{ - appsignalsconfig.NewGenericResolver(hostedIn), + appsignalsconfig.NewECSResolver(hostedIn), } default: cfg.Resolvers = []appsignalsconfig.Resolver{ diff --git a/translator/translate/otel/processor/resourcedetection/configs/ecs_config.yaml b/translator/translate/otel/processor/resourcedetection/configs/ecs_config.yaml new file mode 100644 index 0000000000..9e6c47285a --- /dev/null +++ b/translator/translate/otel/processor/resourcedetection/configs/ecs_config.yaml @@ -0,0 +1,38 @@ +detectors: [env, ecs, ec2] +override: true +timeout: 2s +ecs: + resource_attributes: + aws.ecs.cluster.arn: + enabled: true + aws.ecs.launchtype: + enabled: true + aws.ecs.task.arn: + enabled: false + aws.ecs.task.family: + enabled: false + aws.ecs.task.id: + enabled: false + aws.ecs.task.revision: + enabled: false + aws.log.group.arns: + enabled: false + aws.log.group.names: + enabled: false + aws.log.stream.arns: + enabled: false + aws.log.stream.names: + enabled: false + cloud.account.id: + enabled: true + cloud.availability_zone: + enabled: true + cloud.platform: + enabled: true + cloud.provider: + enabled: true + cloud.region: + enabled: true +ec2: + tags: + - ^aws:autoscaling:groupName \ No newline at end of file diff --git a/translator/translate/otel/processor/resourcedetection/translator.go b/translator/translate/otel/processor/resourcedetection/translator.go index 9ac725b4dd..454e59ed79 100644 --- a/translator/translate/otel/processor/resourcedetection/translator.go +++ b/translator/translate/otel/processor/resourcedetection/translator.go @@ -11,11 +11,17 @@ import ( "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/processor" + "github.com/aws/amazon-cloudwatch-agent/translator/config" + "github.com/aws/amazon-cloudwatch-agent/translator/context" "github.com/aws/amazon-cloudwatch-agent/translator/translate/otel/common" + "github.com/aws/amazon-cloudwatch-agent/translator/util/ecsutil" ) //go:embed configs/config.yaml -var appSignalsAwsResourceDetectionConfig string +var appSignalsDefaultResourceDetectionConfig string + +//go:embed configs/ecs_config.yaml +var appSignalsECSResourceDetectionConfig string type translator struct { name string @@ -57,5 +63,21 @@ func (t *translator) ID() component.ID { func (t *translator) Translate(conf *confmap.Conf) (component.Config, error) { cfg := t.factory.CreateDefaultConfig().(*resourcedetectionprocessor.Config) - return common.GetYamlFileToYamlConfig(cfg, appSignalsAwsResourceDetectionConfig) + + mode := context.CurrentContext().KubernetesMode() + if mode == "" { + mode = context.CurrentContext().Mode() + } + if mode == config.ModeEC2 { + if ecsutil.GetECSUtilSingleton().IsECS() { + mode = config.ModeECS + } + } + + switch mode { + case config.ModeECS: + return common.GetYamlFileToYamlConfig(cfg, appSignalsECSResourceDetectionConfig) + default: + return common.GetYamlFileToYamlConfig(cfg, appSignalsDefaultResourceDetectionConfig) + } } diff --git a/translator/translate/otel/processor/resourcedetection/translator_test.go b/translator/translate/otel/processor/resourcedetection/translator_test.go index 9699596c7f..6de59538b0 100644 --- a/translator/translate/otel/processor/resourcedetection/translator_test.go +++ b/translator/translate/otel/processor/resourcedetection/translator_test.go @@ -11,16 +11,94 @@ import ( "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/confmap" + + translatorconfig "github.com/aws/amazon-cloudwatch-agent/translator/config" + "github.com/aws/amazon-cloudwatch-agent/translator/context" + "github.com/aws/amazon-cloudwatch-agent/translator/util/ecsutil" ) func TestTranslate(t *testing.T) { tt := NewTranslator(WithDataType(component.DataTypeTraces)) testCases := map[string]struct { input map[string]interface{} + mode string + isECS bool want *confmap.Conf wantErr error }{ - "WithAppSignalsEnabled": { + "WithAppSignalsEnabledOnECS": { + mode: translatorconfig.ModeEC2, + isECS: true, + input: map[string]interface{}{ + "traces": map[string]interface{}{ + "traces_collected": map[string]interface{}{ + "app_signals": map[string]interface{}{}, + }, + }}, + want: confmap.NewFromStringMap(map[string]interface{}{ + "detectors": []interface{}{ + "env", + "ecs", + "ec2", + }, + "timeout": "2s", + "override": true, + "ec2": map[string]interface{}{ + "tags": []interface{}{"^aws:autoscaling:groupName"}, + }, + "ecs": map[string]interface{}{ + "resource_attributes": map[string]interface{}{ + "aws.ecs.cluster.arn": map[string]interface{}{ + "enabled": true, + }, + "aws.ecs.launchtype": map[string]interface{}{ + "enabled": true, + }, + "aws.ecs.task.arn": map[string]interface{}{ + "enabled": false, + }, + "aws.ecs.task.family": map[string]interface{}{ + "enabled": false, + }, + "aws.ecs.task.id": map[string]interface{}{ + "enabled": false, + }, + "aws.ecs.task.revision": map[string]interface{}{ + "enabled": false, + }, + "aws.log.group.arns": map[string]interface{}{ + "enabled": false, + }, + "aws.log.group.names": map[string]interface{}{ + "enabled": false, + }, + "aws.log.stream.arns": map[string]interface{}{ + "enabled": false, + }, + "aws.log.stream.names": map[string]interface{}{ + "enabled": false, + }, + "cloud.account.id": map[string]interface{}{ + "enabled": true, + }, + "cloud.availability_zone": map[string]interface{}{ + "enabled": true, + }, + "cloud.platform": map[string]interface{}{ + "enabled": true, + }, + "cloud.provider": map[string]interface{}{ + "enabled": true, + }, + "cloud.region": map[string]interface{}{ + "enabled": true, + }, + }, + }, + }), + }, + "WithAppSignalsEnabledOnEC2": { + mode: translatorconfig.ModeEC2, input: map[string]interface{}{ "traces": map[string]interface{}{ "traces_collected": map[string]interface{}{ @@ -44,6 +122,12 @@ func TestTranslate(t *testing.T) { factory := resourcedetectionprocessor.NewFactory() for name, testCase := range testCases { t.Run(name, func(t *testing.T) { + context.CurrentContext().SetMode(testCase.mode) + if testCase.isECS { + ecsutil.GetECSUtilSingleton().Region = "test-region" + } else { + ecsutil.GetECSUtilSingleton().Region = "" + } conf := confmap.NewFromStringMap(testCase.input) got, err := tt.Translate(conf) assert.Equal(t, testCase.wantErr, err) @@ -52,7 +136,7 @@ func TestTranslate(t *testing.T) { gotCfg, ok := got.(*resourcedetectionprocessor.Config) require.True(t, ok) wantCfg := factory.CreateDefaultConfig() - require.NoError(t, testCase.want.Unmarshal(wantCfg)) + require.NoError(t, testCase.want.Unmarshal(&wantCfg)) assert.Equal(t, wantCfg, gotCfg) } })