diff --git a/docker/resource_docker_container.go b/docker/resource_docker_container.go index b3b4d693..768e4b0b 100644 --- a/docker/resource_docker_container.go +++ b/docker/resource_docker_container.go @@ -114,6 +114,7 @@ func resourceDockerContainer() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, + Computed: true, }, "domainname": { @@ -126,6 +127,7 @@ func resourceDockerContainer() *schema.Resource { Type: schema.TypeList, Optional: true, ForceNew: true, + Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, @@ -133,6 +135,7 @@ func resourceDockerContainer() *schema.Resource { Type: schema.TypeList, Optional: true, ForceNew: true, + Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, @@ -186,7 +189,7 @@ func resourceDockerContainer() *schema.Resource { Optional: true, ForceNew: true, }, - "working_dir": &schema.Schema{ + "working_dir": { Type: schema.TypeString, Optional: true, ForceNew: true, @@ -196,6 +199,7 @@ func resourceDockerContainer() *schema.Resource { Optional: true, ForceNew: true, MaxItems: 1, + // TODO implement DiffSuppressFunc Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "add": { @@ -295,6 +299,7 @@ func resourceDockerContainer() *schema.Resource { Type: schema.TypeList, Description: "Optional configuration for the tmpfs type", Optional: true, + ForceNew: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -452,6 +457,7 @@ func resourceDockerContainer() *schema.Resource { Type: schema.TypeSet, Optional: true, ForceNew: true, + Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, }, @@ -555,6 +561,7 @@ func resourceDockerContainer() *schema.Resource { Type: schema.TypeSet, Optional: true, ForceNew: true, + Computed: true, Elem: labelSchema, }, @@ -576,6 +583,7 @@ func resourceDockerContainer() *schema.Resource { Type: schema.TypeInt, Optional: true, ForceNew: true, + Computed: true, ValidateFunc: validateIntegerGeqThan(0), }, @@ -620,6 +628,16 @@ func resourceDockerContainer() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, + DiffSuppressFunc: func(k, oldV, newV string, d *schema.ResourceData) bool { + // treat "" as "default", which is Docker's default value + if oldV == "" { + oldV = "default" + } + if newV == "" { + newV = "default" + } + return oldV == newV + }, }, "networks": { @@ -763,6 +781,7 @@ func resourceDockerContainer() *schema.Resource { Description: "IPC sharing mode for the container", Optional: true, ForceNew: true, + Computed: true, }, "group_add": { Type: schema.TypeSet, @@ -929,6 +948,16 @@ func resourceDockerContainerV1() *schema.Resource { ForceNew: true, Default: "no", ValidateFunc: validateStringMatchesPattern(`^(no|on-failure|always|unless-stopped)$`), + DiffSuppressFunc: func(k, oldV, newV string, d *schema.ResourceData) bool { + // treat "" as "no", which is Docker's default value + if oldV == "" { + oldV = "no" + } + if newV == "" { + newV = "no" + } + return oldV == newV + }, }, "max_retry_count": { @@ -936,7 +965,7 @@ func resourceDockerContainerV1() *schema.Resource { Optional: true, ForceNew: true, }, - "working_dir": &schema.Schema{ + "working_dir": { Type: schema.TypeString, Optional: true, ForceNew: true, diff --git a/docker/resource_docker_container_funcs.go b/docker/resource_docker_container_funcs.go index f73ba44b..fe43693c 100644 --- a/docker/resource_docker_container_funcs.go +++ b/docker/resource_docker_container_funcs.go @@ -193,7 +193,7 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err for _, rawTmpfsOptions := range value.([]interface{}) { rawTmpfsOptions := rawTmpfsOptions.(map[string]interface{}) if value, ok := rawTmpfsOptions["size_bytes"]; ok { - mountInstance.TmpfsOptions.SizeBytes = value.(int64) + mountInstance.TmpfsOptions.SizeBytes = (int64)(value.(int)) } if value, ok := rawTmpfsOptions["mode"]; ok { mountInstance.TmpfsOptions.Mode = os.FileMode(value.(int)) @@ -580,9 +580,104 @@ func resourceDockerContainerRead(d *schema.ResourceData, meta interface{}) error // TODO all the other attributes d.SetId(container.ID) - // d.Set("name", container.Name) // api prefixes with '/' ... - // d.Set("image", container.Image) - // d.Set("log_driver", container.HostConfig.LogConfig.Type) + d.Set("name", strings.TrimLeft(container.Name, "/")) // api prefixes with '/' ... + d.Set("rm", container.HostConfig.AutoRemove) + d.Set("read_only", container.HostConfig.ReadonlyRootfs) + // "start" can't be imported + // attach + // logs + // "must_run" can't be imported + // container_logs + d.Set("image", container.Image) + d.Set("hostname", container.Config.Hostname) + d.Set("domainname", container.Config.Domainname) + d.Set("command", container.Config.Cmd) + d.Set("entrypoint", container.Config.Entrypoint) + d.Set("user", container.Config.User) + d.Set("dns", container.HostConfig.DNS) + d.Set("dns_opts", container.HostConfig.DNSOptions) + d.Set("dns_search", container.HostConfig.DNSSearch) + d.Set("publish_all_ports", container.HostConfig.PublishAllPorts) + d.Set("restart", container.HostConfig.RestartPolicy.Name) + d.Set("max_retry_count", container.HostConfig.RestartPolicy.MaximumRetryCount) + d.Set("working_dir", container.Config.WorkingDir) + if len(container.HostConfig.CapAdd) > 0 || len(container.HostConfig.CapDrop) > 0 { + // TODO implement DiffSuppressFunc + d.Set("capabilities", []interface{}{ + map[string]interface{}{ + "add": container.HostConfig.CapAdd, + "drop": container.HostConfig.CapDrop, + }, + }) + } + d.Set("mounts", getDockerContainerMounts(container)) + // volumes + d.Set("tmpfs", container.HostConfig.Tmpfs) + d.Set("host", container.HostConfig.ExtraHosts) + ulimits := make([]interface{}, len(container.HostConfig.Ulimits)) + for i, ul := range container.HostConfig.Ulimits { + ulimits[i] = map[string]interface{}{ + "name": ul.Name, + "soft": ul.Soft, + "hard": ul.Hard, + } + } + d.Set("ulimit", ulimits) + d.Set("env", container.Config.Env) + d.Set("links", container.HostConfig.Links) + d.Set("privileged", container.HostConfig.Privileged) + devices := make([]interface{}, len(container.HostConfig.Devices)) + for i, device := range container.HostConfig.Devices { + devices[i] = map[string]interface{}{ + "host_path": device.PathOnHost, + "container_path": device.PathInContainer, + "permissions": device.CgroupPermissions, + } + } + d.Set("devices", devices) + // "destroy_grace_seconds" can't be imported + labels := make([]interface{}, len(container.Config.Labels)) + i := 0 + for k, v := range container.Config.Labels { + labels[i] = map[string]interface{}{ + "label": k, + "value": v, + } + i++ + } + d.Set("labels", labels) + d.Set("memory", container.HostConfig.Memory/1024/1024) + if container.HostConfig.MemorySwap > 0 { + d.Set("memory_swap", container.HostConfig.MemorySwap/1024/1024) + } else { + d.Set("memory_swap", container.HostConfig.MemorySwap) + } + d.Set("shm_size", container.HostConfig.ShmSize/1024/1024) + d.Set("cpu_shares", container.HostConfig.CPUShares) + d.Set("cpu_set", container.HostConfig.CpusetCpus) + d.Set("log_driver", container.HostConfig.LogConfig.Type) + d.Set("log_opts", container.HostConfig.LogConfig.Config) + // "network_alias" is deprecated + d.Set("network_mode", container.HostConfig.NetworkMode) + // networks + // networks_advanced + d.Set("pid_mode", container.HostConfig.PidMode) + d.Set("userns_mode", container.HostConfig.UsernsMode) + // "upload" can't be imported + if container.Config.Healthcheck != nil { + d.Set("healthcheck", []interface{}{ + map[string]interface{}{ + "test": container.Config.Healthcheck.Test, + "interval": container.Config.Healthcheck.Interval.String(), + "timeout": container.Config.Healthcheck.Timeout.String(), + "start_period": container.Config.Healthcheck.StartPeriod.String(), + "retries": container.Config.Healthcheck.Retries, + }, + }) + } + d.Set("sysctls", container.HostConfig.Sysctls) + d.Set("ipc_mode", container.HostConfig.IpcMode) + d.Set("group_add", container.HostConfig.GroupAdd) return nil } @@ -873,3 +968,50 @@ func deviceSetToDockerDevices(devices *schema.Set) []container.DeviceMapping { } return retDevices } + +func getDockerContainerMounts(container types.ContainerJSON) []map[string]interface{} { + mounts := []map[string]interface{}{} + for _, mount := range container.HostConfig.Mounts { + m := map[string]interface{}{ + "target": mount.Target, + "source": mount.Source, + "type": mount.Type, + "read_only": mount.ReadOnly, + } + if mount.BindOptions != nil { + m["bind_options"] = []map[string]interface{}{ + { + "propagation": mount.BindOptions.Propagation, + }, + } + } + if mount.VolumeOptions != nil { + labels := []map[string]string{} + for k, v := range mount.VolumeOptions.Labels { + labels = append(labels, map[string]string{ + "label": k, + "volume": v, + }) + } + m["volume_options"] = []map[string]interface{}{ + { + "no_copy": mount.VolumeOptions.NoCopy, + "labels": labels, + "driver_name": mount.VolumeOptions.DriverConfig.Name, + "driver_options": mount.VolumeOptions.DriverConfig.Options, + }, + } + } + if mount.TmpfsOptions != nil { + m["tmpfs_options"] = []map[string]interface{}{ + { + "size_bytes": mount.TmpfsOptions.SizeBytes, + "mode": mount.TmpfsOptions.Mode, + }, + } + } + mounts = append(mounts, m) + } + + return mounts +} diff --git a/docker/resource_docker_container_test.go b/docker/resource_docker_container_test.go index 29749b9e..82b9710f 100644 --- a/docker/resource_docker_container_test.go +++ b/docker/resource_docker_container_test.go @@ -66,21 +66,29 @@ func TestAccDockerContainer_basic(t *testing.T) { testAccContainerRunning(resourceName, &c), ), }, - // TODO mavogel: Will be done in #219 - // { - // ResourceName: resourceName, - // ImportState: true, - // ImportStateVerify: true, - // ImportStateVerifyIgnore: []string{ - // "attach", - // "log_driver", - // "logs", - // "must_run", - // "restart", - // "rm", - // "start", - // }, - // }, + { + ResourceName: "docker_container.foo", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "attach", + "log_driver", + "logs", + "must_run", + "restart", + "rm", + "start", + "container_logs", + "destroy_grace_seconds", + "upload", + + // TODO mavogel: Will be done in #219 + "volumes", + "network_alias", + "networks", + "network_advanced", + }, + }, }, }) } @@ -257,7 +265,7 @@ func TestAccDockerContainer_tmpfs(t *testing.T) { return fmt.Errorf("Incorrect number of tmpfs: expected 1, got %d", len(c.HostConfig.Tmpfs)) } - for mountPath, _ := range c.HostConfig.Tmpfs { + for mountPath := range c.HostConfig.Tmpfs { if mountPath != "/mount/tmpfs" { return fmt.Errorf("Bad destination on tmpfs: expected /mount/tmpfs, got %q", mountPath) } @@ -567,7 +575,7 @@ func TestAccDockerContainer_customized(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccContainerRunning("docker_container.foo", &c), testCheck, - testCheckLabelMap("docker_container.foo", "labels", map[string]string{"env": "prod", "role": "test"}), + testCheckLabelMap("docker_container.foo", "labels", map[string]string{"env": "prod", "role": "test", "maintainer": "NGINX Docker Maintainers "}), ), }, }, @@ -1548,10 +1556,16 @@ provider "docker" { } } +resource "docker_image" "foo" { + provider = "docker.private" + name = "%s" + keep_locally = true +} + resource "docker_container" "foo" { provider = "docker.private" name = "tf-test" - image = "%s" + image = docker_image.foo.latest } ` @@ -1686,6 +1700,10 @@ resource "docker_container" "foo" { label = "role" value = "test" } + labels { + label = "maintainer" + value = "NGINX Docker Maintainers " + } log_driver = "json-file" log_opts = { max-size = "10m" @@ -1784,6 +1802,7 @@ resource "docker_container" "foo" { devices { host_path = "/dev/zero" container_path = "/dev/zero_test" + permissions = "rwm" } } ` @@ -1920,7 +1939,7 @@ resource "docker_image" "foo" { resource "docker_container" "foo" { name = "tf-test" - image = "nginx:latest" + image = docker_image.foo.latest start = false must_run = false }