summaryrefslogtreecommitdiff
path: root/vendor/github.com/testcontainers/testcontainers-go/docker_auth.go
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-05-11 21:12:57 -0600
committermo khan <mo@mokhan.ca>2025-05-11 21:12:57 -0600
commit60440f90dca28e99a31dd328c5f6d5dc0f9b6a2e (patch)
tree2f54adf55086516f162f0a55a5347e6b25f7f176 /vendor/github.com/testcontainers/testcontainers-go/docker_auth.go
parent05ca9b8d3a9c7203a3a3b590beaa400900bd9007 (diff)
chore: vendor go dependencies
Diffstat (limited to 'vendor/github.com/testcontainers/testcontainers-go/docker_auth.go')
-rw-r--r--vendor/github.com/testcontainers/testcontainers-go/docker_auth.go282
1 files changed, 282 insertions, 0 deletions
diff --git a/vendor/github.com/testcontainers/testcontainers-go/docker_auth.go b/vendor/github.com/testcontainers/testcontainers-go/docker_auth.go
new file mode 100644
index 0000000..58b3ef2
--- /dev/null
+++ b/vendor/github.com/testcontainers/testcontainers-go/docker_auth.go
@@ -0,0 +1,282 @@
+package testcontainers
+
+import (
+ "context"
+ "crypto/md5"
+ "encoding/base64"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/url"
+ "os"
+ "sync"
+
+ "github.com/cpuguy83/dockercfg"
+ "github.com/docker/docker/api/types/registry"
+
+ "github.com/testcontainers/testcontainers-go/internal/core"
+)
+
+// defaultRegistryFn is variable overwritten in tests to check for behaviour with different default values.
+var defaultRegistryFn = defaultRegistry
+
+// getRegistryCredentials is a variable overwritten in tests to mock the dockercfg.GetRegistryCredentials function.
+var getRegistryCredentials = dockercfg.GetRegistryCredentials
+
+// DockerImageAuth returns the auth config for the given Docker image, extracting first its Docker registry.
+// Finally, it will use the credential helpers to extract the information from the docker config file
+// for that registry, if it exists.
+func DockerImageAuth(ctx context.Context, image string) (string, registry.AuthConfig, error) {
+ configs, err := getDockerAuthConfigs()
+ if err != nil {
+ reg := core.ExtractRegistry(image, defaultRegistryFn(ctx))
+ return reg, registry.AuthConfig{}, err
+ }
+
+ return dockerImageAuth(ctx, image, configs)
+}
+
+// dockerImageAuth returns the auth config for the given Docker image.
+func dockerImageAuth(ctx context.Context, image string, configs map[string]registry.AuthConfig) (string, registry.AuthConfig, error) {
+ defaultRegistry := defaultRegistryFn(ctx)
+ reg := core.ExtractRegistry(image, defaultRegistry)
+
+ if cfg, ok := getRegistryAuth(reg, configs); ok {
+ return reg, cfg, nil
+ }
+
+ return reg, registry.AuthConfig{}, dockercfg.ErrCredentialsNotFound
+}
+
+func getRegistryAuth(reg string, cfgs map[string]registry.AuthConfig) (registry.AuthConfig, bool) {
+ if cfg, ok := cfgs[reg]; ok {
+ return cfg, true
+ }
+
+ // fallback match using authentication key host
+ for k, cfg := range cfgs {
+ keyURL, err := url.Parse(k)
+ if err != nil {
+ continue
+ }
+
+ host := keyURL.Host
+ if keyURL.Scheme == "" {
+ // url.Parse: The url may be relative (a path, without a host) [...]
+ host = keyURL.Path
+ }
+
+ if host == reg {
+ return cfg, true
+ }
+ }
+
+ return registry.AuthConfig{}, false
+}
+
+// defaultRegistry returns the default registry to use when pulling images
+// It will use the docker daemon to get the default registry, returning "https://index.docker.io/v1/" if
+// it fails to get the information from the daemon
+func defaultRegistry(ctx context.Context) string {
+ client, err := NewDockerClientWithOpts(ctx)
+ if err != nil {
+ return core.IndexDockerIO
+ }
+ defer client.Close()
+
+ info, err := client.Info(ctx)
+ if err != nil {
+ return core.IndexDockerIO
+ }
+
+ return info.IndexServerAddress
+}
+
+// authConfigResult is a result looking up auth details for key.
+type authConfigResult struct {
+ key string
+ cfg registry.AuthConfig
+ err error
+}
+
+// credentialsCache is a cache for registry credentials.
+type credentialsCache struct {
+ entries map[string]credentials
+ mtx sync.RWMutex
+}
+
+// credentials represents the username and password for a registry.
+type credentials struct {
+ username string
+ password string
+}
+
+var creds = &credentialsCache{entries: map[string]credentials{}}
+
+// AuthConfig updates the details in authConfig for the given hostname
+// as determined by the details in configKey.
+func (c *credentialsCache) AuthConfig(hostname, configKey string, authConfig *registry.AuthConfig) error {
+ u, p, err := creds.get(hostname, configKey)
+ if err != nil {
+ return err
+ }
+
+ if u != "" {
+ authConfig.Username = u
+ authConfig.Password = p
+ } else {
+ authConfig.IdentityToken = p
+ }
+
+ return nil
+}
+
+// get returns the username and password for the given hostname
+// as determined by the details in configPath.
+// If the username is empty, the password is an identity token.
+func (c *credentialsCache) get(hostname, configKey string) (string, string, error) {
+ key := configKey + ":" + hostname
+ c.mtx.RLock()
+ entry, ok := c.entries[key]
+ c.mtx.RUnlock()
+
+ if ok {
+ return entry.username, entry.password, nil
+ }
+
+ // No entry found, request and cache.
+ user, password, err := getRegistryCredentials(hostname)
+ if err != nil {
+ return "", "", fmt.Errorf("getting credentials for %s: %w", hostname, err)
+ }
+
+ c.mtx.Lock()
+ c.entries[key] = credentials{username: user, password: password}
+ c.mtx.Unlock()
+
+ return user, password, nil
+}
+
+// configKey returns a key to use for caching credentials based on
+// the contents of the currently active config.
+func configKey(cfg *dockercfg.Config) (string, error) {
+ h := md5.New()
+ if err := json.NewEncoder(h).Encode(cfg); err != nil {
+ return "", fmt.Errorf("encode config: %w", err)
+ }
+
+ return hex.EncodeToString(h.Sum(nil)), nil
+}
+
+// getDockerAuthConfigs returns a map with the auth configs from the docker config file
+// using the registry as the key
+func getDockerAuthConfigs() (map[string]registry.AuthConfig, error) {
+ cfg, err := getDockerConfig()
+ if err != nil {
+ if errors.Is(err, os.ErrNotExist) {
+ return map[string]registry.AuthConfig{}, nil
+ }
+
+ return nil, err
+ }
+
+ key, err := configKey(cfg)
+ if err != nil {
+ return nil, err
+ }
+
+ size := len(cfg.AuthConfigs) + len(cfg.CredentialHelpers)
+ cfgs := make(map[string]registry.AuthConfig, size)
+ results := make(chan authConfigResult, size)
+ var wg sync.WaitGroup
+ wg.Add(size)
+ for k, v := range cfg.AuthConfigs {
+ go func(k string, v dockercfg.AuthConfig) {
+ defer wg.Done()
+
+ ac := registry.AuthConfig{
+ Auth: v.Auth,
+ Email: v.Email,
+ IdentityToken: v.IdentityToken,
+ Password: v.Password,
+ RegistryToken: v.RegistryToken,
+ ServerAddress: v.ServerAddress,
+ Username: v.Username,
+ }
+
+ switch {
+ case ac.Username == "" && ac.Password == "":
+ // Look up credentials from the credential store.
+ if err := creds.AuthConfig(k, key, &ac); err != nil {
+ results <- authConfigResult{err: err}
+ return
+ }
+ case ac.Auth == "":
+ // Create auth from the username and password encoding.
+ ac.Auth = base64.StdEncoding.EncodeToString([]byte(ac.Username + ":" + ac.Password))
+ }
+
+ results <- authConfigResult{key: k, cfg: ac}
+ }(k, v)
+ }
+
+ // In the case where the auth field in the .docker/conf.json is empty, and the user has
+ // credential helpers registered the auth comes from there.
+ for k := range cfg.CredentialHelpers {
+ go func(k string) {
+ defer wg.Done()
+
+ var ac registry.AuthConfig
+ if err := creds.AuthConfig(k, key, &ac); err != nil {
+ results <- authConfigResult{err: err}
+ return
+ }
+
+ results <- authConfigResult{key: k, cfg: ac}
+ }(k)
+ }
+
+ go func() {
+ wg.Wait()
+ close(results)
+ }()
+
+ var errs []error
+ for result := range results {
+ if result.err != nil {
+ errs = append(errs, result.err)
+ continue
+ }
+
+ cfgs[result.key] = result.cfg
+ }
+
+ if len(errs) > 0 {
+ return nil, errors.Join(errs...)
+ }
+
+ return cfgs, nil
+}
+
+// getDockerConfig returns the docker config file. It will internally check, in this particular order:
+// 1. the DOCKER_AUTH_CONFIG environment variable, unmarshalling it into a dockercfg.Config
+// 2. the DOCKER_CONFIG environment variable, as the path to the config file
+// 3. else it will load the default config file, which is ~/.docker/config.json
+func getDockerConfig() (*dockercfg.Config, error) {
+ if env := os.Getenv("DOCKER_AUTH_CONFIG"); env != "" {
+ var cfg dockercfg.Config
+ if err := json.Unmarshal([]byte(env), &cfg); err != nil {
+ return nil, fmt.Errorf("unmarshal DOCKER_AUTH_CONFIG: %w", err)
+ }
+
+ return &cfg, nil
+ }
+
+ cfg, err := dockercfg.LoadDefaultConfig()
+ if err != nil {
+ return nil, fmt.Errorf("load default config: %w", err)
+ }
+
+ return &cfg, nil
+}