diff --git a/docs/resources/container.md b/docs/resources/container.md index 6c063dbf..6ea274fe 100644 --- a/docs/resources/container.md +++ b/docs/resources/container.md @@ -94,6 +94,8 @@ resource "docker_image" "ubuntu" { - `user` (String) User used for run the first process. Format is `user` or `user:group` which user and group can be passed literraly or by name. - `userns_mode` (String) Sets the usernamespace mode for the container when usernamespace remapping option is enabled. - `volumes` (Block Set) Spec for mounting volumes in the container. (see [below for nested schema](#nestedblock--volumes)) +- `wait` (Boolean) If `true`, then the Docker container is waited for being healthy state after creation. If `false`, then the container health state is not checked. Defaults to `false`. +- `wait_timeout` (Number) The timeout in seconds to wait the container to be healthy after creation. Defaults to `60`. - `working_dir` (String) The working directory for commands to run in. ### Read-Only diff --git a/internal/provider/resource_docker_container.go b/internal/provider/resource_docker_container.go index 717fca00..907819b9 100644 --- a/internal/provider/resource_docker_container.go +++ b/internal/provider/resource_docker_container.go @@ -64,6 +64,20 @@ func resourceDockerContainer() *schema.Resource { Optional: true, }, + "wait": { + Type: schema.TypeBool, + Description: "If `true`, then the Docker container is waited for being healthy state after creation. If `false`, then the container health state is not checked. Defaults to `false`.", + Default: false, + Optional: true, + }, + + "wait_timeout": { + Type: schema.TypeInt, + Description: "The timeout in seconds to wait the container to be healthy after creation. Defaults to `60`.", + Default: 60, + Optional: true, + }, + "attach": { Type: schema.TypeBool, Description: "If `true` attach to the container after its creation and waits the end of its execution. Defaults to `false`.", diff --git a/internal/provider/resource_docker_container_funcs.go b/internal/provider/resource_docker_container_funcs.go index 8f7b9c53..43109a3b 100644 --- a/internal/provider/resource_docker_container_funcs.go +++ b/internal/provider/resource_docker_container_funcs.go @@ -39,6 +39,7 @@ var ( errContainerFailedToBeDeleted = errors.New("container failed to be deleted") errContainerExitedImmediately = errors.New("container exited immediately") errContainerFailedToBeInRunningState = errors.New("container failed to be in running state") + errContainerFailedToBeInHealthyState = errors.New("container failed to be in healthy state") ) // NOTE mavogel: we keep this global var for tracking @@ -506,6 +507,39 @@ func resourceDockerContainerCreate(ctx context.Context, d *schema.ResourceData, if err := client.ContainerStart(ctx, retContainer.ID, options); err != nil { return diag.Errorf("Unable to start container: %s", err) } + + if d.Get("wait").(bool) { + waitForHealthyState := func(result chan<- error) { + for { + infos, err := client.ContainerInspect(ctx, retContainer.ID) + if err != nil { + result <- fmt.Errorf("error inspecting container state: %s", err) + } + if infos.State.Health.Status == types.Healthy { + log.Printf("[DEBUG] container state is healthy") + break + } + log.Printf("[DEBUG] waiting for container healthy state") + time.Sleep(time.Second) + } + result <- nil + } + + ctx, cancel := context.WithTimeout(ctx, time.Duration(d.Get("wait_timeout").(int))*time.Second) + defer cancel() + result := make(chan error, 1) + go waitForHealthyState(result) + select { + case <-ctx.Done(): + log.Printf("[ERROR] Container %s failed to be in healthy state in time", retContainer.ID) + return diag.FromErr(errContainerFailedToBeInHealthyState) + + case err := <-result: + if err != nil { + return diag.FromErr(err) + } + } + } } if d.Get("attach").(bool) { diff --git a/internal/provider/resource_docker_container_test.go b/internal/provider/resource_docker_container_test.go index 56d24188..40e71f54 100644 --- a/internal/provider/resource_docker_container_test.go +++ b/internal/provider/resource_docker_container_test.go @@ -89,6 +89,8 @@ func TestAccDockerContainer_basic(t *testing.T) { "restart", "rm", "start", + "wait", + "wait_timeout", "container_logs", "destroy_grace_seconds", "upload", @@ -132,6 +134,8 @@ func TestAccDockerContainer_init(t *testing.T) { "restart", "rm", "start", + "wait", + "wait_timeout", "container_logs", "destroy_grace_seconds", "upload", @@ -962,12 +966,12 @@ func TestAccDockerContainer_multipleUploadContentsConfig(t *testing.T) { name = "nginx:latest" keep_locally = true } - + resource "docker_container" "foo" { name = "tf-test" image = docker_image.foo.image_id must_run = "false" - + upload { content = "foobar" content_base64 = base64encode("barbaz") @@ -993,12 +997,12 @@ func TestAccDockerContainer_noUploadContentsConfig(t *testing.T) { name = "nginx:latest" keep_locally = true } - + resource "docker_container" "foo" { name = "tf-test" image = docker_image.foo.image_id must_run = "false" - + upload { file = "/terraform/test1.txt" executable = true