From 98ccee6b684e7b921997da64b488c5aac4ea693e Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 18 Apr 2025 17:16:05 +0200 Subject: [PATCH] feat: Implement auth_config for docker_registry_image (#701) * feat: Implement auth_config for docker_registry_image * fix: Formatting --- docs/resources/registry_image.md | 12 +++++- .../resource_docker_registry_image.go | 27 +++++++++++++ .../resource_docker_registry_image_funcs.go | 40 +++++++++++++++---- .../resource_docker_registry_image_test.go | 20 ++++++++++ ...tBuildDockerRegistryImageWithAuthConfig.tf | 24 +++++++++++ 5 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 testdata/resources/docker_registry_image/testBuildDockerRegistryImageWithAuthConfig.tf diff --git a/docs/resources/registry_image.md b/docs/resources/registry_image.md index dc30fec4..4e393252 100644 --- a/docs/resources/registry_image.md +++ b/docs/resources/registry_image.md @@ -37,6 +37,7 @@ resource "docker_image" "image" { ### Optional +- `auth_config` (Block List, Max: 1) Authentication configuration for the Docker registry. It is only used for this resource. (see [below for nested schema](#nestedblock--auth_config)) - `insecure_skip_verify` (Boolean) If `true`, the verification of TLS certificates of the server/registry is disabled. Defaults to `false` - `keep_remotely` (Boolean) If true, then the Docker image won't be deleted on destroy operation. If this is false, it will delete the image from the docker registry on destroy operation. Defaults to `false` - `triggers` (Map of String) A map of arbitrary strings that, when changed, will force the `docker_registry_image` resource to be replaced. This can be used to repush a local image @@ -44,4 +45,13 @@ resource "docker_image" "image" { ### Read-Only - `id` (String) The ID of this resource. -- `sha256_digest` (String) The sha256 digest of the image. \ No newline at end of file +- `sha256_digest` (String) The sha256 digest of the image. + + +### Nested Schema for `auth_config` + +Required: + +- `address` (String) The address of the Docker registry. +- `password` (String, Sensitive) The password for the Docker registry. +- `username` (String) The username for the Docker registry. \ No newline at end of file diff --git a/internal/provider/resource_docker_registry_image.go b/internal/provider/resource_docker_registry_image.go index 7693a634..0d5d6eef 100644 --- a/internal/provider/resource_docker_registry_image.go +++ b/internal/provider/resource_docker_registry_image.go @@ -47,6 +47,33 @@ func resourceDockerRegistryImage() *schema.Resource { Description: "The sha256 digest of the image.", Computed: true, }, + + "auth_config": { + Type: schema.TypeList, + Description: "Authentication configuration for the Docker registry. It is only used for this resource.", + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address": { + Type: schema.TypeString, + Description: "The address of the Docker registry.", + Required: true, + }, + "username": { + Type: schema.TypeString, + Description: "The username for the Docker registry.", + Required: true, + }, + "password": { + Type: schema.TypeString, + Description: "The password for the Docker registry.", + Required: true, + Sensitive: true, + }, + }, + }, + }, }, } } diff --git a/internal/provider/resource_docker_registry_image_funcs.go b/internal/provider/resource_docker_registry_image_funcs.go index 0bd0f196..b11abb8c 100644 --- a/internal/provider/resource_docker_registry_image_funcs.go +++ b/internal/provider/resource_docker_registry_image_funcs.go @@ -22,6 +22,16 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +func buildAuthConfigFromResource(v interface{}) registry.AuthConfig { + auth := v.([]interface{})[0].(map[string]interface{}) + return registry.AuthConfig{ + ServerAddress: normalizeRegistryAddress(auth["address"].(string)), + Username: auth["username"].(string), + Password: auth["password"].(string), + } + +} + func resourceDockerRegistryImageCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*ProviderConfig).DockerClient providerConfig := meta.(*ProviderConfig) @@ -30,10 +40,19 @@ func resourceDockerRegistryImageCreate(ctx context.Context, d *schema.ResourceDa pushOpts := createPushImageOptions(name) - authConfig, err := getAuthConfigForRegistry(pushOpts.Registry, providerConfig) - if err != nil { - return diag.Errorf("resourceDockerRegistryImageCreate: Unable to get authConfig for registry: %s", err) + var authConfig registry.AuthConfig + if v, ok := d.GetOk("auth_config"); ok { + log.Printf("[INFO] Using auth config from resource: %s", v) + authConfig = buildAuthConfigFromResource(v) + } else { + log.Printf("[INFO] Using auth config from provider: %s", v) + var err error + 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) } @@ -41,7 +60,7 @@ func resourceDockerRegistryImageCreate(ctx context.Context, d *schema.ResourceDa insecureSkipVerify := d.Get("insecure_skip_verify").(bool) 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) + return diag.Errorf("Got error getting registry image digest inside resourceDockerRegistryImageCreate: %s", err) } d.SetId(digest) d.Set("sha256_digest", digest) @@ -52,9 +71,16 @@ func resourceDockerRegistryImageRead(ctx context.Context, d *schema.ResourceData providerConfig := meta.(*ProviderConfig) name := d.Get("name").(string) pushOpts := createPushImageOptions(name) - authConfig, err := getAuthConfigForRegistry(pushOpts.Registry, providerConfig) - if err != nil { - return diag.Errorf("resourceDockerRegistryImageRead: Unable to get authConfig for registry: %s", err) + + var authConfig registry.AuthConfig + if v, ok := d.GetOk("auth_config"); ok { + authConfig = buildAuthConfigFromResource(v) + } else { + var err error + 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) diff --git a/internal/provider/resource_docker_registry_image_test.go b/internal/provider/resource_docker_registry_image_test.go index 575a83c4..8af70b07 100644 --- a/internal/provider/resource_docker_registry_image_test.go +++ b/internal/provider/resource_docker_registry_image_test.go @@ -66,6 +66,26 @@ func TestAccDockerRegistryImageResource_pushMissingImage(t *testing.T) { }) } +func TestAccDockerRegistryImageResource_withAuthConfig(t *testing.T) { + pushOptions := createPushImageOptions("127.0.0.1:15000/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", "testBuildDockerRegistryImageWithAuthConfig"), pushOptions.Name, context, pushOptions.Registry, "testuser", "testpwd"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("docker_registry_image.foo", "sha256_digest"), + ), + }, + }, + CheckDestroy: testDockerRegistryImageInRegistry("testuser", "testpwd", pushOptions, true), + }) +} + func testDockerRegistryImageNotInRegistry(pushOpts internalPushImageOptions) resource.TestCheckFunc { return func(s *terraform.State) error { providerConfig := testAccProvider.Meta().(*ProviderConfig) diff --git a/testdata/resources/docker_registry_image/testBuildDockerRegistryImageWithAuthConfig.tf b/testdata/resources/docker_registry_image/testBuildDockerRegistryImageWithAuthConfig.tf new file mode 100644 index 00000000..746b7cde --- /dev/null +++ b/testdata/resources/docker_registry_image/testBuildDockerRegistryImageWithAuthConfig.tf @@ -0,0 +1,24 @@ +provider "docker" { + alias = "private" +} + +resource "docker_image" "foo_image" { + provider = docker.private + name = "%s" + build { + context = "%s" + } +} + +resource "docker_registry_image" "foo" { + provider = docker.private + name = docker_image.foo_image.name + insecure_skip_verify = true + keep_remotely = true + + auth_config { + address = "%s" + username = "%s" + password = "%s" + } +}