Skip to content

Commit beff964

Browse files
fix(ssi): merge target and namespace implementations (#42068)
### What does this PR do? When we introduced targets, we created a separate code path to handle the target based configuration. However, the namespace based configuration can also be represented and supported by the target implementation. This commit merges the two implementations. When I merged these in code, I found bugs in the target implementation surrounding [Local SDK Injection](https://docs.datadoghq.com/tracing/guide/local_sdk_injection/) and how it was handled with targets defined. This change fixes those bugs to match the expectations around enabledNamespaces. ### Motivation We want to reduce the complexity of the auto instrumentation webhook. There is no need to have two implementations to support our customer facing configuration options and as shown with this change, two implementations open up more chances for bugs. See [SSI Kubernetes | Platform Stability](https://docs.google.com/document/d/1NqrPEUn3RfcdS_hQUQB-pJFJN9N-x5I202CB7s7CnwQ/edit?usp=sharing) for more details on all changes related to cleanup. ### Describe how you validated your changes I tested this change heavily using [injector-dev](https://github.com/DataDog/injector-dev). For each scenario listed, I ran with and without the `--build` flag to use the latest agent release vs this branch to ensure there was a seamless migration path. <details> <summary>Broken annotation based injection with target list</summary> This should have injection but it does not currently: ```yaml helm: apps: - name: annotation-example namespace: application values: env: - name: DD_TRACE_DEBUG value: "true" - name: DD_APM_INSTRUMENTATION_DEBUG value: "true" image: repository: registry.ddbuild.io/ci/injector-dev/python tag: 2cd78ded podLabels: admission.datadoghq.com/enabled: "true" tags.datadoghq.com/env: local podAnnotations: admission.datadoghq.com/python-lib.version: "v3" service: port: "8080" versions: agent: 7.71.1 cluster_agent: version: 7.71.1 build: {} injector: 0.48.0 config: datadog: apm: instrumentation: enabled: false targets: - name: python podSelector: matchLabels: language: python ddTraceVersions: python: "3" ``` </details> <details> <summary>Annotation based injection</summary> We expect injection: ```yaml helm: apps: - name: annotation-example namespace: application values: env: - name: DD_TRACE_DEBUG value: "true" - name: DD_APM_INSTRUMENTATION_DEBUG value: "true" image: repository: registry.ddbuild.io/ci/injector-dev/python tag: 2cd78ded podLabels: admission.datadoghq.com/enabled: "true" tags.datadoghq.com/env: local podAnnotations: admission.datadoghq.com/python-lib.version: "v3" service: port: "8080" versions: agent: 7.71.1 cluster_agent: version: 7.71.1 build: {} injector: 0.48.0 config: datadog: apm: instrumentation: enabled: false ``` </details> <details> <summary>Namespace based selection</summary> We expect injection: ```yaml helm: apps: - name: namespace-selection-example namespace: application values: env: - name: DD_TRACE_DEBUG value: "true" - name: DD_APM_INSTRUMENTATION_DEBUG value: "true" image: repository: registry.ddbuild.io/ci/injector-dev/python tag: 2cd78ded podLabels: tags.datadoghq.com/env: local service: port: "8080" versions: agent: 7.71.1 cluster_agent: version: 7.71.1 build: {} injector: 0.48.0 config: datadog: apm: instrumentation: enabled: true enabledNamespaces: - "application" libVersions: python: "3" ``` </details> <details> <summary>Workload selection</summary> We expect injection: ```yaml helm: apps: - name: workload-selection-example namespace: application values: env: - name: DD_TRACE_DEBUG value: "true" - name: DD_APM_INSTRUMENTATION_DEBUG value: "true" image: repository: registry.ddbuild.io/ci/injector-dev/python tag: 2cd78ded podLabels: language: python tags.datadoghq.com/env: local service: port: "8080" versions: agent: 7.71.1 cluster_agent: version: 7.71.1 build: {} injector: 0.48.0 config: datadog: apm: instrumentation: enabled: true targets: - name: python podSelector: matchLabels: language: python ddTraceVersions: python: "3" ``` </details> <details> <summary>Instrumentation disabled</summary> We expect no injection to occur: ```yaml helm: apps: - name: disabled-example namespace: application values: env: - name: DD_TRACE_DEBUG value: "true" - name: DD_APM_INSTRUMENTATION_DEBUG value: "true" image: repository: registry.ddbuild.io/ci/injector-dev/python tag: 2cd78ded podLabels: tags.datadoghq.com/env: local service: port: "8080" versions: agent: 7.71.1 cluster_agent: version: 7.71.1 build: {} injector: 0.48.0 config: datadog: apm: instrumentation: enabled: false ``` </details> ### Additional Notes This change opted to not change the behavior of any tests and to keep test changes minimal. A follow up PR will rewrite some of these tests to test the webhook behavior more directly. Co-authored-by: stanistan <[email protected]> Co-authored-by: mark.spicer <[email protected]>
1 parent d5e91be commit beff964

File tree

12 files changed

+424
-369
lines changed

12 files changed

+424
-369
lines changed

pkg/clusteragent/admission/controllers/webhook/controller_base.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ func generateAutoInstrumentationWebhook(wmeta workloadmeta.Component, datadogCon
189189
return nil, fmt.Errorf("failed to create auto instrumentation config: %v", err)
190190
}
191191

192-
apm, err := autoinstrumentation.NewMutatorWithFilter(config, wmeta, imageResolver)
192+
apm, err := autoinstrumentation.NewTargetMutator(config, wmeta, imageResolver)
193193
if err != nil {
194194
return nil, fmt.Errorf("failed to create auto instrumentation namespace mutator: %v", err)
195195
}

pkg/clusteragent/admission/mutate/autoinstrumentation/auto_instrumentation.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,10 +218,6 @@ func (s libInfoSource) injectionType() string {
218218
}
219219
}
220220

