package testcontainers import ( "context" "encoding/base64" "encoding/json" "os" "github.com/cpuguy83/dockercfg" "github.com/docker/docker/api/types/registry" "github.com/testcontainers/testcontainers-go/internal/testcontainersdocker" ) // 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) { defaultRegistry := defaultRegistry(ctx) reg := testcontainersdocker.ExtractRegistry(image, defaultRegistry) cfgs, err := getDockerAuthConfigs() if err != nil { return reg, registry.AuthConfig{}, err } if cfg, ok := cfgs[reg]; ok { return reg, cfg, nil } return reg, registry.AuthConfig{}, dockercfg.ErrCredentialsNotFound } // 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 := testcontainersdocker.NewClient(ctx) if err != nil { return testcontainersdocker.IndexDockerIO } defer client.Close() info, err := client.Info(ctx) if err != nil { return testcontainersdocker.IndexDockerIO } return info.IndexServerAddress } // 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 { return nil, err } cfgs := map[string]registry.AuthConfig{} for k, v := range cfg.AuthConfigs { ac := registry.AuthConfig{ Auth: v.Auth, Email: v.Email, IdentityToken: v.IdentityToken, Password: v.Password, RegistryToken: v.RegistryToken, ServerAddress: v.ServerAddress, Username: v.Username, } if v.Username == "" && v.Password == "" { u, p, _ := dockercfg.GetRegistryCredentials(k) ac.Username = u ac.Password = p } if v.Auth == "" { ac.Auth = base64.StdEncoding.EncodeToString([]byte(ac.Username + ":" + ac.Password)) } cfgs[k] = ac } // 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 { ac := registry.AuthConfig{} u, p, _ := dockercfg.GetRegistryCredentials(k) ac.Username = u ac.Password = p cfgs[k] = ac } 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) { dockerAuthConfig := os.Getenv("DOCKER_AUTH_CONFIG") if dockerAuthConfig != "" { cfg := dockercfg.Config{} err := json.Unmarshal([]byte(dockerAuthConfig), &cfg) if err == nil { return cfg, nil } } cfg, err := dockercfg.LoadDefaultConfig() if err != nil { return cfg, err } return cfg, nil }