feat: Implement support for insecure registries (#414)

* feat: Add new tests for insecure registries.

* chore: Refactor code into parseImageOptions and add tests.

* feat: normalizeRegistryAddress supports http addresses.

* feat: keys of authConfigs are now stored without protocol.

* chore: Refactor of docker registry fallback in parseImageOptions.

* refactor: Improve tests and implementation of parseImageOptions

* feat: Implement support for http registries.

* fix: authConfig unit tests now reflect newest structure.

* fix: docker_image_registry data source can pull without authentication.

* fix: Refactor setup of http headers for registry requests.

* docs: Add note about http registries.

* docs: Fix linting error in docs.
This commit is contained in:
Martin 2022-07-22 11:19:15 +02:00 committed by GitHub
parent 984edeed30
commit cf3f8d6457
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 243 additions and 113 deletions

View file

@ -81,9 +81,10 @@ provider "docker" {
Registry credentials can be provided on a per-registry basis with the `registry_auth`
field, passing either a config file or the username/password directly.
If you want to use an insecure http registry, please explicitly specify the `address` with the `http` protocol.
-> **Note**
The location of the config file is on the machine terraform runs on, nevertheless if the specified docker host is on another machine.
The config file is loaded from the machine `terraform` runs on. This also applies when the specified docker host is on another machine.
```terraform
provider "docker" {

View file

@ -3,6 +3,7 @@ package provider
import (
b64 "encoding/base64"
"log"
"net/http"
"regexp"
"strings"
)
@ -34,7 +35,7 @@ func normalizeECRPasswordForDockerCLIUsage(password string) string {
if err != nil {
log.Fatalf("Error creating registry request: %s", err)
}
password = string(decodedPassword)
return string(decodedPassword)
}
return password[4:]
@ -48,3 +49,16 @@ func isECRRepositoryURL(url string) bool {
var ecrRexp = regexp.MustCompile(`^.*?dkr\.ecr\..*?\.amazonaws\.com$`)
return ecrRexp.MatchString(url)
}
func setupHTTPHeadersForRegistryRequests(req *http.Request, fallback bool) {
// We accept schema v2 manifests and manifest lists, and also OCI types
req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.v2+json")
req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.list.v2+json")
req.Header.Add("Accept", "application/vnd.oci.image.manifest.v1+json")
req.Header.Add("Accept", "application/vnd.oci.image.index.v1+json")
if fallback {
// Fallback to this header if the registry does not support the v2 manifest like gcr.io
req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v1+prettyjws")
}
}

View file

@ -151,7 +151,11 @@ type ProviderConfig struct {
// The registry address can be referenced in various places (registry auth, docker config file, image name)
// with or without the http(s):// prefix; this function is used to standardize the inputs
// To support insecure (http) registries, if the address explicitly states "http://" we do not change it.
func normalizeRegistryAddress(address string) string {
if strings.HasPrefix(address, "http://") {
return address
}
if !strings.HasPrefix(address, "https://") && !strings.HasPrefix(address, "http://") {
return "https://" + address
}

View file

@ -0,0 +1,32 @@
package provider
import (
"testing"
)
func TestNormalizeRegistryAddress(t *testing.T) {
t.Run("Should return same address if http:// is used", func(t *testing.T) {
address := "http://registry.com"
expected := "http://registry.com"
actual := normalizeRegistryAddress(address)
if actual != expected {
t.Fatalf("Expected %s, got %s", expected, actual)
}
})
t.Run("Should return https address if no protocol is specified", func(t *testing.T) {
address := "registry.com"
expected := "https://registry.com"
actual := normalizeRegistryAddress(address)
if actual != expected {
t.Fatalf("Expected %s, got %s", expected, actual)
}
})
t.Run("Should return https address if https protocol is specified", func(t *testing.T) {
address := "https://registry.com"
expected := "https://registry.com"
actual := normalizeRegistryAddress(address)
if actual != expected {
t.Fatalf("Expected %s, got %s", expected, actual)
}
})
}

View file

@ -3,7 +3,6 @@ package provider
import (
"context"
"crypto/sha256"
"crypto/tls"
b64 "encoding/base64"
"encoding/json"
"fmt"
@ -47,39 +46,21 @@ func dataSourceDockerRegistryImage() *schema.Resource {
func dataSourceDockerRegistryImageRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
pullOpts := parseImageOptions(d.Get("name").(string))
authConfig := meta.(*ProviderConfig).AuthConfigs
// Use the official Docker Hub if a registry isn't specified
if pullOpts.Registry == "" {
pullOpts.Registry = "registry-1.docker.io"
} else {
// Otherwise, filter the registry name out of the repo name
pullOpts.Repository = strings.Replace(pullOpts.Repository, pullOpts.Registry+"/", "", 1)
}
if pullOpts.Registry == "registry-1.docker.io" {
// Docker prefixes 'library' to official images in the path; 'consul' becomes 'library/consul'
if !strings.Contains(pullOpts.Repository, "/") {
pullOpts.Repository = "library/" + pullOpts.Repository
}
}
if pullOpts.Tag == "" {
pullOpts.Tag = "latest"
}
username := ""
password := ""
if auth, ok := authConfig.Configs[normalizeRegistryAddress(pullOpts.Registry)]; ok {
username = auth.Username
password = auth.Password
authConfig, err := getAuthConfigForRegistry(pullOpts.Registry, meta.(*ProviderConfig))
if err != nil {
// The user did not provide a credential for this registry.
// But there are many registries where you can pull without a credential.
// We are setting default values for the authConfig here.
authConfig.Username = ""
authConfig.Password = ""
authConfig.ServerAddress = "https://" + pullOpts.Registry
}
insecureSkipVerify := d.Get("insecure_skip_verify").(bool)
digest, err := getImageDigest(pullOpts.Registry, pullOpts.Repository, pullOpts.Tag, username, password, insecureSkipVerify, false)
digest, err := getImageDigest(pullOpts.Registry, authConfig.ServerAddress, pullOpts.Repository, pullOpts.Tag, authConfig.Username, authConfig.Password, insecureSkipVerify, false)
if err != nil {
digest, err = getImageDigest(pullOpts.Registry, pullOpts.Repository, pullOpts.Tag, username, password, insecureSkipVerify, true)
digest, err = getImageDigest(pullOpts.Registry, authConfig.ServerAddress, pullOpts.Repository, pullOpts.Tag, authConfig.Username, authConfig.Password, insecureSkipVerify, true)
if err != nil {
return diag.Errorf("Got error when attempting to fetch image version %s:%s from registry: %s", pullOpts.Repository, pullOpts.Tag, err)
}
@ -91,12 +72,10 @@ func dataSourceDockerRegistryImageRead(ctx context.Context, d *schema.ResourceDa
return nil
}
func getImageDigest(registry, image, tag, username, password string, insecureSkipVerify, fallback bool) (string, error) {
client := http.DefaultClient
// DevSkim: ignore DS440000
client.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: insecureSkipVerify}}
func getImageDigest(registry string, registryWithProtocol string, image, tag, username, password string, insecureSkipVerify, fallback bool) (string, error) {
client := buildHttpClientForRegistry(registryWithProtocol, insecureSkipVerify)
req, err := http.NewRequest("GET", "https://"+registry+"/v2/"+image+"/manifests/"+tag, nil)
req, err := http.NewRequest("GET", registryWithProtocol+"/v2/"+image+"/manifests/"+tag, nil)
if err != nil {
return "", fmt.Errorf("Error creating registry request: %s", err)
}
@ -114,16 +93,7 @@ func getImageDigest(registry, image, tag, username, password string, insecureSki
}
}
// We accept schema v2 manifests and manifest lists, and also OCI types
req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.v2+json")
req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.list.v2+json")
req.Header.Add("Accept", "application/vnd.oci.image.manifest.v1+json")
req.Header.Add("Accept", "application/vnd.oci.image.index.v1+json")
if fallback {
// Fallback to this header if the registry does not support the v2 manifest like gcr.io
req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v1+prettyjws")
}
setupHTTPHeadersForRegistryRequests(req, fallback)
resp, err := client.Do(req)
if err != nil {

View file

@ -66,6 +66,27 @@ func TestAccDockerRegistryImage_auth(t *testing.T) {
})
}
func TestAccDockerRegistryImage_httpAuth(t *testing.T) {
registry := "http://127.0.0.1:15001"
image := "127.0.0.1:15001/tftest-service:v1"
ctx := context.Background()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(loadTestConfiguration(t, DATA_SOURCE, "docker_registry_image", "testAccDockerImageDataSourceAuthConfig"), registry, image),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("data.docker_registry_image.foobar", "sha256_digest", registryDigestRegexp),
),
},
},
CheckDestroy: func(state *terraform.State) error {
return checkAndRemoveImages(ctx, state)
},
})
}
func TestGetDigestFromResponse(t *testing.T) {
headerContent := "sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"
respWithHeaders := &http.Response{

View file

@ -181,7 +181,7 @@ func configure(version string, p *schema.Provider) func(context.Context, *schema
authConfigs := &AuthConfigs{}
if v, ok := d.GetOk("registry_auth"); ok { // TODO load them anyway
if v, ok := d.GetOk("registry_auth"); ok {
authConfigs, err = providerSetToRegistryAuth(v.(*schema.Set))
if err != nil {
return nil, diag.Errorf("Error loading registry auth config: %s", err)
@ -274,7 +274,7 @@ func providerSetToRegistryAuth(authList *schema.Set) (*AuthConfigs, error) {
authConfig.Password = authFileConfig.Password
}
authConfigs.Configs[authConfig.ServerAddress] = authConfig
authConfigs.Configs[registryHostname] = authConfig
}
return &authConfigs, nil

View file

@ -197,17 +197,9 @@ func fetchLocalImages(ctx context.Context, data *Data, client *client.Client) er
func pullImage(ctx context.Context, data *Data, client *client.Client, authConfig *AuthConfigs, image string) error {
pullOpts := parseImageOptions(image)
// If a registry was specified in the image name, try to find auth for it
auth := types.AuthConfig{}
if pullOpts.Registry != "" {
if authConfig, ok := authConfig.Configs[normalizeRegistryAddress(pullOpts.Registry)]; ok {
auth = authConfig
}
} else {
// Try to find an auth config for the public docker hub if a registry wasn't given
if authConfig, ok := authConfig.Configs["https://registry-1.docker.io"]; ok {
auth = authConfig
}
if authConfig, ok := authConfig.Configs[pullOpts.Registry]; ok {
auth = authConfig
}
encodedJSON, err := json.Marshal(auth)
@ -243,6 +235,9 @@ type internalPullImageOptions struct {
Registry string
}
// Parses an image name into a PullImageOptions struct.
// If the name has no registry, the registry-1.docker.io is used
// If the name has no tag, the tag "latest" is used
func parseImageOptions(image string) internalPullImageOptions {
pullOpts := internalPullImageOptions{}
@ -271,6 +266,18 @@ func parseImageOptions(image string) internalPullImageOptions {
pullOpts.Tag = "latest"
}
// Use the official Docker Hub if a registry isn't specified
if pullOpts.Registry == "" {
pullOpts.Registry = "registry-1.docker.io"
// Docker prefixes 'library' to official images in the path; 'consul' becomes 'library/consul'
if !strings.Contains(pullOpts.Repository, "/") {
pullOpts.Repository = "library/" + pullOpts.Repository
}
} else {
// Otherwise, filter the registry name out of the repo name
pullOpts.Repository = strings.Replace(pullOpts.Repository, pullOpts.Registry+"/", "", 1)
}
return pullOpts
}

View file

@ -7,6 +7,7 @@ import (
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
"strings"
"testing"
@ -381,3 +382,41 @@ func testAccImageCreated(resourceName string, image *types.ImageInspect) resourc
}
}
func TestParseImageOptions(t *testing.T) {
t.Run("Should parse image name with registry", func(t *testing.T) {
expected := internalPullImageOptions{Registry: "registry.com", Repository: "image", Tag: "tag"}
result := parseImageOptions("registry.com/image:tag")
if !reflect.DeepEqual(expected, result) {
t.Fatalf("Result %#v did not match expectation %#v", result, expected)
}
})
t.Run("Should parse image name with registryPort", func(t *testing.T) {
expected := internalPullImageOptions{Registry: "registry.com:8080", Repository: "image", Tag: "tag"}
result := parseImageOptions("registry.com:8080/image:tag")
if !reflect.DeepEqual(expected, result) {
t.Fatalf("Result %#v did not match expectation %#v", result, expected)
}
})
t.Run("Should parse image name with registry and proper repository", func(t *testing.T) {
expected := internalPullImageOptions{Registry: "registry.com", Repository: "repo/image", Tag: "tag"}
result := parseImageOptions("registry.com/repo/image:tag")
if !reflect.DeepEqual(expected, result) {
t.Fatalf("Result %#v did not match expectation %#v", result, expected)
}
})
t.Run("Should parse image with no tag", func(t *testing.T) {
expected := internalPullImageOptions{Registry: "registry.com", Repository: "repo/image", Tag: "latest"}
result := parseImageOptions("registry.com/repo/image")
if !reflect.DeepEqual(expected, result) {
t.Fatalf("Result %#v did not match expectation %#v", result, expected)
}
})
t.Run("Should parse image name without registry and default to docker registry", func(t *testing.T) {
expected := internalPullImageOptions{Registry: "registry-1.docker.io", Repository: "library/image", Tag: "tag"}
result := parseImageOptions("image:tag")
if !reflect.DeepEqual(expected, result) {
t.Fatalf("Result %#v did not match expectation %#v", result, expected)
}
})
}

View file

@ -46,13 +46,16 @@ func resourceDockerRegistryImageCreate(ctx context.Context, d *schema.ResourceDa
}
}
username, password := getDockerRegistryImageRegistryUserNameAndPassword(pushOpts, providerConfig)
if err := pushDockerRegistryImage(ctx, client, pushOpts, username, password); err != nil {
authConfig, err := getAuthConfigForRegistry(pushOpts.Registry, providerConfig)
if err != nil {
return diag.Errorf("resourceDockerRegistryImageCreate: Unable to get authConfig for registry: %s", err)
}
if err := pushDockerRegistryImage(ctx, client, pushOpts, authConfig.Username, authConfig.Password); err != nil {
return diag.Errorf("Error pushing docker image: %s", err)
}
insecureSkipVerify := d.Get("insecure_skip_verify").(bool)
digest, err := getImageDigestWithFallback(pushOpts, username, password, insecureSkipVerify)
digest, err := getImageDigestWithFallback(pushOpts, authConfig.ServerAddress, authConfig.Username, authConfig.Password, insecureSkipVerify)
if err != nil {
return diag.Errorf("Unable to create image, image not found: %s", err)
}
@ -65,10 +68,13 @@ func resourceDockerRegistryImageRead(ctx context.Context, d *schema.ResourceData
providerConfig := meta.(*ProviderConfig)
name := d.Get("name").(string)
pushOpts := createPushImageOptions(name)
username, password := getDockerRegistryImageRegistryUserNameAndPassword(pushOpts, providerConfig)
authConfig, err := getAuthConfigForRegistry(pushOpts.Registry, providerConfig)
if err != nil {
return diag.Errorf("resourceDockerRegistryImageRead: Unable to get authConfig for registry: %s", err)
}
insecureSkipVerify := d.Get("insecure_skip_verify").(bool)
digest, err := getImageDigestWithFallback(pushOpts, username, password, insecureSkipVerify)
digest, err := getImageDigestWithFallback(pushOpts, authConfig.ServerAddress, authConfig.Username, authConfig.Password, insecureSkipVerify)
if err != nil {
log.Printf("Got error getting registry image digest: %s", err)
d.SetId("")
@ -85,11 +91,15 @@ func resourceDockerRegistryImageDelete(ctx context.Context, d *schema.ResourceDa
providerConfig := meta.(*ProviderConfig)
name := d.Get("name").(string)
pushOpts := createPushImageOptions(name)
username, password := getDockerRegistryImageRegistryUserNameAndPassword(pushOpts, providerConfig)
digest := d.Get("sha256_digest").(string)
err := deleteDockerRegistryImage(pushOpts, digest, username, password, true, false)
authConfig, err := getAuthConfigForRegistry(pushOpts.Registry, providerConfig)
if err != nil {
err = deleteDockerRegistryImage(pushOpts, pushOpts.Tag, username, password, true, true)
return diag.Errorf("resourceDockerRegistryImageDelete: Unable to get authConfig for registry: %s", err)
}
digest := d.Get("sha256_digest").(string)
err = deleteDockerRegistryImage(pushOpts, authConfig.ServerAddress, digest, authConfig.Username, authConfig.Password, true, false)
if err != nil {
err = deleteDockerRegistryImage(pushOpts, authConfig.ServerAddress, pushOpts.Tag, authConfig.Username, authConfig.Password, true, true)
if err != nil {
return diag.Errorf("Got error deleting registry image: %s", err)
}
@ -446,26 +456,28 @@ func pushDockerRegistryImage(ctx context.Context, client *client.Client, pushOpt
return nil
}
func getDockerRegistryImageRegistryUserNameAndPassword(
pushOpts internalPushImageOptions,
providerConfig *ProviderConfig) (string, string) {
registry := pushOpts.NormalizedRegistry
username := ""
password := ""
if authConfig, ok := providerConfig.AuthConfigs.Configs[registry]; ok {
username = authConfig.Username
password = authConfig.Password
func getAuthConfigForRegistry(
registryWithoutProtocol string,
providerConfig *ProviderConfig) (types.AuthConfig, error) {
if authConfig, ok := providerConfig.AuthConfigs.Configs[registryWithoutProtocol]; ok {
return authConfig, nil
}
return username, password
return types.AuthConfig{}, fmt.Errorf("no auth config found for registry %s in auth configs: %#v", registryWithoutProtocol, providerConfig.AuthConfigs.Configs)
}
func deleteDockerRegistryImage(pushOpts internalPushImageOptions, sha256Digest, username, password string, insecureSkipVerify, fallback bool) error {
func buildHttpClientForRegistry(registryAddressWithProtocol string, insecureSkipVerify bool) *http.Client {
client := http.DefaultClient
// DevSkim: ignore DS440000
client.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: insecureSkipVerify}}
if strings.HasPrefix(registryAddressWithProtocol, "https://") {
client.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: insecureSkipVerify}}
}
return client
}
req, err := http.NewRequest("DELETE", pushOpts.NormalizedRegistry+"/v2/"+pushOpts.Repository+"/manifests/"+sha256Digest, nil)
func deleteDockerRegistryImage(pushOpts internalPushImageOptions, registryWithProtocol string, sha256Digest, username, password string, insecureSkipVerify, fallback bool) error {
client := buildHttpClientForRegistry(registryWithProtocol, insecureSkipVerify)
req, err := http.NewRequest("DELETE", registryWithProtocol+"/v2/"+pushOpts.Repository+"/manifests/"+sha256Digest, nil)
if err != nil {
return fmt.Errorf("Error deleting registry image: %s", err)
}
@ -474,16 +486,7 @@ func deleteDockerRegistryImage(pushOpts internalPushImageOptions, sha256Digest,
req.SetBasicAuth(username, password)
}
// We accept schema v2 manifests and manifest lists, and also OCI types
req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.v2+json")
req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.list.v2+json")
req.Header.Add("Accept", "application/vnd.oci.image.manifest.v1+json")
req.Header.Add("Accept", "application/vnd.oci.image.index.v1+json")
if fallback {
// Fallback to this header if the registry does not support the v2 manifest like gcr.io
req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v1+prettyjws")
}
setupHTTPHeadersForRegistryRequests(req, fallback)
resp, err := client.Do(req)
if err != nil {
@ -553,10 +556,10 @@ func deleteDockerRegistryImage(pushOpts internalPushImageOptions, sha256Digest,
}
}
func getImageDigestWithFallback(opts internalPushImageOptions, username, password string, insecureSkipVerify bool) (string, error) {
digest, err := getImageDigest(opts.Registry, opts.Repository, opts.Tag, username, password, insecureSkipVerify, false)
func getImageDigestWithFallback(opts internalPushImageOptions, serverAddress string, username, password string, insecureSkipVerify bool) (string, error) {
digest, err := getImageDigest(opts.Registry, serverAddress, opts.Repository, opts.Tag, username, password, insecureSkipVerify, false)
if err != nil {
digest, err = getImageDigest(opts.Registry, opts.Repository, opts.Tag, username, password, insecureSkipVerify, true)
digest, err = getImageDigest(opts.Registry, serverAddress, opts.Repository, opts.Tag, username, password, insecureSkipVerify, true)
if err != nil {
return "", fmt.Errorf("unable to get digest: %s", err)
}
@ -566,11 +569,6 @@ func getImageDigestWithFallback(opts internalPushImageOptions, username, passwor
func createPushImageOptions(image string) internalPushImageOptions {
pullOpts := parseImageOptions(image)
if pullOpts.Registry == "" {
pullOpts.Registry = "registry-1.docker.io"
} else {
pullOpts.Repository = strings.Replace(pullOpts.Repository, pullOpts.Registry+"/", "", 1)
}
pushOpts := internalPushImageOptions{
Name: image,
Registry: pullOpts.Registry,

View file

@ -132,6 +132,24 @@ func TestAccDockerRegistryImageResource_build(t *testing.T) {
CheckDestroy: testDockerRegistryImageNotInRegistry(pushOptions),
})
}
func TestAccDockerRegistryImageResource_build_insecure_registry(t *testing.T) {
pushOptions := createPushImageOptions("127.0.0.1:15001/tftest-dockerregistryimage:1.0")
wd, _ := os.Getwd()
context := strings.ReplaceAll((filepath.Join(wd, "..", "..", "scripts", "testing", "docker_registry_image_context")), "\\", "\\\\")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(loadTestConfiguration(t, RESOURCE, "docker_registry_image", "testBuildDockerRegistryImageNoKeepConfig"), "http://127.0.0.1:15001", pushOptions.Name, context),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("docker_registry_image.foo", "sha256_digest"),
),
},
},
CheckDestroy: testDockerRegistryImageNotInRegistry(pushOptions),
})
}
func TestAccDockerRegistryImageResource_buildAndKeep(t *testing.T) {
pushOptions := createPushImageOptions("127.0.0.1:15000/tftest-dockerregistryimage:1.0")
@ -290,8 +308,8 @@ func TestAccDockerRegistryImageResource_pushMissingImage(t *testing.T) {
func testDockerRegistryImageNotInRegistry(pushOpts internalPushImageOptions) resource.TestCheckFunc {
return func(s *terraform.State) error {
providerConfig := testAccProvider.Meta().(*ProviderConfig)
username, password := getDockerRegistryImageRegistryUserNameAndPassword(pushOpts, providerConfig)
digest, _ := getImageDigestWithFallback(pushOpts, username, password, true)
authConfig, _ := getAuthConfigForRegistry(pushOpts.Registry, providerConfig)
digest, _ := getImageDigestWithFallback(pushOpts, normalizeRegistryAddress(pushOpts.Registry), authConfig.Username, authConfig.Password, true)
if digest != "" {
return fmt.Errorf("image found")
}
@ -301,12 +319,12 @@ func testDockerRegistryImageNotInRegistry(pushOpts internalPushImageOptions) res
func testDockerRegistryImageInRegistry(username, password string, pushOpts internalPushImageOptions, cleanup bool) resource.TestCheckFunc {
return func(s *terraform.State) error {
digest, err := getImageDigestWithFallback(pushOpts, username, password, true)
digest, err := getImageDigestWithFallback(pushOpts, normalizeRegistryAddress(pushOpts.Registry), username, password, true)
if err != nil || len(digest) < 1 {
return fmt.Errorf("image '%s' with credentials('%s' - '%s') not found: %w", pushOpts.Name, username, password, err)
}
if cleanup {
err := deleteDockerRegistryImage(pushOpts, digest, username, password, true, false)
err := deleteDockerRegistryImage(pushOpts, normalizeRegistryAddress(pushOpts.Registry), digest, username, password, true, false)
if err != nil {
return fmt.Errorf("Unable to remove test image '%s': %w", pushOpts.Name, err)
}

View file

@ -620,7 +620,7 @@ func fromRegistryAuth(image string, authConfigs map[string]types.AuthConfig) typ
// No auth given and image name has no slash like 'alpine:3.1'
if lastBin != -1 {
serverAddress := image[0:lastBin]
if fromRegistryAuth, ok := authConfigs[normalizeRegistryAddress(serverAddress)]; ok {
if fromRegistryAuth, ok := authConfigs[serverAddress]; ok {
return fromRegistryAuth
}
}

View file

@ -228,40 +228,52 @@ func TestMigrateServiceLabelState_with_labels(t *testing.T) {
func TestDockerSecretFromRegistryAuth_basic(t *testing.T) {
authConfigs := make(map[string]types.AuthConfig)
authConfigs["https://repo.my-company.com:8787"] = types.AuthConfig{
authConfigs["repo.my-company.com:8787"] = types.AuthConfig{
Username: "myuser",
Password: "mypass",
Email: "",
ServerAddress: "repo.my-company.com:8787",
ServerAddress: "https://repo.my-company.com:8787",
}
foundAuthConfig := fromRegistryAuth("repo.my-company.com:8787/my_image", authConfigs)
checkAttribute(t, "Username", foundAuthConfig.Username, "myuser")
checkAttribute(t, "Password", foundAuthConfig.Password, "mypass")
checkAttribute(t, "Email", foundAuthConfig.Email, "")
checkAttribute(t, "ServerAddress", foundAuthConfig.ServerAddress, "repo.my-company.com:8787")
checkAttribute(t, "ServerAddress", foundAuthConfig.ServerAddress, "https://repo.my-company.com:8787")
}
func TestDockerSecretFromRegistryAuth_multiple(t *testing.T) {
authConfigs := make(map[string]types.AuthConfig)
authConfigs["https://repo.my-company.com:8787"] = types.AuthConfig{
authConfigs["repo.my-company.com:8787"] = types.AuthConfig{
Username: "myuser",
Password: "mypass",
Email: "",
ServerAddress: "repo.my-company.com:8787",
ServerAddress: "https://repo.my-company.com:8787",
}
authConfigs["https://nexus.my-fancy-company.com"] = types.AuthConfig{
authConfigs["nexus.my-fancy-company.com"] = types.AuthConfig{
Username: "myuser33",
Password: "mypass123",
Email: "test@example.com",
ServerAddress: "nexus.my-fancy-company.com",
ServerAddress: "https://nexus.my-fancy-company.com",
}
authConfigs["http-nexus.my-fancy-company.com"] = types.AuthConfig{
Username: "myuser33",
Password: "mypass123",
Email: "test@example.com",
ServerAddress: "http://http-nexus.my-fancy-company.com",
}
foundAuthConfig := fromRegistryAuth("nexus.my-fancy-company.com/the_image", authConfigs)
checkAttribute(t, "Username", foundAuthConfig.Username, "myuser33")
checkAttribute(t, "Password", foundAuthConfig.Password, "mypass123")
checkAttribute(t, "Email", foundAuthConfig.Email, "test@example.com")
checkAttribute(t, "ServerAddress", foundAuthConfig.ServerAddress, "nexus.my-fancy-company.com")
checkAttribute(t, "ServerAddress", foundAuthConfig.ServerAddress, "https://nexus.my-fancy-company.com")
foundAuthConfig = fromRegistryAuth("http-nexus.my-fancy-company.com/the_image", authConfigs)
checkAttribute(t, "Username", foundAuthConfig.Username, "myuser33")
checkAttribute(t, "Password", foundAuthConfig.Password, "mypass123")
checkAttribute(t, "Email", foundAuthConfig.Email, "test@example.com")
checkAttribute(t, "ServerAddress", foundAuthConfig.ServerAddress, "http://http-nexus.my-fancy-company.com")
foundAuthConfig = fromRegistryAuth("alpine:3.1", authConfigs)
checkAttribute(t, "Username", foundAuthConfig.Username, "")

View file

@ -29,10 +29,19 @@ docker run -d -p 15000:5000 --rm --name private_registry \
-e "REGISTRY_HTTP_TLS_KEY=/certs/registry_auth.key" \
-e "REGISTRY_STORAGE_DELETE_ENABLED=true" \
registry:2.7.0
docker run -d -p 15001:5000 --rm --name http_private_registry \
-v "$(pwd)"/scripts/testing/auth:/auth \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
-e "REGISTRY_STORAGE_DELETE_ENABLED=true" \
registry:2.7.0
# wait a bit for travis...
sleep 5
# Login to private registry
docker login -u testuser -p testpwd 127.0.0.1:15000
docker login -u testuser -p testpwd 127.0.0.1:15001
# Build private images
for i in $(seq 1 3); do
docker build -t tftest-service --build-arg MAIN_FILE_PATH=v${i}/main.go "$(pwd)"/scripts/testing -f "$(pwd)"/scripts/testing/Dockerfile
@ -40,6 +49,10 @@ for i in $(seq 1 3); do
docker push 127.0.0.1:15000/tftest-service:v${i}
docker tag tftest-service 127.0.0.1:15000/tftest-service
docker push 127.0.0.1:15000/tftest-service
docker tag tftest-service 127.0.0.1:15001/tftest-service:v${i}
docker push 127.0.0.1:15001/tftest-service:v${i}
docker tag tftest-service 127.0.0.1:15001/tftest-service
docker push 127.0.0.1:15001/tftest-service
done
# Remove images from host machine before starting the tests
for i in $(docker images -aq 127.0.0.1:15000/tftest-service); do docker rmi -f "$i"; done

View file

@ -36,9 +36,10 @@ The configuration would look as follows:
Registry credentials can be provided on a per-registry basis with the `registry_auth`
field, passing either a config file or the username/password directly.
If you want to use an insecure http registry, please explicitly specify the `address` with the `http` protocol.
-> **Note**
The location of the config file is on the machine terraform runs on, nevertheless if the specified docker host is on another machine.
The config file is loaded from the machine `terraform` runs on. This also applies when the specified docker host is on another machine.
{{tffile "examples/provider/provider-credentials.tf"}}