221-
func (s libInfoSource) isSingleStep() bool {
222-
return s.injectionType() == singleStepInstrumentationInstallType
223-
}
224-
225221
// isFromLanguageDetection tells us whether this source comes from
226222
// the language detection reporting and annotation.
227223
func (s libInfoSource) isFromLanguageDetection() bool {

pkg/clusteragent/admission/mutate/autoinstrumentation/auto_instrumentation_test.go

Lines changed: 49 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ func TestInjectAutoInstruConfigV2(t *testing.T) {
447447
tt.expectedInstallType = "k8s_single_step"
448448
}
449449

450-
mutator, err := NewNamespaceMutator(config, wmeta, imageResolver)
450+
mutator, err := NewTargetMutator(config, wmeta, imageResolver)
451451
require.NoError(t, err)
452452

453453
err = mutator.core.injectTracers(tt.pod, tt.libInfo)
@@ -581,7 +581,7 @@ func TestMutatorCoreNewInjector(t *testing.T) {
581581
)
582582
config, err := NewConfig(mockConfig)
583583
require.NoError(t, err)
584-
m, err := NewNamespaceMutator(config, wmeta, imageResolver)
584+
m, err := NewTargetMutator(config, wmeta, imageResolver)
585585
require.NoError(t, err)
586586
core := m.core
587587

@@ -618,14 +618,12 @@ func TestExtractLibInfo(t *testing.T) {
618618

619619
var mockConfig model.Config
620620
tests := []struct {
621-
name string
622-
pod *corev1.Pod
623-
deployments []common.MockDeployment
624-
assertExtractedLibInfo func(*testing.T, extractedPodLibInfo)
625-
containerRegistry string
626-
expectedLibsToInject []libInfo
627-
expectedPodEligible *bool
628-
setupConfig func()
621+
name string
622+
pod *corev1.Pod
623+
containerRegistry string
624+
expectedLibsToInject []libInfo
625+
expectedPodEligible *bool
626+
setupConfig func()
629627
}{
630628
{
631629
name: "java",
@@ -669,13 +667,11 @@ func TestExtractLibInfo(t *testing.T) {
669667
},
670668
},
671669
{
672-
name: "python with unlabelled injection off",
673-
pod: common.FakePodWithAnnotation("admission.datadoghq.com/python-lib.version", "v1"),
674-
containerRegistry: "registry",
675-
expectedPodEligible: pointer.Ptr(false),
676-
expectedLibsToInject: []libInfo{
677-
defaultLibInfoWithVersion(python, "v1"),
678-
},
670+
name: "python with unlabelled injection off",
671+
pod: common.FakePodWithAnnotation("admission.datadoghq.com/python-lib.version", "v1"),
672+
containerRegistry: "registry",
673+
expectedPodEligible: pointer.Ptr(false),
674+
expectedLibsToInject: []libInfo{},
679675
setupConfig: func() {
680676
mockConfig.SetWithoutSource("admission_controller.mutate_unlabelled", false)
681677
},
@@ -756,7 +752,7 @@ func TestExtractLibInfo(t *testing.T) {
756752
pod: common.FakePodWithAnnotation("admission.datadoghq.com/all-lib.version", "latest"),
757753
containerRegistry: "registry",
758754
expectedPodEligible: pointer.Ptr(false),
759-
expectedLibsToInject: allLatestDefaultLibs,
755+
expectedLibsToInject: []libInfo{},
760756
setupConfig: func() {
761757
mockConfig.SetWithoutSource("admission_controller.mutate_unlabelled", false)
762758
},
@@ -780,16 +776,6 @@ func TestExtractLibInfo(t *testing.T) {
780776
mockConfig.SetWithoutSource("admission_controller.mutate_unlabelled", false)
781777
},
782778
},
783-
{
784-
name: "all with mutate_unlabelled off",
785-
pod: common.FakePodWithAnnotation("admission.datadoghq.com/all-lib.version", "latest"),
786-
containerRegistry: "registry",
787-
expectedPodEligible: pointer.Ptr(false),
788-
expectedLibsToInject: allLatestDefaultLibs,
789-
setupConfig: func() {
790-
mockConfig.SetWithoutSource("admission_controller.mutate_unlabelled", false)
791-
},
792-
},
793779
{
794780
name: "all with mutate_unlabelled off, but labelled admission enabled",
795781
pod: &corev1.Pod{
@@ -810,32 +796,41 @@ func TestExtractLibInfo(t *testing.T) {
810796
},
811797
},
812798
{
813-
name: "all with mutate_unlabelled off",
814-
pod: common.FakePodWithAnnotation("admission.datadoghq.com/all-lib.version", "latest"),
799+
name: "all with mutate_unlabelled off, but labelled admission enabled",
800+
pod: &corev1.Pod{
801+
ObjectMeta: metav1.ObjectMeta{
802+
Annotations: map[string]string{
803+
"admission.datadoghq.com/all-lib.version": "latest",
804+
},
805+
Labels: map[string]string{
806+
"admission.datadoghq.com/enabled": "true",
807+
},
808+
},
809+
},
815810
containerRegistry: "registry",
816-
expectedPodEligible: pointer.Ptr(false),
811+
expectedPodEligible: pointer.Ptr(true),
817812
expectedLibsToInject: allLatestDefaultLibs,
818813
setupConfig: func() {
819814
mockConfig.SetWithoutSource("admission_controller.mutate_unlabelled", false)
820815
},
821816
},
822817
{
823-
name: "all with mutate_unlabelled off, but labelled admission enabled",
818+
name: "java with mutate_unlabelled on and labelled admission disabled",
824819
pod: &corev1.Pod{
825820
ObjectMeta: metav1.ObjectMeta{
826821
Annotations: map[string]string{
827-
"admission.datadoghq.com/all-lib.version": "latest",
822+
"admission.datadoghq.com/java-lib.version": "v1",
828823
},
829824
Labels: map[string]string{
830-
"admission.datadoghq.com/enabled": "true",
825+
"admission.datadoghq.com/enabled": "false",
831826
},
832827
},
833828
},
834829
containerRegistry: "registry",
835-
expectedPodEligible: pointer.Ptr(true),
836-
expectedLibsToInject: allLatestDefaultLibs,
830+
expectedPodEligible: pointer.Ptr(false),
831+
expectedLibsToInject: []libInfo{},
837832
setupConfig: func() {
838-
mockConfig.SetWithoutSource("admission_controller.mutate_unlabelled", false)
833+
mockConfig.SetWithoutSource("admission_controller.mutate_unlabelled", true)
839834
},
840835
},
841836
{
@@ -917,74 +912,6 @@ func TestExtractLibInfo(t *testing.T) {
917912
mockConfig.SetWithoutSource("admission_controller.mutate_unlabelled", true)
918913
},
919914
},
920-
{
921-
name: "pod with lang-detection deployment and default libs",
922-
pod: common.FakePodSpec{
923-
ParentKind: "replicaset",
924-
ParentName: "deployment-123",
925-
}.Create(),
926-
deployments: []common.MockDeployment{
927-
{
928-
ContainerName: "pod",
929-
DeploymentName: "deployment",
930-
Namespace: "ns",
931-
Languages: languageSetOf("python"),
932-
},
933-
},
934-
containerRegistry: "registry",
935-
assertExtractedLibInfo: func(t *testing.T, i extractedPodLibInfo) {
936-
t.Helper()
937-
require.Equal(t, &libInfoLanguageDetection{
938-
libs: []libInfo{
939-
python.defaultLibInfo("registry", "pod"),
940-
},
941-
injectionEnabled: true,
942-
}, i.languageDetection)
943-
},
944-
expectedLibsToInject: []libInfo{
945-
python.defaultLibInfo("registry", "pod"),
946-
},
947-
setupConfig: func() {
948-
mockConfig.SetWithoutSource("apm_config.instrumentation.enabled", true)
949-
mockConfig.SetWithoutSource("apm_config.instrumentation.lib_versions", defaultLibraries)
950-
mockConfig.SetWithoutSource("language_detection.enabled", true)
951-
mockConfig.SetWithoutSource("language_detection.reporting.enabled", true)
952-
mockConfig.SetWithoutSource("admission_controller.auto_instrumentation.inject_auto_detected_libraries", true)
953-
},
954-
},
955-
{
956-
name: "pod with lang-detection deployment and libs set",
957-
pod: common.FakePodSpec{
958-
ParentKind: "replicaset",
959-
ParentName: "deployment-123",
960-
}.Create(),
961-
deployments: []common.MockDeployment{
962-
{
963-
ContainerName: "pod",
964-
DeploymentName: "deployment",
965-
Namespace: "ns",
966-
Languages: languageSetOf("python"),
967-
},
968-
},
969-
containerRegistry: "registry",
970-
assertExtractedLibInfo: func(t *testing.T, i extractedPodLibInfo) {
971-
t.Helper()
972-
require.Equal(t, &libInfoLanguageDetection{
973-
libs: []libInfo{python.defaultLibInfo("registry", "pod")},
974-
injectionEnabled: true,
975-
}, i.languageDetection)
976-
},
977-
expectedLibsToInject: []libInfo{
978-
java.defaultLibInfo("registry", ""),
979-
},
980-
setupConfig: func() {
981-
mockConfig.SetWithoutSource("apm_config.instrumentation.enabled", true)
982-
mockConfig.SetWithoutSource("apm_config.instrumentation.lib_versions", defaultLibrariesFor("java"))
983-
mockConfig.SetWithoutSource("language_detection.enabled", true)
984-
mockConfig.SetWithoutSource("language_detection.reporting.enabled", true)
985-
mockConfig.SetWithoutSource("admission_controller.auto_instrumentation.inject_auto_detected_libraries", true)
986-
},
987-
},
988915
{
989916
name: "php",
990917
pod: common.FakePodWithAnnotation("admission.datadoghq.com/php-lib.version", "v1"),
@@ -1008,7 +935,7 @@ func TestExtractLibInfo(t *testing.T) {
1008935
mockConfig.SetWithoutSource(k, v)
1009936
}
1010937

1011-
wmeta := common.FakeStoreWithDeployment(t, tt.deployments)
938+
wmeta := common.FakeStoreWithDeployment(t, nil)
1012939
mockConfig = configmock.New(t)
1013940
for k, v := range overrides {
1014941
mockConfig.SetWithoutSource(k, v)
@@ -1020,18 +947,19 @@ func TestExtractLibInfo(t *testing.T) {
1020947

1021948
config, err := NewConfig(mockConfig)
1022949
require.NoError(t, err)
1023-
mutator, err := NewNamespaceMutator(config, wmeta, imageResolver)
950+
mutator, err := NewTargetMutator(config, wmeta, imageResolver)
1024951
require.NoError(t, err)
1025952

1026953
if tt.expectedPodEligible != nil {
1027-
require.Equal(t, *tt.expectedPodEligible, mutator.isPodEligible(tt.pod))
954+
require.Equal(t, *tt.expectedPodEligible, mutator.ShouldMutatePod(tt.pod))
1028955
}
1029956

1030-
extracted := mutator.extractLibInfo(tt.pod)
1031-
if tt.assertExtractedLibInfo != nil {
1032-
tt.assertExtractedLibInfo(t, extracted)
957+
libVersions := []libInfo{}
958+
internalTarget := mutator.getTarget(tt.pod)
959+
if internalTarget != nil {
960+
libVersions = internalTarget.libVersions
1033961
}
1034-
require.ElementsMatch(t, tt.expectedLibsToInject, extracted.libs)
962+
require.ElementsMatch(t, tt.expectedLibsToInject, libVersions)
1035963
})
1036964
}
1037965
}
@@ -1743,7 +1671,7 @@ func TestInjectLibInitContainer(t *testing.T) {
17431671
// N.B. this is a bit hacky but consistent.
17441672
config.initSecurityContext = tt.secCtx
17451673

1746-
mutator, err := NewNamespaceMutator(config, wmeta, imageResolver)
1674+
mutator, err := NewTargetMutator(config, wmeta, imageResolver)
17471675
require.NoError(t, err)
17481676

17491677
c := tt.lang.libInfo("", tt.image).initContainers(imageResolver)[0]
@@ -2017,14 +1945,20 @@ func TestShouldInject(t *testing.T) {
20171945
workloadmetafxmock.MockModule(workloadmeta.NewParams()),
20181946
)
20191947

1948+
// Explicitly adding an annotation because we are testing the enabled label, mutateUnlabelled, and the
1949+
// interactions with apm enabled. Users need an explicit annotation for local library injection to work.
1950+
tt.pod.Annotations = map[string]string{
1951+
fmt.Sprintf(customLibAnnotationKeyFormat, "java"): "v1",
1952+
}
1953+
20201954
mockConfig = configmock.New(t)
20211955
tt.setupConfig()
20221956

20231957
config, err := NewConfig(mockConfig)
20241958
require.NoError(t, err)
2025-
mutator, err := NewNamespaceMutator(config, wmeta, imageResolver)
1959+
mutator, err := NewTargetMutator(config, wmeta, imageResolver)
20261960
require.NoError(t, err)
2027-
require.Equal(t, tt.want, mutator.isPodEligible(tt.pod), "expected webhook.isPodEligible() to be %t", tt.want)
1961+
require.Equal(t, tt.want, mutator.ShouldMutatePod(tt.pod), "expected webhook.isPodEligible() to be %t", tt.want)
20281962
})
20291963
}
20301964
}

pkg/clusteragent/admission/mutate/autoinstrumentation/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ type Config struct {
3838
// containerRegistry is the container registry to use for the autoinstrumentation logic
3939
containerRegistry string
4040

41+
// mutateUnlabelled is used to control if we require workloads to have a label when using Local Lib Injection.
42+
mutateUnlabelled bool
43+
4144
// precomputed containerMutators for the security and profiling products
4245
securityClientLibraryMutator containerMutator
4346
profilingClientLibraryMutator containerMutator
@@ -106,11 +109,14 @@ func NewConfig(datadogConfig config.Component) (*Config, error) {
106109
}
107110

108111
containerRegistry := mutatecommon.ContainerRegistry(datadogConfig, "admission_controller.auto_instrumentation.container_registry")
112+
mutateUnlabelled := datadogConfig.GetBool("admission_controller.mutate_unlabelled")
113+
109114
return &Config{
110115
Webhook: NewWebhookConfig(datadogConfig),
111116
LanguageDetection: NewLanguageDetectionConfig(datadogConfig),
112117
Instrumentation: instrumentationConfig,
113118
containerRegistry: containerRegistry,
119+
mutateUnlabelled: mutateUnlabelled,
114120
initResources: initResources,
115121
initSecurityContext: initSecurityContext,
116122
defaultResourceRequirements: defaultResourceRequirements,

pkg/clusteragent/admission/mutate/autoinstrumentation/config_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ func TestNewInstrumentationConfig(t *testing.T) {
3030
shouldErr: false,
3131
expected: &InstrumentationConfig{
3232
Enabled: true,
33-
EnabledNamespaces: []string{"default"},
33+
EnabledNamespaces: []string{"application"},
3434
DisabledNamespaces: []string{},
3535
LibVersions: map[string]string{
36-
"python": "default",
36+
"python": "v3",
3737
},
3838
InjectorImageTag: "foo",
3939
},

pkg/clusteragent/admission/mutate/autoinstrumentation/mutator.go

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)