Skip to content

Commit 818a050

Browse files
authored
[APMLP-849] Refactor remaining ImageResolver files into imageresolver package (#45151)
### What does this PR do? Continuing the refactor of the `ImageResolver` code into the `imageresolver` package for clear delineation of the code. See first part [here](#43308). Namely, the changes are: - Moving `image_resolver.go` and `image_resolver_test.go` into `imageresolver/` - Renaming functions and types to avoid stutter like `imageresolver.NewImageResolver(...)` - Creating an `image.go` file to separate definitions representing images handled by the Resolvers - Updating references outside of the `imageresolver` package to reflect the above changes No functional changes are introduced by this PR, only refactor/clean up. ### Motivation The original `imageresolver` code was hard to read, and the goal here is to make this easier to read and maintain for all repo contributors. This is also to help make future changes easier to implement along with the general refactor initiative for the autoinstrumentation cluster agent code. ### Describe how you validated your changes Verified existing tests all pass. ### Additional Notes Co-authored-by: erika.yasuda <erika.yasuda@datadoghq.com>
1 parent 29ab9b1 commit 818a050

File tree

11 files changed

+137
-131
lines changed

11 files changed

+137
-131
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func NewAutoInstrumentation(datadogConfig config.Component, wmeta workloadmeta.C
2929
return nil, fmt.Errorf("failed to create auto instrumentation config: %v", err)
3030
}
3131

32-
imageResolver := NewImageResolver(imageresolver.NewConfig(datadogConfig, rcClient))
32+
imageResolver := imageresolver.New(imageresolver.NewConfig(datadogConfig, rcClient))
3333
apm, err := NewTargetMutator(config, wmeta, imageResolver)
3434
if err != nil {
3535
return nil, fmt.Errorf("failed to create auto instrumentation namespace mutator: %v", err)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type RemoteConfigClient interface {
2222
Subscribe(product string, callback func(map[string]state.RawConfig, func(string, state.ApplyStatus)))
2323
}
2424

25-
// Config contains information needed to create an ImageResolver
25+
// Config contains information needed to create a Resolver
2626
type Config struct {
2727
Site string
2828
DDRegistries map[string]struct{}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2016-present Datadog, Inc.
5+
6+
//go:build kubeapiserver
7+
8+
package imageresolver
9+
10+
// ImageInfo represents information about an image from remote configuration.
11+
type ImageInfo struct {
12+
Tag string `json:"tag"`
13+
Digest string `json:"digest"`
14+
CanonicalVersion string `json:"canonical_version"`
15+
}
16+
17+
// RepositoryConfig represents a repository configuration from remote config.
18+
type RepositoryConfig struct {
19+
RepositoryName string `json:"repository_name"`
20+
Images []ImageInfo `json:"images"`
21+
}
22+
23+
// ResolvedImage represents a fully resolved image with digest and metadata.
24+
type ResolvedImage struct {
25+
FullImageRef string // e.g., "gcr.io/datadoghq/dd-lib-python-init@sha256:abc123..."
26+
CanonicalVersion string // e.g., "3.1.0"
27+
}
28+
29+
func newResolvedImage(registry string, repositoryName string, imageInfo ImageInfo) *ResolvedImage {
30+
return &ResolvedImage{
31+
FullImageRef: registry + "/" + repositoryName + "@" + imageInfo.Digest,
32+
CanonicalVersion: imageInfo.CanonicalVersion,
33+
}
34+
}

pkg/clusteragent/admission/mutate/autoinstrumentation/image_resolver.go renamed to pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver/image_resolver.go

Lines changed: 22 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,22 @@
55

66
//go:build kubeapiserver
77

8-
package autoinstrumentation
8+
package imageresolver
99

1010
import (
11-
"encoding/json"
1211
"fmt"
1312
"reflect"
1413
"strings"
1514
"sync"
1615
"time"
1716

18-
"github.com/opencontainers/go-digest"
19-
2017
"github.com/DataDog/datadog-agent/pkg/clusteragent/admission/metrics"
21-
"github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation/imageresolver"
2218
"github.com/DataDog/datadog-agent/pkg/remoteconfig/state"
2319
"github.com/DataDog/datadog-agent/pkg/util/log"
2420
)
2521

26-
// ImageResolver resolves container image references from tag-based to digest-based.
27-
type ImageResolver interface {
22+
// Resolver resolves container image references from tag-based to digest-based.
23+
type Resolver interface {
2824
// Resolve takes a registry, repository, and tag string (e.g., "gcr.io/datadoghq", "dd-lib-python-init", "v3")
2925
// and returns a resolved image reference (e.g., "gcr.io/datadoghq/dd-lib-python-init@sha256:abc123")
3026
// If resolution fails or is not available, it returns the original image reference ("gcr.io/datadoghq/dd-lib-python-init:v3", false).
@@ -33,24 +29,25 @@ type ImageResolver interface {
3329

3430
// noOpImageResolver is a simple implementation that returns the original image unchanged.
3531
// This is used when no remote config client is available.
36-
type noOpImageResolver struct{}
32+
type noOpResolver struct{}
3733

38-
// newNoOpImageResolver creates a new noOpImageResolver.
39-
func newNoOpImageResolver() ImageResolver {
40-
return &noOpImageResolver{}
34+
// NewNoOpResolver creates a new noOpImageResolver.
35+
// This is useful for testing or when image resolution is not needed.
36+
func NewNoOpResolver() Resolver {
37+
return &noOpResolver{}
4138
}
4239

4340
// ResolveImage returns the original image reference.
44-
func (r *noOpImageResolver) Resolve(registry string, repository string, tag string) (*ResolvedImage, bool) {
41+
func (r *noOpResolver) Resolve(registry string, repository string, tag string) (*ResolvedImage, bool) {
4542
log.Debugf("Cannot resolve %s/%s:%s without remote config", registry, repository, tag)
4643
metrics.ImageResolutionAttempts.Inc(repository, tag, tag)
4744
return nil, false
4845
}
4946

5047
// remoteConfigImageResolver resolves image references using remote configuration data.
5148
// It maintains a cache of image mappings received from the remote config service.
52-
type remoteConfigImageResolver struct {
53-
rcClient imageresolver.RemoteConfigClient
49+
type rcResolver struct {
50+
rcClient RemoteConfigClient
5451

5552
mu sync.RWMutex
5653
imageMappings map[string]map[string]ImageInfo // repository name -> tag -> resolved image
@@ -61,8 +58,8 @@ type remoteConfigImageResolver struct {
6158
retryDelay time.Duration
6259
}
6360

64-
func newRcImageResolver(cfg imageresolver.Config) ImageResolver {
65-
resolver := &remoteConfigImageResolver{
61+
func newRcResolver(cfg Config) Resolver {
62+
resolver := &rcResolver{
6663
rcClient: cfg.RCClient,
6764
imageMappings: make(map[string]map[string]ImageInfo),
6865
maxRetries: cfg.MaxInitRetries,
@@ -84,7 +81,7 @@ func newRcImageResolver(cfg imageresolver.Config) ImageResolver {
8481
return resolver
8582
}
8683

87-
func (r *remoteConfigImageResolver) waitForInitialConfig() error {
84+
func (r *rcResolver) waitForInitialConfig() error {
8885
if currentConfigs := r.rcClient.GetConfigs(state.ProductGradualRollout); len(currentConfigs) > 0 {
8986
log.Debugf("Initial configs available immediately: %d configurations", len(currentConfigs))
9087
r.updateCache(currentConfigs)
@@ -112,7 +109,7 @@ func (r *remoteConfigImageResolver) waitForInitialConfig() error {
112109
// Output: "dd-lib-python-init@sha256:abc123...", true
113110
// If resolution fails or is not available, it returns nil.
114111
// Output: nil, false
115-
func (r *remoteConfigImageResolver) Resolve(registry string, repository string, tag string) (*ResolvedImage, bool) {
112+
func (r *rcResolver) Resolve(registry string, repository string, tag string) (*ResolvedImage, bool) {
116113
if !isDatadoghqRegistry(registry, r.datadoghqRegistries) {
117114
log.Debugf("Not a Datadoghq registry, not resolving")
118115
return nil, false
@@ -148,20 +145,9 @@ func (r *remoteConfigImageResolver) Resolve(registry string, repository string,
148145
return resolvedImage, true
149146
}
150147

151-
func isDatadoghqRegistry(registry string, datadoghqRegistries map[string]struct{}) bool {
152-
_, exists := datadoghqRegistries[registry]
153-
return exists
154-
}
155-
156-
// isValidDigest validates that a digest string follows the OCI image specification format
157-
func isValidDigest(digestStr string) bool {
158-
_, err := digest.Parse(digestStr)
159-
return err == nil
160-
}
161-
162148
// updateCache processes configuration data and updates the image mappings cache.
163149
// This is the core logic shared by both initialization and remote config updates.
164-
func (r *remoteConfigImageResolver) updateCache(configs map[string]state.RawConfig) {
150+
func (r *rcResolver) updateCache(configs map[string]state.RawConfig) {
165151
validConfigs, errors := parseAndValidateConfigs(configs)
166152

167153
for configKey, err := range errors {
@@ -171,26 +157,7 @@ func (r *remoteConfigImageResolver) updateCache(configs map[string]state.RawConf
171157
r.updateCacheFromParsedConfigs(validConfigs)
172158
}
173159

174-
func parseAndValidateConfigs(configs map[string]state.RawConfig) (map[string]RepositoryConfig, map[string]error) {
175-
validConfigs := make(map[string]RepositoryConfig)
176-
errors := make(map[string]error)
177-
for configKey, rawConfig := range configs {
178-
var repo RepositoryConfig
179-
if err := json.Unmarshal(rawConfig.Config, &repo); err != nil {
180-
errors[configKey] = fmt.Errorf("failed to unmarshal: %w", err)
181-
continue
182-
}
183-
184-
if repo.RepositoryName == "" {
185-
errors[configKey] = fmt.Errorf("missing repository_name in config %s", configKey)
186-
continue
187-
}
188-
validConfigs[configKey] = repo
189-
}
190-
return validConfigs, errors
191-
}
192-
193-
func (r *remoteConfigImageResolver) updateCacheFromParsedConfigs(validConfigs map[string]RepositoryConfig) {
160+
func (r *rcResolver) updateCacheFromParsedConfigs(validConfigs map[string]RepositoryConfig) {
194161
newCache := make(map[string]map[string]ImageInfo)
195162

196163
for _, repo := range validConfigs {
@@ -225,7 +192,7 @@ func (r *remoteConfigImageResolver) updateCacheFromParsedConfigs(validConfigs ma
225192
// - When processUpdate is called, it receives the complete current state via GetConfigs()
226193
// - If a repository is not in the update, it means it's no longer active
227194
// - Therefore, replacing the entire cache ensures we stay in sync with remote config
228-
func (r *remoteConfigImageResolver) processUpdate(update map[string]state.RawConfig, applyStateCallback func(string, state.ApplyStatus)) {
195+
func (r *rcResolver) processUpdate(update map[string]state.RawConfig, applyStateCallback func(string, state.ApplyStatus)) {
229196
validConfigs, errors := parseAndValidateConfigs(update)
230197

231198
r.updateCacheFromParsedConfigs(validConfigs)
@@ -244,48 +211,13 @@ func (r *remoteConfigImageResolver) processUpdate(update map[string]state.RawCon
244211
}
245212
}
246213

247-
// ImageInfo represents information about an image from remote configuration.
248-
type ImageInfo struct {
249-
Tag string `json:"tag"`
250-
Digest string `json:"digest"`
251-
CanonicalVersion string `json:"canonical_version"`
252-
}
253-
254-
// RepositoryConfig represents a repository configuration from remote config.
255-
type RepositoryConfig struct {
256-
RepositoryName string `json:"repository_name"`
257-
Images []ImageInfo `json:"images"`
258-
}
259-
260-
// ResolvedImage represents a fully resolved image with digest and metadata.
261-
type ResolvedImage struct {
262-
FullImageRef string // e.g., "gcr.io/datadoghq/dd-lib-python-init@sha256:abc123..."
263-
CanonicalVersion string // e.g., "3.1.0"
264-
}
265-
266-
func newResolvedImage(registry string, repositoryName string, imageInfo ImageInfo) *ResolvedImage {
267-
return &ResolvedImage{
268-
FullImageRef: registry + "/" + repositoryName + "@" + imageInfo.Digest,
269-
CanonicalVersion: imageInfo.CanonicalVersion,
270-
}
271-
}
272-
273-
// NewImageResolver creates the appropriate ImageResolver based on whether
214+
// New creates the appropriate Resolver based on whether
274215
// a remote config client is available.
275-
func NewImageResolver(cfg imageresolver.Config) ImageResolver {
216+
func New(cfg Config) Resolver {
276217
if cfg.RCClient == nil || reflect.ValueOf(cfg.RCClient).IsNil() {
277218
log.Debugf("No remote config client available")
278-
return newNoOpImageResolver()
219+
return NewNoOpResolver()
279220
}
280221

281-
return newRcImageResolver(cfg)
282-
}
283-
284-
// DEV: Delete this in favor of the imageresolver package one after the refactor is complete
285-
func newDatadoghqRegistries(datadogRegistriesList []string) map[string]struct{} {
286-
datadoghqRegistries := make(map[string]struct{})
287-
for _, registry := range datadogRegistriesList {
288-
datadoghqRegistries[registry] = struct{}{}
289-
}
290-
return datadoghqRegistries
222+
return newRcResolver(cfg)
291223
}

0 commit comments

Comments
 (0)