From ac05128fbacc15df6aeb1cdbd05c5a14d41f4f54 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 8 Oct 2025 19:39:40 +0200 Subject: [PATCH] feat: Add build attribute for docker_registry_image (#805) --- internal/provider/resource_docker_image.go | 720 +++++++++--------- .../provider/resource_docker_image_funcs.go | 82 +- .../resource_docker_registry_image.go | 7 + .../resource_docker_registry_image_funcs.go | 8 + .../resource_docker_registry_image_test.go | 21 + ...irectBuildDockerRegistryImageKeepConfig.tf | 17 + 6 files changed, 459 insertions(+), 396 deletions(-) create mode 100644 testdata/resources/docker_registry_image/testDirectBuildDockerRegistryImageKeepConfig.tf diff --git a/internal/provider/resource_docker_image.go b/internal/provider/resource_docker_image.go index 08ffad6a..828ba720 100644 --- a/internal/provider/resource_docker_image.go +++ b/internal/provider/resource_docker_image.go @@ -79,365 +79,7 @@ func resourceDockerImage() *schema.Resource { Optional: true, MaxItems: 1, ConflictsWith: []string{"pull_triggers"}, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "dockerfile": { - Type: schema.TypeString, - Description: "Name of the Dockerfile. Defaults to `Dockerfile`.", - Optional: true, - Default: "Dockerfile", - ForceNew: true, - }, - "tag": { - Type: schema.TypeList, - Description: "Name and optionally a tag in the 'name:tag' format", - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "remove": { - Type: schema.TypeBool, - Description: "Remove intermediate containers after a successful build. Defaults to `true`.", - Default: true, - Optional: true, - }, - "secrets": { - Type: schema.TypeList, - Description: "Set build-time secrets. Only available when you use a buildx builder.", - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Description: "ID of the secret. By default, secrets are mounted to /run/secrets/", - Optional: false, - Required: true, - ForceNew: true, - }, - "src": { - Type: schema.TypeString, - Description: "File source of the secret. Takes precedence over `env`", - Optional: true, - Required: false, - ForceNew: true, - }, - "env": { - Type: schema.TypeString, - Description: "Environment variable source of the secret", - Optional: true, - Required: false, - ForceNew: true, - }, - }, - }, - }, - "label": { - Type: schema.TypeMap, - Description: "Set metadata for an image", - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "suppress_output": { - Type: schema.TypeBool, - Description: "Suppress the build output and print image ID on success", - Optional: true, - ForceNew: true, - }, - "remote_context": { - Type: schema.TypeString, - Description: "A Git repository URI or HTTP/HTTPS context URI. Will be ignored if `builder` is set.", - Optional: true, - ForceNew: true, - }, - "no_cache": { - Type: schema.TypeBool, - Description: "Do not use the cache when building the image", - Optional: true, - ForceNew: true, - }, - "force_remove": { - Type: schema.TypeBool, - Description: "Always remove intermediate containers", - Optional: true, - ForceNew: true, - }, - "pull_parent": { - Type: schema.TypeBool, - Description: "Attempt to pull the image even if an older image exists locally", - Optional: true, - ForceNew: true, - }, - "isolation": { - Type: schema.TypeString, - Description: "Isolation represents the isolation technology of a container. The supported values are ", - Optional: true, - ForceNew: true, - }, - "cpu_set_cpus": { - Type: schema.TypeString, - Description: "CPUs in which to allow execution (e.g., `0-3`, `0`, `1`)", - Optional: true, - ForceNew: true, - }, - "cpu_set_mems": { - Type: schema.TypeString, - Description: "MEMs in which to allow execution (`0-3`, `0`, `1`)", - Optional: true, - ForceNew: true, - }, - "cpu_shares": { - Type: schema.TypeInt, - Description: "CPU shares (relative weight)", - Optional: true, - ForceNew: true, - }, - "cpu_quota": { - Type: schema.TypeInt, - Description: "Microseconds of CPU time that the container can get in a CPU period", - Optional: true, - ForceNew: true, - }, - "cpu_period": { - Type: schema.TypeInt, - Description: "The length of a CPU period in microseconds", - Optional: true, - ForceNew: true, - }, - "memory": { - Type: schema.TypeInt, - Description: "Set memory limit for build", - Optional: true, - ForceNew: true, - }, - "memory_swap": { - Type: schema.TypeInt, - Description: "Total memory (memory + swap), -1 to enable unlimited swap", - Optional: true, - ForceNew: true, - }, - "cgroup_parent": { - Type: schema.TypeString, - Description: "Optional parent cgroup for the container", - Optional: true, - ForceNew: true, - }, - "network_mode": { - Type: schema.TypeString, - Description: "Set the networking mode for the RUN instructions during build", - Optional: true, - ForceNew: true, - }, - "shm_size": { - Type: schema.TypeInt, - Description: "Size of /dev/shm in bytes. The size must be greater than 0", - Optional: true, - ForceNew: true, - }, - "ulimit": { - Type: schema.TypeList, - Description: "Configuration for ulimits", - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Description: "type of ulimit, e.g. `nofile`", - Required: true, - ForceNew: true, - }, - "hard": { - Type: schema.TypeInt, - Description: "soft limit", - Required: true, - ForceNew: true, - }, - "soft": { - Type: schema.TypeInt, - Description: "hard limit", - Required: true, - ForceNew: true, - }, - }, - }, - }, - "build_args": { - Type: schema.TypeMap, - Description: "Pairs for build-time variables in the form of `ENDPOINT : \"https://example.com\"`", - Optional: true, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - Description: "The argument", - }, - }, - "auth_config": { - Type: schema.TypeList, - Description: "The configuration for the authentication", - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "host_name": { - Type: schema.TypeString, - Description: "hostname of the registry", - Required: true, - }, - "user_name": { - Type: schema.TypeString, - Description: "the registry user name", - Optional: true, - }, - "password": { - Type: schema.TypeString, - Description: "the registry password", - Optional: true, - }, - "auth": { - Type: schema.TypeString, - Description: "the auth token", - Optional: true, - }, - "email": { - Type: schema.TypeString, - Description: "the user emal", - Optional: true, - }, - "server_address": { - Type: schema.TypeString, - Description: "the server address", - Optional: true, - }, - "identity_token": { - Type: schema.TypeString, - Description: "the identity token", - Optional: true, - }, - "registry_token": { - Type: schema.TypeString, - Description: "the registry token", - Optional: true, - }, - }, - }, - }, - "context": { - Type: schema.TypeString, - Description: "Value to specify the build context. Currently, only a `PATH` context is supported. You can use the helper function '${path.cwd}/context-dir'. This always refers to the local working directory, even when building images on remote hosts. Please see https://docs.docker.com/build/building/context/ for more information about build contexts.", - Required: true, - ForceNew: true, - }, - "additional_contexts": { - Type: schema.TypeList, - Description: "A list of additional build contexts. Only supported when using a buildx builder. Example: `[\"name=path\", \"src = https://example.org\"}`. Please see https://docs.docker.com/reference/cli/docker/buildx/build/#build-context for more information.", - Optional: true, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - Description: "An additional context in the form of `key=value`", - }, - }, - "labels": { - Type: schema.TypeMap, - Description: "User-defined key/value metadata", - Optional: true, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - Description: "The key/value pair", - }, - }, - "squash": { - Type: schema.TypeBool, - Description: "If true the new layers are squashed into a new image with a single new layer", - Optional: true, - ForceNew: true, - }, - "cache_from": { - Type: schema.TypeList, - Description: "External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`). Only supported when using a buildx builder.", - Optional: true, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - Description: "A cache source", - }, - }, - "cache_to": { - Type: schema.TypeList, - Description: "Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`). Only supported when using a buildx builder.", - Optional: true, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - Description: "A cache destination", - }, - }, - "security_opt": { - Type: schema.TypeList, - Description: "The security options", - Optional: true, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - Description: "The option", - }, - }, - "extra_hosts": { - Type: schema.TypeList, - Description: "A list of hostnames/IP mappings to add to the container’s /etc/hosts file. Specified in the form [\"hostname:IP\"]", - Optional: true, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - Description: "", - }, - }, - "target": { - Type: schema.TypeString, - Description: "Set the target build stage to build", - Optional: true, - ForceNew: true, - }, - "session_id": { - Type: schema.TypeString, - Description: "Set an ID for the build session", - Optional: true, - ForceNew: true, - }, - "platform": { - Type: schema.TypeString, - Description: "Set the target platform for the build. Defaults to `GOOS/GOARCH`. For more information see the [docker documentation](https://github.com/docker/buildx/blob/master/docs/reference/buildx.md#-set-the-target-platforms-for-the-build---platform)", - Optional: true, - ForceNew: true, - }, - "version": { - Type: schema.TypeString, - Description: "Version of the underlying builder to use", - Optional: true, - ForceNew: true, - }, - "build_id": { - Type: schema.TypeString, - Description: "BuildID is an optional identifier that can be passed together with the build request. The same identifier can be used to gracefully cancel the build with the cancel request.", - Optional: true, - ForceNew: true, - }, - "builder": { - Type: schema.TypeString, - Description: "Set the name of the buildx builder to use. If not set, the legacy builder is used.", - Optional: true, - ForceNew: true, - }, - "build_log_file": { - Type: schema.TypeString, - Description: "Path to a file where the buildx log are written to. Only available when `builder` is set. If not set, no logs are available. The path is taken as is, so make sure to use a path that is available.", - Optional: true, - ForceNew: true, - }, - }, - }, + Elem: buildSchema, }, "triggers": { Description: "A map of arbitrary strings that, when changed, will force the `docker_image` resource to be replaced. This can be used to rebuild an image when contents of source code folders change", @@ -455,3 +97,363 @@ func resourceDockerImage() *schema.Resource { }, } } + +var buildSchema = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "dockerfile": { + Type: schema.TypeString, + Description: "Name of the Dockerfile. Defaults to `Dockerfile`.", + Optional: true, + Default: "Dockerfile", + ForceNew: true, + }, + "tag": { + Type: schema.TypeList, + Description: "Name and optionally a tag in the 'name:tag' format", + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "remove": { + Type: schema.TypeBool, + Description: "Remove intermediate containers after a successful build. Defaults to `true`.", + Default: true, + Optional: true, + }, + "secrets": { + Type: schema.TypeList, + Description: "Set build-time secrets. Only available when you use a buildx builder.", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Description: "ID of the secret. By default, secrets are mounted to /run/secrets/", + Optional: false, + Required: true, + ForceNew: true, + }, + "src": { + Type: schema.TypeString, + Description: "File source of the secret. Takes precedence over `env`", + Optional: true, + Required: false, + ForceNew: true, + }, + "env": { + Type: schema.TypeString, + Description: "Environment variable source of the secret", + Optional: true, + Required: false, + ForceNew: true, + }, + }, + }, + }, + "label": { + Type: schema.TypeMap, + Description: "Set metadata for an image", + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "suppress_output": { + Type: schema.TypeBool, + Description: "Suppress the build output and print image ID on success", + Optional: true, + ForceNew: true, + }, + "remote_context": { + Type: schema.TypeString, + Description: "A Git repository URI or HTTP/HTTPS context URI. Will be ignored if `builder` is set.", + Optional: true, + ForceNew: true, + }, + "no_cache": { + Type: schema.TypeBool, + Description: "Do not use the cache when building the image", + Optional: true, + ForceNew: true, + }, + "force_remove": { + Type: schema.TypeBool, + Description: "Always remove intermediate containers", + Optional: true, + ForceNew: true, + }, + "pull_parent": { + Type: schema.TypeBool, + Description: "Attempt to pull the image even if an older image exists locally", + Optional: true, + ForceNew: true, + }, + "isolation": { + Type: schema.TypeString, + Description: "Isolation represents the isolation technology of a container. The supported values are ", + Optional: true, + ForceNew: true, + }, + "cpu_set_cpus": { + Type: schema.TypeString, + Description: "CPUs in which to allow execution (e.g., `0-3`, `0`, `1`)", + Optional: true, + ForceNew: true, + }, + "cpu_set_mems": { + Type: schema.TypeString, + Description: "MEMs in which to allow execution (`0-3`, `0`, `1`)", + Optional: true, + ForceNew: true, + }, + "cpu_shares": { + Type: schema.TypeInt, + Description: "CPU shares (relative weight)", + Optional: true, + ForceNew: true, + }, + "cpu_quota": { + Type: schema.TypeInt, + Description: "Microseconds of CPU time that the container can get in a CPU period", + Optional: true, + ForceNew: true, + }, + "cpu_period": { + Type: schema.TypeInt, + Description: "The length of a CPU period in microseconds", + Optional: true, + ForceNew: true, + }, + "memory": { + Type: schema.TypeInt, + Description: "Set memory limit for build", + Optional: true, + ForceNew: true, + }, + "memory_swap": { + Type: schema.TypeInt, + Description: "Total memory (memory + swap), -1 to enable unlimited swap", + Optional: true, + ForceNew: true, + }, + "cgroup_parent": { + Type: schema.TypeString, + Description: "Optional parent cgroup for the container", + Optional: true, + ForceNew: true, + }, + "network_mode": { + Type: schema.TypeString, + Description: "Set the networking mode for the RUN instructions during build", + Optional: true, + ForceNew: true, + }, + "shm_size": { + Type: schema.TypeInt, + Description: "Size of /dev/shm in bytes. The size must be greater than 0", + Optional: true, + ForceNew: true, + }, + "ulimit": { + Type: schema.TypeList, + Description: "Configuration for ulimits", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "type of ulimit, e.g. `nofile`", + Required: true, + ForceNew: true, + }, + "hard": { + Type: schema.TypeInt, + Description: "soft limit", + Required: true, + ForceNew: true, + }, + "soft": { + Type: schema.TypeInt, + Description: "hard limit", + Required: true, + ForceNew: true, + }, + }, + }, + }, + "build_args": { + Type: schema.TypeMap, + Description: "Pairs for build-time variables in the form of `ENDPOINT : \"https://example.com\"`", + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + Description: "The argument", + }, + }, + "auth_config": { + Type: schema.TypeList, + Description: "The configuration for the authentication", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "host_name": { + Type: schema.TypeString, + Description: "hostname of the registry", + Required: true, + }, + "user_name": { + Type: schema.TypeString, + Description: "the registry user name", + Optional: true, + }, + "password": { + Type: schema.TypeString, + Description: "the registry password", + Optional: true, + }, + "auth": { + Type: schema.TypeString, + Description: "the auth token", + Optional: true, + }, + "email": { + Type: schema.TypeString, + Description: "the user emal", + Optional: true, + }, + "server_address": { + Type: schema.TypeString, + Description: "the server address", + Optional: true, + }, + "identity_token": { + Type: schema.TypeString, + Description: "the identity token", + Optional: true, + }, + "registry_token": { + Type: schema.TypeString, + Description: "the registry token", + Optional: true, + }, + }, + }, + }, + "context": { + Type: schema.TypeString, + Description: "Value to specify the build context. Currently, only a `PATH` context is supported. You can use the helper function '${path.cwd}/context-dir'. This always refers to the local working directory, even when building images on remote hosts. Please see https://docs.docker.com/build/building/context/ for more information about build contexts.", + Required: true, + ForceNew: true, + }, + "additional_contexts": { + Type: schema.TypeList, + Description: "A list of additional build contexts. Only supported when using a buildx builder. Example: `[\"name=path\", \"src = https://example.org\"}`. Please see https://docs.docker.com/reference/cli/docker/buildx/build/#build-context for more information.", + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + Description: "An additional context in the form of `key=value`", + }, + }, + "labels": { + Type: schema.TypeMap, + Description: "User-defined key/value metadata", + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + Description: "The key/value pair", + }, + }, + "squash": { + Type: schema.TypeBool, + Description: "If true the new layers are squashed into a new image with a single new layer", + Optional: true, + ForceNew: true, + }, + "cache_from": { + Type: schema.TypeList, + Description: "External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`). Only supported when using a buildx builder.", + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + Description: "A cache source", + }, + }, + "cache_to": { + Type: schema.TypeList, + Description: "Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`). Only supported when using a buildx builder.", + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + Description: "A cache destination", + }, + }, + "security_opt": { + Type: schema.TypeList, + Description: "The security options", + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + Description: "The option", + }, + }, + "extra_hosts": { + Type: schema.TypeList, + Description: "A list of hostnames/IP mappings to add to the container’s /etc/hosts file. Specified in the form [\"hostname:IP\"]", + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + Description: "", + }, + }, + "target": { + Type: schema.TypeString, + Description: "Set the target build stage to build", + Optional: true, + ForceNew: true, + }, + "session_id": { + Type: schema.TypeString, + Description: "Set an ID for the build session", + Optional: true, + ForceNew: true, + }, + "platform": { + Type: schema.TypeString, + Description: "Set the target platform for the build. Defaults to `GOOS/GOARCH`. For more information see the [docker documentation](https://github.com/docker/buildx/blob/master/docs/reference/buildx.md#-set-the-target-platforms-for-the-build---platform)", + Optional: true, + ForceNew: true, + }, + "version": { + Type: schema.TypeString, + Description: "Version of the underlying builder to use", + Optional: true, + ForceNew: true, + }, + "build_id": { + Type: schema.TypeString, + Description: "BuildID is an optional identifier that can be passed together with the build request. The same identifier can be used to gracefully cancel the build with the cancel request.", + Optional: true, + ForceNew: true, + }, + "builder": { + Type: schema.TypeString, + Description: "Set the name of the buildx builder to use. If not set, the legacy builder is used.", + Optional: true, + ForceNew: true, + }, + "build_log_file": { + Type: schema.TypeString, + Description: "Path to a file where the buildx log are written to. Only available when `builder` is set. If not set, no logs are available. The path is taken as is, so make sure to use a path that is available.", + Optional: true, + ForceNew: true, + }, + }, +} diff --git a/internal/provider/resource_docker_image_funcs.go b/internal/provider/resource_docker_image_funcs.go index 921298a9..d71a7e11 100644 --- a/internal/provider/resource_docker_image_funcs.go +++ b/internal/provider/resource_docker_image_funcs.go @@ -37,42 +37,9 @@ func resourceDockerImageCreate(ctx context.Context, d *schema.ResourceData, meta if value, ok := d.GetOk("build"); ok { for _, rawBuild := range value.(*schema.Set).List() { - rawBuild := rawBuild.(map[string]interface{}) - // now we need to determine whether we can use buildx or need to use the legacy builder - canUseBuildx, err := canUseBuildx(ctx, client) - if err != nil { - return diag.FromErr(err) - } - - builder := rawBuild["builder"].(string) - log.Printf("[DEBUG] canUseBuildx: %v, builder %s", canUseBuildx, builder) - // buildx is enabled - if canUseBuildx && builder != "" { - log.Printf("[DEBUG] Using buildx") - dockerCli, err := createAndInitDockerCli(client) - if err != nil { - return diag.FromErr(fmt.Errorf("failed to create and init Docker CLI: %w", err)) - } - - options, err := mapBuildAttributesToBuildOptions(rawBuild, imageName) - - if err != nil { - return diag.FromErr(fmt.Errorf("Error mapping build attributes: %v", err)) - } - buildLogFile := rawBuild["build_log_file"].(string) - - log.Printf("[DEBUG] build options %#v", options) - - err = runBuild(ctx, dockerCli, options, buildLogFile) - if err != nil { - return diag.Errorf("Error running buildx build: %v", err) - } - } else { - - err := buildDockerImage(ctx, rawBuild, imageName, client) - if err != nil { - return diag.Errorf("Error running legacy build: %v", err) - } + shouldReturn, d1 := buildImage(ctx, rawBuild, client, imageName) + if shouldReturn { + return d1 } } } @@ -85,6 +52,47 @@ func resourceDockerImageCreate(ctx context.Context, d *schema.ResourceData, meta return resourceDockerImageRead(ctx, d, meta) } +func buildImage(ctx context.Context, rawBuild interface{}, client *client.Client, imageName string) (bool, diag.Diagnostics) { + rawBuildValue := rawBuild.(map[string]interface{}) + // now we need to determine whether we can use buildx or need to use the legacy builder + canUseBuildx, err := canUseBuildx(ctx, client) + if err != nil { + return true, diag.FromErr(err) + } + + builder := rawBuildValue["builder"].(string) + log.Printf("[DEBUG] canUseBuildx: %v, builder %s", canUseBuildx, builder) + // buildx is enabled + if canUseBuildx && builder != "" { + log.Printf("[DEBUG] Using buildx") + dockerCli, err := createAndInitDockerCli(client) + if err != nil { + return true, diag.FromErr(fmt.Errorf("failed to create and init Docker CLI: %w", err)) + } + + options, err := mapBuildAttributesToBuildOptions(rawBuildValue, imageName) + + if err != nil { + return true, diag.FromErr(fmt.Errorf("Error mapping build attributes: %v", err)) + } + buildLogFile := rawBuildValue["build_log_file"].(string) + + log.Printf("[DEBUG] build options %#v", options) + + err = runBuild(ctx, dockerCli, options, buildLogFile) + if err != nil { + return true, diag.Errorf("Error running buildx build: %v", err) + } + } else { + + err := legacyBuildDockerImage(ctx, rawBuildValue, imageName, client) + if err != nil { + return true, diag.Errorf("Error running legacy build: %v", err) + } + } + return false, nil +} + func resourceDockerImageRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*ProviderConfig).DockerClient var data Data @@ -353,7 +361,7 @@ func findImage(ctx context.Context, imageName string, client *client.Client, aut return nil, fmt.Errorf("unable to find or pull image %s", imageName) } -func buildDockerImage(ctx context.Context, rawBuild map[string]interface{}, imageName string, client *client.Client) error { +func legacyBuildDockerImage(ctx context.Context, rawBuild map[string]interface{}, imageName string, client *client.Client) error { var ( err error ) diff --git a/internal/provider/resource_docker_registry_image.go b/internal/provider/resource_docker_registry_image.go index 1e969c1b..d918ff6c 100644 --- a/internal/provider/resource_docker_registry_image.go +++ b/internal/provider/resource_docker_registry_image.go @@ -63,6 +63,13 @@ func resourceDockerRegistryImage() *schema.Resource { }, "auth_config": AuthConfigSchema, + "build": { + Type: schema.TypeSet, + Description: "Configuration to build an image. Requires the `Use containerd for pulling and storing images` option to be disabled in the Docker Host(https://github.com/kreuzwerker/terraform-provider-docker/issues/534). Please see [docker build command reference](https://docs.docker.com/engine/reference/commandline/build/#options) too.", + Optional: true, + MaxItems: 1, + Elem: buildSchema, + }, }, } } diff --git a/internal/provider/resource_docker_registry_image_funcs.go b/internal/provider/resource_docker_registry_image_funcs.go index c1673101..39b29d17 100644 --- a/internal/provider/resource_docker_registry_image_funcs.go +++ b/internal/provider/resource_docker_registry_image_funcs.go @@ -37,6 +37,14 @@ func resourceDockerRegistryImageCreate(ctx context.Context, d *schema.ResourceDa providerConfig := meta.(*ProviderConfig) name := d.Get("name").(string) log.Printf("[DEBUG] Creating docker image %s", name) + if value, ok := d.GetOk("build"); ok { + for _, rawBuild := range value.(*schema.Set).List() { + shouldReturn, d1 := buildImage(ctx, rawBuild, client, name) + if shouldReturn { + return d1 + } + } + } pushOpts := createPushImageOptions(name) diff --git a/internal/provider/resource_docker_registry_image_test.go b/internal/provider/resource_docker_registry_image_test.go index 8af70b07..6837383e 100644 --- a/internal/provider/resource_docker_registry_image_test.go +++ b/internal/provider/resource_docker_registry_image_test.go @@ -52,6 +52,27 @@ func TestAccDockerRegistryImageResource_buildAndKeep(t *testing.T) { CheckDestroy: testDockerRegistryImageInRegistry("testuser", "testpwd", pushOptions, true), }) } +func TestAccDockerRegistryImageResource_directBuildAndKeep(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", "testDirectBuildDockerRegistryImageKeepConfig"), pushOptions.Registry, pushOptions.Name, context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("docker_registry_image.foo", "sha256_digest"), + ), + }, + }, + // as the providerConfig obtained from testAccProvider.Meta().(*ProviderConfig) + // is empty after the test the credetials are passed here manually + CheckDestroy: testDockerRegistryImageInRegistry("testuser", "testpwd", pushOptions, true), + }) +} func TestAccDockerRegistryImageResource_pushMissingImage(t *testing.T) { resource.Test(t, resource.TestCase{ diff --git a/testdata/resources/docker_registry_image/testDirectBuildDockerRegistryImageKeepConfig.tf b/testdata/resources/docker_registry_image/testDirectBuildDockerRegistryImageKeepConfig.tf new file mode 100644 index 00000000..7490cb23 --- /dev/null +++ b/testdata/resources/docker_registry_image/testDirectBuildDockerRegistryImageKeepConfig.tf @@ -0,0 +1,17 @@ +provider "docker" { + alias = "private" + registry_auth { + address = "%s" + } +} + +resource "docker_registry_image" "foo" { + provider = "docker.private" + name = "%s" + + build { + context = "%s" + } + insecure_skip_verify = true + keep_remotely = true +}