From 49e35c46b2fb7121b3202f1ae7685d431b354fab Mon Sep 17 00:00:00 2001 From: Boris HUISGEN Date: Mon, 8 Oct 2018 15:02:13 +0200 Subject: [PATCH 1/3] Add container healthcheck Signed-off-by: Boris HUISGEN --- docker/resource_docker_container.go | 45 ++++++++++++++++ docker/resource_docker_container_funcs.go | 24 +++++++++ docker/resource_docker_container_test.go | 62 +++++++++++++++++++++++ 3 files changed, 131 insertions(+) diff --git a/docker/resource_docker_container.go b/docker/resource_docker_container.go index f853ba75..27dd292a 100644 --- a/docker/resource_docker_container.go +++ b/docker/resource_docker_container.go @@ -450,6 +450,51 @@ func resourceDockerContainer() *schema.Resource { }, }, }, + + "healthcheck": &schema.Schema{ + Type: schema.TypeList, + Description: "A test to perform to check that the container is healthy", + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "test": &schema.Schema{ + Type: schema.TypeList, + Description: "The test to perform as list", + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "interval": &schema.Schema{ + Type: schema.TypeString, + Description: "Time between running the check (ms|s|m|h)", + Optional: true, + Default: "0s", + ValidateFunc: validateDurationGeq0(), + }, + "timeout": &schema.Schema{ + Type: schema.TypeString, + Description: "Maximum time to allow one check to run (ms|s|m|h)", + Optional: true, + Default: "0s", + ValidateFunc: validateDurationGeq0(), + }, + "start_period": &schema.Schema{ + Type: schema.TypeString, + Description: "Start period for the container to initialize before counting retries towards unstable (ms|s|m|h)", + Optional: true, + Default: "0s", + ValidateFunc: validateDurationGeq0(), + }, + "retries": &schema.Schema{ + Type: schema.TypeInt, + Description: "Consecutive failures needed to report unhealthy", + Optional: true, + Default: 0, + ValidateFunc: validateIntegerGeqThan(0), + }, + }, + }, + }, }, } } diff --git a/docker/resource_docker_container_funcs.go b/docker/resource_docker_container_funcs.go index ca4ca9a4..9a67d97f 100644 --- a/docker/resource_docker_container_funcs.go +++ b/docker/resource_docker_container_funcs.go @@ -108,6 +108,30 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err config.Labels = mapTypeMapValsToString(v.(map[string]interface{})) } + if value, ok := d.GetOk("healthcheck"); ok { + config.Healthcheck = &container.HealthConfig{} + if len(value.([]interface{})) > 0 { + for _, rawHealthCheck := range value.([]interface{}) { + rawHealthCheck := rawHealthCheck.(map[string]interface{}) + if testCommand, ok := rawHealthCheck["test"]; ok { + config.Healthcheck.Test = stringListToStringSlice(testCommand.([]interface{})) + } + if rawInterval, ok := rawHealthCheck["interval"]; ok { + config.Healthcheck.Interval, _ = time.ParseDuration(rawInterval.(string)) + } + if rawTimeout, ok := rawHealthCheck["timeout"]; ok { + config.Healthcheck.Timeout, _ = time.ParseDuration(rawTimeout.(string)) + } + if rawStartPeriod, ok := rawHealthCheck["start_period"]; ok { + config.Healthcheck.StartPeriod, _ = time.ParseDuration(rawStartPeriod.(string)) + } + if rawRetries, ok := rawHealthCheck["retries"]; ok { + config.Healthcheck.Retries, _ = rawRetries.(int) + } + } + } + } + hostConfig := &container.HostConfig{ Privileged: d.Get("privileged").(bool), PublishAllPorts: d.Get("publish_all_ports").(bool), diff --git a/docker/resource_docker_container_test.go b/docker/resource_docker_container_test.go index 3d5a425c..a505c065 100644 --- a/docker/resource_docker_container_test.go +++ b/docker/resource_docker_container_test.go @@ -4,6 +4,7 @@ import ( "archive/tar" "bytes" "fmt" + "reflect" "strconv" "strings" "testing" @@ -613,6 +614,48 @@ func TestAccDockerContainer_multiple_ports(t *testing.T) { }) } +func TestAccDockerContainer_healthcheck(t *testing.T) { + var c types.ContainerJSON + + testCheck := func(*terraform.State) error { + if !reflect.DeepEqual(c.Config.Healthcheck.Test, []string{"CMD", "/bin/true"}) { + return fmt.Errorf("Container doesn't have a correct healthcheck test") + } + + if c.Config.Healthcheck.Interval != 30000000000 { + return fmt.Errorf("Container doesn't have a correct healthcheck interval") + } + + if c.Config.Healthcheck.Timeout != 5000000000 { + return fmt.Errorf("Container doesn't have a correct healthcheck timeout") + } + + if c.Config.Healthcheck.StartPeriod != 15000000000 { + return fmt.Errorf("Container doesn't have a correct healthcheck retries") + } + + if c.Config.Healthcheck.Retries != 10 { + return fmt.Errorf("Container doesn't have a correct healthcheck retries") + } + + return nil + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDockerContainerHealthcheckConfig, + Check: resource.ComposeTestCheckFunc( + testAccContainerRunning("docker_container.foo", &c), + testCheck, + ), + }, + }, + }) +} + func testAccContainerRunning(n string, container *types.ContainerJSON) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -885,3 +928,22 @@ resource "docker_container" "foo" { ] } ` +const testAccDockerContainerHealthcheckConfig = ` +resource "docker_image" "foo" { + name = "nginx:latest" + keep_locally = true +} + +resource "docker_container" "foo" { + name = "tf-test" + image = "${docker_image.foo.latest}" + + healthcheck { + test = ["CMD", "/bin/true"] + interval = "30s" + timeout = "5s" + start_period = "15s" + retries = 10 + } +} +` From fc9014d480f0cb3658531c5f7b6146d1f15c6617 Mon Sep 17 00:00:00 2001 From: Boris HUISGEN Date: Mon, 8 Oct 2018 19:46:55 +0200 Subject: [PATCH 2/3] Update doc Signed-off-by: Boris HUISGEN --- website/docs/r/container.html.markdown | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/website/docs/r/container.html.markdown b/website/docs/r/container.html.markdown index 72558fa6..7be0429d 100644 --- a/website/docs/r/container.html.markdown +++ b/website/docs/r/container.html.markdown @@ -94,6 +94,7 @@ data is stored in them. See [the docker documentation][linkdoc] for more details details. * `pid_mode` - (Optional, string) The PID (Process) Namespace mode for the container. Either `container:` or `host`. * `userns_mode` - (Optional, string) Sets the usernamespace mode for the container when usernamespace remapping option is enabled. +* `healthcheck` - (Optional, block) See [Healthcheck](#healthcheck) below for details. ### Capabilities @@ -201,6 +202,18 @@ the following: * `soft` - (Required, int) * `hard` - (Required, int) + +### Healthcheck + +`healthcheck` is a block within the configuration that can be repeated only **once** to specify the extra healthcheck configuration for the container. The `healthcheck` block is a test to perform to check that the container is healthy and supports the following: + +* `test` - (Required, list of strings) Command to run to check health. For example, to run `curl -f http://localhost/health` set the + command to be `["CMD", "curl", "-f", "http://localhost/health"]`. +* `interval` - (Optional, string) Time between running the check `(ms|s|m|h)`. Default: `0s`. +* `timeout` - (Optional, string) Maximum time to allow one check to run `(ms|s|m|h)`. Default: `0s`. +* `start_period` - (Optional, string) Start period for the container to initialize before counting retries towards unstable `(ms|s|m|h)`. Default: `0s`. +* `retries` - (Optional, int) Consecutive failures needed to report unhealthy. Default: `0`. + ## Attributes Reference The following attributes are exported: From da71464d20bf152ac3594fcc7f340ba406dfeaaa Mon Sep 17 00:00:00 2001 From: Boris HUISGEN Date: Sat, 20 Oct 2018 12:47:17 +0200 Subject: [PATCH 3/3] Fix other tests as port internal/external are not strings but int Signed-off-by: Boris HUISGEN --- docker/resource_docker_container_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docker/resource_docker_container_test.go b/docker/resource_docker_container_test.go index a505c065..8e7a5bc5 100644 --- a/docker/resource_docker_container_test.go +++ b/docker/resource_docker_container_test.go @@ -865,7 +865,7 @@ resource "docker_container" "foo" { image = "${docker_image.foo.latest}" ports { - internal = "80" + internal = 80 } } ` @@ -882,10 +882,10 @@ resource "docker_container" "foo" { ports = [ { - internal = "80" + internal = 80 }, { - internal = "81" + internal = 81 } ] } @@ -901,8 +901,8 @@ resource "docker_container" "foo" { image = "${docker_image.foo.latest}" ports { - internal = "80" - external = "32787" + internal = 80 + external = 32787 } } ` @@ -918,12 +918,12 @@ resource "docker_container" "foo" { ports = [ { - internal = "80" - external = "32787" + internal = 80 + external = 32787 }, { - internal = "81" - external = "32788" + internal = 81 + external = 32788 } ] }