terraform-provider-docker/internal/provider/resource_docker_container.go

1077 lines
36 KiB
Go

package provider
import (
"context"
"log"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func resourceDockerContainer() *schema.Resource {
return &schema.Resource{
Description: "Manages the lifecycle of a Docker container.",
CreateContext: resourceDockerContainerCreate,
ReadContext: resourceDockerContainerRead,
UpdateContext: resourceDockerContainerUpdate,
DeleteContext: resourceDockerContainerDelete,
MigrateState: resourceDockerContainerMigrateState,
SchemaVersion: 2,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
StateUpgraders: []schema.StateUpgrader{
{
Version: 1,
Type: resourceDockerContainerV1().CoreConfigSchema().ImpliedType(),
Upgrade: func(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
// TODO do the ohter V0-to-V1 migration, unless we're okay
// with breaking for users who straggled on their docker
// provider version
return migrateContainerLabels(rawState), nil
},
},
},
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Description: "The name of the container.",
Required: true,
ForceNew: true,
},
"rm": {
Type: schema.TypeBool,
Description: "If `true`, then the container will be automatically removed when it exits. Defaults to `false`.",
Default: false,
Optional: true,
},
"read_only": {
Type: schema.TypeBool,
Description: "If `true`, the container will be started as readonly. Defaults to `false`.",
Default: false,
Optional: true,
ForceNew: true,
},
"start": {
Type: schema.TypeBool,
Description: "If `true`, then the Docker container will be started after creation. If `false`, then the container is only created. Defaults to `true`.",
Default: true,
Optional: true,
},
"wait": {
Type: schema.TypeBool,
Description: "If `true`, then the Docker container is waited for being healthy state after creation. This requires your container to have a healthcheck, otherwise this provider will error. 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`.",
Default: false,
Optional: true,
},
"logs": {
Type: schema.TypeBool,
Description: "Save the container logs (`attach` must be enabled). Defaults to `false`.",
Default: false,
Optional: true,
},
// Indicates whether the container must be running.
//
// An assumption is made that configured containers
// should be running; if not, they should not be in
// the configuration. Therefore a stopped container
// should be started. Set to false to have the
// provider leave the container alone.
//
// Actively-debugged containers are likely to be
// stopped and started manually, and Docker has
// some provisions for restarting containers that
// stop. The utility here comes from the fact that
// this will delete and re-create the container
// following the principle that the containers
// should be pristine when started.
"must_run": {
Type: schema.TypeBool,
Description: "If `true`, then the Docker container will be kept running. If `false`, then as long as the container exists, Terraform assumes it is successful. Defaults to `true`.",
Default: true,
Optional: true,
},
"exit_code": {
Type: schema.TypeInt,
Description: "The exit code of the container if its execution is done (`must_run` must be disabled).",
Computed: true,
},
"container_logs": {
Type: schema.TypeString,
Description: "The logs of the container if its execution is done (`attach` must be disabled).",
Computed: true,
},
"image": {
Type: schema.TypeString,
Description: "The ID of the image to back this container. The easiest way to get this value is to use the `image_id` attribute of the `docker_image` resource as is shown in the example.",
Required: true,
ForceNew: true,
},
"hostname": {
Type: schema.TypeString,
Description: "Hostname of the container.",
Optional: true,
ForceNew: true,
Computed: true,
},
"domainname": {
Type: schema.TypeString,
Description: "Domain name of the container.",
Optional: true,
ForceNew: true,
},
"command": {
Type: schema.TypeList,
Description: "The command to use to start the container. For example, to run `/usr/bin/myprogram -f baz.conf` set the command to be `[\"/usr/bin/myprogram\",\"-f\",\"baz.conf\"]`.",
Optional: true,
ForceNew: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"entrypoint": {
Type: schema.TypeList,
Description: "The command to use as the Entrypoint for the container. The Entrypoint allows you to configure a container to run as an executable. For example, to run `/usr/bin/myprogram` when starting a container, set the entrypoint to be `\"/usr/bin/myprogram\"]`.",
Optional: true,
ForceNew: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"user": {
Type: schema.TypeString,
Description: "User used for run the first process. Format is `user` or `user:group` which user and group can be passed literraly or by name.",
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
DiffSuppressFunc: func(k, oldV, newV string, d *schema.ResourceData) bool {
// treat "" as a no-op, which is Docker's default value
if newV == "" {
newV = oldV
}
return oldV == newV
},
},
"dns": {
Type: schema.TypeSet,
Description: "DNS servers to use.",
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"dns_opts": {
Type: schema.TypeSet,
Description: "DNS options used by the DNS provider(s), see `resolv.conf` documentation for valid list of options.",
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"dns_search": {
Type: schema.TypeSet,
Description: "DNS search domains that are used when bare unqualified hostnames are used inside of the container.",
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"publish_all_ports": {
Type: schema.TypeBool,
Description: "Publish all ports of the container.",
Optional: true,
ForceNew: true,
},
"restart": {
Type: schema.TypeString,
Description: "The restart policy for the container. Must be one of 'no', 'on-failure', 'always', 'unless-stopped'. Defaults to `no`.",
Default: "no",
Optional: true,
ValidateDiagFunc: validateStringMatchesPattern(`^(no|on-failure|always|unless-stopped)$`),
},
"max_retry_count": {
Type: schema.TypeInt,
Description: "The maximum amount of times to an attempt a restart when `restart` is set to 'on-failure'.",
Optional: true,
},
"working_dir": {
Type: schema.TypeString,
Description: "The working directory for commands to run in.",
Optional: true,
ForceNew: true,
DiffSuppressFunc: func(k, oldV, newV string, d *schema.ResourceData) bool {
// treat "" as a no-op, which is Docker's default behavior
if newV == "" {
newV = oldV
}
return oldV == newV
},
},
"remove_volumes": {
Type: schema.TypeBool,
Description: "If `true`, it will remove anonymous volumes associated with the container. Defaults to `true`.",
Default: true,
Optional: true,
},
"capabilities": {
Type: schema.TypeSet,
Description: "Add or drop certrain linux capabilities.",
Optional: true,
ForceNew: true,
MaxItems: 1,
// TODO implement DiffSuppressFunc
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"add": {
Type: schema.TypeSet,
Description: "List of linux capabilities to add.",
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"drop": {
Type: schema.TypeSet,
Description: "List of linux capabilities to drop.",
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
},
},
"security_opts": {
Type: schema.TypeSet,
Description: "List of string values to customize labels for MLS systems, such as SELinux. See https://docs.docker.com/engine/reference/run/#security-configuration.",
Optional: true,
ForceNew: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"runtime": {
Type: schema.TypeString,
Description: "Runtime to use for the container.",
Optional: true,
ForceNew: true,
Computed: true,
},
"stop_signal": {
Type: schema.TypeString,
Description: "Signal to stop a container (default `SIGTERM`).",
Optional: true,
ForceNew: true,
Computed: true,
},
"stop_timeout": {
Type: schema.TypeInt,
Description: "Timeout (in seconds) to stop a container.",
Optional: true,
ForceNew: true,
Computed: true,
},
"mounts": {
Type: schema.TypeSet,
Description: "Specification for mounts to be added to containers created as part of the service.",
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"target": {
Type: schema.TypeString,
Description: "Container path",
Required: true,
},
"source": {
Type: schema.TypeString,
Description: "Mount source (e.g. a volume name, a host path).",
Optional: true,
},
"type": {
Type: schema.TypeString,
Description: "The mount type",
Required: true,
ValidateDiagFunc: validateStringMatchesPattern(`^(bind|volume|tmpfs)$`),
},
"read_only": {
Type: schema.TypeBool,
Description: "Whether the mount should be read-only.",
Optional: true,
},
"bind_options": {
Type: schema.TypeList,
Description: "Optional configuration for the bind type.",
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"propagation": {
Type: schema.TypeString,
Description: "A propagation mode with the value.",
Optional: true,
ValidateDiagFunc: validateStringMatchesPattern(`^(private|rprivate|shared|rshared|slave|rslave)$`),
},
},
},
},
"volume_options": {
Type: schema.TypeList,
Description: "Optional configuration for the volume type.",
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"no_copy": {
Type: schema.TypeBool,
Description: "Populate volume with data from the target.",
Optional: true,
},
"labels": {
Type: schema.TypeSet,
Description: "User-defined key/value metadata.",
Optional: true,
Elem: labelSchema,
},
"driver_name": {
Type: schema.TypeString,
Description: "Name of the driver to use to create the volume.",
Optional: true,
},
"driver_options": {
Type: schema.TypeMap,
Description: "key/value map of driver specific options.",
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"subpath": {
Type: schema.TypeString,
Description: "Path within the volume to mount. Requires docker server version 1.45 or higher.",
Optional: true,
ForceNew: true,
},
},
},
},
"tmpfs_options": {
Type: schema.TypeList,
Description: "Optional configuration for the tmpfs type.",
Optional: true,
ForceNew: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"size_bytes": {
Type: schema.TypeInt,
Description: "The size for the tmpfs mount in bytes.",
Optional: true,
},
"mode": {
Type: schema.TypeInt,
Description: "The permission mode for the tmpfs mount in an integer.",
Optional: true,
},
},
},
},
},
},
},
"volumes": {
Type: schema.TypeSet,
Description: "Spec for mounting volumes in the container.",
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"from_container": {
Type: schema.TypeString,
Description: "The container where the volume is coming from.",
Optional: true,
ForceNew: true,
},
"container_path": {
Type: schema.TypeString,
Description: "The path in the container where the volume will be mounted.",
Optional: true,
ForceNew: true,
},
"host_path": {
Type: schema.TypeString,
Description: "The path on the host where the volume is coming from.",
Optional: true,
ForceNew: true,
ValidateDiagFunc: validateDockerContainerPath(),
},
"volume_name": {
Type: schema.TypeString,
Description: "The name of the docker volume which should be mounted.",
Optional: true,
ForceNew: true,
},
"read_only": {
Type: schema.TypeBool,
Description: "If `true`, this volume will be readonly. Defaults to `false`.",
Optional: true,
ForceNew: true,
},
},
},
},
"tmpfs": {
Type: schema.TypeMap,
Description: "A map of container directories which should be replaced by `tmpfs mounts`, and their corresponding mount options.",
Optional: true,
},
"ports": {
Type: schema.TypeList,
Description: "Publish a container's port(s) to the host.",
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"internal": {
Type: schema.TypeInt,
Description: "Port within the container.",
Required: true,
ForceNew: true,
},
"external": {
Type: schema.TypeInt,
Description: "Port exposed out of the container. If not given a free random port `>= 32768` will be used.",
Optional: true,
Computed: true,
ForceNew: true,
},
"ip": {
Type: schema.TypeString,
Description: "IP address/mask that can access this port. Defaults to `0.0.0.0`.",
Default: "0.0.0.0",
Optional: true,
ForceNew: true,
StateFunc: func(val interface{}) string {
// Empty IP assignments default to 0.0.0.0
if val.(string) == "" {
return "0.0.0.0"
}
return val.(string)
},
},
"protocol": {
Type: schema.TypeString,
Description: "Protocol that can be used over this port. Defaults to `tcp`.",
Default: "tcp",
Optional: true,
ForceNew: true,
},
},
},
DiffSuppressFunc: suppressIfPortsDidNotChangeForMigrationV0ToV1(),
},
"host": {
Type: schema.TypeSet,
Description: "Additional hosts to add to the container.",
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"ip": {
Type: schema.TypeString,
Description: "IP address this hostname should resolve to.",
Required: true,
ForceNew: true,
},
"host": {
Type: schema.TypeString,
Description: "Hostname to add",
Required: true,
ForceNew: true,
},
},
},
},
"ulimit": {
Type: schema.TypeSet,
Description: "Ulimit options to add.",
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Description: "The name of the ulimit",
Required: true,
ForceNew: true,
},
"soft": {
Type: schema.TypeInt,
Description: "The soft limit",
Required: true,
ForceNew: true,
},
"hard": {
Type: schema.TypeInt,
Description: "The hard limit",
Required: true,
ForceNew: true,
},
},
},
},
"env": {
Type: schema.TypeSet,
Description: "Environment variables to set in the form of `KEY=VALUE`, e.g. `DEBUG=0`",
Optional: true,
ForceNew: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"bridge": {
Type: schema.TypeString,
Description: "The network bridge of the container as read from its NetworkSettings.",
Computed: true,
},
"network_data": {
Type: schema.TypeList,
Description: "The data of the networks the container is connected to.",
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"network_name": {
Type: schema.TypeString,
Description: "The name of the network",
Computed: true,
},
"ip_address": {
Type: schema.TypeString,
Description: "The IP address of the container.",
Computed: true,
},
"ip_prefix_length": {
Type: schema.TypeInt,
Description: "The IP prefix length of the container.",
Computed: true,
},
"gateway": {
Type: schema.TypeString,
Description: "The network gateway of the container.",
Computed: true,
},
"global_ipv6_address": {
Type: schema.TypeString,
Description: "The IPV6 address of the container.",
Computed: true,
},
"global_ipv6_prefix_length": {
Type: schema.TypeInt,
Description: "The IPV6 prefix length address of the container.",
Computed: true,
},
"ipv6_gateway": {
Type: schema.TypeString,
Description: "The IPV6 gateway of the container.",
Computed: true,
},
"mac_address": {
Type: schema.TypeString,
Description: "The MAC address of the container.",
Computed: true,
},
},
},
},
"privileged": {
Type: schema.TypeBool,
Description: "If `true`, the container runs in privileged mode.",
Optional: true,
ForceNew: true,
},
"devices": {
Type: schema.TypeSet,
Description: "Bind devices to the container.",
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"host_path": {
Type: schema.TypeString,
Description: "The path on the host where the device is located.",
Required: true,
ForceNew: true,
},
"container_path": {
Type: schema.TypeString,
Description: "The path in the container where the device will be bound.",
Optional: true,
ForceNew: true,
},
"permissions": {
Type: schema.TypeString,
Description: "The cgroup permissions given to the container to access the device. Defaults to `rwm`.",
Optional: true,
ForceNew: true,
},
},
},
},
"destroy_grace_seconds": {
Type: schema.TypeInt,
Description: "If defined will attempt to stop the container before destroying. Container will be destroyed after `n` seconds or on successful stop.",
Optional: true,
},
"labels": {
Type: schema.TypeSet,
Description: "User-defined key/value metadata",
Optional: true,
ForceNew: true,
Computed: true,
Elem: labelSchema,
},
"memory": {
Type: schema.TypeInt,
Description: "The memory limit for the container in MBs.",
Optional: true,
ValidateDiagFunc: validateIntegerGeqThan(0),
},
"memory_reservation": {
Type: schema.TypeInt,
Description: "The memory-resveration for the container in MBs. Defaults to 0. Allows you to specify a soft limit smaller than `memory` which is activated when Docker detects contention or low memory on the host machine. If you use `memory-reservation`, it must be set lower than `memory` for it to take precedence. Because it is a soft limit, it doesn't guarantee that the container doesn't exceed the limit.",
Optional: true,
ValidateDiagFunc: validateIntegerGeqThan(0),
Default: 0,
},
"memory_swap": {
Type: schema.TypeInt,
Description: "The total memory limit (memory + swap) for the container in MBs. This setting may compute to `-1` after `terraform apply` if the target host doesn't support memory swap, when that is the case docker will use a soft limitation.",
Optional: true,
ValidateDiagFunc: validateIntegerGeqThan(-1),
},
"shm_size": {
Type: schema.TypeInt,
Description: "Size of `/dev/shm` in MBs.",
Optional: true,
ForceNew: true,
Computed: true,
ValidateDiagFunc: validateIntegerGeqThan(0),
},
"cpu_shares": {
Type: schema.TypeInt,
Description: "CPU shares (relative weight) for the container.",
Optional: true,
ValidateDiagFunc: validateIntegerGeqThan(0),
},
"cpu_set": {
Type: schema.TypeString,
Description: "A comma-separated list or hyphen-separated range of CPUs a container can use, e.g. `0-1`.",
Optional: true,
ValidateDiagFunc: validateStringMatchesPattern(`^\d+([,-]\d+)*$`),
},
"log_driver": {
Type: schema.TypeString,
Description: "The logging driver to use for the container.",
Computed: true,
Optional: true,
ForceNew: true,
},
"log_opts": {
Type: schema.TypeMap,
Description: "Key/value pairs to use as options for the logging driver.",
Optional: true,
ForceNew: true,
},
"network_mode": {
Type: schema.TypeString,
Description: "Network mode of the container. Defaults to `bridge`. If your host OS is any other OS, you need to set this value explicitly, e.g. `nat` when your container will be running on an Windows host. See https://docs.docker.com/engine/network/ for more information.",
Optional: true,
ForceNew: true,
Default: "bridge",
},
"networks_advanced": {
Type: schema.TypeSet,
Description: "The networks the container is attached to",
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Description: "The name or id of the network to use. You can use `name` or `id` attribute from a `docker_network` resource.",
Required: true,
ForceNew: true,
},
"aliases": {
Type: schema.TypeSet,
Description: "The network aliases of the container in the specific network.",
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"ipv4_address": {
Type: schema.TypeString,
Description: "The IPV4 address of the container in the specific network.",
Optional: true,
ForceNew: true,
},
"ipv6_address": {
Type: schema.TypeString,
Description: "The IPV6 address of the container in the specific network.",
Optional: true,
ForceNew: true,
},
"mac_address": {
Type: schema.TypeString,
Description: "The MAC address of the container in the specific network.",
Optional: true,
ForceNew: true,
},
},
},
},
"pid_mode": {
Type: schema.TypeString,
Description: "he PID (Process) Namespace mode for the container. Either `container:<name|id>` or `host`.",
Optional: true,
ForceNew: true,
},
"userns_mode": {
Type: schema.TypeString,
Description: "Sets the usernamespace mode for the container when usernamespace remapping option is enabled.",
Optional: true,
ForceNew: true,
},
"upload": {
Type: schema.TypeSet,
Description: "Specifies files to upload to the container before starting it. Only one of `content` or `content_base64` can be set and at least one of them has to be set.",
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"content": {
Type: schema.TypeString,
Description: "Literal string value to use as the object content, which will be uploaded as UTF-8-encoded text. Conflicts with `content_base64` & `source`",
Optional: true,
// This is intentional. The container is mutated once, and never updated later.
// New configuration forces a new deployment, even with the same binaries.
ForceNew: true,
},
"content_base64": {
Type: schema.TypeString,
Description: "Base64-encoded data that will be decoded and uploaded as raw bytes for the object content. This allows safely uploading non-UTF8 binary data, but is recommended only for larger binary content such as the result of the `base64encode` interpolation function. See [here](https://github.com/terraform-providers/terraform-provider-docker/issues/48#issuecomment-374174588) for the reason. Conflicts with `content` & `source`",
Optional: true,
ForceNew: true,
ValidateDiagFunc: validateStringIsBase64Encoded(),
},
"file": {
Type: schema.TypeString,
Description: "Path to the file in the container where is upload goes to",
Required: true,
ForceNew: true,
},
"executable": {
Type: schema.TypeBool,
Description: "If `true`, the file will be uploaded with user executable permission. Defaults to `false`.",
Default: false,
Optional: true,
ForceNew: true,
},
"permissions": {
Type: schema.TypeString,
Description: "The permission mode for the file in the container. Has precedence over `executable`.",
Optional: true,
ForceNew: true,
ValidateDiagFunc: validateStringMatchesPattern(`^0[0-7]{3}$`),
},
"source": {
Type: schema.TypeString,
Description: "A filename that references a file which will be uploaded as the object content. This allows for large file uploads that do not get stored in state. Conflicts with `content` & `content_base64`",
Optional: true,
ForceNew: true,
},
"source_hash": {
Type: schema.TypeString,
Description: "If using `source`, this will force an update if the file content has updated but the filename has not. ",
Optional: true,
ForceNew: true,
},
},
},
},
"healthcheck": {
Type: schema.TypeList,
Description: "A test to perform to check that the container is healthy",
MaxItems: 1,
Optional: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"test": {
Type: schema.TypeList,
Description: "Command to run to check health. For example, to run `curl -f localhost/health` set the command to be `[\"CMD\", \"curl\", \"-f\", \"localhost/health\"]`.",
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"interval": {
Type: schema.TypeString,
Description: "Time between running the check (ms|s|m|h). Defaults to `0s`.",
Default: "0s",
Optional: true,
ValidateDiagFunc: validateDurationGeq0(),
},
"timeout": {
Type: schema.TypeString,
Description: "Maximum time to allow one check to run (ms|s|m|h). Defaults to `0s`.",
Default: "0s",
Optional: true,
ValidateDiagFunc: validateDurationGeq0(),
},
"start_period": {
Type: schema.TypeString,
Description: "Start period for the container to initialize before counting retries towards unstable (ms|s|m|h). Defaults to `0s`.",
Default: "0s",
Optional: true,
ValidateDiagFunc: validateDurationGeq0(),
},
"start_interval": {
Type: schema.TypeString,
Description: "Interval before the healthcheck starts (ms|s|m|h). Defaults to `0s`.",
Default: "0s",
Optional: true,
ValidateDiagFunc: validateDurationGeq0(),
},
"retries": {
Type: schema.TypeInt,
Description: "Consecutive failures needed to report unhealthy. Defaults to `0`.",
Default: 0,
Optional: true,
ValidateDiagFunc: validateIntegerGeqThan(0),
},
},
},
},
"container_read_refresh_timeout_milliseconds": {
Type: schema.TypeInt,
Description: "The total number of milliseconds to wait for the container to reach status 'running'",
Optional: true,
Default: containerReadRefreshTimeoutMillisecondsDefault,
},
"sysctls": {
Type: schema.TypeMap,
Description: "A map of kernel parameters (sysctls) to set in the container.",
Optional: true,
ForceNew: true,
},
"ipc_mode": {
Type: schema.TypeString,
Description: "IPC sharing mode for the container. Possible values are: `none`, `private`, `shareable`, `container:<name|id>` or `host`.",
Optional: true,
ForceNew: true,
Computed: true,
},
"group_add": {
Type: schema.TypeSet,
Description: "Additional groups for the container user",
Optional: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"init": {
Type: schema.TypeBool,
Description: "Configured whether an init process should be injected for this container. If unset this will default to the `dockerd` defaults.",
Optional: true,
Computed: true,
},
"tty": {
Type: schema.TypeBool,
Description: "If `true`, allocate a pseudo-tty (`docker run -t`). Defaults to `false`.",
Default: false,
Optional: true,
ForceNew: true,
},
"stdin_open": {
Type: schema.TypeBool,
Description: "If `true`, keep STDIN open even if not attached (`docker run -i`). Defaults to `false`.",
Default: false,
Optional: true,
ForceNew: true,
},
"storage_opts": {
Type: schema.TypeMap,
Description: "Key/value pairs for the storage driver options, e.g. `size`: `120G`",
Optional: true,
ForceNew: true,
},
"gpus": {
Type: schema.TypeString,
Description: "GPU devices to add to the container. Currently, only the value `all` is supported. Passing any other value will result in unexpected behavior.",
Optional: true,
ForceNew: true,
},
"cpus": {
Type: schema.TypeString,
Description: "Specify how much of the available CPU resources a container can use. e.g a value of 1.5 means the container is guaranteed at most one and a half of the CPUs. Has precedence over `cpu_period` and `cpu_quota`.",
Optional: true,
ForceNew: true,
},
"cpu_period": {
Type: schema.TypeInt,
Description: "Specify the CPU CFS scheduler period (in microseconds), which is used alongside `cpu-quota`. Is ignored if `cpus` is set.",
Optional: true,
ValidateDiagFunc: validateIntegerGeqThan(0),
},
"cpu_quota": {
Type: schema.TypeInt,
Description: "Impose a CPU CFS quota on the container (in microseconds). The number of microseconds per `cpu-period` that the container is limited to before throttled. Is ignored if `cpus` is set.",
Optional: true,
ValidateDiagFunc: validateIntegerGeqThan(0),
},
"cgroupns_mode": {
Type: schema.TypeString,
Description: "Cgroup namespace mode to use for the container. Possible values are: `private`, `host`.",
Optional: true,
ForceNew: true,
},
"cgroup_parent": {
Type: schema.TypeString,
Description: "Optional parent cgroup for the container",
Optional: true,
ForceNew: true,
},
},
}
}
func suppressIfPortsDidNotChangeForMigrationV0ToV1() schema.SchemaDiffSuppressFunc {
return func(k, old, new string, d *schema.ResourceData) bool {
if k == "ports.#" && old != new {
log.Printf("[DEBUG] suppress diff ports: old and new don't have the same length")
return false
}
portsOldRaw, portsNewRaw := d.GetChange("ports")
portsOld := portsOldRaw.([]interface{})
portsNew := portsNewRaw.([]interface{})
if len(portsOld) != len(portsNew) {
log.Printf("[DEBUG] suppress diff ports: old and new don't have the same length")
return false
}
log.Printf("[DEBUG] suppress diff ports: old and new have same length")
for _, portOld := range portsOld {
portOldMapped := portOld.(map[string]interface{})
oldInternalPort := portOldMapped["internal"]
portFound := false
for _, portNew := range portsNew {
portNewMapped := portNew.(map[string]interface{})
newInternalPort := portNewMapped["internal"]
// port is still there in new
if newInternalPort == oldInternalPort {
log.Printf("[DEBUG] suppress diff ports: comparing port '%v'", oldInternalPort)
if portNewMapped["protocol"] != portOldMapped["protocol"] {
if containsPortWithProtocol(portsNew, portOldMapped["internal"], portOldMapped["protocol"]) {
log.Printf("[DEBUG] suppress diff ports: found another port in new list with the same protocol for '%v", oldInternalPort)
continue
}
log.Printf("[DEBUG] suppress diff ports: 'protocol' changed for '%v'", oldInternalPort)
return false
}
if portNewMapped["external"] != portOldMapped["external"] {
log.Printf("[DEBUG] suppress diff ports: 'external' changed for '%v'", oldInternalPort)
return false
}
if portNewMapped["ip"] != portOldMapped["ip"] {
log.Printf("[DEBUG] suppress diff ports: 'ip' changed for '%v'", oldInternalPort)
return false
}
portFound = true
break
}
}
// port was deleted or exchanges in new
if !portFound {
log.Printf("[DEBUG] suppress diff ports: port was deleted '%v'", oldInternalPort)
return false
}
}
return true
}
}
func containsPortWithProtocol(ports []interface{}, searchInternalPort, searchProtocol interface{}) bool {
for _, port := range ports {
portMapped := port.(map[string]interface{})
internalPort := portMapped["internal"]
protocol := portMapped["protocol"]
if internalPort == searchInternalPort && protocol == searchProtocol {
return true
}
}
return false
}