mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2025-12-18 23:06:10 -05:00
Co-authored-by: Gustavo Michels <gmichels@nanthealth.com> Co-authored-by: Martin <Junkern@users.noreply.github.com>
979 lines
24 KiB
Go
979 lines
24 KiB
Go
package provider
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"sort"
|
|
|
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
|
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
|
|
)
|
|
|
|
func resourceDockerContainerV1() *schema.Resource {
|
|
return &schema.Resource{
|
|
// This is only used for state migration, so the CRUD
|
|
// callbacks are no longer relevant
|
|
SchemaVersion: 1,
|
|
Schema: map[string]*schema.Schema{
|
|
"name": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"rm": {
|
|
Type: schema.TypeBool,
|
|
Default: false,
|
|
Optional: true,
|
|
},
|
|
|
|
"read_only": {
|
|
Type: schema.TypeBool,
|
|
Default: false,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"start": {
|
|
Type: schema.TypeBool,
|
|
Default: true,
|
|
Optional: true,
|
|
},
|
|
|
|
"attach": {
|
|
Type: schema.TypeBool,
|
|
Default: false,
|
|
Optional: true,
|
|
},
|
|
|
|
"logs": {
|
|
Type: schema.TypeBool,
|
|
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,
|
|
Default: true,
|
|
Optional: true,
|
|
},
|
|
|
|
"exit_code": {
|
|
Type: schema.TypeInt,
|
|
Computed: true,
|
|
},
|
|
|
|
"container_logs": {
|
|
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.
|
|
"image": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"hostname": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"domainname": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"command": {
|
|
Type: schema.TypeList,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Computed: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
|
|
"entrypoint": {
|
|
Type: schema.TypeList,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Computed: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
|
|
"user": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
|
|
"dns": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Computed: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
Set: schema.HashString,
|
|
},
|
|
|
|
"dns_opts": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Computed: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
Set: schema.HashString,
|
|
},
|
|
|
|
"dns_search": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Computed: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
Set: schema.HashString,
|
|
},
|
|
|
|
"publish_all_ports": {
|
|
Type: schema.TypeBool,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"restart": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Default: "no",
|
|
ValidateDiagFunc: 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": {
|
|
Type: schema.TypeInt,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
"working_dir": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
Computed: true,
|
|
ForceNew: true,
|
|
},
|
|
"remove_volumes": {
|
|
Type: schema.TypeBool,
|
|
Optional: true,
|
|
Default: true,
|
|
},
|
|
"capabilities": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
MaxItems: 1,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"add": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
Set: schema.HashString,
|
|
},
|
|
|
|
"drop": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ForceNew: 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,
|
|
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.TypeMap,
|
|
Description: "User-defined key/value metadata",
|
|
Optional: true,
|
|
},
|
|
"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},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"tmpfs_options": {
|
|
Type: schema.TypeList,
|
|
Description: "Optional configuration for the tmpfs type",
|
|
Optional: 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,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"from_container": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"container_path": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"host_path": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
ValidateDiagFunc: validateDockerContainerPath(),
|
|
},
|
|
|
|
"volume_name": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"read_only": {
|
|
Type: schema.TypeBool,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"tmpfs": {
|
|
Type: schema.TypeMap,
|
|
Optional: true,
|
|
},
|
|
"ports": {
|
|
Type: schema.TypeList,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"internal": {
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"external": {
|
|
Type: schema.TypeInt,
|
|
Optional: true,
|
|
Computed: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"ip": {
|
|
Type: schema.TypeString,
|
|
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,
|
|
Default: "tcp",
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
},
|
|
},
|
|
DiffSuppressFunc: suppressIfPortsDidNotChangeForMigrationV0ToV1(),
|
|
},
|
|
|
|
"host": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"ip": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"host": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
"ulimit": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"name": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
"soft": {
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
"hard": {
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
"env": {
|
|
Type: schema.TypeSet,
|
|
Description: "The environment variables to 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,
|
|
},
|
|
|
|
"links": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
Set: schema.HashString,
|
|
Deprecated: "The --link flag is a legacy feature of Docker. It may eventually be removed.",
|
|
},
|
|
|
|
"ip_address": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
Deprecated: "Use ip_adresses_data instead. This field exposes the data of the container's first network.",
|
|
},
|
|
|
|
"ip_prefix_length": {
|
|
Type: schema.TypeInt,
|
|
Computed: true,
|
|
Deprecated: "Use ip_prefix_length from ip_adresses_data instead. This field exposes the data of the container's first network.",
|
|
},
|
|
|
|
"gateway": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
Deprecated: "Use gateway from ip_adresses_data instead. This field exposes the data of the container's first network.",
|
|
},
|
|
|
|
"bridge": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
|
|
"network_data": {
|
|
Type: schema.TypeList,
|
|
Computed: true,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"network_name": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"ip_address": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"ip_prefix_length": {
|
|
Type: schema.TypeInt,
|
|
Computed: true,
|
|
},
|
|
"gateway": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"global_ipv6_address": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"global_ipv6_prefix_length": {
|
|
Type: schema.TypeInt,
|
|
Computed: true,
|
|
},
|
|
"ipv6_gateway": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"mac_address": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
"privileged": {
|
|
Type: schema.TypeBool,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"devices": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"host_path": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"container_path": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"permissions": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
"destroy_grace_seconds": {
|
|
Type: schema.TypeInt,
|
|
Optional: true,
|
|
},
|
|
|
|
"labels": {
|
|
Type: schema.TypeMap,
|
|
Optional: true,
|
|
Computed: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"memory": {
|
|
Type: schema.TypeInt,
|
|
Optional: true,
|
|
ValidateDiagFunc: validateIntegerGeqThan(0),
|
|
},
|
|
|
|
"memory_swap": {
|
|
Type: schema.TypeInt,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
ValidateDiagFunc: validateIntegerGeqThan(-1),
|
|
},
|
|
|
|
"shm_size": {
|
|
Type: schema.TypeInt,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
ValidateDiagFunc: validateIntegerGeqThan(0),
|
|
},
|
|
|
|
"cpu_shares": {
|
|
Type: schema.TypeInt,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
ValidateDiagFunc: validateIntegerGeqThan(0),
|
|
},
|
|
|
|
"cpu_set": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ValidateDiagFunc: validateStringMatchesPattern(`^\d+([,-]\d+)*$`),
|
|
},
|
|
|
|
"log_driver": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Default: "json-file",
|
|
},
|
|
|
|
"log_opts": {
|
|
Type: schema.TypeMap,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"network_alias": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
Set: schema.HashString,
|
|
Description: "Set an alias for the container in all specified networks",
|
|
Deprecated: "Use networks_advanced instead. Will be removed in v2.0.0",
|
|
},
|
|
|
|
"network_mode": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"networks": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
Set: schema.HashString,
|
|
Deprecated: "Use networks_advanced instead. Will be removed in v2.0.0",
|
|
},
|
|
|
|
"networks_advanced": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"name": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
"aliases": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
Set: schema.HashString,
|
|
},
|
|
"ipv4_address": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
"ipv6_address": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
"pid_mode": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
"userns_mode": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"upload": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"content": {
|
|
Type: schema.TypeString,
|
|
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,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
ValidateDiagFunc: validateStringIsBase64Encoded(),
|
|
},
|
|
"file": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
"executable": {
|
|
Type: schema.TypeBool,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
Default: false,
|
|
},
|
|
"source": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
"source_hash": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
"healthcheck": {
|
|
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": {
|
|
Type: schema.TypeList,
|
|
Description: "The test to perform as list",
|
|
Required: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
"interval": {
|
|
Type: schema.TypeString,
|
|
Description: "Time between running the check (ms|s|m|h)",
|
|
Optional: true,
|
|
Default: "0s",
|
|
ValidateDiagFunc: validateDurationGeq0(),
|
|
},
|
|
"timeout": {
|
|
Type: schema.TypeString,
|
|
Description: "Maximum time to allow one check to run (ms|s|m|h)",
|
|
Optional: true,
|
|
Default: "0s",
|
|
ValidateDiagFunc: validateDurationGeq0(),
|
|
},
|
|
"start_period": {
|
|
Type: schema.TypeString,
|
|
Description: "Start period for the container to initialize before counting retries towards unstable (ms|s|m|h)",
|
|
Optional: true,
|
|
Default: "0s",
|
|
ValidateDiagFunc: validateDurationGeq0(),
|
|
},
|
|
"retries": {
|
|
Type: schema.TypeInt,
|
|
Description: "Consecutive failures needed to report unhealthy",
|
|
Optional: true,
|
|
Default: 0,
|
|
ValidateDiagFunc: validateIntegerGeqThan(0),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
"sysctls": {
|
|
Type: schema.TypeMap,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
"ipc_mode": {
|
|
Type: schema.TypeString,
|
|
Description: "IPC sharing mode for the container",
|
|
Optional: true,
|
|
ForceNew: 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,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func resourceDockerContainerMigrateState(
|
|
v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) {
|
|
switch v {
|
|
case 0:
|
|
log.Println("[INFO] Found Docker Container State v0; migrating to v1")
|
|
return migrateDockerContainerMigrateStateV0toV1(is, meta)
|
|
default:
|
|
return is, fmt.Errorf("unexpected schema version: %d", v)
|
|
}
|
|
}
|
|
|
|
func migrateDockerContainerMigrateStateV0toV1(is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) {
|
|
if is.Empty() {
|
|
log.Println("[DEBUG] Empty InstanceState; nothing to migrate.")
|
|
return is, nil
|
|
}
|
|
|
|
log.Printf("[DEBUG] Docker Container Attributes before Migration: %#v", is.Attributes)
|
|
|
|
err := updateV0ToV1PortsOrder(is, meta)
|
|
|
|
log.Printf("[DEBUG] Docker Container Attributes after State Migration: %#v", is.Attributes)
|
|
|
|
return is, err
|
|
}
|
|
|
|
type mappedPort struct {
|
|
internal int
|
|
external int
|
|
ip string
|
|
protocol string
|
|
}
|
|
|
|
type byPort []mappedPort
|
|
|
|
func (s byPort) Len() int {
|
|
return len(s)
|
|
}
|
|
|
|
func (s byPort) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|
|
|
|
func (s byPort) Less(i, j int) bool {
|
|
return s[i].internal < s[j].internal
|
|
}
|
|
|
|
func updateV0ToV1PortsOrder(is *terraform.InstanceState, meta interface{}) error {
|
|
reader := &schema.MapFieldReader{
|
|
Schema: resourceDockerContainer().Schema,
|
|
Map: schema.BasicMapReader(is.Attributes),
|
|
}
|
|
|
|
writer := &schema.MapFieldWriter{
|
|
Schema: resourceDockerContainer().Schema,
|
|
}
|
|
|
|
result, err := reader.ReadField([]string{"ports"})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.Value == nil {
|
|
return nil
|
|
}
|
|
|
|
// map the ports into a struct, so they can be sorted easily
|
|
portsMapped := make([]mappedPort, 0)
|
|
portsRaw := result.Value.([]interface{})
|
|
for _, portRaw := range portsRaw {
|
|
if portRaw == nil {
|
|
continue
|
|
}
|
|
portTyped := portRaw.(map[string]interface{})
|
|
portMapped := mappedPort{
|
|
internal: portTyped["internal"].(int),
|
|
external: portTyped["external"].(int),
|
|
ip: portTyped["ip"].(string),
|
|
protocol: portTyped["protocol"].(string),
|
|
}
|
|
|
|
portsMapped = append(portsMapped, portMapped)
|
|
}
|
|
sort.Sort(byPort(portsMapped))
|
|
|
|
// map the sorted ports to an output structure tf can write
|
|
outputPorts := make([]interface{}, 0)
|
|
for _, mappedPort := range portsMapped {
|
|
outputPort := make(map[string]interface{})
|
|
outputPort["internal"] = mappedPort.internal
|
|
outputPort["external"] = mappedPort.external
|
|
outputPort["ip"] = mappedPort.ip
|
|
outputPort["protocol"] = mappedPort.protocol
|
|
outputPorts = append(outputPorts, outputPort)
|
|
}
|
|
|
|
// store them back to state
|
|
if err := writer.WriteField([]string{"ports"}, outputPorts); err != nil {
|
|
return err
|
|
}
|
|
for k, v := range writer.Map() {
|
|
is.Attributes[k] = v
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func replaceLabelsMapFieldWithSetField(rawState map[string]interface{}) map[string]interface{} {
|
|
if rawState == nil {
|
|
return nil
|
|
}
|
|
labelMapIFace := rawState["labels"]
|
|
if labelMapIFace != nil {
|
|
labelMap := labelMapIFace.(map[string]interface{})
|
|
rawState["labels"] = mapStringInterfaceToLabelList(labelMap)
|
|
} else {
|
|
rawState["labels"] = []interface{}{}
|
|
}
|
|
|
|
return rawState
|
|
}
|
|
|
|
func migrateContainerLabels(rawState map[string]interface{}) map[string]interface{} {
|
|
if rawState == nil {
|
|
// https://github.com/kreuzwerker/terraform-provider-docker/issues/176
|
|
// to prevent error `assignment to entry in nil map`
|
|
rawState = map[string]interface{}{}
|
|
}
|
|
replaceLabelsMapFieldWithSetField(rawState)
|
|
|
|
m, ok := rawState["mounts"]
|
|
if !ok || m == nil {
|
|
// https://github.com/terraform-providers/terraform-provider-docker/issues/264
|
|
rawState["mounts"] = []interface{}{}
|
|
return rawState
|
|
}
|
|
|
|
mounts := m.([]interface{})
|
|
newMounts := make([]interface{}, len(mounts))
|
|
for i, mountI := range mounts {
|
|
mount := mountI.(map[string]interface{})
|
|
volumeOptionsList := mount["volume_options"].([]interface{})
|
|
|
|
if len(volumeOptionsList) != 0 {
|
|
replaceLabelsMapFieldWithSetField(volumeOptionsList[0].(map[string]interface{}))
|
|
}
|
|
newMounts[i] = mount
|
|
}
|
|
rawState["mounts"] = newMounts
|
|
|
|
return rawState
|
|
}
|