Adds container logs option. Closes #108.

This commit is contained in:
Manuel Vogel 2018-10-29 07:07:37 +01:00
commit aaee1b5c47
No known key found for this signature in database
GPG key ID: 533006C7B61DB1CE
5 changed files with 186 additions and 115 deletions

View file

@ -7,6 +7,8 @@ IMPROVEMENTS
* Adds the docker container start flag [GH-62] and [[#94](https://github.com/terraform-providers/terraform-provider-docker/pull/94)] * Adds the docker container start flag [GH-62] and [[#94](https://github.com/terraform-providers/terraform-provider-docker/pull/94)]
* Adds `cpu_set` to docker container [[#41](https://github.com/terraform-providers/terraform-provider-docker/pull/41)] * Adds `cpu_set` to docker container [[#41](https://github.com/terraform-providers/terraform-provider-docker/pull/41)]
* Simplifies the image options parser and adds missing registry combinations [[#49](https://github.com/terraform-providers/terraform-provider-docker/pull/49)] * Simplifies the image options parser and adds missing registry combinations [[#49](https://github.com/terraform-providers/terraform-provider-docker/pull/49)]
* Adds container static IPv4/IPv6 address. Marks network and network_alias as deprecated. [[#105](https://github.com/terraform-providers/terraform-provider-docker/pull/105)]
* Adds container logs option [[#108](https://github.com/terraform-providers/terraform-provider-docker/pull/108)]
BUG FIXES BUG FIXES
* Fixes that new network were appended to the default bridge [GH-10] * Fixes that new network were appended to the default bridge [GH-10]

View file

@ -30,6 +30,18 @@ func resourceDockerContainer() *schema.Resource {
Optional: true, 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. // Indicates whether the container must be running.
// //
// An assumption is made that configured containers // An assumption is made that configured containers
@ -56,6 +68,11 @@ func resourceDockerContainer() *schema.Resource {
Computed: true, Computed: true,
}, },
"container_logs": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
// ForceNew is not true for image because we need to // ForceNew is not true for image because we need to
// sane this against Docker image IDs, as each image // sane this against Docker image IDs, as each image
// can have multiple names/tags attached do it. // can have multiple names/tags attached do it.
@ -77,12 +94,6 @@ func resourceDockerContainer() *schema.Resource {
ForceNew: true, ForceNew: true,
}, },
"attach": &schema.Schema{
Type: schema.TypeBool,
Default: false,
Optional: true,
},
"command": &schema.Schema{ "command": &schema.Schema{
Type: schema.TypeList, Type: schema.TypeList,
Optional: true, Optional: true,

View file

@ -2,6 +2,7 @@ package docker
import ( import (
"archive/tar" "archive/tar"
"bufio"
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors" "errors"
@ -46,12 +47,9 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
} }
config := &container.Config{ config := &container.Config{
Image: image, Image: image,
Hostname: d.Get("hostname").(string), Hostname: d.Get("hostname").(string),
Domainname: d.Get("domainname").(string), Domainname: d.Get("domainname").(string),
AttachStdin: d.Get("attach").(bool),
AttachStdout: d.Get("attach").(bool),
AttachStderr: d.Get("attach").(bool),
} }
if v, ok := d.GetOk("env"); ok { if v, ok := d.GetOk("env"); ok {
@ -336,13 +334,47 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
} }
if d.Get("attach").(bool) { 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 { select {
case err := <-errCh: case err := <-errAttachCh:
if err != nil { if err != nil {
return fmt.Errorf("Unable to wait container end of execution: %s", err) 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())
}
} }
} }

View file

@ -720,59 +720,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) { func TestAccDockerContainer_healthcheck(t *testing.T) {
var c types.ContainerJSON var c types.ContainerJSON
testCheck := func(*terraform.State) error { testCheck := func(*terraform.State) error {
@ -824,6 +771,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 TestAccDockerContainer_ipv4address(t *testing.T) { func TestAccDockerContainer_ipv4address(t *testing.T) {
var c types.ContainerJSON var c types.ContainerJSON
@ -979,9 +987,11 @@ func testAccContainerNotRunning(n string, container *types.ContainerJSON) resour
if !ok { if !ok {
return fmt.Errorf("Not found: %s", n) return fmt.Errorf("Not found: %s", n)
} }
if rs.Primary.ID == "" { if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set") return fmt.Errorf("No ID is set")
} }
client := testAccProvider.Meta().(*ProviderConfig).DockerClient client := testAccProvider.Meta().(*ProviderConfig).DockerClient
containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{ containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{
All: true, All: true,
@ -989,6 +999,7 @@ func testAccContainerNotRunning(n string, container *types.ContainerJSON) resour
if err != nil { if err != nil {
return err return err
} }
for _, c := range containers { for _, c := range containers {
if c.ID == rs.Primary.ID { if c.ID == rs.Primary.ID {
inspected, err := client.ContainerInspect(context.Background(), c.ID) inspected, err := client.ContainerInspect(context.Background(), c.ID)
@ -1002,6 +1013,7 @@ func testAccContainerNotRunning(n string, container *types.ContainerJSON) resour
} }
} }
} }
return nil return nil
} }
} }
@ -1338,49 +1350,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 = ` const testAccDockerContainer2NetworksConfig = `
resource "docker_image" "foo" { resource "docker_image" "foo" {
@ -1412,6 +1381,7 @@ resource "docker_container" "bar" {
network_alias = ["tftest-container-foo"] network_alias = ["tftest-container-foo"]
} }
` `
const testAccDockerContainerHealthcheckConfig = ` const testAccDockerContainerHealthcheckConfig = `
resource "docker_image" "foo" { resource "docker_image" "foo" {
name = "nginx:latest" name = "nginx:latest"
@ -1518,3 +1488,56 @@ 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; 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
}
`

View file

@ -61,14 +61,16 @@ data is stored in them. See [the docker documentation][linkdoc] for more details
* `hostname` - (Optional, string) Hostname of the container. * `hostname` - (Optional, string) Hostname of the container.
* `domainname` - (Optional, string) Domain name 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 * `restart` - (Optional, string) The restart policy for the container. Must be
one of "no", "on-failure", "always", "unless-stopped". one of "no", "on-failure", "always", "unless-stopped".
* `max_retry_count` - (Optional, int) The maximum amount of times to an attempt * `max_retry_count` - (Optional, int) The maximum amount of times to an attempt
a restart when `restart` is set to "on-failure" 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 * `start` - (Optional, bool) If true, then the Docker container will be
started after creation. If false, then the container is only created. 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 * `must_run` - (Optional, bool) If true, then the Docker container will be
kept running. If false, then as long as the container exists, Terraform kept running. If false, then as long as the container exists, Terraform
assumes it is successful. assumes it is successful.
@ -236,8 +238,9 @@ the following:
The following attributes are exported: 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). * `exit_code` - The exit code of the container if its execution is done (`must_run` must be disabled).
* `network_data` - (Map of a block) The IP addresses of the container on each * `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. network. Key are the network names, values are the IP addresses.
* `ip_address` - The IP address of the container. * `ip_address` - The IP address of the container.
* `ip_prefix_length` - The IP prefix length of the container. * `ip_prefix_length` - The IP prefix length of the container.