diff --git a/pkg/api/exp/enrichment.go b/pkg/api/exp/enrichment.go new file mode 100644 index 0000000000..0cca358e5d --- /dev/null +++ b/pkg/api/exp/enrichment.go @@ -0,0 +1,9 @@ +package exp + +const ( + EnrichmentEnableAttributesDtKubernetes = FFPrefix + "enable-attributes-dt.kubernetes" +) + +func (ff *FeatureFlags) EnableAttributesDtKubernetes() bool { + return ff.getBoolWithDefault(EnrichmentEnableAttributesDtKubernetes, false) +} diff --git a/pkg/api/exp/enrichment_test.go b/pkg/api/exp/enrichment_test.go new file mode 100644 index 0000000000..b130831841 --- /dev/null +++ b/pkg/api/exp/enrichment_test.go @@ -0,0 +1,55 @@ +package exp + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEnableAttributesDtKubernetes(t *testing.T) { + type testCase struct { + title string + in string + out bool + } + + cases := []testCase{ + { + title: "default", + in: "", + out: false, + }, + { + title: "overrule to true", + in: "true", + out: true, + }, + { + title: "overrule to false", + in: "false", + out: false, + }, + { + title: "uppercase TRUE", + in: "TRUE", + out: true, + }, + { + title: "uppercase FALSE", + in: "FALSE", + out: false, + }, + } + + for _, c := range cases { + t.Run(c.title, func(t *testing.T) { + ff := FeatureFlags{annotations: map[string]string{ + EnrichmentEnableAttributesDtKubernetes: c.in, + }} + + out := ff.EnableAttributesDtKubernetes() + + assert.Equal(t, c.out, out) + }) + } +} diff --git a/pkg/webhook/mutation/pod/handler/injection/attributes.go b/pkg/webhook/mutation/pod/handler/injection/attributes.go index 2becfd75f8..71e66fa0fe 100644 --- a/pkg/webhook/mutation/pod/handler/injection/attributes.go +++ b/pkg/webhook/mutation/pod/handler/injection/attributes.go @@ -28,7 +28,9 @@ func addPodAttributes(request *dtwebhook.MutationRequest) error { UserDefined: map[string]string{}, } - setDeprecatedAttributes(&attrs) + if request.DynaKube.FF().EnableAttributesDtKubernetes() { + setDeprecatedAttributes(&attrs) + } envs := []corev1.EnvVar{ {Name: K8sPodNameEnv, ValueFrom: k8senv.NewSourceForField("metadata.name")}, diff --git a/pkg/webhook/mutation/pod/handler/injection/attributes_test.go b/pkg/webhook/mutation/pod/handler/injection/attributes_test.go index 024dae90a9..6a305ffbed 100644 --- a/pkg/webhook/mutation/pod/handler/injection/attributes_test.go +++ b/pkg/webhook/mutation/pod/handler/injection/attributes_test.go @@ -9,6 +9,7 @@ import ( containerattr "github.com/Dynatrace/dynatrace-bootstrapper/cmd/configure/attributes/container" podattr "github.com/Dynatrace/dynatrace-bootstrapper/cmd/configure/attributes/pod" + "github.com/Dynatrace/dynatrace-operator/pkg/api/exp" "github.com/Dynatrace/dynatrace-operator/pkg/api/latest/dynakube" "github.com/Dynatrace/dynatrace-operator/pkg/api/latest/dynakube/metadataenrichment" "github.com/Dynatrace/dynatrace-operator/pkg/api/latest/dynakube/oneagent" @@ -47,8 +48,6 @@ func TestAddPodAttributes(t *testing.T) { assert.Contains(t, attr.NodeName, K8sNodeNameEnv) assert.Equal(t, request.Pod.Namespace, attr.NamespaceName) - assertDeprecatedAttributes(t, attr) - require.Len(t, request.InstallContainer.Env, 3) assert.NotNil(t, k8senv.Find(request.InstallContainer.Env, K8sPodNameEnv)) assert.NotNil(t, k8senv.Find(request.InstallContainer.Env, K8sPodUIDEnv)) @@ -58,40 +57,67 @@ func TestAddPodAttributes(t *testing.T) { } t.Run("args and envs added", func(t *testing.T) { - initContainer := corev1.Container{ - Args: []string{}, - } - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test"}, - } - - expectedPod := pod.DeepCopy() - - request := dtwebhook.MutationRequest{ - BaseRequest: &dtwebhook.BaseRequest{ - Pod: &pod, - DynaKube: dynakube.DynaKube{ - Spec: dynakube.DynaKubeSpec{ - MetadataEnrichment: metadataenrichment.Spec{ - Enabled: ptr.To(true), + type testCase struct { + name string + annotations map[string]string + assertDeprecatedAnnotations func(t *testing.T, attrs podattr.Attributes) + } + + testCases := []testCase{ + { + name: "without deprecated annotations", + annotations: map[string]string{}, + assertDeprecatedAnnotations: assertNoDeprecatedAttributes, + }, + { + name: "with deprecated annotations", + annotations: map[string]string{exp.EnrichmentEnableAttributesDtKubernetes: "true"}, + assertDeprecatedAnnotations: assertDeprecatedAttributes, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + initContainer := corev1.Container{ + Args: []string{}, + } + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test"}, + } + + expectedPod := pod.DeepCopy() + + request := dtwebhook.MutationRequest{ + BaseRequest: &dtwebhook.BaseRequest{ + Pod: &pod, + DynaKube: dynakube.DynaKube{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: test.annotations, + }, + Spec: dynakube.DynaKubeSpec{ + MetadataEnrichment: metadataenrichment.Spec{ + Enabled: ptr.To(true), + }, + }, + Status: dynakube.DynaKubeStatus{ + KubernetesClusterMEID: "meid", + KubeSystemUUID: "systemuuid", + KubernetesClusterName: "meidname", + }, }, }, - Status: dynakube.DynaKubeStatus{ - KubernetesClusterMEID: "meid", - KubeSystemUUID: "systemuuid", - KubernetesClusterName: "meidname", - }, - }, - }, - InstallContainer: &initContainer, - } + InstallContainer: &initContainer, + } - err := addPodAttributes(&request) - require.NoError(t, err) - require.Equal(t, *expectedPod, *request.Pod) + err := addPodAttributes(&request) + require.NoError(t, err) + require.Equal(t, *expectedPod, *request.Pod) - validateAttributes(t, request) + attr := validateAttributes(t, request) + test.assertDeprecatedAnnotations(t, attr) + }) + } }) } diff --git a/pkg/webhook/mutation/pod/handler/injection/deprecated_test.go b/pkg/webhook/mutation/pod/handler/injection/deprecated_test.go index b8de5b9c5e..a65d88a0de 100644 --- a/pkg/webhook/mutation/pod/handler/injection/deprecated_test.go +++ b/pkg/webhook/mutation/pod/handler/injection/deprecated_test.go @@ -29,3 +29,10 @@ func assertDeprecatedAttributes(t *testing.T, attrs podattr.Attributes) { require.True(t, ok) assert.Equal(t, attrs.ClusterUID, depValue) } + +func assertNoDeprecatedAttributes(t *testing.T, attrs podattr.Attributes) { + t.Helper() + + _, ok := attrs.UserDefined[deprecatedClusterIDKey] + require.False(t, ok) +} diff --git a/pkg/webhook/mutation/pod/mutator/metadata/mutator.go b/pkg/webhook/mutation/pod/mutator/metadata/mutator.go index 145dcb714d..7ffbdffa1e 100644 --- a/pkg/webhook/mutation/pod/mutator/metadata/mutator.go +++ b/pkg/webhook/mutation/pod/mutator/metadata/mutator.go @@ -68,7 +68,9 @@ func (mut *Mutator) Mutate(request *dtwebhook.MutationRequest) error { WorkloadName: workloadInfo.Name, } - SetDeprecatedAttributes(&attrs) + if request.DynaKube.FF().EnableAttributesDtKubernetes() { + SetDeprecatedAttributes(&attrs) + } addMetadataToInitArgs(request, &attrs) setInjectedAnnotation(request.Pod) diff --git a/pkg/webhook/mutation/pod/mutator/metadata/mutator_test.go b/pkg/webhook/mutation/pod/mutator/metadata/mutator_test.go index 606f694367..c7636a0afe 100644 --- a/pkg/webhook/mutation/pod/mutator/metadata/mutator_test.go +++ b/pkg/webhook/mutation/pod/mutator/metadata/mutator_test.go @@ -310,104 +310,133 @@ func TestMutate(t *testing.T) { testClusterMEID = "KUBERNETES_CLUSTER-DE4AF78E24729521" ) - initContainer := corev1.Container{ - Args: []string{}, + type testCase struct { + name string + annotations map[string]string + withDeprecatedAnnotations bool } - pod := pod.DeepCopy() - owner := &corev1.ReplicationController{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "ReplicationController", + testCases := []testCase{ + { + name: "without deprecated annotations", + annotations: map[string]string{}, + withDeprecatedAnnotations: false, }, - ObjectMeta: metav1.ObjectMeta{ - Name: "owner", - Namespace: pod.Namespace, + { + name: "with deprecated annotations", + annotations: map[string]string{exp.EnrichmentEnableAttributesDtKubernetes: "true"}, + withDeprecatedAnnotations: true, }, } - expectedPod := pod.DeepCopy() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + initContainer := corev1.Container{ + Args: []string{}, + } + pod := pod.DeepCopy() - request := dtwebhook.MutationRequest{ - BaseRequest: &dtwebhook.BaseRequest{ - Pod: pod, - DynaKube: dynakube.DynaKube{ - Spec: dynakube.DynaKubeSpec{ - MetadataEnrichment: metadataenrichment.Spec{ - Enabled: ptr.To(true), - }, + owner := &corev1.ReplicationController{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "ReplicationController", }, - Status: dynakube.DynaKubeStatus{ - KubeSystemUUID: testKubeSystemID, - KubernetesClusterMEID: testClusterMEID, - KubernetesClusterName: testClusterName, - MetadataEnrichment: metadataenrichment.Status{ - Rules: []metadataenrichment.Rule{ - { - Type: "LABEL", - Source: testSecContextLabel, - Target: "dt.security_context", + ObjectMeta: metav1.ObjectMeta{ + Name: "owner", + Namespace: pod.Namespace, + }, + } + + expectedPod := pod.DeepCopy() + + request := dtwebhook.MutationRequest{ + BaseRequest: &dtwebhook.BaseRequest{ + Pod: pod, + DynaKube: dynakube.DynaKube{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: tc.annotations, + }, + Spec: dynakube.DynaKubeSpec{ + MetadataEnrichment: metadataenrichment.Spec{ + Enabled: ptr.To(true), }, - { - Type: "LABEL", - Source: testCustomMetadataLabel, + }, + Status: dynakube.DynaKubeStatus{ + KubeSystemUUID: testKubeSystemID, + KubernetesClusterMEID: testClusterMEID, + KubernetesClusterName: testClusterName, + MetadataEnrichment: metadataenrichment.Status{ + Rules: []metadataenrichment.Rule{ + { + Type: "LABEL", + Source: testSecContextLabel, + Target: "dt.security_context", + }, + { + Type: "LABEL", + Source: testCustomMetadataLabel, + }, + { + Type: "ANNOTATION", + Source: testCostCenterAnnotation, + Target: "dt.cost.costcenter", + }, + { + Type: "ANNOTATION", + Source: testCustomMetadataAnnotation, + }, + }, }, - { - Type: "ANNOTATION", - Source: testCostCenterAnnotation, - Target: "dt.cost.costcenter", + }, + }, + Namespace: corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: pod.Namespace, + Annotations: map[string]string{ + metadataenrichment.Prefix + nsMetaAnnotationKey: nsMetaAnnotationValue, + testCostCenterAnnotation: "cost-center", + testCustomMetadataAnnotation: "custom-meta-annotation", }, - { - Type: "ANNOTATION", - Source: testCustomMetadataAnnotation, + Labels: map[string]string{ + testSecContextLabel: "high", + testCustomMetadataLabel: "custom-meta-label", }, }, }, }, - }, - Namespace: corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: pod.Namespace, - Annotations: map[string]string{ - metadataenrichment.Prefix + nsMetaAnnotationKey: nsMetaAnnotationValue, - testCostCenterAnnotation: "cost-center", - testCustomMetadataAnnotation: "custom-meta-annotation", - }, - Labels: map[string]string{ - testSecContextLabel: "high", - testCustomMetadataLabel: "custom-meta-label", - }, - }, - }, - }, - InstallContainer: &initContainer, - } + InstallContainer: &initContainer, + } - mut := NewMutator(fake.NewClient(owner, pod)) + mut := NewMutator(fake.NewClient(owner, pod)) - err := mut.Mutate(&request) - require.NoError(t, err) - require.NotEqual(t, *expectedPod, *request.Pod) - require.NotEmpty(t, request.Pod.OwnerReferences) - - assert.Contains(t, request.InstallContainer.Args, buildArgument("k8s.workload.kind", request.Pod.OwnerReferences[0].Kind)) - assert.Contains(t, request.InstallContainer.Args, buildArgument("k8s.workload.name", request.Pod.OwnerReferences[0].Name)) - assert.Contains(t, request.InstallContainer.Args, buildArgument(DeprecatedWorkloadKindKey, request.Pod.OwnerReferences[0].Kind)) - assert.Contains(t, request.InstallContainer.Args, buildArgument(DeprecatedWorkloadNameKey, request.Pod.OwnerReferences[0].Name)) - assert.Contains(t, request.InstallContainer.Args, buildArgument(nsMetaAnnotationKey, nsMetaAnnotationValue)) - - assert.Contains(t, request.InstallContainer.Args, buildArgument("dt.security_context", "high")) - assert.Contains(t, request.InstallContainer.Args, buildArgument("dt.cost.costcenter", "cost-center")) - assert.Contains(t, request.InstallContainer.Args, buildArgument("k8s.namespace.label."+testCustomMetadataLabel, "custom-meta-label")) - assert.Contains(t, request.InstallContainer.Args, buildArgument("k8s.namespace.annotation."+testCustomMetadataAnnotation, "custom-meta-annotation")) - assert.Contains(t, request.InstallContainer.Args, "--"+bootstrapper.MetadataEnrichmentFlag) - - require.Len(t, request.Pod.Annotations, 7) // workload.kind + workload.name + dt.security_context + dt.cost.costcenter + injected + propagated ns annotations - assert.Equal(t, strings.ToLower(request.Pod.OwnerReferences[0].Kind), request.Pod.Annotations[AnnotationWorkloadKind]) - assert.Equal(t, request.Pod.OwnerReferences[0].Name, request.Pod.Annotations[AnnotationWorkloadName]) - assert.Equal(t, "true", request.Pod.Annotations[AnnotationInjected]) - assert.Equal(t, nsMetaAnnotationValue, request.Pod.Annotations[metadataenrichment.Prefix+nsMetaAnnotationKey]) - assert.NotEmpty(t, request.Pod.Annotations[metadataenrichment.Annotation]) + err := mut.Mutate(&request) + require.NoError(t, err) + require.NotEqual(t, *expectedPod, *request.Pod) + require.NotEmpty(t, request.Pod.OwnerReferences) + + assert.Contains(t, request.InstallContainer.Args, buildArgument("k8s.workload.kind", request.Pod.OwnerReferences[0].Kind)) + assert.Contains(t, request.InstallContainer.Args, buildArgument("k8s.workload.name", request.Pod.OwnerReferences[0].Name)) + assert.Contains(t, request.InstallContainer.Args, buildArgument(nsMetaAnnotationKey, nsMetaAnnotationValue)) + + if tc.withDeprecatedAnnotations { + assert.Contains(t, request.InstallContainer.Args, buildArgument(DeprecatedWorkloadKindKey, request.Pod.OwnerReferences[0].Kind)) + assert.Contains(t, request.InstallContainer.Args, buildArgument(DeprecatedWorkloadNameKey, request.Pod.OwnerReferences[0].Name)) + } + + assert.Contains(t, request.InstallContainer.Args, buildArgument("dt.security_context", "high")) + assert.Contains(t, request.InstallContainer.Args, buildArgument("dt.cost.costcenter", "cost-center")) + assert.Contains(t, request.InstallContainer.Args, buildArgument("k8s.namespace.label."+testCustomMetadataLabel, "custom-meta-label")) + assert.Contains(t, request.InstallContainer.Args, buildArgument("k8s.namespace.annotation."+testCustomMetadataAnnotation, "custom-meta-annotation")) + assert.Contains(t, request.InstallContainer.Args, "--"+bootstrapper.MetadataEnrichmentFlag) + + require.Len(t, request.Pod.Annotations, 7) // workload.kind + workload.name + dt.security_context + dt.cost.costcenter + injected + propagated ns annotations + assert.Equal(t, strings.ToLower(request.Pod.OwnerReferences[0].Kind), request.Pod.Annotations[AnnotationWorkloadKind]) + assert.Equal(t, request.Pod.OwnerReferences[0].Name, request.Pod.Annotations[AnnotationWorkloadName]) + assert.Equal(t, "true", request.Pod.Annotations[AnnotationInjected]) + assert.Equal(t, nsMetaAnnotationValue, request.Pod.Annotations[metadataenrichment.Prefix+nsMetaAnnotationKey]) + assert.NotEmpty(t, request.Pod.Annotations[metadataenrichment.Annotation]) + }) + } }) } diff --git a/pkg/webhook/mutation/pod/mutator/otlp/resourceattributes/mutator.go b/pkg/webhook/mutation/pod/mutator/otlp/resourceattributes/mutator.go index ccec1d4b1f..b10e1a7af8 100644 --- a/pkg/webhook/mutation/pod/mutator/otlp/resourceattributes/mutator.go +++ b/pkg/webhook/mutation/pod/mutator/otlp/resourceattributes/mutator.go @@ -108,7 +108,6 @@ func (m *Mutator) addResourceAttributes(request *dtwebhook.BaseRequest, c *corev kubernetesMetaDataAttributes := Attributes{ "k8s.namespace.name": request.Pod.Namespace, "k8s.cluster.uid": request.DynaKube.Status.KubeSystemUUID, - "dt.kubernetes.cluster.id": request.DynaKube.Status.KubeSystemUUID, "k8s.cluster.name": request.DynaKube.Status.KubernetesClusterName, "dt.entity.kubernetes_cluster": request.DynaKube.Status.KubernetesClusterMEID, "k8s.container.name": c.Name, @@ -117,13 +116,24 @@ func (m *Mutator) addResourceAttributes(request *dtwebhook.BaseRequest, c *corev "k8s.node.name": "$(K8S_NODE_NAME)", } + if request.DynaKube.FF().EnableAttributesDtKubernetes() { + kubernetesMetaDataAttributes.Merge(Attributes{ + "dt.kubernetes.cluster.id": request.DynaKube.Status.KubeSystemUUID, + }) + } + kubernetesMetaDataAttributes = sanitizeMap(kubernetesMetaDataAttributes) // add workload Attributes (only once fetched per pod, but appended per container to env var if not already present) if ownerInfo != nil { _ = kubernetesMetaDataAttributes.Merge(Attributes{ - "k8s.workload.kind": ownerInfo.Kind, - "k8s.workload.name": ownerInfo.Name, + "k8s.workload.kind": ownerInfo.Kind, + "k8s.workload.name": ownerInfo.Name, + }) + } + + if ownerInfo != nil && request.DynaKube.FF().EnableAttributesDtKubernetes() { + _ = kubernetesMetaDataAttributes.Merge(Attributes{ metadata.DeprecatedWorkloadNameKey: ownerInfo.Name, metadata.DeprecatedWorkloadKindKey: ownerInfo.Kind, }) diff --git a/pkg/webhook/mutation/pod/mutator/otlp/resourceattributes/mutator_test.go b/pkg/webhook/mutation/pod/mutator/otlp/resourceattributes/mutator_test.go index 83209fec95..4896f9d836 100644 --- a/pkg/webhook/mutation/pod/mutator/otlp/resourceattributes/mutator_test.go +++ b/pkg/webhook/mutation/pod/mutator/otlp/resourceattributes/mutator_test.go @@ -1,11 +1,13 @@ package resourceattributes import ( + "maps" "net/url" "slices" "strings" "testing" + "github.com/Dynatrace/dynatrace-operator/pkg/api/exp" latestdynakube "github.com/Dynatrace/dynatrace-operator/pkg/api/latest/dynakube" "github.com/Dynatrace/dynatrace-operator/pkg/api/latest/dynakube/metadataenrichment" "github.com/Dynatrace/dynatrace-operator/pkg/util/kubernetes/fields/k8senv" @@ -23,6 +25,14 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" ) +type mutatorTestCase struct { + name string + objects []runtime.Object + namespace corev1.Namespace + pod *corev1.Pod + wantAttributes map[string][]string +} + func Test_Mutator_Mutate(t *testing.T) { //nolint:revive // cognitive-complexity const ( testSecContextLabel = "test-security-context-label" @@ -66,13 +76,7 @@ func Test_Mutator_Mutate(t *testing.T) { //nolint:revive // cognitive-complexity OwnerReferences: []metav1.OwnerReference{deploymentOwner}, }} - tests := []struct { - name string - objects []runtime.Object - namespace corev1.Namespace - pod *corev1.Pod - wantAttributes map[string][]string - }{ + tests := []mutatorTestCase{ { name: "adds Attributes with deployment workload via replicaset lookup", objects: []runtime.Object{ @@ -277,8 +281,43 @@ func Test_Mutator_Mutate(t *testing.T) { //nolint:revive // cognitive-complexity }, } + t.Run("with deprecated annotations", func(t *testing.T) { + dk := baseDK.DeepCopy() + dk.Annotations = map[string]string{exp.EnrichmentEnableAttributesDtKubernetes: "true"} + runMutatorTests(t, *dk, tests, false) + }) + + t.Run("without deprecated annotations", func(t *testing.T) { + dk := baseDK.DeepCopy() + dk.Annotations = map[string]string{} + runMutatorTests(t, *dk, tests, true) + }) +} + +func runMutatorTests(t *testing.T, dk latestdynakube.DynaKube, tests []mutatorTestCase, removeDeprecatedAttr bool) { //nolint:revive + t.Helper() + + removeDtKubernetesAnnotations := func(attributes map[string][]string) map[string][]string { + attributesCopy := maps.Clone(attributes) + for containerName, attrs := range attributesCopy { + filteredAttrs := slices.DeleteFunc(attrs, func(attr string) bool { + return strings.HasPrefix(attr, "dt.kubernetes.") + }) + attributesCopy[containerName] = filteredAttrs + } + + return attributesCopy + } + for _, tt := range tests { + wantAttributes := tt.wantAttributes + if removeDeprecatedAttr { + wantAttributes = removeDtKubernetesAnnotations(tt.wantAttributes) + } + t.Run(tt.name, func(t *testing.T) { + pod := tt.pod.DeepCopy() + builder := fake.NewClientBuilder().WithScheme(scheme.Scheme) if tt.objects != nil { builder = builder.WithRuntimeObjects(tt.objects...) @@ -290,21 +329,21 @@ func Test_Mutator_Mutate(t *testing.T) { //nolint:revive // cognitive-complexity t.Context(), tt.namespace, nil, - tt.pod, - baseDK, + pod, + dk, ) err := mut.Mutate(req) require.NoError(t, err) - require.Len(t, tt.pod.Spec.Containers, len(tt.wantAttributes)) + require.Len(t, pod.Spec.Containers, len(wantAttributes)) - for _, container := range tt.pod.Spec.Containers { + for _, container := range pod.Spec.Containers { var resourceAttributes []string if env := k8senv.Find(container.Env, "OTEL_RESOURCE_ATTRIBUTES"); env != nil { resourceAttributes = slices.Sorted(strings.SplitSeq(env.Value, ",")) } - if len(tt.wantAttributes[container.Name]) == 0 { + if len(wantAttributes[container.Name]) == 0 { assert.Empty(t, resourceAttributes, "container should be skipped, no Attributes injected") // also check pod/node env vars are not injected for _, envName := range []string{injection.K8sNodeNameEnv, injection.K8sPodNameEnv, injection.K8sPodUIDEnv} { @@ -316,9 +355,9 @@ func Test_Mutator_Mutate(t *testing.T) { //nolint:revive // cognitive-complexity require.NotEmpty(t, resourceAttributes) assert.Equal(t, resourceAttributes, slices.Compact(slices.Clone(resourceAttributes)), "contains duplicate elements") - assert.Len(t, resourceAttributes, len(tt.wantAttributes[container.Name]), "container should have right amount of attributes") + assert.Len(t, resourceAttributes, len(wantAttributes[container.Name]), "container should have right amount of attributes") - for _, expected := range tt.wantAttributes[container.Name] { + for _, expected := range wantAttributes[container.Name] { assert.Contains(t, resourceAttributes, expected) } diff --git a/pkg/webhook/mutation/pod/webhook_integration_test.go b/pkg/webhook/mutation/pod/webhook_integration_test.go index 6997623766..16ddaa7b2a 100644 --- a/pkg/webhook/mutation/pod/webhook_integration_test.go +++ b/pkg/webhook/mutation/pod/webhook_integration_test.go @@ -126,74 +126,12 @@ func TestWebhook(t *testing.T) { createObject(t, clt, otlpExporterSecret) t.Run("success incl. enrichment rules, custom metadata and metadata annotation propagation", func(t *testing.T) { - dk := &dynakube.DynaKube{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dynakube", - Namespace: testNamespace, - Annotations: map[string]string{ - exp.InjectionAutomaticKey: "true", - }, - }, - Spec: dynakube.DynaKubeSpec{ - OneAgent: oneagent.Spec{ - CloudNativeFullStack: &oneagent.CloudNativeFullStackSpec{}, - }, - MetadataEnrichment: metadataenrichment.Spec{ - Enabled: ptr.To(true), - }, - }, - Status: dynakube.DynaKubeStatus{ - KubernetesClusterMEID: testMEID, - KubernetesClusterName: testClusterName, - MetadataEnrichment: metadataEnrichmentStatus, - KubeSystemUUID: testClusterUUID, - OneAgent: oneagent.Status{ - ConnectionInfo: communication.ConnectionInfo{ - TenantUUID: uuid.NewString(), - }, - }, - CodeModules: oneagent.CodeModulesStatus{ - VersionStatus: status.VersionStatus{ - Version: "1.2.3", - }, - }, - }, - } - createDynaKube(t, clt, dk) - - dummyOwner, ownerReference := getDummyOwnerDeployment() - createObject(t, clt, dummyOwner) - pod := createPod(t, clt, func(pod *corev1.Pod) { - pod.Annotations = podMetadataAnnotations - pod.OwnerReferences = ownerReference + t.Run("with deprecated annotations", func(t *testing.T) { + PropagationTest(t, clt, true) + }) + t.Run("without deprecated annotations", func(t *testing.T) { + PropagationTest(t, clt, false) }) - - require.True(t, maputils.GetFieldBool(pod.Annotations, podmutator.AnnotationDynatraceInjected, false)) - require.True(t, maputils.GetFieldBool(pod.Annotations, metadatamutator.AnnotationInjected, false)) - require.True(t, maputils.GetFieldBool(pod.Annotations, oneagentmutator.AnnotationInjected, false)) - assert.Equal(t, "sales", maputils.GetField(pod.Annotations, "metadata.dynatrace.com/dt.cost.costcenter", "")) - assert.Equal(t, "high", maputils.GetField(pod.Annotations, "metadata.dynatrace.com/dt.security_context", "")) - assert.Equal(t, "custom-ns-meta-value", maputils.GetField(pod.Annotations, "metadata.dynatrace.com/custom.ns-meta", "")) - require.Len(t, pod.Spec.InitContainers, 1) - assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.workload.kind", strings.ToLower(pod.OwnerReferences[0].Kind))) - assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.workload.name", strings.ToLower(pod.OwnerReferences[0].Name))) - assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument(metadatamutator.DeprecatedWorkloadKindKey, strings.ToLower(pod.OwnerReferences[0].Kind))) - assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument(metadatamutator.DeprecatedWorkloadNameKey, strings.ToLower(pod.OwnerReferences[0].Name))) - assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("custom.ns-meta", "custom-ns-meta-value")) - assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("dt.security_context", "high")) - assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("dt.cost.costcenter", "sales")) - assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.namespace.label."+testCustomMetadataLabel, "custom-label")) - assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.namespace.annotation."+testCustomMetadataAnnotation, "custom-annotation")) - assert.Contains(t, pod.Spec.InitContainers[0].Args, "--"+bootstrapper.MetadataEnrichmentFlag) - assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.pod.uid", "$(K8S_PODUID)")) - assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.pod.name", "$(K8S_PODNAME)")) - assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.node.name", "$(K8S_NODE_NAME)")) - assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.namespace.name", pod.Namespace)) - assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.cluster.uid", testClusterUUID)) - assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.cluster.name", testClusterName)) - assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("dt.entity.kubernetes_cluster", testMEID)) - assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("dt.kubernetes.cluster.id", testClusterUUID)) - assert.Contains(t, pod.Spec.InitContainers[0].Args, "--attribute-container={\"container_image.registry\":\"docker.io\",\"container_image.repository\":\"myapp\",\"container_image.tags\":\"1.2.3\",\"k8s.container.name\":\"app\"}") }) t.Run("success with proper precedence", func(t *testing.T) { @@ -348,7 +286,87 @@ func TestWebhook(t *testing.T) { }) } -func TestOTLPWebhook(t *testing.T) { +func PropagationTest(t *testing.T, clt client.Client, withDeprecatedAnnotations bool) { + dk := &dynakube.DynaKube{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dynakube", + Namespace: testNamespace, + Annotations: map[string]string{ + exp.InjectionAutomaticKey: "true", + }, + }, + Spec: dynakube.DynaKubeSpec{ + OneAgent: oneagent.Spec{ + CloudNativeFullStack: &oneagent.CloudNativeFullStackSpec{}, + }, + MetadataEnrichment: metadataenrichment.Spec{ + Enabled: ptr.To(true), + }, + }, + Status: dynakube.DynaKubeStatus{ + KubernetesClusterMEID: testMEID, + KubernetesClusterName: testClusterName, + MetadataEnrichment: metadataEnrichmentStatus, + KubeSystemUUID: testClusterUUID, + OneAgent: oneagent.Status{ + ConnectionInfo: communication.ConnectionInfo{ + TenantUUID: uuid.NewString(), + }, + }, + CodeModules: oneagent.CodeModulesStatus{ + VersionStatus: status.VersionStatus{ + Version: "1.2.3", + }, + }, + }, + } + + if withDeprecatedAnnotations { + dk.Annotations[exp.EnrichmentEnableAttributesDtKubernetes] = "true" + } + + createDynaKube(t, clt, dk) + + dummyOwner, ownerReference := getDummyOwnerDeployment() + createObject(t, clt, dummyOwner) + pod := createPod(t, clt, func(pod *corev1.Pod) { + pod.Annotations = podMetadataAnnotations + pod.OwnerReferences = ownerReference + }) + + require.True(t, maputils.GetFieldBool(pod.Annotations, podmutator.AnnotationDynatraceInjected, false)) + require.True(t, maputils.GetFieldBool(pod.Annotations, metadatamutator.AnnotationInjected, false)) + require.True(t, maputils.GetFieldBool(pod.Annotations, oneagentmutator.AnnotationInjected, false)) + assert.Equal(t, "sales", maputils.GetField(pod.Annotations, "metadata.dynatrace.com/dt.cost.costcenter", "")) + assert.Equal(t, "high", maputils.GetField(pod.Annotations, "metadata.dynatrace.com/dt.security_context", "")) + assert.Equal(t, "custom-ns-meta-value", maputils.GetField(pod.Annotations, "metadata.dynatrace.com/custom.ns-meta", "")) + require.Len(t, pod.Spec.InitContainers, 1) + assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.workload.kind", strings.ToLower(pod.OwnerReferences[0].Kind))) + assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.workload.name", strings.ToLower(pod.OwnerReferences[0].Name))) + assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("custom.ns-meta", "custom-ns-meta-value")) + assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("dt.security_context", "high")) + assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("dt.cost.costcenter", "sales")) + assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.namespace.label."+testCustomMetadataLabel, "custom-label")) + assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.namespace.annotation."+testCustomMetadataAnnotation, "custom-annotation")) + assert.Contains(t, pod.Spec.InitContainers[0].Args, "--"+bootstrapper.MetadataEnrichmentFlag) + assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.pod.uid", "$(K8S_PODUID)")) + assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.pod.name", "$(K8S_PODNAME)")) + assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.node.name", "$(K8S_NODE_NAME)")) + assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.namespace.name", pod.Namespace)) + assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.cluster.uid", testClusterUUID)) + assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("k8s.cluster.name", testClusterName)) + assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("dt.entity.kubernetes_cluster", testMEID)) + + if withDeprecatedAnnotations { + assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument(metadatamutator.DeprecatedWorkloadKindKey, strings.ToLower(pod.OwnerReferences[0].Kind))) + assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument(metadatamutator.DeprecatedWorkloadNameKey, strings.ToLower(pod.OwnerReferences[0].Name))) + assert.Contains(t, pod.Spec.InitContainers[0].Args, buildArgument("dt.kubernetes.cluster.id", testClusterUUID)) + } + + assert.Contains(t, pod.Spec.InitContainers[0].Args, "--attribute-container={\"container_image.registry\":\"docker.io\",\"container_image.repository\":\"myapp\",\"container_image.tags\":\"1.2.3\",\"k8s.container.name\":\"app\"}") +} + +func TestOTLPWebhook(t *testing.T) { //nolint:revive clt := integrationtests.SetupWebhookTestEnvironment(t, getWebhookInstallOptions(), @@ -375,136 +393,163 @@ func TestOTLPWebhook(t *testing.T) { t.Run("otlp exporter with ns metadata propagation and custom enrichment rules", func(t *testing.T) { apiURL := "https://example.live.dynatrace.com" - dk := &dynakube.DynaKube{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dynakube", - Namespace: testNamespace, - Annotations: map[string]string{ - exp.InjectionAutomaticKey: "true", - }, - }, - Spec: dynakube.DynaKubeSpec{ - APIURL: apiURL, - OTLPExporterConfiguration: &otlpspec.ExporterConfigurationSpec{ - NamespaceSelector: metav1.LabelSelector{ // match test namespace label applied earlier - MatchExpressions: []metav1.LabelSelectorRequirement{ - {Key: podmutator.InjectionInstanceLabel, Operator: metav1.LabelSelectorOpExists}, - }, - }, - Signals: otlpspec.SignalConfiguration{ - Metrics: &otlpspec.MetricsSignal{}, - Logs: &otlpspec.LogsSignal{}, - Traces: &otlpspec.TracesSignal{}, - }, - }, + type testCase struct { + name string + annotations map[string]string + withDeprecatedAttributes bool + } + + testCases := []testCase{ + { + name: "without deprecated annotations", + annotations: map[string]string{}, + withDeprecatedAttributes: false, }, - Status: dynakube.DynaKubeStatus{ - KubernetesClusterMEID: testMEID, - KubernetesClusterName: testClusterName, - MetadataEnrichment: metadataEnrichmentStatus, + { + name: "with deprecated annotations", + annotations: map[string]string{exp.EnrichmentEnableAttributesDtKubernetes: "true"}, + withDeprecatedAttributes: true, }, } - apiTokenSecret := getOTLPExporterSecret(testNamespace) - createObject(t, clt, apiTokenSecret) - - createDynaKube(t, clt, dk) - - dummyOwner, ownerReference := getDummyOwnerDeployment() - createObject(t, clt, dummyOwner) - pod := createPod(t, clt, func(pod *corev1.Pod) { - pod.Annotations = podMetadataAnnotations - pod.OwnerReferences = ownerReference - }) - - // verify mutation occurred by presence of OTLP env vars (annotation may not be set when no OneAgent injection) - - appContainer := pod.Spec.Containers[0] - // Expect DT_API_TOKEN env var via secret ref - var dtTokenEnv *corev1.EnvVar - for i := range appContainer.Env { - if appContainer.Env[i].Name == exporter.DynatraceAPITokenEnv { - dtTokenEnv = &appContainer.Env[i] - - break - } + for _, tc := range testCases { + annotations := map[string]string{exp.InjectionAutomaticKey: "true"} + t.Run(tc.name, func(t *testing.T) { + maps.Copy(annotations, tc.annotations) + dk := &dynakube.DynaKube{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dynakube", + Namespace: testNamespace, + Annotations: annotations, + }, + Spec: dynakube.DynaKubeSpec{ + APIURL: apiURL, + OTLPExporterConfiguration: &otlpspec.ExporterConfigurationSpec{ + NamespaceSelector: metav1.LabelSelector{ // match test namespace label applied earlier + MatchExpressions: []metav1.LabelSelectorRequirement{ + {Key: podmutator.InjectionInstanceLabel, Operator: metav1.LabelSelectorOpExists}, + }, + }, + Signals: otlpspec.SignalConfiguration{ + Metrics: &otlpspec.MetricsSignal{}, + Logs: &otlpspec.LogsSignal{}, + Traces: &otlpspec.TracesSignal{}, + }, + }, + }, + Status: dynakube.DynaKubeStatus{ + KubernetesClusterMEID: testMEID, + KubernetesClusterName: testClusterName, + MetadataEnrichment: metadataEnrichmentStatus, + }, + } + + apiTokenSecret := getOTLPExporterSecret(testNamespace) + createObject(t, clt, apiTokenSecret) + + createDynaKube(t, clt, dk) + + dummyOwner, ownerReference := getDummyOwnerDeployment() + createObject(t, clt, dummyOwner) + pod := createPod(t, clt, func(pod *corev1.Pod) { + pod.Annotations = podMetadataAnnotations + pod.OwnerReferences = ownerReference + }) + + // verify mutation occurred by presence of OTLP env vars (annotation may not be set when no OneAgent injection) + + appContainer := pod.Spec.Containers[0] + // Expect DT_API_TOKEN env var via secret ref + var dtTokenEnv *corev1.EnvVar + for i := range appContainer.Env { + if appContainer.Env[i].Name == exporter.DynatraceAPITokenEnv { + dtTokenEnv = &appContainer.Env[i] + + break + } + } + + require.NotNil(t, dtTokenEnv, "expected DT_API_TOKEN env var to be injected") + require.NotNil(t, dtTokenEnv.ValueFrom) + require.NotNil(t, dtTokenEnv.ValueFrom.SecretKeyRef) + assert.Equal(t, consts.OTLPExporterSecretName, dtTokenEnv.ValueFrom.SecretKeyRef.Name) + assert.Equal(t, dynatrace.DataIngestToken, dtTokenEnv.ValueFrom.SecretKeyRef.Key) + + // Headers env vars should reference DT_API_TOKEN via authorization header literal + assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPMetricsHeadersEnv, Value: exporter.OTLPAuthorizationHeader}) + assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPLogsHeadersEnv, Value: exporter.OTLPAuthorizationHeader}) + assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPTraceHeadersEnv, Value: exporter.OTLPAuthorizationHeader}) + + // Endpoint base constructed by BuildOTLPEndpoint(apiURL) => apiURL + /v2/otlp plus per-signal suffix + baseEndpoint := apiURL + "/v2/otlp" + assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPMetricsEndpointEnv, Value: baseEndpoint + "/v1/metrics"}) + assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPLogsEndpointEnv, Value: baseEndpoint + "/v1/logs"}) + assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPTraceEndpointEnv, Value: baseEndpoint + "/v1/traces"}) + + // metrics temporality preference should be set to delta + assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPMetricsExporterTemporalityPreference, Value: exporter.OTLPMetricsExporterAggregationTemporalityDelta}) + + raEnv := k8senv.Find(appContainer.Env, resourceattributes.OTELResourceAttributesEnv) + + require.NotNil(t, raEnv, "OTEL_RESOURCE_ATTRIBUTES missing") + + gotResourceAttributes, envVarFound := resourceattributes.NewAttributesFromEnv(appContainer.Env, resourceattributes.OTELResourceAttributesEnv) + require.True(t, envVarFound, "OTEL_RESOURCE_ATTRIBUTES missing") + + assert.Equal(t, testNamespace, gotResourceAttributes["k8s.namespace.name"]) + assert.Equal(t, "$(K8S_PODUID)", gotResourceAttributes["k8s.pod.uid"]) + assert.Equal(t, "$(K8S_PODNAME)", gotResourceAttributes["k8s.pod.name"]) + assert.Equal(t, "$(K8S_NODE_NAME)", gotResourceAttributes["k8s.node.name"]) + assert.Contains(t, appContainer.Env, corev1.EnvVar{ + Name: "K8S_PODUID", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.uid", + }, + }, + }) + assert.Contains(t, appContainer.Env, corev1.EnvVar{ + Name: "K8S_PODNAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.name", + }, + }, + }) + assert.Contains(t, appContainer.Env, corev1.EnvVar{ + Name: "K8S_NODE_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "spec.nodeName", + }, + }, + }) + assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPLogsEndpointEnv, Value: baseEndpoint + "/v1/logs"}) + assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPTraceEndpointEnv, Value: baseEndpoint + "/v1/traces"}) + assert.Equal(t, dk.Status.KubernetesClusterName, gotResourceAttributes["k8s.cluster.name"]) + assert.Equal(t, pod.Spec.Containers[0].Name, gotResourceAttributes["k8s.container.name"]) + assert.Equal(t, pod.OwnerReferences[0].Name, gotResourceAttributes["k8s.workload.name"]) + assert.Equal(t, strings.ToLower(pod.OwnerReferences[0].Kind), gotResourceAttributes["k8s.workload.kind"]) + + if tc.withDeprecatedAttributes { + assert.Equal(t, dk.Status.KubeSystemUUID, gotResourceAttributes["dt.kubernetes.cluster.id"]) + assert.Equal(t, dk.Status.KubernetesClusterMEID, gotResourceAttributes["dt.entity.kubernetes_cluster"]) + assert.Equal(t, pod.OwnerReferences[0].Name, gotResourceAttributes[metadatamutator.DeprecatedWorkloadNameKey]) + assert.Equal(t, strings.ToLower(pod.OwnerReferences[0].Kind), gotResourceAttributes[metadatamutator.DeprecatedWorkloadKindKey]) + } + + assert.Equal(t, url.QueryEscape(nsMetadataAnnotations["metadata.dynatrace.com/custom.ns-meta"]), gotResourceAttributes["custom.ns-meta"]) + assert.Equal(t, url.QueryEscape(podMetadataAnnotations["metadata.dynatrace.com/service.name"]), gotResourceAttributes["service.name"]) + assert.Equal(t, url.QueryEscape(podMetadataAnnotations["metadata.dynatrace.com/custom.key"]), gotResourceAttributes["custom.key"]) + assert.Equal(t, url.QueryEscape(nsMetadataAnnotations[testCustomMetadataAnnotation]), gotResourceAttributes["k8s.namespace.annotation."+testCustomMetadataAnnotation]) + assert.Equal(t, url.QueryEscape(nsMetadataAnnotations[testCostCenterAnnotation]), gotResourceAttributes["dt.cost.costcenter"]) + assert.Equal(t, url.QueryEscape(nsMetadataLabels[testSecContextLabel]), gotResourceAttributes["dt.security_context"]) + assert.Equal(t, url.QueryEscape(nsMetadataLabels[testCustomMetadataLabel]), gotResourceAttributes["k8s.namespace.label."+testCustomMetadataLabel]) + }) } - - require.NotNil(t, dtTokenEnv, "expected DT_API_TOKEN env var to be injected") - require.NotNil(t, dtTokenEnv.ValueFrom) - require.NotNil(t, dtTokenEnv.ValueFrom.SecretKeyRef) - assert.Equal(t, consts.OTLPExporterSecretName, dtTokenEnv.ValueFrom.SecretKeyRef.Name) - assert.Equal(t, dynatrace.DataIngestToken, dtTokenEnv.ValueFrom.SecretKeyRef.Key) - - // Headers env vars should reference DT_API_TOKEN via authorization header literal - assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPMetricsHeadersEnv, Value: exporter.OTLPAuthorizationHeader}) - assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPLogsHeadersEnv, Value: exporter.OTLPAuthorizationHeader}) - assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPTraceHeadersEnv, Value: exporter.OTLPAuthorizationHeader}) - - // Endpoint base constructed by BuildOTLPEndpoint(apiURL) => apiURL + /v2/otlp plus per-signal suffix - baseEndpoint := apiURL + "/v2/otlp" - assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPMetricsEndpointEnv, Value: baseEndpoint + "/v1/metrics"}) - assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPLogsEndpointEnv, Value: baseEndpoint + "/v1/logs"}) - assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPTraceEndpointEnv, Value: baseEndpoint + "/v1/traces"}) - - // metrics temporality preference should be set to delta - assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPMetricsExporterTemporalityPreference, Value: exporter.OTLPMetricsExporterAggregationTemporalityDelta}) - - raEnv := k8senv.Find(appContainer.Env, resourceattributes.OTELResourceAttributesEnv) - - require.NotNil(t, raEnv, "OTEL_RESOURCE_ATTRIBUTES missing") - - gotResourceAttributes, envVarFound := resourceattributes.NewAttributesFromEnv(appContainer.Env, resourceattributes.OTELResourceAttributesEnv) - require.True(t, envVarFound, "OTEL_RESOURCE_ATTRIBUTES missing") - - assert.Equal(t, testNamespace, gotResourceAttributes["k8s.namespace.name"]) - assert.Equal(t, "$(K8S_PODUID)", gotResourceAttributes["k8s.pod.uid"]) - assert.Equal(t, "$(K8S_PODNAME)", gotResourceAttributes["k8s.pod.name"]) - assert.Equal(t, "$(K8S_NODE_NAME)", gotResourceAttributes["k8s.node.name"]) - assert.Contains(t, appContainer.Env, corev1.EnvVar{ - Name: "K8S_PODUID", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.uid", - }, - }, - }) - assert.Contains(t, appContainer.Env, corev1.EnvVar{ - Name: "K8S_PODNAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.name", - }, - }, - }) - assert.Contains(t, appContainer.Env, corev1.EnvVar{ - Name: "K8S_NODE_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "spec.nodeName", - }, - }, - }) - assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPLogsEndpointEnv, Value: baseEndpoint + "/v1/logs"}) - assert.Contains(t, appContainer.Env, corev1.EnvVar{Name: exporter.OTLPTraceEndpointEnv, Value: baseEndpoint + "/v1/traces"}) - assert.Equal(t, dk.Status.KubernetesClusterName, gotResourceAttributes["k8s.cluster.name"]) - assert.Equal(t, pod.Spec.Containers[0].Name, gotResourceAttributes["k8s.container.name"]) - assert.Equal(t, dk.Status.KubeSystemUUID, gotResourceAttributes["dt.kubernetes.cluster.id"]) - assert.Equal(t, dk.Status.KubernetesClusterMEID, gotResourceAttributes["dt.entity.kubernetes_cluster"]) - assert.Equal(t, pod.OwnerReferences[0].Name, gotResourceAttributes["k8s.workload.name"]) - assert.Equal(t, pod.OwnerReferences[0].Name, gotResourceAttributes[metadatamutator.DeprecatedWorkloadNameKey]) - assert.Equal(t, strings.ToLower(pod.OwnerReferences[0].Kind), gotResourceAttributes["k8s.workload.kind"]) - assert.Equal(t, strings.ToLower(pod.OwnerReferences[0].Kind), gotResourceAttributes[metadatamutator.DeprecatedWorkloadKindKey]) - assert.Equal(t, url.QueryEscape(nsMetadataAnnotations["metadata.dynatrace.com/custom.ns-meta"]), gotResourceAttributes["custom.ns-meta"]) - assert.Equal(t, url.QueryEscape(podMetadataAnnotations["metadata.dynatrace.com/service.name"]), gotResourceAttributes["service.name"]) - assert.Equal(t, url.QueryEscape(podMetadataAnnotations["metadata.dynatrace.com/custom.key"]), gotResourceAttributes["custom.key"]) - assert.Equal(t, url.QueryEscape(nsMetadataAnnotations[testCustomMetadataAnnotation]), gotResourceAttributes["k8s.namespace.annotation."+testCustomMetadataAnnotation]) - assert.Equal(t, url.QueryEscape(nsMetadataAnnotations[testCostCenterAnnotation]), gotResourceAttributes["dt.cost.costcenter"]) - assert.Equal(t, url.QueryEscape(nsMetadataLabels[testSecContextLabel]), gotResourceAttributes["dt.security_context"]) - assert.Equal(t, url.QueryEscape(nsMetadataLabels[testCustomMetadataLabel]), gotResourceAttributes["k8s.namespace.label."+testCustomMetadataLabel]) }) t.Run("otlp exporter attribute precedence", func(t *testing.T) {