From 160b3f204abbc28f6bfee92ce13ff6bba8d15b7d Mon Sep 17 00:00:00 2001 From: erikayasuda Date: Thu, 20 Nov 2025 11:24:09 -0500 Subject: [PATCH 01/17] add image resolver config type --- .../autoinstrumentation/image_resolver.go | 65 +++++++------------ .../imageresolver/config.go | 48 ++++++++++++++ 2 files changed, 72 insertions(+), 41 deletions(-) create mode 100644 pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go index 8307a000cff48e..f9c52f3c23f5d9 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go @@ -17,8 +17,8 @@ import ( "github.com/opencontainers/go-digest" - "github.com/DataDog/datadog-agent/comp/core/config" "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/metrics" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver" "github.com/DataDog/datadog-agent/pkg/remoteconfig/state" "github.com/DataDog/datadog-agent/pkg/util/log" ) @@ -67,37 +67,6 @@ type remoteConfigImageResolver struct { retryDelay time.Duration } -// newRemoteConfigImageResolver creates a new remoteConfigImageResolver. -// Assumes rcClient is non-nil. -func newRemoteConfigImageResolver(rcClient RemoteConfigClient, datadoghqRegistries map[string]any) ImageResolver { - return newRemoteConfigImageResolverWithRetryConfig(rcClient, 5, 1*time.Second, datadoghqRegistries) -} - -// newRemoteConfigImageResolverWithRetryConfig creates a resolver with configurable retry behavior. -// Useful for testing with faster retry settings. -func newRemoteConfigImageResolverWithRetryConfig(rcClient RemoteConfigClient, maxRetries int, retryDelay time.Duration, datadoghqRegistries map[string]any) ImageResolver { - resolver := &remoteConfigImageResolver{ - rcClient: rcClient, - imageMappings: make(map[string]map[string]ImageInfo), - maxRetries: maxRetries, - retryDelay: retryDelay, - datadoghqRegistries: datadoghqRegistries, - } - - rcClient.Subscribe(state.ProductGradualRollout, resolver.processUpdate) - log.Debugf("Subscribed to %s", state.ProductGradualRollout) - - go func() { - if err := resolver.waitForInitialConfig(); err != nil { - log.Warnf("Failed to load initial image resolution config: %v. Image resolution will remain disabled.", err) - } else { - log.Infof("Image resolution cache initialized successfully") - } - }() - - return resolver -} - func (r *remoteConfigImageResolver) waitForInitialConfig() error { if currentConfigs := r.rcClient.GetConfigs(state.ProductGradualRollout); len(currentConfigs) > 0 { log.Debugf("Initial configs available immediately: %d configurations", len(currentConfigs)) @@ -284,20 +253,34 @@ func newResolvedImage(registry string, repositoryName string, imageInfo ImageInf // NewImageResolver creates the appropriate ImageResolver based on whether // a remote config client is available. -func NewImageResolver(rcClient RemoteConfigClient, cfg config.Component) ImageResolver { - - if rcClient == nil || reflect.ValueOf(rcClient).IsNil() { +func NewImageResolver(cfg imageresolver.ImageResolverConfig) ImageResolver { + if cfg.RCClient == nil || reflect.ValueOf(cfg.RCClient).IsNil() { log.Debugf("No remote config client available") return newNoOpImageResolver() } - datadogRegistriesSet := cfg.GetStringMap("admission_controller.auto_instrumentation.default_dd_registries") - - return newRemoteConfigImageResolverWithDefaultDatadoghqRegistries(rcClient, datadogRegistriesSet) + return newRcImageResolver(cfg) } -func newRemoteConfigImageResolverWithDefaultDatadoghqRegistries(rcClient RemoteConfigClient, datadoghqRegistries map[string]any) ImageResolver { - resolver := newRemoteConfigImageResolver(rcClient, datadoghqRegistries) - resolver.(*remoteConfigImageResolver).datadoghqRegistries = datadoghqRegistries +func newRcImageResolver(cfg imageresolver.ImageResolverConfig) ImageResolver { + resolver := &remoteConfigImageResolver{ + rcClient: cfg.RCClient, + imageMappings: make(map[string]map[string]ImageInfo), + maxRetries: cfg.MaxInitRetries, + retryDelay: cfg.InitRetryDelay, + datadoghqRegistries: cfg.DDRegistries, + } + + resolver.rcClient.Subscribe(state.ProductGradualRollout, resolver.processUpdate) + log.Debugf("Subscribed to %s", state.ProductGradualRollout) + + go func() { + if err := resolver.waitForInitialConfig(); err != nil { + log.Warnf("Failed to load initial image resolution config: %v. Image resolution will remain disabled.", err) + } else { + log.Infof("Image resolution cache initialized successfully") + } + }() + return resolver } diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go new file mode 100644 index 00000000000000..909e23fa97e1a3 --- /dev/null +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go @@ -0,0 +1,48 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +//go:build kubeapiserver + +package imageresolver + +import ( + "time" + + "github.com/DataDog/datadog-agent/comp/core/config" + "github.com/DataDog/datadog-agent/pkg/remoteconfig/state" +) + +type RemoteConfigClient interface { + GetConfigs(product string) map[string]state.RawConfig + Subscribe(product string, callback func(map[string]state.RawConfig, func(string, state.ApplyStatus))) +} + +type ImageResolverConfig struct { + Site string + DDRegistries map[string]any + RCClient RemoteConfigClient + MaxInitRetries int + InitRetryDelay time.Duration +} + +func NewImageResolverConfig(cfg config.Component, rcClient RemoteConfigClient) *ImageResolverConfig { + return &ImageResolverConfig{ + Site: cfg.GetString("site"), + DDRegistries: cfg.GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), + RCClient: rcClient, + MaxInitRetries: 5, + InitRetryDelay: 1 * time.Second, + } +} + +func NewTestImageResolverConfig(site string, ddRegistries map[string]any, rcClient RemoteConfigClient, maxRetries int, retryDelay time.Duration) *ImageResolverConfig { + return &ImageResolverConfig{ + Site: site, + DDRegistries: ddRegistries, + RCClient: rcClient, + MaxInitRetries: maxRetries, + InitRetryDelay: retryDelay, + } +} From 9e1153346f515b301ad0dd3869bc8772528b748b Mon Sep 17 00:00:00 2001 From: erikayasuda Date: Thu, 20 Nov 2025 13:23:36 -0500 Subject: [PATCH 02/17] update image_resolver_test --- .../autoinstrumentation/image_resolver.go | 46 +++++++++---------- .../image_resolver_test.go | 38 +++++---------- 2 files changed, 35 insertions(+), 49 deletions(-) diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go index f9c52f3c23f5d9..6008234acf2fdc 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go @@ -67,6 +67,29 @@ type remoteConfigImageResolver struct { retryDelay time.Duration } +func newRcImageResolver(cfg imageresolver.ImageResolverConfig) remoteConfigImageResolver { + resolver := &remoteConfigImageResolver{ + rcClient: cfg.RCClient, + imageMappings: make(map[string]map[string]ImageInfo), + maxRetries: cfg.MaxInitRetries, + retryDelay: cfg.InitRetryDelay, + datadoghqRegistries: cfg.DDRegistries, + } + + resolver.rcClient.Subscribe(state.ProductGradualRollout, resolver.processUpdate) + log.Debugf("Subscribed to %s", state.ProductGradualRollout) + + go func() { + if err := resolver.waitForInitialConfig(); err != nil { + log.Warnf("Failed to load initial image resolution config: %v. Image resolution will remain disabled.", err) + } else { + log.Infof("Image resolution cache initialized successfully") + } + }() + + return resolver +} + func (r *remoteConfigImageResolver) waitForInitialConfig() error { if currentConfigs := r.rcClient.GetConfigs(state.ProductGradualRollout); len(currentConfigs) > 0 { log.Debugf("Initial configs available immediately: %d configurations", len(currentConfigs)) @@ -261,26 +284,3 @@ func NewImageResolver(cfg imageresolver.ImageResolverConfig) ImageResolver { return newRcImageResolver(cfg) } - -func newRcImageResolver(cfg imageresolver.ImageResolverConfig) ImageResolver { - resolver := &remoteConfigImageResolver{ - rcClient: cfg.RCClient, - imageMappings: make(map[string]map[string]ImageInfo), - maxRetries: cfg.MaxInitRetries, - retryDelay: cfg.InitRetryDelay, - datadoghqRegistries: cfg.DDRegistries, - } - - resolver.rcClient.Subscribe(state.ProductGradualRollout, resolver.processUpdate) - log.Debugf("Subscribed to %s", state.ProductGradualRollout) - - go func() { - if err := resolver.waitForInitialConfig(); err != nil { - log.Warnf("Failed to load initial image resolution config: %v. Image resolution will remain disabled.", err) - } else { - log.Infof("Image resolution cache initialized successfully") - } - }() - - return resolver -} diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go index bf23f78f88094c..050e4a3a7d09fa 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/require" "github.com/DataDog/datadog-agent/comp/core/config" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver" "github.com/DataDog/datadog-agent/pkg/remoteconfig/state" "github.com/DataDog/datadog-agent/pkg/util/log" ) @@ -107,24 +108,24 @@ func (m *mockRCClient) setBlocking(block bool) { func TestNewImageResolver(t *testing.T) { t.Run("with_remote_config_client", func(t *testing.T) { mockClient := newMockRCClient("image_resolver_multi_repo.json") - mockConfig := config.NewMock(t) - resolver := NewImageResolver(mockClient, mockConfig) + mockConfig := imageresolver.NewTestImageResolverConfig("testsite", map[string]any{"gcr.io/datadoghq": nil}, mockClient, 5, 1*time.Second) + resolver := NewImageResolver(*mockConfig) _, ok := resolver.(*remoteConfigImageResolver) assert.True(t, ok, "Should return remoteConfigImageResolver when rcClient is not nil") }) t.Run("without_remote_config_client__typed_nil", func(t *testing.T) { - mockConfig := config.NewMock(t) - resolver := NewImageResolver((*mockRCClient)(nil), mockConfig) + mockConfig := imageresolver.NewTestImageResolverConfig("testsite", map[string]any{"gcr.io/datadoghq": nil}, (*mockRCClient)(nil), 5, 1*time.Second) + resolver := NewImageResolver(*mockConfig) _, ok := resolver.(*noOpImageResolver) assert.True(t, ok, "Should return noOpImageResolver when rcClient is nil") }) t.Run("without_remote_config_client__untyped_nil", func(t *testing.T) { - mockConfig := config.NewMock(t) - resolver := NewImageResolver(nil, mockConfig) + mockConfig := imageresolver.NewTestImageResolverConfig("testsite", map[string]any{"gcr.io/datadoghq": nil}, nil, 5, 1*time.Second) + resolver := NewImageResolver(*mockConfig) _, ok := resolver.(*noOpImageResolver) assert.True(t, ok, "Should return noOpImageResolver when rcClient is nil") @@ -229,7 +230,7 @@ func TestImageResolverEmptyConfig(t *testing.T) { func TestRemoteConfigImageResolver_Resolve(t *testing.T) { mockRCClient := newMockRCClient("image_resolver_multi_repo.json") datadoghqRegistries := config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries") - resolver := newRemoteConfigImageResolver(mockRCClient, datadoghqRegistries) + resolver := newRcImageResolver(*imageresolver.NewTestImageResolverConfig("testsite", datadoghqRegistries, mockRCClient, 5, 1*time.Second)) testCases := []struct { name string @@ -415,7 +416,7 @@ func TestRemoteConfigImageResolver_InvalidDigestValidation(t *testing.T) { func TestRemoteConfigImageResolver_ConcurrentAccess(t *testing.T) { datadoghqRegistries := config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries") - resolver := newRemoteConfigImageResolver(newMockRCClient("image_resolver_multi_repo.json"), datadoghqRegistries).(*remoteConfigImageResolver) + resolver := newRcImageResolver(*imageresolver.NewTestImageResolverConfig("testsite", datadoghqRegistries, newMockRCClient("image_resolver_multi_repo.json"), 5, 1*time.Second)) t.Run("concurrent_read_write", func(_ *testing.T) { var wg sync.WaitGroup @@ -496,12 +497,7 @@ func TestAsyncInitialization(t *testing.T) { mockClient.setBlocking(true) // Block initialization config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries") - resolver := newRemoteConfigImageResolverWithRetryConfig( - mockClient, - 2, - 10*time.Millisecond, - config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), - ) + resolver := newRcImageResolver(*imageresolver.NewTestImageResolverConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) resolved, ok := resolver.Resolve("gcr.io/datadoghq", "dd-lib-python-init", "latest") assert.False(t, ok, "Should not complete image resolution during initialization") @@ -512,12 +508,7 @@ func TestAsyncInitialization(t *testing.T) { mockClient := newMockRCClient("image_resolver_multi_repo.json") mockClient.setBlocking(true) - resolver := newRemoteConfigImageResolverWithRetryConfig( - mockClient, - 2, - 10*time.Millisecond, - config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), - ) + resolver := newRcImageResolver(*imageresolver.NewTestImageResolverConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) resolved, ok := resolver.Resolve("gcr.io/datadoghq", "dd-lib-python-init", "latest") assert.False(t, ok, "Should not complete image resolution during initialization") @@ -541,12 +532,7 @@ func TestAsyncInitialization(t *testing.T) { } close(mockClient.configsReady) - resolver := newRemoteConfigImageResolverWithRetryConfig( - mockClient, - 2, - 10*time.Millisecond, - config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), - ) + resolver := newRcImageResolver(*imageresolver.NewTestImageResolverConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) time.Sleep(50 * time.Millisecond) resolved, ok := resolver.Resolve("gcr.io/datadoghq", "dd-lib-python-init", "latest") From 88fd76802872d7ceb385656f66d8c68bbd6eb153 Mon Sep 17 00:00:00 2001 From: erikayasuda Date: Thu, 20 Nov 2025 13:34:26 -0500 Subject: [PATCH 03/17] update injector_test --- .../mutate/autoinstrumentation/image_resolver.go | 2 +- .../autoinstrumentation/image_resolver_test.go | 2 +- .../mutate/autoinstrumentation/injector_test.go | 15 +++------------ 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go index 6008234acf2fdc..779a951e53b814 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go @@ -67,7 +67,7 @@ type remoteConfigImageResolver struct { retryDelay time.Duration } -func newRcImageResolver(cfg imageresolver.ImageResolverConfig) remoteConfigImageResolver { +func newRcImageResolver(cfg imageresolver.ImageResolverConfig) ImageResolver { resolver := &remoteConfigImageResolver{ rcClient: cfg.RCClient, imageMappings: make(map[string]map[string]ImageInfo), diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go index 050e4a3a7d09fa..ef5ff15bd4601a 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go @@ -438,7 +438,7 @@ func TestRemoteConfigImageResolver_ConcurrentAccess(t *testing.T) { go func() { defer wg.Done() for j := 0; j < 10; j++ { - resolver.processUpdate(map[string]state.RawConfig{}, func(string, state.ApplyStatus) {}) + resolver.(*remoteConfigImageResolver).processUpdate(map[string]state.RawConfig{}, func(string, state.ApplyStatus) {}) time.Sleep(10 * time.Millisecond) } }() diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go index 948210b5e0d118..d35189dc465f18 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go @@ -16,6 +16,7 @@ import ( corev1 "k8s.io/api/core/v1" "github.com/DataDog/datadog-agent/comp/core/config" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver" "github.com/DataDog/datadog-agent/pkg/util/pointer" ) @@ -97,12 +98,7 @@ func TestInjectorWithRemoteConfigImageResolver(t *testing.T) { var resolver ImageResolver if tc.hasRemoteData { mockClient := newMockRCClient("image_resolver_multi_repo.json") - resolver = newRemoteConfigImageResolverWithRetryConfig( - mockClient, - 2, - 1*time.Millisecond, - config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), - ) + resolver = newRcImageResolver(*imageresolver.NewTestImageResolverConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) } else { resolver = newNoOpImageResolver() } @@ -118,12 +114,7 @@ func TestInjectorWithRemoteConfigImageResolver(t *testing.T) { func TestInjectorWithRemoteConfigImageResolverAfterInit(t *testing.T) { mockClient := newMockRCClient("image_resolver_multi_repo.json") - resolver := newRemoteConfigImageResolverWithRetryConfig( - mockClient, - 2, - 1*time.Millisecond, - config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), - ) + resolver := newRcImageResolver(*imageresolver.NewTestImageResolverConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) assert.Eventually(t, func() bool { _, ok := resolver.Resolve("gcr.io/datadoghq", "apm-inject", "0") From a060afe1307d5fd0cd0379d687b859b562e0092c Mon Sep 17 00:00:00 2001 From: erikayasuda Date: Thu, 20 Nov 2025 13:39:46 -0500 Subject: [PATCH 04/17] remove duplicate remoteconfigclient interface --- .../mutate/autoinstrumentation/image_resolver.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go index 779a951e53b814..f6d635afbc999e 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go @@ -23,12 +23,6 @@ import ( "github.com/DataDog/datadog-agent/pkg/util/log" ) -// RemoteConfigClient defines the interface we need for remote config operations -type RemoteConfigClient interface { - GetConfigs(product string) map[string]state.RawConfig - Subscribe(product string, callback func(map[string]state.RawConfig, func(string, state.ApplyStatus))) -} - // ImageResolver resolves container image references from tag-based to digest-based. type ImageResolver interface { // Resolve takes a registry, repository, and tag string (e.g., "gcr.io/datadoghq", "dd-lib-python-init", "v3") @@ -56,7 +50,7 @@ func (r *noOpImageResolver) Resolve(registry string, repository string, tag stri // remoteConfigImageResolver resolves image references using remote configuration data. // It maintains a cache of image mappings received from the remote config service. type remoteConfigImageResolver struct { - rcClient RemoteConfigClient + rcClient imageresolver.RemoteConfigClient mu sync.RWMutex imageMappings map[string]map[string]ImageInfo // repository name -> tag -> resolved image From 9e53643a6af2668218a65fefc1021353a498fe6c Mon Sep 17 00:00:00 2001 From: erikayasuda Date: Thu, 20 Nov 2025 13:44:39 -0500 Subject: [PATCH 05/17] update start command to use an ImageResolverConfig to init an ImageResolver --- cmd/cluster-agent/subcommands/start/command.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/cluster-agent/subcommands/start/command.go b/cmd/cluster-agent/subcommands/start/command.go index 46d36d595be593..d606b1d830aa8d 100644 --- a/cmd/cluster-agent/subcommands/start/command.go +++ b/cmd/cluster-agent/subcommands/start/command.go @@ -79,6 +79,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/clusteragent" admissionpkg "github.com/DataDog/datadog-agent/pkg/clusteragent/admission" "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver" admissionpatch "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/patch" apidca "github.com/DataDog/datadog-agent/pkg/clusteragent/api" "github.com/DataDog/datadog-agent/pkg/clusteragent/autoscaling/workload" @@ -538,7 +539,8 @@ func start(log log.Component, } else { log.Info("Auto instrumentation patcher is disabled") } - imageResolver := autoinstrumentation.NewImageResolver(rcClient, datadogConfig) + imageResolverConfig := imageresolver.NewImageResolverConfig(datadogConfig, rcClient) + imageResolver := autoinstrumentation.NewImageResolver(*imageResolverConfig) admissionCtx := admissionpkg.ControllerContext{ LeadershipStateSubscribeFunc: le.Subscribe, From 9296434cddf3818487c6443103905198990a0f08 Mon Sep 17 00:00:00 2001 From: erikayasuda Date: Thu, 20 Nov 2025 16:05:23 -0500 Subject: [PATCH 06/17] update tests --- .../admission/controllers/webhook/controller_base_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/clusteragent/admission/controllers/webhook/controller_base_test.go b/pkg/clusteragent/admission/controllers/webhook/controller_base_test.go index 3f17afcda37428..bacd6c7f6b1adf 100644 --- a/pkg/clusteragent/admission/controllers/webhook/controller_base_test.go +++ b/pkg/clusteragent/admission/controllers/webhook/controller_base_test.go @@ -28,6 +28,7 @@ import ( workloadmetafxmock "github.com/DataDog/datadog-agent/comp/core/workloadmeta/fx-mock" workloadmetamock "github.com/DataDog/datadog-agent/comp/core/workloadmeta/mock" "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver" configmock "github.com/DataDog/datadog-agent/pkg/config/mock" "github.com/DataDog/datadog-agent/pkg/util/fxutil" ) @@ -37,7 +38,7 @@ func TestNewController(t *testing.T) { wmeta := fxutil.Test[workloadmeta.Component](t, core.MockBundle(), workloadmetafxmock.MockModule(workloadmeta.NewParams())) datadogConfig := config.NewMock(t) factory := informers.NewSharedInformerFactory(client, time.Duration(0)) - imageResolver := autoinstrumentation.NewImageResolver(nil, datadogConfig) + imageResolver := autoinstrumentation.NewImageResolver(*imageresolver.NewImageResolverConfig(datadogConfig, nil)) // V1 controller := NewController( @@ -142,7 +143,7 @@ func TestAutoInstrumentation(t *testing.T) { )) // Create APM webhook. - imageResolver := autoinstrumentation.NewImageResolver(nil, mockConfig) + imageResolver := autoinstrumentation.NewImageResolver(*imageresolver.NewImageResolverConfig(mockConfig, nil)) apm, err := generateAutoInstrumentationWebhook(wmeta, mockConfig, imageResolver) assert.NoError(t, err) From 61db2a2e6d8c1b3b805e357084c919c110f0331d Mon Sep 17 00:00:00 2001 From: erikayasuda Date: Thu, 20 Nov 2025 16:23:46 -0500 Subject: [PATCH 07/17] add comments --- .../mutate/autoinstrumentation/imageresolver/config.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go index 909e23fa97e1a3..cda487434ab65f 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go @@ -14,11 +14,13 @@ import ( "github.com/DataDog/datadog-agent/pkg/remoteconfig/state" ) +// RemoteConfigClient defines the interface we need for remote config operations type RemoteConfigClient interface { GetConfigs(product string) map[string]state.RawConfig Subscribe(product string, callback func(map[string]state.RawConfig, func(string, state.ApplyStatus))) } +// ImageResolverConfig contains information needed to create an ImageResolver type ImageResolverConfig struct { Site string DDRegistries map[string]any @@ -27,6 +29,7 @@ type ImageResolverConfig struct { InitRetryDelay time.Duration } +// NewImageResolverConfig creates a new ImageResolverConfig func NewImageResolverConfig(cfg config.Component, rcClient RemoteConfigClient) *ImageResolverConfig { return &ImageResolverConfig{ Site: cfg.GetString("site"), @@ -37,6 +40,7 @@ func NewImageResolverConfig(cfg config.Component, rcClient RemoteConfigClient) * } } +// NewTestImageResolverConfig creates a new ImageResolverConfig for testing func NewTestImageResolverConfig(site string, ddRegistries map[string]any, rcClient RemoteConfigClient, maxRetries int, retryDelay time.Duration) *ImageResolverConfig { return &ImageResolverConfig{ Site: site, From f4a874e007dee543c5216947dc6f384d8c663159 Mon Sep 17 00:00:00 2001 From: erikayasuda Date: Thu, 20 Nov 2025 16:41:37 -0500 Subject: [PATCH 08/17] remove stutter --- cmd/cluster-agent/subcommands/start/command.go | 2 +- .../controllers/webhook/controller_base_test.go | 4 ++-- .../mutate/autoinstrumentation/image_resolver.go | 4 ++-- .../autoinstrumentation/image_resolver_test.go | 16 ++++++++-------- .../autoinstrumentation/imageresolver/config.go | 16 ++++++++-------- .../mutate/autoinstrumentation/injector_test.go | 4 ++-- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/cmd/cluster-agent/subcommands/start/command.go b/cmd/cluster-agent/subcommands/start/command.go index d606b1d830aa8d..60b255deafce08 100644 --- a/cmd/cluster-agent/subcommands/start/command.go +++ b/cmd/cluster-agent/subcommands/start/command.go @@ -539,7 +539,7 @@ func start(log log.Component, } else { log.Info("Auto instrumentation patcher is disabled") } - imageResolverConfig := imageresolver.NewImageResolverConfig(datadogConfig, rcClient) + imageResolverConfig := imageresolver.NewConfig(datadogConfig, rcClient) imageResolver := autoinstrumentation.NewImageResolver(*imageResolverConfig) admissionCtx := admissionpkg.ControllerContext{ diff --git a/pkg/clusteragent/admission/controllers/webhook/controller_base_test.go b/pkg/clusteragent/admission/controllers/webhook/controller_base_test.go index bacd6c7f6b1adf..86ca82b29a360a 100644 --- a/pkg/clusteragent/admission/controllers/webhook/controller_base_test.go +++ b/pkg/clusteragent/admission/controllers/webhook/controller_base_test.go @@ -38,7 +38,7 @@ func TestNewController(t *testing.T) { wmeta := fxutil.Test[workloadmeta.Component](t, core.MockBundle(), workloadmetafxmock.MockModule(workloadmeta.NewParams())) datadogConfig := config.NewMock(t) factory := informers.NewSharedInformerFactory(client, time.Duration(0)) - imageResolver := autoinstrumentation.NewImageResolver(*imageresolver.NewImageResolverConfig(datadogConfig, nil)) + imageResolver := autoinstrumentation.NewImageResolver(*imageresolver.NewConfig(datadogConfig, nil)) // V1 controller := NewController( @@ -143,7 +143,7 @@ func TestAutoInstrumentation(t *testing.T) { )) // Create APM webhook. - imageResolver := autoinstrumentation.NewImageResolver(*imageresolver.NewImageResolverConfig(mockConfig, nil)) + imageResolver := autoinstrumentation.NewImageResolver(*imageresolver.NewConfig(mockConfig, nil)) apm, err := generateAutoInstrumentationWebhook(wmeta, mockConfig, imageResolver) assert.NoError(t, err) diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go index f6d635afbc999e..77538fe0934459 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go @@ -61,7 +61,7 @@ type remoteConfigImageResolver struct { retryDelay time.Duration } -func newRcImageResolver(cfg imageresolver.ImageResolverConfig) ImageResolver { +func newRcImageResolver(cfg imageresolver.Config) ImageResolver { resolver := &remoteConfigImageResolver{ rcClient: cfg.RCClient, imageMappings: make(map[string]map[string]ImageInfo), @@ -270,7 +270,7 @@ func newResolvedImage(registry string, repositoryName string, imageInfo ImageInf // NewImageResolver creates the appropriate ImageResolver based on whether // a remote config client is available. -func NewImageResolver(cfg imageresolver.ImageResolverConfig) ImageResolver { +func NewImageResolver(cfg imageresolver.Config) ImageResolver { if cfg.RCClient == nil || reflect.ValueOf(cfg.RCClient).IsNil() { log.Debugf("No remote config client available") return newNoOpImageResolver() diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go index ef5ff15bd4601a..d52f6ac82f44c2 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go @@ -108,7 +108,7 @@ func (m *mockRCClient) setBlocking(block bool) { func TestNewImageResolver(t *testing.T) { t.Run("with_remote_config_client", func(t *testing.T) { mockClient := newMockRCClient("image_resolver_multi_repo.json") - mockConfig := imageresolver.NewTestImageResolverConfig("testsite", map[string]any{"gcr.io/datadoghq": nil}, mockClient, 5, 1*time.Second) + mockConfig := imageresolver.NewTestConfig("testsite", map[string]any{"gcr.io/datadoghq": nil}, mockClient, 5, 1*time.Second) resolver := NewImageResolver(*mockConfig) _, ok := resolver.(*remoteConfigImageResolver) @@ -116,7 +116,7 @@ func TestNewImageResolver(t *testing.T) { }) t.Run("without_remote_config_client__typed_nil", func(t *testing.T) { - mockConfig := imageresolver.NewTestImageResolverConfig("testsite", map[string]any{"gcr.io/datadoghq": nil}, (*mockRCClient)(nil), 5, 1*time.Second) + mockConfig := imageresolver.NewTestConfig("testsite", map[string]any{"gcr.io/datadoghq": nil}, (*mockRCClient)(nil), 5, 1*time.Second) resolver := NewImageResolver(*mockConfig) _, ok := resolver.(*noOpImageResolver) @@ -124,7 +124,7 @@ func TestNewImageResolver(t *testing.T) { }) t.Run("without_remote_config_client__untyped_nil", func(t *testing.T) { - mockConfig := imageresolver.NewTestImageResolverConfig("testsite", map[string]any{"gcr.io/datadoghq": nil}, nil, 5, 1*time.Second) + mockConfig := imageresolver.NewTestConfig("testsite", map[string]any{"gcr.io/datadoghq": nil}, nil, 5, 1*time.Second) resolver := NewImageResolver(*mockConfig) _, ok := resolver.(*noOpImageResolver) @@ -230,7 +230,7 @@ func TestImageResolverEmptyConfig(t *testing.T) { func TestRemoteConfigImageResolver_Resolve(t *testing.T) { mockRCClient := newMockRCClient("image_resolver_multi_repo.json") datadoghqRegistries := config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries") - resolver := newRcImageResolver(*imageresolver.NewTestImageResolverConfig("testsite", datadoghqRegistries, mockRCClient, 5, 1*time.Second)) + resolver := newRcImageResolver(*imageresolver.NewTestConfig("testsite", datadoghqRegistries, mockRCClient, 5, 1*time.Second)) testCases := []struct { name string @@ -416,7 +416,7 @@ func TestRemoteConfigImageResolver_InvalidDigestValidation(t *testing.T) { func TestRemoteConfigImageResolver_ConcurrentAccess(t *testing.T) { datadoghqRegistries := config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries") - resolver := newRcImageResolver(*imageresolver.NewTestImageResolverConfig("testsite", datadoghqRegistries, newMockRCClient("image_resolver_multi_repo.json"), 5, 1*time.Second)) + resolver := newRcImageResolver(*imageresolver.NewTestConfig("testsite", datadoghqRegistries, newMockRCClient("image_resolver_multi_repo.json"), 5, 1*time.Second)) t.Run("concurrent_read_write", func(_ *testing.T) { var wg sync.WaitGroup @@ -497,7 +497,7 @@ func TestAsyncInitialization(t *testing.T) { mockClient.setBlocking(true) // Block initialization config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries") - resolver := newRcImageResolver(*imageresolver.NewTestImageResolverConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) + resolver := newRcImageResolver(*imageresolver.NewTestConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) resolved, ok := resolver.Resolve("gcr.io/datadoghq", "dd-lib-python-init", "latest") assert.False(t, ok, "Should not complete image resolution during initialization") @@ -508,7 +508,7 @@ func TestAsyncInitialization(t *testing.T) { mockClient := newMockRCClient("image_resolver_multi_repo.json") mockClient.setBlocking(true) - resolver := newRcImageResolver(*imageresolver.NewTestImageResolverConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) + resolver := newRcImageResolver(*imageresolver.NewTestConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) resolved, ok := resolver.Resolve("gcr.io/datadoghq", "dd-lib-python-init", "latest") assert.False(t, ok, "Should not complete image resolution during initialization") @@ -532,7 +532,7 @@ func TestAsyncInitialization(t *testing.T) { } close(mockClient.configsReady) - resolver := newRcImageResolver(*imageresolver.NewTestImageResolverConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) + resolver := newRcImageResolver(*imageresolver.NewTestConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) time.Sleep(50 * time.Millisecond) resolved, ok := resolver.Resolve("gcr.io/datadoghq", "dd-lib-python-init", "latest") diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go index cda487434ab65f..37b12c5dad7b70 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go @@ -20,8 +20,8 @@ type RemoteConfigClient interface { Subscribe(product string, callback func(map[string]state.RawConfig, func(string, state.ApplyStatus))) } -// ImageResolverConfig contains information needed to create an ImageResolver -type ImageResolverConfig struct { +// Config contains information needed to create an ImageResolver +type Config struct { Site string DDRegistries map[string]any RCClient RemoteConfigClient @@ -29,9 +29,9 @@ type ImageResolverConfig struct { InitRetryDelay time.Duration } -// NewImageResolverConfig creates a new ImageResolverConfig -func NewImageResolverConfig(cfg config.Component, rcClient RemoteConfigClient) *ImageResolverConfig { - return &ImageResolverConfig{ +// NewConfig creates a new Config +func NewConfig(cfg config.Component, rcClient RemoteConfigClient) *Config { + return &Config{ Site: cfg.GetString("site"), DDRegistries: cfg.GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), RCClient: rcClient, @@ -40,9 +40,9 @@ func NewImageResolverConfig(cfg config.Component, rcClient RemoteConfigClient) * } } -// NewTestImageResolverConfig creates a new ImageResolverConfig for testing -func NewTestImageResolverConfig(site string, ddRegistries map[string]any, rcClient RemoteConfigClient, maxRetries int, retryDelay time.Duration) *ImageResolverConfig { - return &ImageResolverConfig{ +// NewTestConfig creates a new Config for testing +func NewTestConfig(site string, ddRegistries map[string]any, rcClient RemoteConfigClient, maxRetries int, retryDelay time.Duration) *Config { + return &Config{ Site: site, DDRegistries: ddRegistries, RCClient: rcClient, diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go index d35189dc465f18..004051fad0f5d8 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go @@ -98,7 +98,7 @@ func TestInjectorWithRemoteConfigImageResolver(t *testing.T) { var resolver ImageResolver if tc.hasRemoteData { mockClient := newMockRCClient("image_resolver_multi_repo.json") - resolver = newRcImageResolver(*imageresolver.NewTestImageResolverConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) + resolver = newRcImageResolver(*imageresolver.NewTestConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) } else { resolver = newNoOpImageResolver() } @@ -114,7 +114,7 @@ func TestInjectorWithRemoteConfigImageResolver(t *testing.T) { func TestInjectorWithRemoteConfigImageResolverAfterInit(t *testing.T) { mockClient := newMockRCClient("image_resolver_multi_repo.json") - resolver := newRcImageResolver(*imageresolver.NewTestImageResolverConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) + resolver := newRcImageResolver(*imageresolver.NewTestConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) assert.Eventually(t, func() bool { _, ok := resolver.Resolve("gcr.io/datadoghq", "apm-inject", "0") From 59ee11914756418cc1d9f56486540695ebb6ef67 Mon Sep 17 00:00:00 2001 From: erikayasuda Date: Thu, 20 Nov 2025 16:57:17 -0500 Subject: [PATCH 09/17] pass config by value not pointer --- cmd/cluster-agent/subcommands/start/command.go | 2 +- .../controllers/webhook/controller_base_test.go | 4 ++-- .../autoinstrumentation/image_resolver_test.go | 16 ++++++++-------- .../autoinstrumentation/imageresolver/config.go | 8 ++++---- .../mutate/autoinstrumentation/injector_test.go | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/cmd/cluster-agent/subcommands/start/command.go b/cmd/cluster-agent/subcommands/start/command.go index 60b255deafce08..d545eda837a592 100644 --- a/cmd/cluster-agent/subcommands/start/command.go +++ b/cmd/cluster-agent/subcommands/start/command.go @@ -540,7 +540,7 @@ func start(log log.Component, log.Info("Auto instrumentation patcher is disabled") } imageResolverConfig := imageresolver.NewConfig(datadogConfig, rcClient) - imageResolver := autoinstrumentation.NewImageResolver(*imageResolverConfig) + imageResolver := autoinstrumentation.NewImageResolver(imageResolverConfig) admissionCtx := admissionpkg.ControllerContext{ LeadershipStateSubscribeFunc: le.Subscribe, diff --git a/pkg/clusteragent/admission/controllers/webhook/controller_base_test.go b/pkg/clusteragent/admission/controllers/webhook/controller_base_test.go index 86ca82b29a360a..0871fa2590348e 100644 --- a/pkg/clusteragent/admission/controllers/webhook/controller_base_test.go +++ b/pkg/clusteragent/admission/controllers/webhook/controller_base_test.go @@ -38,7 +38,7 @@ func TestNewController(t *testing.T) { wmeta := fxutil.Test[workloadmeta.Component](t, core.MockBundle(), workloadmetafxmock.MockModule(workloadmeta.NewParams())) datadogConfig := config.NewMock(t) factory := informers.NewSharedInformerFactory(client, time.Duration(0)) - imageResolver := autoinstrumentation.NewImageResolver(*imageresolver.NewConfig(datadogConfig, nil)) + imageResolver := autoinstrumentation.NewImageResolver(imageresolver.NewConfig(datadogConfig, nil)) // V1 controller := NewController( @@ -143,7 +143,7 @@ func TestAutoInstrumentation(t *testing.T) { )) // Create APM webhook. - imageResolver := autoinstrumentation.NewImageResolver(*imageresolver.NewConfig(mockConfig, nil)) + imageResolver := autoinstrumentation.NewImageResolver(imageresolver.NewConfig(mockConfig, nil)) apm, err := generateAutoInstrumentationWebhook(wmeta, mockConfig, imageResolver) assert.NoError(t, err) diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go index d52f6ac82f44c2..ea172b6df2c70e 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go @@ -109,7 +109,7 @@ func TestNewImageResolver(t *testing.T) { t.Run("with_remote_config_client", func(t *testing.T) { mockClient := newMockRCClient("image_resolver_multi_repo.json") mockConfig := imageresolver.NewTestConfig("testsite", map[string]any{"gcr.io/datadoghq": nil}, mockClient, 5, 1*time.Second) - resolver := NewImageResolver(*mockConfig) + resolver := NewImageResolver(mockConfig) _, ok := resolver.(*remoteConfigImageResolver) assert.True(t, ok, "Should return remoteConfigImageResolver when rcClient is not nil") @@ -117,7 +117,7 @@ func TestNewImageResolver(t *testing.T) { t.Run("without_remote_config_client__typed_nil", func(t *testing.T) { mockConfig := imageresolver.NewTestConfig("testsite", map[string]any{"gcr.io/datadoghq": nil}, (*mockRCClient)(nil), 5, 1*time.Second) - resolver := NewImageResolver(*mockConfig) + resolver := NewImageResolver(mockConfig) _, ok := resolver.(*noOpImageResolver) assert.True(t, ok, "Should return noOpImageResolver when rcClient is nil") @@ -125,7 +125,7 @@ func TestNewImageResolver(t *testing.T) { t.Run("without_remote_config_client__untyped_nil", func(t *testing.T) { mockConfig := imageresolver.NewTestConfig("testsite", map[string]any{"gcr.io/datadoghq": nil}, nil, 5, 1*time.Second) - resolver := NewImageResolver(*mockConfig) + resolver := NewImageResolver(mockConfig) _, ok := resolver.(*noOpImageResolver) assert.True(t, ok, "Should return noOpImageResolver when rcClient is nil") @@ -230,7 +230,7 @@ func TestImageResolverEmptyConfig(t *testing.T) { func TestRemoteConfigImageResolver_Resolve(t *testing.T) { mockRCClient := newMockRCClient("image_resolver_multi_repo.json") datadoghqRegistries := config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries") - resolver := newRcImageResolver(*imageresolver.NewTestConfig("testsite", datadoghqRegistries, mockRCClient, 5, 1*time.Second)) + resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", datadoghqRegistries, mockRCClient, 5, 1*time.Second)) testCases := []struct { name string @@ -416,7 +416,7 @@ func TestRemoteConfigImageResolver_InvalidDigestValidation(t *testing.T) { func TestRemoteConfigImageResolver_ConcurrentAccess(t *testing.T) { datadoghqRegistries := config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries") - resolver := newRcImageResolver(*imageresolver.NewTestConfig("testsite", datadoghqRegistries, newMockRCClient("image_resolver_multi_repo.json"), 5, 1*time.Second)) + resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", datadoghqRegistries, newMockRCClient("image_resolver_multi_repo.json"), 5, 1*time.Second)) t.Run("concurrent_read_write", func(_ *testing.T) { var wg sync.WaitGroup @@ -497,7 +497,7 @@ func TestAsyncInitialization(t *testing.T) { mockClient.setBlocking(true) // Block initialization config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries") - resolver := newRcImageResolver(*imageresolver.NewTestConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) + resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) resolved, ok := resolver.Resolve("gcr.io/datadoghq", "dd-lib-python-init", "latest") assert.False(t, ok, "Should not complete image resolution during initialization") @@ -508,7 +508,7 @@ func TestAsyncInitialization(t *testing.T) { mockClient := newMockRCClient("image_resolver_multi_repo.json") mockClient.setBlocking(true) - resolver := newRcImageResolver(*imageresolver.NewTestConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) + resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) resolved, ok := resolver.Resolve("gcr.io/datadoghq", "dd-lib-python-init", "latest") assert.False(t, ok, "Should not complete image resolution during initialization") @@ -532,7 +532,7 @@ func TestAsyncInitialization(t *testing.T) { } close(mockClient.configsReady) - resolver := newRcImageResolver(*imageresolver.NewTestConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) + resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) time.Sleep(50 * time.Millisecond) resolved, ok := resolver.Resolve("gcr.io/datadoghq", "dd-lib-python-init", "latest") diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go index 37b12c5dad7b70..db77b351b0cd22 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go @@ -30,8 +30,8 @@ type Config struct { } // NewConfig creates a new Config -func NewConfig(cfg config.Component, rcClient RemoteConfigClient) *Config { - return &Config{ +func NewConfig(cfg config.Component, rcClient RemoteConfigClient) Config { + return Config{ Site: cfg.GetString("site"), DDRegistries: cfg.GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), RCClient: rcClient, @@ -41,8 +41,8 @@ func NewConfig(cfg config.Component, rcClient RemoteConfigClient) *Config { } // NewTestConfig creates a new Config for testing -func NewTestConfig(site string, ddRegistries map[string]any, rcClient RemoteConfigClient, maxRetries int, retryDelay time.Duration) *Config { - return &Config{ +func NewTestConfig(site string, ddRegistries map[string]any, rcClient RemoteConfigClient, maxRetries int, retryDelay time.Duration) Config { + return Config{ Site: site, DDRegistries: ddRegistries, RCClient: rcClient, diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go index 004051fad0f5d8..593dfa903b2ae5 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go @@ -98,7 +98,7 @@ func TestInjectorWithRemoteConfigImageResolver(t *testing.T) { var resolver ImageResolver if tc.hasRemoteData { mockClient := newMockRCClient("image_resolver_multi_repo.json") - resolver = newRcImageResolver(*imageresolver.NewTestConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) + resolver = newRcImageResolver(imageresolver.NewTestConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) } else { resolver = newNoOpImageResolver() } @@ -114,7 +114,7 @@ func TestInjectorWithRemoteConfigImageResolver(t *testing.T) { func TestInjectorWithRemoteConfigImageResolverAfterInit(t *testing.T) { mockClient := newMockRCClient("image_resolver_multi_repo.json") - resolver := newRcImageResolver(*imageresolver.NewTestConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) + resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), mockClient, 2, 10*time.Millisecond)) assert.Eventually(t, func() bool { _, ok := resolver.Resolve("gcr.io/datadoghq", "apm-inject", "0") From 479b0dbd70731b70ae107f7483d91c3c804617e9 Mon Sep 17 00:00:00 2001 From: erikayasuda Date: Thu, 20 Nov 2025 17:08:23 -0500 Subject: [PATCH 10/17] pkg comment --- .../mutate/autoinstrumentation/imageresolver/config.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go index db77b351b0cd22..24cdfd069f3116 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go @@ -5,6 +5,8 @@ //go:build kubeapiserver +// Package imageresolver provides configuration and utilities for resolving +// container image references from mutable tags to digests. package imageresolver import ( From 4026dbbe5ea81e33028f7a6d24cbe0c355a3e4a9 Mon Sep 17 00:00:00 2001 From: erikayasuda Date: Wed, 3 Dec 2025 13:16:38 -0500 Subject: [PATCH 11/17] update imageresolver.config to have a string slice for the dd registries --- .../mutate/autoinstrumentation/imageresolver/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go index 24cdfd069f3116..7b828df3bc1e12 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go @@ -25,7 +25,7 @@ type RemoteConfigClient interface { // Config contains information needed to create an ImageResolver type Config struct { Site string - DDRegistries map[string]any + DDRegistries []string RCClient RemoteConfigClient MaxInitRetries int InitRetryDelay time.Duration From 8e501a7d10d3786c8f564b84c4de10abeac695e6 Mon Sep 17 00:00:00 2001 From: erikayasuda Date: Thu, 18 Dec 2025 15:16:21 -0500 Subject: [PATCH 12/17] update tests to reflect changes --- .../auto_instrumentation.go | 3 ++- .../autoinstrumentation/image_resolver.go | 1 + .../image_resolver_test.go | 22 +++++++------------ .../imageresolver/config.go | 8 +++---- .../autoinstrumentation/imageresolver/util.go | 18 +++++++++++++++ .../autoinstrumentation/injector_test.go | 7 ++---- 6 files changed, 35 insertions(+), 24 deletions(-) create mode 100644 pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/util.go diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/auto_instrumentation.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/auto_instrumentation.go index 1cad237c902490..66b374ea0b7650 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/auto_instrumentation.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/auto_instrumentation.go @@ -14,6 +14,7 @@ import ( "github.com/DataDog/datadog-agent/comp/core/config" workloadmeta "github.com/DataDog/datadog-agent/comp/core/workloadmeta/def" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver" mutatecommon "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/common" configWebhook "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/config" "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/tagsfromlabels" @@ -28,7 +29,7 @@ func NewAutoInstrumentation(datadogConfig config.Component, wmeta workloadmeta.C return nil, fmt.Errorf("failed to create auto instrumentation config: %v", err) } - imageResolver := NewImageResolver(rcClient, datadogConfig) + imageResolver := NewImageResolver(imageresolver.NewConfig(datadogConfig, rcClient)) apm, err := NewTargetMutator(config, wmeta, imageResolver) if err != nil { return nil, fmt.Errorf("failed to create auto instrumentation namespace mutator: %v", err) diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go index 2021991587f09a..0d10231dd1ffb6 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go @@ -281,6 +281,7 @@ func NewImageResolver(cfg imageresolver.Config) ImageResolver { return newRcImageResolver(cfg) } +// DEV: Move this to the imageresolver package after the refactor is complete func newDatadoghqRegistries(datadogRegistriesList []string) map[string]struct{} { datadoghqRegistries := make(map[string]struct{}) for _, registry := range datadogRegistriesList { diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go index 2bb0252420d4d2..6774eb04777425 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go @@ -108,7 +108,7 @@ func (m *mockRCClient) setBlocking(block bool) { func TestNewImageResolver(t *testing.T) { t.Run("with_remote_config_client", func(t *testing.T) { mockClient := newMockRCClient("image_resolver_multi_repo.json") - mockConfig := imageresolver.NewTestConfig("testsite", map[string]any{"gcr.io/datadoghq": nil}, mockClient, 5, 1*time.Second) + mockConfig := imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, mockClient, 5, 1*time.Second) resolver := NewImageResolver(mockConfig) _, ok := resolver.(*remoteConfigImageResolver) @@ -116,7 +116,7 @@ func TestNewImageResolver(t *testing.T) { }) t.Run("without_remote_config_client__typed_nil", func(t *testing.T) { - mockConfig := imageresolver.NewTestConfig("testsite", map[string]any{"gcr.io/datadoghq": nil}, (*mockRCClient)(nil), 5, 1*time.Second) + mockConfig := imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, (*mockRCClient)(nil), 5, 1*time.Second) resolver := NewImageResolver(mockConfig) _, ok := resolver.(*noOpImageResolver) @@ -124,7 +124,7 @@ func TestNewImageResolver(t *testing.T) { }) t.Run("without_remote_config_client__untyped_nil", func(t *testing.T) { - mockConfig := imageresolver.NewTestConfig("testsite", map[string]any{"gcr.io/datadoghq": nil}, nil, 5, 1*time.Second) + mockConfig := imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, nil, 5, 1*time.Second) resolver := NewImageResolver(mockConfig) _, ok := resolver.(*noOpImageResolver) @@ -229,8 +229,7 @@ func TestImageResolverEmptyConfig(t *testing.T) { func TestRemoteConfigImageResolver_Resolve(t *testing.T) { mockRCClient := newMockRCClient("image_resolver_multi_repo.json") - datadoghqRegistries := newDatadoghqRegistries(config.NewMock(t).GetStringSlice("admission_controller.auto_instrumentation.default_dd_registries")) - resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", datadoghqRegistries, mockRCClient, 5, 1*time.Second)) + resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, mockRCClient, 5, 1*time.Second)) testCases := []struct { name string @@ -415,8 +414,7 @@ func TestRemoteConfigImageResolver_InvalidDigestValidation(t *testing.T) { } func TestRemoteConfigImageResolver_ConcurrentAccess(t *testing.T) { - datadoghqRegistries := newDatadoghqRegistries(config.NewMock(t).GetStringSlice("admission_controller.auto_instrumentation.default_dd_registries")) - resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", datadoghqRegistries, newMockRCClient("image_resolver_multi_repo.json"), 5, 1*time.Second)) + resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, newMockRCClient("image_resolver_multi_repo.json"), 5, 1*time.Second)) t.Run("concurrent_read_write", func(_ *testing.T) { var wg sync.WaitGroup @@ -496,10 +494,8 @@ func TestAsyncInitialization(t *testing.T) { t.Run("noop_during_initialization", func(t *testing.T) { mockClient := newMockRCClient("image_resolver_multi_repo.json") mockClient.setBlocking(true) // Block initialization - datadoghqRegistries := newDatadoghqRegistries(config.NewMock(t).GetStringSlice("admission_controller.auto_instrumentation.default_dd_registries")) - datadoghqRegistries := newDatadoghqRegistries(config.NewMock(t).GetStringSlice("admission_controller.auto_instrumentation.default_dd_registries")) - resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", datadoghqRegistries, mockClient, 2, 10*time.Millisecond)) + resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, mockClient, 2, 10*time.Millisecond)) resolved, ok := resolver.Resolve("gcr.io/datadoghq", "dd-lib-python-init", "latest") assert.False(t, ok, "Should not complete image resolution during initialization") @@ -510,8 +506,7 @@ func TestAsyncInitialization(t *testing.T) { mockClient := newMockRCClient("image_resolver_multi_repo.json") mockClient.setBlocking(true) - datadoghqRegistries := newDatadoghqRegistries(config.NewMock(t).GetStringSlice("admission_controller.auto_instrumentation.default_dd_registries")) - resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", datadoghqRegistries, mockClient, 2, 10*time.Millisecond)) + resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, mockClient, 2, 10*time.Millisecond)) resolved, ok := resolver.Resolve("gcr.io/datadoghq", "dd-lib-python-init", "latest") assert.False(t, ok, "Should not complete image resolution during initialization") @@ -535,8 +530,7 @@ func TestAsyncInitialization(t *testing.T) { } close(mockClient.configsReady) - datadoghqRegistries := newDatadoghqRegistries(config.NewMock(t).GetStringSlice("admission_controller.auto_instrumentation.default_dd_registries")) - resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", datadoghqRegistries, mockClient, 2, 10*time.Millisecond)) + resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, mockClient, 2, 10*time.Millisecond)) time.Sleep(50 * time.Millisecond) resolved, ok := resolver.Resolve("gcr.io/datadoghq", "dd-lib-python-init", "latest") diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go index 7b828df3bc1e12..edabfbb914caaa 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go @@ -25,7 +25,7 @@ type RemoteConfigClient interface { // Config contains information needed to create an ImageResolver type Config struct { Site string - DDRegistries []string + DDRegistries map[string]struct{} RCClient RemoteConfigClient MaxInitRetries int InitRetryDelay time.Duration @@ -35,7 +35,7 @@ type Config struct { func NewConfig(cfg config.Component, rcClient RemoteConfigClient) Config { return Config{ Site: cfg.GetString("site"), - DDRegistries: cfg.GetStringMap("admission_controller.auto_instrumentation.default_dd_registries"), + DDRegistries: newDatadoghqRegistries(cfg.GetStringSlice("admission_controller.auto_instrumentation.default_dd_registries")), RCClient: rcClient, MaxInitRetries: 5, InitRetryDelay: 1 * time.Second, @@ -43,10 +43,10 @@ func NewConfig(cfg config.Component, rcClient RemoteConfigClient) Config { } // NewTestConfig creates a new Config for testing -func NewTestConfig(site string, ddRegistries map[string]any, rcClient RemoteConfigClient, maxRetries int, retryDelay time.Duration) Config { +func NewTestConfig(site string, ddRegistries []string, rcClient RemoteConfigClient, maxRetries int, retryDelay time.Duration) Config { return Config{ Site: site, - DDRegistries: ddRegistries, + DDRegistries: newDatadoghqRegistries(ddRegistries), RCClient: rcClient, MaxInitRetries: maxRetries, InitRetryDelay: retryDelay, diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/util.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/util.go new file mode 100644 index 00000000000000..85d462651151bc --- /dev/null +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/util.go @@ -0,0 +1,18 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +//go:build kubeapiserver + +// Package imageresolver provides configuration and utilities for resolving +// container image references from mutable tags to digests. +package imageresolver + +func newDatadoghqRegistries(datadogRegistriesList []string) map[string]struct{} { + datadoghqRegistries := make(map[string]struct{}) + for _, registry := range datadogRegistriesList { + datadoghqRegistries[registry] = struct{}{} + } + return datadoghqRegistries +} diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go index a5d43f6802527e..bec7d9d9153beb 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go @@ -15,7 +15,6 @@ import ( "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" - "github.com/DataDog/datadog-agent/comp/core/config" "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver" "github.com/DataDog/datadog-agent/pkg/util/pointer" ) @@ -98,8 +97,7 @@ func TestInjectorWithRemoteConfigImageResolver(t *testing.T) { var resolver ImageResolver if tc.hasRemoteData { mockClient := newMockRCClient("image_resolver_multi_repo.json") - datadoghqRegistries := config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries") - resolver = newRcImageResolver(imageresolver.NewTestConfig("testsite", datadoghqRegistries, mockClient, 2, 10*time.Millisecond)) + resolver = newRcImageResolver(imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, mockClient, 2, 10*time.Millisecond)) } else { resolver = newNoOpImageResolver() } @@ -115,8 +113,7 @@ func TestInjectorWithRemoteConfigImageResolver(t *testing.T) { func TestInjectorWithRemoteConfigImageResolverAfterInit(t *testing.T) { mockClient := newMockRCClient("image_resolver_multi_repo.json") - datadoghqRegistries := config.NewMock(t).GetStringMap("admission_controller.auto_instrumentation.default_dd_registries") - resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", datadoghqRegistries, mockClient, 2, 10*time.Millisecond)) + resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, mockClient, 2, 10*time.Millisecond)) assert.Eventually(t, func() bool { _, ok := resolver.Resolve("gcr.io/datadoghq", "apm-inject", "0") From d887719fb0a0772ada83d2317e6ff8536eb98d43 Mon Sep 17 00:00:00 2001 From: erikayasuda Date: Thu, 18 Dec 2025 15:28:03 -0500 Subject: [PATCH 13/17] clean up --- cmd/cluster-agent/subcommands/start/command.go | 2 -- .../admission/controllers/webhook/controller_base_test.go | 5 +---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/cmd/cluster-agent/subcommands/start/command.go b/cmd/cluster-agent/subcommands/start/command.go index 6e44bd3b7403ef..8e22572e51d352 100644 --- a/cmd/cluster-agent/subcommands/start/command.go +++ b/cmd/cluster-agent/subcommands/start/command.go @@ -557,8 +557,6 @@ func start(log log.Component, } else { log.Info("Auto instrumentation patcher is disabled") } - imageResolverConfig := imageresolver.NewConfig(datadogConfig, rcClient) - imageResolver := autoinstrumentation.NewImageResolver(imageResolverConfig) admissionCtx := admissionpkg.ControllerContext{ LeadershipStateSubscribeFunc: le.Subscribe, diff --git a/pkg/clusteragent/admission/controllers/webhook/controller_base_test.go b/pkg/clusteragent/admission/controllers/webhook/controller_base_test.go index 02d66c1dabfc5b..1ed25127f1da03 100644 --- a/pkg/clusteragent/admission/controllers/webhook/controller_base_test.go +++ b/pkg/clusteragent/admission/controllers/webhook/controller_base_test.go @@ -28,7 +28,6 @@ import ( workloadmetafxmock "github.com/DataDog/datadog-agent/comp/core/workloadmeta/fx-mock" workloadmetamock "github.com/DataDog/datadog-agent/comp/core/workloadmeta/mock" "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation" - "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver" configmock "github.com/DataDog/datadog-agent/pkg/config/mock" "github.com/DataDog/datadog-agent/pkg/util/fxutil" ) @@ -38,7 +37,6 @@ func TestNewController(t *testing.T) { wmeta := fxutil.Test[workloadmeta.Component](t, core.MockBundle(), workloadmetafxmock.MockModule(workloadmeta.NewParams())) datadogConfig := config.NewMock(t) factory := informers.NewSharedInformerFactory(client, time.Duration(0)) - imageResolver := autoinstrumentation.NewImageResolver(imageresolver.NewConfig(datadogConfig, nil)) // V1 controller := NewController( @@ -143,8 +141,7 @@ func TestAutoInstrumentation(t *testing.T) { )) // Create APM webhook. - imageResolver := autoinstrumentation.NewImageResolver(imageresolver.NewConfig(mockConfig, nil)) - apm, err := generateAutoInstrumentationWebhook(wmeta, mockConfig, imageResolver) + apm, err := autoinstrumentation.NewAutoInstrumentation(mockConfig, wmeta, nil) assert.NoError(t, err) // Create request. From 825728a269ababcdf68fa55fd2798f9233c3d1d2 Mon Sep 17 00:00:00 2001 From: erikayasuda Date: Thu, 18 Dec 2025 15:29:54 -0500 Subject: [PATCH 14/17] clean up --- cmd/cluster-agent/subcommands/start/command.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/cluster-agent/subcommands/start/command.go b/cmd/cluster-agent/subcommands/start/command.go index 8e22572e51d352..fd59f8316fd360 100644 --- a/cmd/cluster-agent/subcommands/start/command.go +++ b/cmd/cluster-agent/subcommands/start/command.go @@ -80,8 +80,6 @@ import ( metricscompressionfx "github.com/DataDog/datadog-agent/comp/serializer/metricscompression/fx" "github.com/DataDog/datadog-agent/pkg/clusteragent" admissionpkg "github.com/DataDog/datadog-agent/pkg/clusteragent/admission" - "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation" - "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver" admissionpatch "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/patch" apidca "github.com/DataDog/datadog-agent/pkg/clusteragent/api" "github.com/DataDog/datadog-agent/pkg/clusteragent/autoscaling/cluster" From 28c8c78feb8848e6c1f8352a711aad6ebb99cc0e Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:48:01 -0500 Subject: [PATCH 15/17] Update pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go --- .../admission/mutate/autoinstrumentation/image_resolver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go index 0d10231dd1ffb6..42f179cf0bc63f 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go @@ -281,7 +281,7 @@ func NewImageResolver(cfg imageresolver.Config) ImageResolver { return newRcImageResolver(cfg) } -// DEV: Move this to the imageresolver package after the refactor is complete +// DEV: Delete this in favor of the imageresolver package one after the refactor is complete func newDatadoghqRegistries(datadogRegistriesList []string) map[string]struct{} { datadoghqRegistries := make(map[string]struct{}) for _, registry := range datadogRegistriesList { From 57878bde6bd0714a084f19a205030f23d3e154d5 Mon Sep 17 00:00:00 2001 From: erikayasuda Date: Fri, 19 Dec 2025 16:22:15 -0500 Subject: [PATCH 16/17] added simple tests to validate config --- .../imageresolver/config_test.go | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config_test.go diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config_test.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config_test.go new file mode 100644 index 00000000000000..9377f6563854db --- /dev/null +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config_test.go @@ -0,0 +1,81 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +//go:build kubeapiserver + +package imageresolver + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/DataDog/datadog-agent/comp/core/config" +) + +func TestNewConfig(t *testing.T) { + tests := []struct { + name string + configFactory func(*testing.T) config.Component + expectedState Config + }{ + { + name: "default_config", + configFactory: func(t *testing.T) config.Component { + mockConfig := config.NewMock(t) + mockConfig.SetWithoutSource("site", "datadoghq.com") + return mockConfig + }, + expectedState: Config{ + Site: "datadoghq.com", + DDRegistries: map[string]struct{}{"gcr.io/datadoghq": {}, "docker.io/datadog": {}, "public.ecr.aws/datadog": {}}, + RCClient: nil, + MaxInitRetries: 5, + InitRetryDelay: 1 * time.Second, + }, + }, + { + name: "custom_dd_registries", + configFactory: func(t *testing.T) config.Component { + mockConfig := config.NewMock(t) + mockConfig.SetWithoutSource("site", "datadoghq.com") + mockConfig.SetWithoutSource("admission_controller.auto_instrumentation.default_dd_registries", []string{"helloworld.io/datadog"}) + return mockConfig + }, + expectedState: Config{ + Site: "datadoghq.com", + DDRegistries: map[string]struct{}{"helloworld.io/datadog": {}}, + RCClient: nil, + MaxInitRetries: 5, + InitRetryDelay: 1 * time.Second, + }, + }, + { + name: "configured_site", + configFactory: func(t *testing.T) config.Component { + mockConfig := config.NewMock(t) + mockConfig.SetWithoutSource("site", "datad0g.com") + return mockConfig + }, + expectedState: Config{ + Site: "datad0g.com", + DDRegistries: map[string]struct{}{"gcr.io/datadoghq": {}, "docker.io/datadog": {}, "public.ecr.aws/datadog": {}}, + RCClient: nil, + MaxInitRetries: 5, + InitRetryDelay: 1 * time.Second, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockConfig := tt.configFactory(t) + result := NewConfig(mockConfig, nil) + + require.Equal(t, tt.expectedState, result) + }) + } +} From d2503614db5a5e7fbbf699291c2e32d69c12cf99 Mon Sep 17 00:00:00 2001 From: erikayasuda Date: Mon, 22 Dec 2025 13:01:26 -0800 Subject: [PATCH 17/17] remove NewTestConfig() --- .../autoinstrumentation/image_resolver_test.go | 16 ++++++++-------- .../autoinstrumentation/imageresolver/config.go | 11 ----------- .../mutate/autoinstrumentation/injector_test.go | 5 +++-- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go index 6774eb04777425..d6dd9351ee22af 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver_test.go @@ -108,7 +108,7 @@ func (m *mockRCClient) setBlocking(block bool) { func TestNewImageResolver(t *testing.T) { t.Run("with_remote_config_client", func(t *testing.T) { mockClient := newMockRCClient("image_resolver_multi_repo.json") - mockConfig := imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, mockClient, 5, 1*time.Second) + mockConfig := imageresolver.NewConfig(config.NewMock(t), mockClient) resolver := NewImageResolver(mockConfig) _, ok := resolver.(*remoteConfigImageResolver) @@ -116,7 +116,7 @@ func TestNewImageResolver(t *testing.T) { }) t.Run("without_remote_config_client__typed_nil", func(t *testing.T) { - mockConfig := imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, (*mockRCClient)(nil), 5, 1*time.Second) + mockConfig := imageresolver.NewConfig(config.NewMock(t), (*mockRCClient)(nil)) resolver := NewImageResolver(mockConfig) _, ok := resolver.(*noOpImageResolver) @@ -124,7 +124,7 @@ func TestNewImageResolver(t *testing.T) { }) t.Run("without_remote_config_client__untyped_nil", func(t *testing.T) { - mockConfig := imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, nil, 5, 1*time.Second) + mockConfig := imageresolver.NewConfig(config.NewMock(t), nil) resolver := NewImageResolver(mockConfig) _, ok := resolver.(*noOpImageResolver) @@ -229,7 +229,7 @@ func TestImageResolverEmptyConfig(t *testing.T) { func TestRemoteConfigImageResolver_Resolve(t *testing.T) { mockRCClient := newMockRCClient("image_resolver_multi_repo.json") - resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, mockRCClient, 5, 1*time.Second)) + resolver := newRcImageResolver(imageresolver.NewConfig(config.NewMock(t), mockRCClient)) testCases := []struct { name string @@ -414,7 +414,7 @@ func TestRemoteConfigImageResolver_InvalidDigestValidation(t *testing.T) { } func TestRemoteConfigImageResolver_ConcurrentAccess(t *testing.T) { - resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, newMockRCClient("image_resolver_multi_repo.json"), 5, 1*time.Second)) + resolver := newRcImageResolver(imageresolver.NewConfig(config.NewMock(t), newMockRCClient("image_resolver_multi_repo.json"))) t.Run("concurrent_read_write", func(_ *testing.T) { var wg sync.WaitGroup @@ -495,7 +495,7 @@ func TestAsyncInitialization(t *testing.T) { mockClient := newMockRCClient("image_resolver_multi_repo.json") mockClient.setBlocking(true) // Block initialization - resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, mockClient, 2, 10*time.Millisecond)) + resolver := newRcImageResolver(imageresolver.NewConfig(config.NewMock(t), mockClient)) resolved, ok := resolver.Resolve("gcr.io/datadoghq", "dd-lib-python-init", "latest") assert.False(t, ok, "Should not complete image resolution during initialization") @@ -506,7 +506,7 @@ func TestAsyncInitialization(t *testing.T) { mockClient := newMockRCClient("image_resolver_multi_repo.json") mockClient.setBlocking(true) - resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, mockClient, 2, 10*time.Millisecond)) + resolver := newRcImageResolver(imageresolver.NewConfig(config.NewMock(t), mockClient)) resolved, ok := resolver.Resolve("gcr.io/datadoghq", "dd-lib-python-init", "latest") assert.False(t, ok, "Should not complete image resolution during initialization") @@ -530,7 +530,7 @@ func TestAsyncInitialization(t *testing.T) { } close(mockClient.configsReady) - resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, mockClient, 2, 10*time.Millisecond)) + resolver := newRcImageResolver(imageresolver.NewConfig(config.NewMock(t), mockClient)) time.Sleep(50 * time.Millisecond) resolved, ok := resolver.Resolve("gcr.io/datadoghq", "dd-lib-python-init", "latest") diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go index edabfbb914caaa..c2110a2642649e 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/config.go @@ -41,14 +41,3 @@ func NewConfig(cfg config.Component, rcClient RemoteConfigClient) Config { InitRetryDelay: 1 * time.Second, } } - -// NewTestConfig creates a new Config for testing -func NewTestConfig(site string, ddRegistries []string, rcClient RemoteConfigClient, maxRetries int, retryDelay time.Duration) Config { - return Config{ - Site: site, - DDRegistries: newDatadoghqRegistries(ddRegistries), - RCClient: rcClient, - MaxInitRetries: maxRetries, - InitRetryDelay: retryDelay, - } -} diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go index bec7d9d9153beb..2e7602336ac668 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/injector_test.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" + "github.com/DataDog/datadog-agent/comp/core/config" "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver" "github.com/DataDog/datadog-agent/pkg/util/pointer" ) @@ -97,7 +98,7 @@ func TestInjectorWithRemoteConfigImageResolver(t *testing.T) { var resolver ImageResolver if tc.hasRemoteData { mockClient := newMockRCClient("image_resolver_multi_repo.json") - resolver = newRcImageResolver(imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, mockClient, 2, 10*time.Millisecond)) + resolver = newRcImageResolver(imageresolver.NewConfig(config.NewMock(t), mockClient)) } else { resolver = newNoOpImageResolver() } @@ -113,7 +114,7 @@ func TestInjectorWithRemoteConfigImageResolver(t *testing.T) { func TestInjectorWithRemoteConfigImageResolverAfterInit(t *testing.T) { mockClient := newMockRCClient("image_resolver_multi_repo.json") - resolver := newRcImageResolver(imageresolver.NewTestConfig("testsite", []string{"gcr.io/datadoghq"}, mockClient, 2, 10*time.Millisecond)) + resolver := newRcImageResolver(imageresolver.NewConfig(config.NewMock(t), mockClient)) assert.Eventually(t, func() bool { _, ok := resolver.Resolve("gcr.io/datadoghq", "apm-inject", "0")