mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2025-12-20 22:59:42 -05:00
fix: Enable authentication to multiple registries again. (#400)
* fix: Enable authentication to multiple registries again. * fix: Tests run when conflictsWith is disabled. * chore: Improve docs for multiple registry auth. * tests: Add multiple registry auth test. * fix: Correct index of auth structure. * chore: Add newest docs [ci skip]
This commit is contained in:
parent
d12e13fac9
commit
df4b76aa04
6 changed files with 84 additions and 48 deletions
|
|
@ -120,6 +120,9 @@ When passing in a config file either the corresponding `auth` string of the repo
|
||||||
[credential helpers](https://github.com/docker/docker-credential-helpers#available-programs) are
|
[credential helpers](https://github.com/docker/docker-credential-helpers#available-programs) are
|
||||||
used to retrieve the authentication credentials.
|
used to retrieve the authentication credentials.
|
||||||
|
|
||||||
|
-> **Note**
|
||||||
|
`config_file` has predence over all other options. You can theoretically specify values for every attribute but the credentials obtained through the `config_file` will override the manually set `username`/`password`
|
||||||
|
|
||||||
You can still use the environment variables `DOCKER_REGISTRY_USER` and `DOCKER_REGISTRY_PASS`.
|
You can still use the environment variables `DOCKER_REGISTRY_USER` and `DOCKER_REGISTRY_PASS`.
|
||||||
|
|
||||||
An example content of the file `~/.docker/config.json` on macOS may look like follows:
|
An example content of the file `~/.docker/config.json` on macOS may look like follows:
|
||||||
|
|
@ -165,7 +168,7 @@ provider "docker" {
|
||||||
- `cert_path` (String) Path to directory with Docker TLS config
|
- `cert_path` (String) Path to directory with Docker TLS config
|
||||||
- `host` (String) The Docker daemon address
|
- `host` (String) The Docker daemon address
|
||||||
- `key_material` (String) PEM-encoded content of Docker client private key
|
- `key_material` (String) PEM-encoded content of Docker client private key
|
||||||
- `registry_auth` (Block List, Max: 1) (see [below for nested schema](#nestedblock--registry_auth))
|
- `registry_auth` (Block Set) (see [below for nested schema](#nestedblock--registry_auth))
|
||||||
- `ssh_opts` (List of String) Additional SSH option flags to be appended when using `ssh://` protocol
|
- `ssh_opts` (List of String) Additional SSH option flags to be appended when using `ssh://` protocol
|
||||||
|
|
||||||
<a id="nestedblock--registry_auth"></a>
|
<a id="nestedblock--registry_auth"></a>
|
||||||
|
|
@ -177,7 +180,7 @@ Required:
|
||||||
|
|
||||||
Optional:
|
Optional:
|
||||||
|
|
||||||
- `config_file` (String) Path to docker json file for registry auth
|
- `config_file` (String) Path to docker json file for registry auth. Defaults to `~/.docker/config.json`. If `DOCKER_CONFIG` is set, the value of `DOCKER_CONFIG` is used as the path. `config_file` has predencen over all other options.
|
||||||
- `config_file_content` (String) Plain content of the docker json file for registry auth
|
- `config_file_content` (String) Plain content of the docker json file for registry auth. `config_file_content` has precedence over username/password.
|
||||||
- `password` (String, Sensitive) Password for the registry
|
- `password` (String, Sensitive) Password for the registry. Defaults to `DOCKER_REGISTRY_PASS` env variable if set.
|
||||||
- `username` (String) Username for the registry
|
- `username` (String) Username for the registry. Defaults to `DOCKER_REGISTRY_USER` env variable if set.
|
||||||
|
|
@ -48,6 +48,7 @@ resource "docker_image" "ubuntu" {
|
||||||
- `domainname` (String) Domain name of the container.
|
- `domainname` (String) Domain name of the container.
|
||||||
- `entrypoint` (List of String) The command to use as the Entrypoint for the container. The Entrypoint allows you to configure a container to run as an executable. For example, to run `/usr/bin/myprogram` when starting a container, set the entrypoint to be `"/usr/bin/myprogra"]`.
|
- `entrypoint` (List of String) The command to use as the Entrypoint for the container. The Entrypoint allows you to configure a container to run as an executable. For example, to run `/usr/bin/myprogram` when starting a container, set the entrypoint to be `"/usr/bin/myprogra"]`.
|
||||||
- `env` (Set of String) Environment variables to set in the form of `KEY=VALUE`, e.g. `DEBUG=0`
|
- `env` (Set of String) Environment variables to set in the form of `KEY=VALUE`, e.g. `DEBUG=0`
|
||||||
|
- `gpus` (String) GPU devices to add to the container. Currently, only the value `all` is supported. Passing any other value will result in unexpected behavior.
|
||||||
- `group_add` (Set of String) Additional groups for the container user
|
- `group_add` (Set of String) Additional groups for the container user
|
||||||
- `healthcheck` (Block List, Max: 1) A test to perform to check that the container is healthy (see [below for nested schema](#nestedblock--healthcheck))
|
- `healthcheck` (Block List, Max: 1) A test to perform to check that the container is healthy (see [below for nested schema](#nestedblock--healthcheck))
|
||||||
- `host` (Block Set) Additional hosts to add to the container. (see [below for nested schema](#nestedblock--host))
|
- `host` (Block Set) Additional hosts to add to the container. (see [below for nested schema](#nestedblock--host))
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -83,47 +84,43 @@ func New(version string) func() *schema.Provider {
|
||||||
},
|
},
|
||||||
|
|
||||||
"registry_auth": {
|
"registry_auth": {
|
||||||
Type: schema.TypeList,
|
Type: schema.TypeSet,
|
||||||
MaxItems: 1,
|
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Elem: &schema.Resource{
|
Elem: &schema.Resource{
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
"address": {
|
"address": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
Description: "Address of the registry",
|
ValidateFunc: validation.StringIsNotEmpty,
|
||||||
|
Description: "Address of the registry",
|
||||||
},
|
},
|
||||||
|
|
||||||
"username": {
|
"username": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ConflictsWith: []string{"registry_auth.config_file", "registry_auth.config_file_content"},
|
DefaultFunc: schema.EnvDefaultFunc("DOCKER_REGISTRY_USER", ""),
|
||||||
DefaultFunc: schema.EnvDefaultFunc("DOCKER_REGISTRY_USER", ""),
|
Description: "Username for the registry. Defaults to `DOCKER_REGISTRY_USER` env variable if set.",
|
||||||
Description: "Username for the registry",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"password": {
|
"password": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Sensitive: true,
|
Sensitive: true,
|
||||||
ConflictsWith: []string{"registry_auth.config_file", "registry_auth.config_file_content"},
|
DefaultFunc: schema.EnvDefaultFunc("DOCKER_REGISTRY_PASS", ""),
|
||||||
DefaultFunc: schema.EnvDefaultFunc("DOCKER_REGISTRY_PASS", ""),
|
Description: "Password for the registry. Defaults to `DOCKER_REGISTRY_PASS` env variable if set.",
|
||||||
Description: "Password for the registry",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"config_file": {
|
"config_file": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ConflictsWith: []string{"registry_auth.username", "registry_auth.password", "registry_auth.config_file_content"},
|
DefaultFunc: schema.EnvDefaultFunc("DOCKER_CONFIG", "~/.docker/config.json"),
|
||||||
DefaultFunc: schema.EnvDefaultFunc("DOCKER_CONFIG", "~/.docker/config.json"),
|
Description: "Path to docker json file for registry auth. Defaults to `~/.docker/config.json`. If `DOCKER_CONFIG` is set, the value of `DOCKER_CONFIG` is used as the path. `config_file` has predencen over all other options.",
|
||||||
Description: "Path to docker json file for registry auth",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"config_file_content": {
|
"config_file_content": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ConflictsWith: []string{"registry_auth.username", "registry_auth.password", "registry_auth.config_file"},
|
Description: "Plain content of the docker json file for registry auth. `config_file_content` has precedence over username/password.",
|
||||||
Description: "Plain content of the docker json file for registry auth",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -185,8 +182,7 @@ func configure(version string, p *schema.Provider) func(context.Context, *schema
|
||||||
authConfigs := &AuthConfigs{}
|
authConfigs := &AuthConfigs{}
|
||||||
|
|
||||||
if v, ok := d.GetOk("registry_auth"); ok { // TODO load them anyway
|
if v, ok := d.GetOk("registry_auth"); ok { // TODO load them anyway
|
||||||
authConfigs, err = providerSetToRegistryAuth(v.([]interface{}))
|
authConfigs, err = providerSetToRegistryAuth(v.(*schema.Set))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, diag.Errorf("Error loading registry auth config: %s", err)
|
return nil, diag.Errorf("Error loading registry auth config: %s", err)
|
||||||
}
|
}
|
||||||
|
|
@ -208,34 +204,34 @@ type AuthConfigs struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take the given registry_auth schemas and return a map of registry auth configurations
|
// Take the given registry_auth schemas and return a map of registry auth configurations
|
||||||
func providerSetToRegistryAuth(authList []interface{}) (*AuthConfigs, error) {
|
func providerSetToRegistryAuth(authList *schema.Set) (*AuthConfigs, error) {
|
||||||
authConfigs := AuthConfigs{
|
authConfigs := AuthConfigs{
|
||||||
Configs: make(map[string]types.AuthConfig),
|
Configs: make(map[string]types.AuthConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, authInt := range authList {
|
for _, auth := range authList.List() {
|
||||||
auth := authInt.(map[string]interface{})
|
|
||||||
authConfig := types.AuthConfig{}
|
authConfig := types.AuthConfig{}
|
||||||
authConfig.ServerAddress = normalizeRegistryAddress(auth["address"].(string))
|
address := auth.(map[string]interface{})["address"].(string)
|
||||||
|
authConfig.ServerAddress = normalizeRegistryAddress(address)
|
||||||
registryHostname := convertToHostname(authConfig.ServerAddress)
|
registryHostname := convertToHostname(authConfig.ServerAddress)
|
||||||
|
|
||||||
// For each registry_auth block, generate an AuthConfiguration using either
|
// For each registry_auth block, generate an AuthConfiguration using either
|
||||||
// username/password or the given config file
|
// username/password or the given config file
|
||||||
if username, ok := auth["username"]; ok && username.(string) != "" {
|
if username, ok := auth.(map[string]interface{})["username"].(string); ok && username != "" {
|
||||||
log.Println("[DEBUG] Using username for registry auths:", username)
|
log.Println("[DEBUG] Using username for registry auths:", username)
|
||||||
password := auth["password"].(string)
|
password := auth.(map[string]interface{})["password"].(string)
|
||||||
if isECRRepositoryURL(registryHostname) {
|
if isECRRepositoryURL(registryHostname) {
|
||||||
password = normalizeECRPasswordForDockerCLIUsage(password)
|
password = normalizeECRPasswordForDockerCLIUsage(password)
|
||||||
}
|
}
|
||||||
authConfig.Username = auth["username"].(string)
|
authConfig.Username = username
|
||||||
authConfig.Password = password
|
authConfig.Password = password
|
||||||
|
|
||||||
// Note: check for config_file_content first because config_file has a default which would be used
|
// Note: check for config_file_content first because config_file has a default which would be used
|
||||||
// nevertheless config_file_content is set or not. The default has to be kept to check for the
|
// nevertheless config_file_content is set or not. The default has to be kept to check for the
|
||||||
// environment variable and to be backwards compatible
|
// environment variable and to be backwards compatible
|
||||||
} else if configFileContent, ok := auth["config_file_content"]; ok && configFileContent.(string) != "" {
|
} else if configFileContent, ok := auth.(map[string]interface{})["config_file_content"].(string); ok && configFileContent != "" {
|
||||||
log.Println("[DEBUG] Parsing file content for registry auths:", configFileContent.(string))
|
log.Println("[DEBUG] Parsing file content for registry auths:", configFileContent)
|
||||||
r := strings.NewReader(configFileContent.(string))
|
r := strings.NewReader(configFileContent)
|
||||||
|
|
||||||
c, err := loadConfigFile(r)
|
c, err := loadConfigFile(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -249,8 +245,8 @@ func providerSetToRegistryAuth(authList []interface{}) (*AuthConfigs, error) {
|
||||||
authConfig.Password = authFileConfig.Password
|
authConfig.Password = authFileConfig.Password
|
||||||
|
|
||||||
// As last step we check if a config file path is given
|
// As last step we check if a config file path is given
|
||||||
} else if configFile, ok := auth["config_file"]; ok && configFile.(string) != "" {
|
} else if configFile, ok := auth.(map[string]interface{})["config_file"].(string); ok && configFile != "" {
|
||||||
filePath := configFile.(string)
|
filePath := configFile
|
||||||
log.Println("[DEBUG] Parsing file for registry auths:", filePath)
|
log.Println("[DEBUG] Parsing file for registry auths:", filePath)
|
||||||
|
|
||||||
// We manually expand the path and do not use the 'pathexpand' interpolation function
|
// We manually expand the path and do not use the 'pathexpand' interpolation function
|
||||||
|
|
@ -264,15 +260,15 @@ func providerSetToRegistryAuth(authList []interface{}) (*AuthConfigs, error) {
|
||||||
}
|
}
|
||||||
r, err := os.Open(filePath)
|
r, err := os.Open(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Could not open config file from filePath: %s. Error: %v", filePath, err)
|
return nil, fmt.Errorf("could not open config file from filePath: %s. Error: %v", filePath, err)
|
||||||
}
|
}
|
||||||
c, err := loadConfigFile(r)
|
c, err := loadConfigFile(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Could not read and load config file: %v", err)
|
return nil, fmt.Errorf("could not read and load config file: %v", err)
|
||||||
}
|
}
|
||||||
authFileConfig, err := c.GetAuthConfig(registryHostname)
|
authFileConfig, err := c.GetAuthConfig(registryHostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Could not get auth config (the credentialhelper did not work or was not found): %v", err)
|
return nil, fmt.Errorf("could not get auth config (the credentialhelper did not work or was not found): %v", err)
|
||||||
}
|
}
|
||||||
authConfig.Username = authFileConfig.Username
|
authConfig.Username = authFileConfig.Username
|
||||||
authConfig.Password = authFileConfig.Password
|
authConfig.Password = authFileConfig.Password
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -51,7 +52,23 @@ func TestAccDockerProvider_WithIncompleteRegistryAuth(t *testing.T) {
|
||||||
Steps: []resource.TestStep{
|
Steps: []resource.TestStep{
|
||||||
{
|
{
|
||||||
Config: testAccDockerProviderWithIncompleteAuthConfig,
|
Config: testAccDockerProviderWithIncompleteAuthConfig,
|
||||||
ExpectError: regexp.MustCompile(`401 Unauthorized`),
|
ExpectError: regexp.MustCompile(`expected "registry_auth.0.address" to not be an empty string, got `),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccDockerProvider_WithMultipleRegistryAuth(t *testing.T) {
|
||||||
|
pushOptions := createPushImageOptions("127.0.0.1:15000/tftest-dockerregistryimage-testtest:1.0")
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
ProviderFactories: providerFactories,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: fmt.Sprintf(loadTestConfiguration(t, RESOURCE, "provider", "testAccDockerProviderMultipleRegistryAuth"), pushOptions.Registry),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttrSet("data.docker_registry_image.foobar", "sha256_digest"),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,9 @@ When passing in a config file either the corresponding `auth` string of the repo
|
||||||
[credential helpers](https://github.com/docker/docker-credential-helpers#available-programs) are
|
[credential helpers](https://github.com/docker/docker-credential-helpers#available-programs) are
|
||||||
used to retrieve the authentication credentials.
|
used to retrieve the authentication credentials.
|
||||||
|
|
||||||
|
-> **Note**
|
||||||
|
`config_file` has predence over all other options. You can theoretically specify values for every attribute but the credentials obtained through the `config_file` will override the manually set `username`/`password`
|
||||||
|
|
||||||
You can still use the environment variables `DOCKER_REGISTRY_USER` and `DOCKER_REGISTRY_PASS`.
|
You can still use the environment variables `DOCKER_REGISTRY_USER` and `DOCKER_REGISTRY_PASS`.
|
||||||
|
|
||||||
An example content of the file `~/.docker/config.json` on macOS may look like follows:
|
An example content of the file `~/.docker/config.json` on macOS may look like follows:
|
||||||
|
|
|
||||||
16
testdata/resources/provider/testAccDockerProviderMultipleRegistryAuth.tf
vendored
Normal file
16
testdata/resources/provider/testAccDockerProviderMultipleRegistryAuth.tf
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
provider "docker" {
|
||||||
|
alias = "private"
|
||||||
|
registry_auth {
|
||||||
|
address = "%s"
|
||||||
|
}
|
||||||
|
registry_auth {
|
||||||
|
address = "public.ecr.aws"
|
||||||
|
username = "test"
|
||||||
|
password = "user"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data "docker_registry_image" "foobar" {
|
||||||
|
provider = "docker.private"
|
||||||
|
name = "127.0.0.1:15000/tftest-service:v1"
|
||||||
|
insecure_skip_verify = true
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue