From 1bc69158d218084fba0cc8dfb1a74825c03d0710 Mon Sep 17 00:00:00 2001 From: Boris HUISGEN Date: Sun, 28 Oct 2018 18:41:10 +0100 Subject: [PATCH 1/3] Add logs attribute to get container logs when attach option is enabled Signed-off-by: Boris HUISGEN --- docker/resource_docker_container.go | 23 ++++++++--- docker/resource_docker_container_funcs.go | 50 +++++++++++++++++++---- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/docker/resource_docker_container.go b/docker/resource_docker_container.go index f69ac100..eac43bbd 100644 --- a/docker/resource_docker_container.go +++ b/docker/resource_docker_container.go @@ -30,6 +30,18 @@ func resourceDockerContainer() *schema.Resource { Optional: true, }, + "attach": &schema.Schema{ + Type: schema.TypeBool, + Default: false, + Optional: true, + }, + + "logs": &schema.Schema{ + Type: schema.TypeBool, + Default: false, + Optional: true, + }, + // Indicates whether the container must be running. // // An assumption is made that configured containers @@ -56,6 +68,11 @@ func resourceDockerContainer() *schema.Resource { Computed: true, }, + "container_logs": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + // ForceNew is not true for image because we need to // sane this against Docker image IDs, as each image // can have multiple names/tags attached do it. @@ -77,12 +94,6 @@ func resourceDockerContainer() *schema.Resource { ForceNew: true, }, - "attach": &schema.Schema{ - Type: schema.TypeBool, - Default: false, - Optional: true, - }, - "command": &schema.Schema{ Type: schema.TypeList, Optional: true, diff --git a/docker/resource_docker_container_funcs.go b/docker/resource_docker_container_funcs.go index fbdb934f..d08dabf1 100644 --- a/docker/resource_docker_container_funcs.go +++ b/docker/resource_docker_container_funcs.go @@ -2,6 +2,7 @@ package docker import ( "archive/tar" + "bufio" "bytes" "encoding/json" "errors" @@ -46,12 +47,9 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err } config := &container.Config{ - Image: image, - Hostname: d.Get("hostname").(string), - Domainname: d.Get("domainname").(string), - AttachStdin: d.Get("attach").(bool), - AttachStdout: d.Get("attach").(bool), - AttachStderr: d.Get("attach").(bool), + Image: image, + Hostname: d.Get("hostname").(string), + Domainname: d.Get("domainname").(string), } if v, ok := d.GetOk("env"); ok { @@ -306,13 +304,47 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err } if d.Get("attach").(bool) { - statusCh, errCh := client.ContainerWait(context.Background(), retContainer.ID, container.WaitConditionNotRunning) + var b bytes.Buffer + + ctx := context.Background() + + if d.Get("logs").(bool) { + go func() { + reader, err := client.ContainerLogs(ctx, retContainer.ID, types.ContainerLogsOptions{ + ShowStdout: true, + ShowStderr: true, + Follow: true, + Timestamps: false, + }) + if err != nil { + log.Panic(err) + } + defer reader.Close() + + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + line := scanner.Text() + b.WriteString(line) + b.WriteString("\n") + + log.Printf("[DEBUG] container logs: %s", line) + } + if err := scanner.Err(); err != nil { + log.Fatal(err) + } + }() + } + + attachCh, errAttachCh := client.ContainerWait(ctx, retContainer.ID, container.WaitConditionNotRunning) select { - case err := <-errCh: + case err := <-errAttachCh: if err != nil { return fmt.Errorf("Unable to wait container end of execution: %s", err) } - case <-statusCh: + case <-attachCh: + if d.Get("logs").(bool) { + d.Set("container_logs", b.String()) + } } } From 9d67619581c91b7371c3ae1ecf86a8d16e44e2be Mon Sep 17 00:00:00 2001 From: Boris HUISGEN Date: Sun, 28 Oct 2018 18:41:47 +0100 Subject: [PATCH 2/3] Update tests Signed-off-by: Boris HUISGEN --- docker/resource_docker_container_test.go | 222 +++++++++++++---------- 1 file changed, 126 insertions(+), 96 deletions(-) diff --git a/docker/resource_docker_container_test.go b/docker/resource_docker_container_test.go index c6021f39..006a478b 100644 --- a/docker/resource_docker_container_test.go +++ b/docker/resource_docker_container_test.go @@ -717,59 +717,6 @@ func TestAccDockerContainer_rm(t *testing.T) { }) } -func TestAccDockerContainer_attach(t *testing.T) { - var c types.ContainerJSON - - testCheck := func(*terraform.State) error { - if !c.Config.AttachStdin { - return fmt.Errorf("Container doesn't have the correct value to stdin attach flag") - } - if !c.Config.AttachStdout { - return fmt.Errorf("Container doesn't have the correct value to stdout flag") - } - if !c.Config.AttachStderr { - return fmt.Errorf("Container doesn't have the correct value to stderr attach flag") - } - - return nil - } - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: testAccDockerContainerAttachConfig, - Check: resource.ComposeTestCheckFunc( - testAccContainerNotRunning("docker_container.foo", &c), - testCheck, - resource.TestCheckResourceAttr("docker_container.foo", "name", "tf-test"), - resource.TestCheckResourceAttr("docker_container.foo", "attach", "true"), - ), - }, - }, - }) -} - -func TestAccDockerContainer_exitcode(t *testing.T) { - var c types.ContainerJSON - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: testAccDockerContainerExitCodeConfig, - Check: resource.ComposeTestCheckFunc( - testAccContainerWaitConditionNotRunning("docker_container.foo", &c), - resource.TestCheckResourceAttr("docker_container.foo", "name", "tf-test"), - resource.TestCheckResourceAttr("docker_container.foo", "exit_code", "123"), - ), - }, - }, - }) -} - func TestAccDockerContainer_healthcheck(t *testing.T) { var c types.ContainerJSON testCheck := func(*terraform.State) error { @@ -821,6 +768,67 @@ func TestAccDockerContainer_nostart(t *testing.T) { }) } +func TestAccDockerContainer_attach(t *testing.T) { + var c types.ContainerJSON + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDockerContainerAttachConfig, + Check: resource.ComposeTestCheckFunc( + testAccContainerNotRunning("docker_container.foo", &c), + resource.TestCheckResourceAttr("docker_container.foo", "name", "tf-test"), + resource.TestCheckResourceAttr("docker_container.foo", "attach", "true"), + resource.TestCheckResourceAttr("docker_container.foo", "must_run", "false"), + ), + }, + }, + }) +} + +func TestAccDockerContainer_logs(t *testing.T) { + var c types.ContainerJSON + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDockerContainerLogsConfig, + Check: resource.ComposeTestCheckFunc( + testAccContainerNotRunning("docker_container.foo", &c), + resource.TestCheckResourceAttr("docker_container.foo", "name", "tf-test"), + resource.TestCheckResourceAttr("docker_container.foo", "attach", "true"), + resource.TestCheckResourceAttr("docker_container.foo", "logs", "true"), + resource.TestCheckResourceAttr("docker_container.foo", "must_run", "false"), + resource.TestCheckResourceAttr("docker_container.foo", "container_logs", "\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u00021\n\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u00022\n\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u00023\n\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u00024\n\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u00025\n\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u00026\n\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u00027\n\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u00028\n\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u00029\n\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u000310\n"), + ), + }, + }, + }) +} + +func TestAccDockerContainer_exitcode(t *testing.T) { + var c types.ContainerJSON + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDockerContainerExitCodeConfig, + Check: resource.ComposeTestCheckFunc( + testAccContainerWaitConditionNotRunning("docker_container.foo", &c), + resource.TestCheckResourceAttr("docker_container.foo", "name", "tf-test"), + resource.TestCheckResourceAttr("docker_container.foo", "exit_code", "123"), + ), + }, + }, + }) +} + func testAccContainerRunning(n string, container *types.ContainerJSON) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -859,9 +867,11 @@ func testAccContainerNotRunning(n string, container *types.ContainerJSON) resour if !ok { return fmt.Errorf("Not found: %s", n) } + if rs.Primary.ID == "" { return fmt.Errorf("No ID is set") } + client := testAccProvider.Meta().(*ProviderConfig).DockerClient containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{ All: true, @@ -869,6 +879,7 @@ func testAccContainerNotRunning(n string, container *types.ContainerJSON) resour if err != nil { return err } + for _, c := range containers { if c.ID == rs.Primary.ID { inspected, err := client.ContainerInspect(context.Background(), c.ID) @@ -882,6 +893,7 @@ func testAccContainerNotRunning(n string, container *types.ContainerJSON) resour } } } + return nil } } @@ -1214,49 +1226,6 @@ resource "docker_container" "foo" { ] } ` -const testAccDockerContainerRmConfig = ` -resource "docker_image" "foo" { - name = "busybox:latest" - keep_locally = true -} - -resource "docker_container" "foo" { - name = "tf-test" - image = "${docker_image.foo.latest}" - command = ["/bin/sleep", "15"] - rm = true -} -` - -const testAccDockerContainerAttachConfig = ` -resource "docker_image" "foo" { - name = "busybox:latest" - keep_locally = true -} - -resource "docker_container" "foo" { - name = "tf-test" - image = "${docker_image.foo.latest}" - command = ["/bin/sh", "-c", "for i in $(seq 1 15); do sleep 1 && echo \"test $i\"; done"] - attach = true - must_run = false -} -` - -const testAccDockerContainerExitCodeConfig = ` -resource "docker_image" "foo" { - name = "busybox:latest" - keep_locally = true -} - -resource "docker_container" "foo" { - name = "tf-test" - image = "${docker_image.foo.latest}" - command = ["/bin/sh", "-c", "exit 123"] - attach = true - must_run = false -} -` const testAccDockerContainer2NetworksConfig = ` resource "docker_image" "foo" { @@ -1288,6 +1257,7 @@ resource "docker_container" "bar" { network_alias = ["tftest-container-foo"] } ` + const testAccDockerContainerHealthcheckConfig = ` resource "docker_image" "foo" { name = "nginx:latest" @@ -1320,3 +1290,63 @@ resource "docker_container" "foo" { must_run = false } ` + +const testAccDockerContainerRmConfig = ` +resource "docker_image" "foo" { + name = "busybox:latest" + keep_locally = true +} + +resource "docker_container" "foo" { + name = "tf-test" + image = "${docker_image.foo.latest}" + command = ["/bin/sleep", "15"] + rm = true +} +` + +const testAccDockerContainerAttachConfig = ` +resource "docker_image" "foo" { + name = "busybox:latest" + keep_locally = true +} + +resource "docker_container" "foo" { + name = "tf-test" + image = "${docker_image.foo.latest}" + command = ["/bin/sh", "-c", "for i in $(seq 1 15); do sleep 1; done"] + attach = true + must_run = false +} +` + +const testAccDockerContainerLogsConfig = ` +resource "docker_image" "foo" { +name = "busybox:latest" +keep_locally = true +} + +resource "docker_container" "foo" { +name = "tf-test" +image = "${docker_image.foo.latest}" +command = ["/bin/sh", "-c", "for i in $(seq 1 10); do echo \"$i\"; done"] +attach = true +logs = true +must_run = false +} +` + +const testAccDockerContainerExitCodeConfig = ` +resource "docker_image" "foo" { +name = "busybox:latest" +keep_locally = true +} + +resource "docker_container" "foo" { +name = "tf-test" +image = "${docker_image.foo.latest}" +command = ["/bin/sh", "-c", "exit 123"] +attach = true +must_run = false +} +` From 82255e03d6a6f31749141d5158d875d15a7f7681 Mon Sep 17 00:00:00 2001 From: Boris HUISGEN Date: Sun, 28 Oct 2018 18:41:54 +0100 Subject: [PATCH 3/3] Update doc Signed-off-by: Boris HUISGEN --- website/docs/r/container.html.markdown | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/website/docs/r/container.html.markdown b/website/docs/r/container.html.markdown index d5e8c730..3403dd58 100644 --- a/website/docs/r/container.html.markdown +++ b/website/docs/r/container.html.markdown @@ -61,14 +61,16 @@ data is stored in them. See [the docker documentation][linkdoc] for more details * `hostname` - (Optional, string) Hostname of the container. * `domainname` - (Optional, string) Domain name of the container. -* `attach` - (Optional, bool) Attach to container. * `restart` - (Optional, string) The restart policy for the container. Must be one of "no", "on-failure", "always", "unless-stopped". * `max_retry_count` - (Optional, int) The maximum amount of times to an attempt a restart when `restart` is set to "on-failure" -* `rm` - (Optional, bool) If true, then the container will be automatically removed after his execution. Terraform won't check this container after creation. +* `rm` - (Optional, bool) If true, then the container will be automatically removed after his execution. Terraform + won't check this container after creation. * `start` - (Optional, bool) If true, then the Docker container will be started after creation. If false, then the container is only created. +* `attach` - (Optional, bool) If true attach to the container after its creation and waits the end of his execution. +* `logs` - (Optional, bool) Save the container logs (`attach` must be enabled). * `must_run` - (Optional, bool) If true, then the Docker container will be kept running. If false, then as long as the container exists, Terraform assumes it is successful. @@ -223,8 +225,9 @@ the following: The following attributes are exported: - * `exit_code` - The exit code of the container if it is not running (and should not i.e. `must_run` is disabled). - * `network_data` - (Map of a block) The IP addresses of the container on each + * `exit_code` - The exit code of the container if its execution is done (`must_run` must be disabled). + * `container_logs` - The logs of the container if its execution is done (`attach` must be disabled). +* `network_data` - (Map of a block) The IP addresses of the container on each network. Key are the network names, values are the IP addresses. * `ip_address` - The IP address of the container. * `ip_prefix_length` - The IP prefix length of the container.