2015-02-17 11:28:33 -05:00
|
|
|
package docker
|
|
|
|
|
|
|
|
|
|
import (
|
2016-12-05 06:06:34 -05:00
|
|
|
"archive/tar"
|
2018-10-28 13:41:10 -04:00
|
|
|
"bufio"
|
2016-12-05 06:06:34 -05:00
|
|
|
"bytes"
|
2020-12-02 06:06:39 -05:00
|
|
|
"context"
|
2019-10-07 17:00:57 -04:00
|
|
|
"encoding/base64"
|
2018-10-09 16:32:26 -04:00
|
|
|
"encoding/json"
|
2015-02-17 11:28:33 -05:00
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
2020-02-03 15:44:46 -05:00
|
|
|
"io/ioutil"
|
2018-10-09 16:32:26 -04:00
|
|
|
"log"
|
2019-05-26 05:59:29 -04:00
|
|
|
"os"
|
2018-10-26 01:24:22 -04:00
|
|
|
"sort"
|
2015-02-17 11:28:33 -05:00
|
|
|
"strconv"
|
2018-10-09 16:32:26 -04:00
|
|
|
"strings"
|
2015-06-12 14:44:37 -04:00
|
|
|
"time"
|
2015-02-17 11:28:33 -05:00
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
|
"github.com/docker/docker/api/types/container"
|
2019-05-26 05:59:29 -04:00
|
|
|
"github.com/docker/docker/api/types/mount"
|
2018-07-03 11:30:53 -04:00
|
|
|
"github.com/docker/docker/api/types/network"
|
|
|
|
|
"github.com/docker/docker/client"
|
|
|
|
|
"github.com/docker/go-connections/nat"
|
|
|
|
|
"github.com/docker/go-units"
|
2019-10-09 14:25:38 -04:00
|
|
|
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
|
2015-02-17 11:28:33 -05:00
|
|
|
)
|
|
|
|
|
|
2020-12-02 06:06:39 -05:00
|
|
|
var creationTime time.Time
|
2015-06-25 10:38:56 -04:00
|
|
|
|
2015-02-17 11:28:33 -05:00
|
|
|
func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) error {
|
2015-03-28 21:37:20 -04:00
|
|
|
var err error
|
2017-11-21 04:14:07 -05:00
|
|
|
client := meta.(*ProviderConfig).DockerClient
|
2019-05-26 05:42:53 -04:00
|
|
|
authConfigs := meta.(*ProviderConfig).AuthConfigs
|
2015-02-17 11:28:33 -05:00
|
|
|
image := d.Get("image").(string)
|
2019-05-26 05:42:53 -04:00
|
|
|
_, err = findImage(image, client, authConfigs)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Unable to create container with image %s: %s", image, err)
|
2015-02-17 11:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
config := &container.Config{
|
2018-10-28 13:41:10 -04:00
|
|
|
Image: image,
|
|
|
|
|
Hostname: d.Get("hostname").(string),
|
|
|
|
|
Domainname: d.Get("domainname").(string),
|
2021-01-18 03:00:26 -05:00
|
|
|
Tty: d.Get("tty").(bool),
|
|
|
|
|
OpenStdin: d.Get("stdin_open").(bool),
|
2015-02-17 11:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("env"); ok {
|
2018-07-03 11:30:53 -04:00
|
|
|
config.Env = stringSetToStringSlice(v.(*schema.Set))
|
2015-02-17 11:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("command"); ok {
|
2018-07-03 11:30:53 -04:00
|
|
|
config.Cmd = stringListToStringSlice(v.([]interface{}))
|
|
|
|
|
for _, v := range config.Cmd {
|
2016-04-29 19:42:24 -04:00
|
|
|
if v == "" {
|
|
|
|
|
return fmt.Errorf("values for command may not be empty")
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-02-17 11:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2015-10-26 17:24:48 -04:00
|
|
|
if v, ok := d.GetOk("entrypoint"); ok {
|
2018-07-03 11:30:53 -04:00
|
|
|
config.Entrypoint = stringListToStringSlice(v.([]interface{}))
|
2015-10-26 17:24:48 -04:00
|
|
|
}
|
|
|
|
|
|
2016-04-04 22:43:59 -04:00
|
|
|
if v, ok := d.GetOk("user"); ok {
|
2018-07-03 11:30:53 -04:00
|
|
|
config.User = v.(string)
|
2016-04-04 22:43:59 -04:00
|
|
|
}
|
|
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
exposedPorts := map[nat.Port]struct{}{}
|
|
|
|
|
portBindings := map[nat.Port][]nat.PortBinding{}
|
2015-02-17 11:28:33 -05:00
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("ports"); ok {
|
2018-10-16 12:49:57 -04:00
|
|
|
exposedPorts, portBindings = portSetToDockerPorts(v.([]interface{}))
|
2015-02-17 11:28:33 -05:00
|
|
|
}
|
|
|
|
|
if len(exposedPorts) != 0 {
|
2018-07-03 11:30:53 -04:00
|
|
|
config.ExposedPorts = exposedPorts
|
2015-02-17 11:28:33 -05:00
|
|
|
}
|
2019-09-23 13:41:20 -04:00
|
|
|
if v, ok := d.GetOk("working_dir"); ok {
|
|
|
|
|
config.WorkingDir = v.(string)
|
|
|
|
|
}
|
2015-10-09 09:05:43 -04:00
|
|
|
extraHosts := []string{}
|
2016-01-14 21:59:07 -05:00
|
|
|
if v, ok := d.GetOk("host"); ok {
|
2015-10-09 09:05:43 -04:00
|
|
|
extraHosts = extraHostsSetToDockerExtraHosts(v.(*schema.Set))
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
extraUlimits := []*units.Ulimit{}
|
2018-04-20 05:35:49 -04:00
|
|
|
if v, ok := d.GetOk("ulimit"); ok {
|
|
|
|
|
extraUlimits = ulimitsToDockerUlimits(v.(*schema.Set))
|
|
|
|
|
}
|
2015-02-17 11:28:33 -05:00
|
|
|
volumes := map[string]struct{}{}
|
|
|
|
|
binds := []string{}
|
|
|
|
|
volumesFrom := []string{}
|
|
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("volumes"); ok {
|
|
|
|
|
volumes, binds, volumesFrom, err = volumeSetToDockerVolumes(v.(*schema.Set))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Unable to parse volumes: %s", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if len(volumes) != 0 {
|
2018-07-03 11:30:53 -04:00
|
|
|
config.Volumes = volumes
|
2015-02-17 11:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2015-11-03 15:20:58 -05:00
|
|
|
if v, ok := d.GetOk("labels"); ok {
|
2019-11-10 15:15:42 -05:00
|
|
|
config.Labels = labelSetToMap(v.(*schema.Set))
|
2015-11-03 15:20:58 -05:00
|
|
|
}
|
|
|
|
|
|
2018-10-08 09:02:13 -04:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-26 05:59:29 -04:00
|
|
|
mounts := []mount.Mount{}
|
|
|
|
|
|
|
|
|
|
if value, ok := d.GetOk("mounts"); ok {
|
|
|
|
|
for _, rawMount := range value.(*schema.Set).List() {
|
|
|
|
|
rawMount := rawMount.(map[string]interface{})
|
|
|
|
|
mountType := mount.Type(rawMount["type"].(string))
|
|
|
|
|
mountInstance := mount.Mount{
|
|
|
|
|
Type: mountType,
|
|
|
|
|
Target: rawMount["target"].(string),
|
|
|
|
|
Source: rawMount["source"].(string),
|
|
|
|
|
}
|
|
|
|
|
if value, ok := rawMount["read_only"]; ok {
|
|
|
|
|
mountInstance.ReadOnly = value.(bool)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if mountType == mount.TypeBind {
|
|
|
|
|
if value, ok := rawMount["bind_options"]; ok {
|
|
|
|
|
if len(value.([]interface{})) > 0 {
|
|
|
|
|
mountInstance.BindOptions = &mount.BindOptions{}
|
|
|
|
|
for _, rawBindOptions := range value.([]interface{}) {
|
|
|
|
|
rawBindOptions := rawBindOptions.(map[string]interface{})
|
|
|
|
|
if value, ok := rawBindOptions["propagation"]; ok {
|
|
|
|
|
mountInstance.BindOptions.Propagation = mount.Propagation(value.(string))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if mountType == mount.TypeVolume {
|
|
|
|
|
if value, ok := rawMount["volume_options"]; ok {
|
|
|
|
|
if len(value.([]interface{})) > 0 {
|
|
|
|
|
mountInstance.VolumeOptions = &mount.VolumeOptions{}
|
|
|
|
|
for _, rawVolumeOptions := range value.([]interface{}) {
|
|
|
|
|
rawVolumeOptions := rawVolumeOptions.(map[string]interface{})
|
|
|
|
|
if value, ok := rawVolumeOptions["no_copy"]; ok {
|
|
|
|
|
mountInstance.VolumeOptions.NoCopy = value.(bool)
|
|
|
|
|
}
|
|
|
|
|
if value, ok := rawVolumeOptions["labels"]; ok {
|
2019-11-10 15:15:42 -05:00
|
|
|
mountInstance.VolumeOptions.Labels = labelSetToMap(value.(*schema.Set))
|
2019-05-26 05:59:29 -04:00
|
|
|
}
|
|
|
|
|
// because it is not possible to nest maps
|
|
|
|
|
if value, ok := rawVolumeOptions["driver_name"]; ok {
|
|
|
|
|
if mountInstance.VolumeOptions.DriverConfig == nil {
|
|
|
|
|
mountInstance.VolumeOptions.DriverConfig = &mount.Driver{}
|
|
|
|
|
}
|
|
|
|
|
mountInstance.VolumeOptions.DriverConfig.Name = value.(string)
|
|
|
|
|
}
|
|
|
|
|
if value, ok := rawVolumeOptions["driver_options"]; ok {
|
|
|
|
|
if mountInstance.VolumeOptions.DriverConfig == nil {
|
|
|
|
|
mountInstance.VolumeOptions.DriverConfig = &mount.Driver{}
|
|
|
|
|
}
|
|
|
|
|
mountInstance.VolumeOptions.DriverConfig.Options = mapTypeMapValsToString(value.(map[string]interface{}))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if mountType == mount.TypeTmpfs {
|
|
|
|
|
if value, ok := rawMount["tmpfs_options"]; ok {
|
|
|
|
|
if len(value.([]interface{})) > 0 {
|
|
|
|
|
mountInstance.TmpfsOptions = &mount.TmpfsOptions{}
|
|
|
|
|
for _, rawTmpfsOptions := range value.([]interface{}) {
|
|
|
|
|
rawTmpfsOptions := rawTmpfsOptions.(map[string]interface{})
|
|
|
|
|
if value, ok := rawTmpfsOptions["size_bytes"]; ok {
|
2020-02-01 10:15:36 -05:00
|
|
|
mountInstance.TmpfsOptions.SizeBytes = (int64)(value.(int))
|
2019-05-26 05:59:29 -04:00
|
|
|
}
|
|
|
|
|
if value, ok := rawTmpfsOptions["mode"]; ok {
|
|
|
|
|
mountInstance.TmpfsOptions.Mode = os.FileMode(value.(int))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mounts = append(mounts, mountInstance)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
hostConfig := &container.HostConfig{
|
2015-06-24 01:31:24 -04:00
|
|
|
Privileged: d.Get("privileged").(bool),
|
2015-02-17 11:28:33 -05:00
|
|
|
PublishAllPorts: d.Get("publish_all_ports").(bool),
|
2018-07-03 11:30:53 -04:00
|
|
|
RestartPolicy: container.RestartPolicy{
|
2015-10-27 12:08:57 -04:00
|
|
|
Name: d.Get("restart").(string),
|
|
|
|
|
MaximumRetryCount: d.Get("max_retry_count").(int),
|
|
|
|
|
},
|
2019-10-25 06:14:09 -04:00
|
|
|
Mounts: mounts,
|
|
|
|
|
AutoRemove: d.Get("rm").(bool),
|
|
|
|
|
ReadonlyRootfs: d.Get("read_only").(bool),
|
2018-07-03 11:30:53 -04:00
|
|
|
LogConfig: container.LogConfig{
|
2015-11-04 12:42:55 -05:00
|
|
|
Type: d.Get("log_driver").(string),
|
|
|
|
|
},
|
2015-02-17 11:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2019-05-26 05:59:29 -04:00
|
|
|
if v, ok := d.GetOk("tmpfs"); ok {
|
|
|
|
|
hostConfig.Tmpfs = mapTypeMapValsToString(v.(map[string]interface{}))
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-17 11:28:33 -05:00
|
|
|
if len(portBindings) != 0 {
|
|
|
|
|
hostConfig.PortBindings = portBindings
|
|
|
|
|
}
|
2015-10-09 09:05:43 -04:00
|
|
|
if len(extraHosts) != 0 {
|
|
|
|
|
hostConfig.ExtraHosts = extraHosts
|
|
|
|
|
}
|
2015-02-17 11:28:33 -05:00
|
|
|
if len(binds) != 0 {
|
|
|
|
|
hostConfig.Binds = binds
|
|
|
|
|
}
|
|
|
|
|
if len(volumesFrom) != 0 {
|
|
|
|
|
hostConfig.VolumesFrom = volumesFrom
|
|
|
|
|
}
|
2018-04-20 05:35:49 -04:00
|
|
|
if len(extraUlimits) != 0 {
|
|
|
|
|
hostConfig.Ulimits = extraUlimits
|
|
|
|
|
}
|
2015-02-17 11:28:33 -05:00
|
|
|
|
2017-03-07 11:48:20 -05:00
|
|
|
if v, ok := d.GetOk("capabilities"); ok {
|
|
|
|
|
for _, capInt := range v.(*schema.Set).List() {
|
|
|
|
|
capa := capInt.(map[string]interface{})
|
|
|
|
|
hostConfig.CapAdd = stringSetToStringSlice(capa["add"].(*schema.Set))
|
|
|
|
|
hostConfig.CapDrop = stringSetToStringSlice(capa["drop"].(*schema.Set))
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-20 05:14:44 -04:00
|
|
|
if v, ok := d.GetOk("devices"); ok {
|
|
|
|
|
hostConfig.Devices = deviceSetToDockerDevices(v.(*schema.Set))
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-17 11:28:33 -05:00
|
|
|
if v, ok := d.GetOk("dns"); ok {
|
|
|
|
|
hostConfig.DNS = stringSetToStringSlice(v.(*schema.Set))
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-29 08:38:46 -04:00
|
|
|
if v, ok := d.GetOk("dns_opts"); ok {
|
|
|
|
|
hostConfig.DNSOptions = stringSetToStringSlice(v.(*schema.Set))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("dns_search"); ok {
|
|
|
|
|
hostConfig.DNSSearch = stringSetToStringSlice(v.(*schema.Set))
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-16 15:42:21 -04:00
|
|
|
if v, ok := d.GetOk("links"); ok {
|
|
|
|
|
hostConfig.Links = stringSetToStringSlice(v.(*schema.Set))
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-15 12:19:19 -05:00
|
|
|
if v, ok := d.GetOk("security_opts"); ok {
|
|
|
|
|
hostConfig.SecurityOpt = stringSetToStringSlice(v.(*schema.Set))
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-27 19:53:49 -04:00
|
|
|
if v, ok := d.GetOk("memory"); ok {
|
2015-11-09 19:36:23 -05:00
|
|
|
hostConfig.Memory = int64(v.(int)) * 1024 * 1024
|
2015-10-27 19:53:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if v, ok := d.GetOk("memory_swap"); ok {
|
|
|
|
|
swap := int64(v.(int))
|
2015-11-09 19:36:23 -05:00
|
|
|
if swap > 0 {
|
|
|
|
|
swap = swap * 1024 * 1024
|
2015-10-27 19:53:49 -04:00
|
|
|
}
|
2015-11-09 19:36:23 -05:00
|
|
|
hostConfig.MemorySwap = swap
|
2015-10-27 19:53:49 -04:00
|
|
|
}
|
|
|
|
|
|
2019-10-06 05:16:27 -04:00
|
|
|
if v, ok := d.GetOk("shm_size"); ok {
|
|
|
|
|
hostConfig.ShmSize = int64(v.(int)) * 1024 * 1024
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-27 19:53:49 -04:00
|
|
|
if v, ok := d.GetOk("cpu_shares"); ok {
|
2015-11-09 19:36:23 -05:00
|
|
|
hostConfig.CPUShares = int64(v.(int))
|
2015-10-27 19:53:49 -04:00
|
|
|
}
|
|
|
|
|
|
2018-10-28 04:12:38 -04:00
|
|
|
if v, ok := d.GetOk("cpu_set"); ok {
|
|
|
|
|
hostConfig.CpusetCpus = v.(string)
|
2018-01-24 16:03:01 -05:00
|
|
|
}
|
|
|
|
|
|
2015-11-04 12:42:55 -05:00
|
|
|
if v, ok := d.GetOk("log_opts"); ok {
|
|
|
|
|
hostConfig.LogConfig.Config = mapTypeMapValsToString(v.(map[string]interface{}))
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
networkingConfig := &network.NetworkingConfig{}
|
2016-01-01 03:57:21 -05:00
|
|
|
if v, ok := d.GetOk("network_mode"); ok {
|
2018-07-03 11:30:53 -04:00
|
|
|
hostConfig.NetworkMode = container.NetworkMode(v.(string))
|
2016-01-01 03:57:21 -05:00
|
|
|
}
|
|
|
|
|
|
2018-10-11 04:55:18 -04:00
|
|
|
if v, ok := d.GetOk("userns_mode"); ok {
|
|
|
|
|
hostConfig.UsernsMode = container.UsernsMode(v.(string))
|
|
|
|
|
}
|
|
|
|
|
if v, ok := d.GetOk("pid_mode"); ok {
|
|
|
|
|
hostConfig.PidMode = container.PidMode(v.(string))
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-15 11:23:39 -04:00
|
|
|
if v, ok := d.GetOk("sysctls"); ok {
|
|
|
|
|
hostConfig.Sysctls = mapTypeMapValsToString(v.(map[string]interface{}))
|
|
|
|
|
}
|
2019-09-23 14:11:59 -04:00
|
|
|
if v, ok := d.GetOk("ipc_mode"); ok {
|
|
|
|
|
hostConfig.IpcMode = container.IpcMode(v.(string))
|
|
|
|
|
}
|
2019-10-06 05:13:24 -04:00
|
|
|
if v, ok := d.GetOk("group_add"); ok {
|
|
|
|
|
hostConfig.GroupAdd = stringSetToStringSlice(v.(*schema.Set))
|
|
|
|
|
}
|
2019-08-15 11:23:39 -04:00
|
|
|
|
2020-11-11 10:14:49 -05:00
|
|
|
init := d.Get("init").(bool)
|
|
|
|
|
hostConfig.Init = &init
|
|
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
var retContainer container.ContainerCreateCreatedBody
|
2015-11-04 15:46:41 -05:00
|
|
|
|
2021-03-08 07:18:21 -05:00
|
|
|
// TODO mavogel add platform later which comes from API v1.41. Currently we pass nil
|
|
|
|
|
if retContainer, err = client.ContainerCreate(context.Background(), config, hostConfig, networkingConfig, nil, d.Get("name").(string)); err != nil {
|
2015-11-04 15:46:41 -05:00
|
|
|
return fmt.Errorf("Unable to create container: %s", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d.SetId(retContainer.ID)
|
|
|
|
|
|
2018-10-29 01:36:21 -04:00
|
|
|
// Still support the deprecated properties
|
2016-01-04 14:58:54 -05:00
|
|
|
if v, ok := d.GetOk("networks"); ok {
|
2018-10-29 01:36:21 -04:00
|
|
|
if err := client.NetworkDisconnect(context.Background(), "bridge", retContainer.ID, false); err != nil {
|
|
|
|
|
if !strings.Contains(err.Error(), "is not connected to the network bridge") {
|
|
|
|
|
return fmt.Errorf("Unable to disconnect the default network: %s", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-03 11:30:53 -04:00
|
|
|
endpointConfig := &network.EndpointSettings{}
|
2017-05-22 09:20:32 -04:00
|
|
|
if v, ok := d.GetOk("network_alias"); ok {
|
|
|
|
|
endpointConfig.Aliases = stringSetToStringSlice(v.(*schema.Set))
|
|
|
|
|
}
|
2016-01-04 14:58:54 -05:00
|
|
|
|
2018-10-29 01:36:21 -04:00
|
|
|
for _, rawNetwork := range v.(*schema.Set).List() {
|
|
|
|
|
networkID := rawNetwork.(string)
|
|
|
|
|
if err := client.NetworkConnect(context.Background(), networkID, retContainer.ID, endpointConfig); err != nil {
|
|
|
|
|
return fmt.Errorf("Unable to connect to network '%s': %s", networkID, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// But overwrite them with the future ones, if set
|
|
|
|
|
if v, ok := d.GetOk("networks_advanced"); ok {
|
2018-10-18 06:39:58 -04:00
|
|
|
if err := client.NetworkDisconnect(context.Background(), "bridge", retContainer.ID, false); err != nil {
|
2018-10-25 01:21:48 -04:00
|
|
|
if !strings.Contains(err.Error(), "is not connected to the network bridge") {
|
|
|
|
|
return fmt.Errorf("Unable to disconnect the default network: %s", err)
|
|
|
|
|
}
|
2018-10-18 06:39:58 -04:00
|
|
|
}
|
|
|
|
|
|
2016-01-30 16:31:30 -05:00
|
|
|
for _, rawNetwork := range v.(*schema.Set).List() {
|
2018-10-29 01:36:21 -04:00
|
|
|
networkID := rawNetwork.(map[string]interface{})["name"].(string)
|
|
|
|
|
|
|
|
|
|
endpointConfig := &network.EndpointSettings{}
|
|
|
|
|
endpointIPAMConfig := &network.EndpointIPAMConfig{}
|
|
|
|
|
if v, ok := rawNetwork.(map[string]interface{})["aliases"]; ok {
|
|
|
|
|
endpointConfig.Aliases = stringSetToStringSlice(v.(*schema.Set))
|
|
|
|
|
}
|
|
|
|
|
if v, ok := rawNetwork.(map[string]interface{})["ipv4_address"]; ok {
|
|
|
|
|
endpointIPAMConfig.IPv4Address = v.(string)
|
|
|
|
|
}
|
|
|
|
|
if v, ok := rawNetwork.(map[string]interface{})["ipv6_address"]; ok {
|
|
|
|
|
endpointIPAMConfig.IPv6Address = v.(string)
|
|
|
|
|
}
|
|
|
|
|
endpointConfig.IPAMConfig = endpointIPAMConfig
|
|
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
if err := client.NetworkConnect(context.Background(), networkID, retContainer.ID, endpointConfig); err != nil {
|
|
|
|
|
return fmt.Errorf("Unable to connect to network '%s': %s", networkID, err)
|
2016-01-30 16:31:30 -05:00
|
|
|
}
|
2016-01-04 14:58:54 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-05 06:06:34 -05:00
|
|
|
if v, ok := d.GetOk("upload"); ok {
|
2018-04-20 05:30:45 -04:00
|
|
|
|
|
|
|
|
var mode int64
|
2016-12-05 06:06:34 -05:00
|
|
|
for _, upload := range v.(*schema.Set).List() {
|
|
|
|
|
content := upload.(map[string]interface{})["content"].(string)
|
2019-10-07 17:00:57 -04:00
|
|
|
contentBase64 := upload.(map[string]interface{})["content_base64"].(string)
|
2020-02-03 15:44:46 -05:00
|
|
|
source := upload.(map[string]interface{})["source"].(string)
|
|
|
|
|
|
|
|
|
|
testParams := []string{content, contentBase64, source}
|
|
|
|
|
setParams := 0
|
|
|
|
|
for _, v := range testParams {
|
|
|
|
|
if v != "" {
|
|
|
|
|
setParams++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if setParams == 0 {
|
|
|
|
|
return fmt.Errorf("error with upload content: one of 'content', 'content_base64', or 'source' must be set")
|
2019-10-07 17:00:57 -04:00
|
|
|
}
|
2020-02-03 15:44:46 -05:00
|
|
|
if setParams > 1 {
|
|
|
|
|
return fmt.Errorf("error with upload content: only one of 'content', 'content_base64', or 'source' can be set")
|
2019-10-07 17:00:57 -04:00
|
|
|
}
|
2020-02-03 15:44:46 -05:00
|
|
|
|
2019-10-07 17:00:57 -04:00
|
|
|
var contentToUpload string
|
|
|
|
|
if content != "" {
|
|
|
|
|
contentToUpload = content
|
|
|
|
|
}
|
|
|
|
|
if contentBase64 != "" {
|
|
|
|
|
decoded, _ := base64.StdEncoding.DecodeString(contentBase64)
|
|
|
|
|
contentToUpload = string(decoded)
|
|
|
|
|
}
|
2020-02-03 15:44:46 -05:00
|
|
|
if source != "" {
|
|
|
|
|
sourceContent, err := ioutil.ReadFile(source)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("could not read file: %s", err)
|
|
|
|
|
}
|
|
|
|
|
contentToUpload = string(sourceContent)
|
|
|
|
|
}
|
2016-12-05 06:06:34 -05:00
|
|
|
file := upload.(map[string]interface{})["file"].(string)
|
2018-04-20 05:30:45 -04:00
|
|
|
executable := upload.(map[string]interface{})["executable"].(bool)
|
2016-12-05 06:06:34 -05:00
|
|
|
|
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
|
tw := tar.NewWriter(buf)
|
2018-04-20 05:30:45 -04:00
|
|
|
if executable {
|
2020-12-02 06:06:39 -05:00
|
|
|
mode = 0o744
|
2018-04-20 05:30:45 -04:00
|
|
|
} else {
|
2020-12-02 06:06:39 -05:00
|
|
|
mode = 0o644
|
2018-04-20 05:30:45 -04:00
|
|
|
}
|
2016-12-05 06:06:34 -05:00
|
|
|
hdr := &tar.Header{
|
|
|
|
|
Name: file,
|
2018-04-20 05:30:45 -04:00
|
|
|
Mode: mode,
|
2019-10-07 17:00:57 -04:00
|
|
|
Size: int64(len(contentToUpload)),
|
2016-12-05 06:06:34 -05:00
|
|
|
}
|
|
|
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
|
|
|
return fmt.Errorf("Error creating tar archive: %s", err)
|
|
|
|
|
}
|
2019-10-07 17:00:57 -04:00
|
|
|
if _, err := tw.Write([]byte(contentToUpload)); err != nil {
|
2016-12-05 06:06:34 -05:00
|
|
|
return fmt.Errorf("Error creating tar archive: %s", err)
|
|
|
|
|
}
|
|
|
|
|
if err := tw.Close(); err != nil {
|
|
|
|
|
return fmt.Errorf("Error creating tar archive: %s", err)
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
dstPath := "/"
|
|
|
|
|
uploadContent := bytes.NewReader(buf.Bytes())
|
|
|
|
|
options := types.CopyToContainerOptions{}
|
|
|
|
|
if err := client.CopyToContainer(context.Background(), retContainer.ID, dstPath, uploadContent, options); err != nil {
|
2016-12-05 06:06:34 -05:00
|
|
|
return fmt.Errorf("Unable to upload volume content: %s", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-08 14:14:01 -04:00
|
|
|
if d.Get("start").(bool) {
|
|
|
|
|
creationTime = time.Now()
|
|
|
|
|
options := types.ContainerStartOptions{}
|
|
|
|
|
if err := client.ContainerStart(context.Background(), retContainer.ID, options); err != nil {
|
|
|
|
|
return fmt.Errorf("Unable to start container: %s", err)
|
|
|
|
|
}
|
2015-02-17 11:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2018-10-25 02:01:38 -04:00
|
|
|
if d.Get("attach").(bool) {
|
2018-10-28 13:41:10 -04:00
|
|
|
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)
|
2018-10-25 02:01:38 -04:00
|
|
|
select {
|
2018-10-28 13:41:10 -04:00
|
|
|
case err := <-errAttachCh:
|
2018-10-25 02:01:38 -04:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Unable to wait container end of execution: %s", err)
|
|
|
|
|
}
|
2018-10-28 13:41:10 -04:00
|
|
|
case <-attachCh:
|
|
|
|
|
if d.Get("logs").(bool) {
|
|
|
|
|
d.Set("container_logs", b.String())
|
|
|
|
|
}
|
2018-10-25 02:01:38 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-17 11:28:33 -05:00
|
|
|
return resourceDockerContainerRead(d, meta)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func resourceDockerContainerRead(d *schema.ResourceData, meta interface{}) error {
|
2017-11-21 04:14:07 -05:00
|
|
|
client := meta.(*ProviderConfig).DockerClient
|
2015-02-17 11:28:33 -05:00
|
|
|
|
2015-12-02 17:27:24 -05:00
|
|
|
apiContainer, err := fetchDockerContainer(d.Id(), client)
|
2015-02-17 11:28:33 -05:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if apiContainer == nil {
|
|
|
|
|
// This container doesn't exist anymore
|
|
|
|
|
d.SetId("")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
var container types.ContainerJSON
|
2015-06-25 10:38:56 -04:00
|
|
|
|
2018-05-16 12:00:04 -04:00
|
|
|
// TODO fix this with statefunc
|
2015-06-25 10:38:56 -04:00
|
|
|
loops := 1 // if it hasn't just been created, don't delay
|
|
|
|
|
if !creationTime.IsZero() {
|
|
|
|
|
loops = 30 // with 500ms spacing, 15 seconds; ought to be plenty
|
|
|
|
|
}
|
|
|
|
|
sleepTime := 500 * time.Millisecond
|
|
|
|
|
|
|
|
|
|
for i := loops; i > 0; i-- {
|
2018-07-03 11:30:53 -04:00
|
|
|
container, err = client.ContainerInspect(context.Background(), apiContainer.ID)
|
2015-06-25 10:38:56 -04:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Error inspecting container %s: %s", apiContainer.ID, err)
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-09 16:32:26 -04:00
|
|
|
jsonObj, _ := json.MarshalIndent(container, "", "\t")
|
2019-11-23 08:42:05 -05:00
|
|
|
log.Printf("[INFO] Docker container inspect: %s", jsonObj)
|
2018-10-09 16:32:26 -04:00
|
|
|
|
2015-06-25 10:38:56 -04:00
|
|
|
if container.State.Running ||
|
2015-10-08 08:48:04 -04:00
|
|
|
!container.State.Running && !d.Get("must_run").(bool) {
|
2015-06-25 10:38:56 -04:00
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if creationTime.IsZero() { // We didn't just create it, so don't wait around
|
|
|
|
|
return resourceDockerContainerDelete(d, meta)
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
finishTime, err := time.Parse(time.RFC3339, container.State.FinishedAt)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Container finish time could not be parsed: %s", container.State.FinishedAt)
|
|
|
|
|
}
|
|
|
|
|
if finishTime.After(creationTime) {
|
2015-06-25 10:38:56 -04:00
|
|
|
// It exited immediately, so error out so dependent containers
|
|
|
|
|
// aren't started
|
2020-12-20 05:04:51 -05:00
|
|
|
if err := resourceDockerContainerDelete(d, meta); err != nil {
|
|
|
|
|
log.Printf("[ERROR] Container %s failed to be deleted: %v", apiContainer.ID, err)
|
|
|
|
|
}
|
2015-06-25 10:38:56 -04:00
|
|
|
return fmt.Errorf("Container %s exited after creation, error was: %s", apiContainer.ID, container.State.Error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
time.Sleep(sleepTime)
|
2015-02-17 11:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2015-06-25 10:38:56 -04:00
|
|
|
// Handle the case of the for loop above running its course
|
|
|
|
|
if !container.State.Running && d.Get("must_run").(bool) {
|
2020-12-20 05:04:51 -05:00
|
|
|
if err := resourceDockerContainerDelete(d, meta); err != nil {
|
|
|
|
|
log.Printf("[ERROR] Container %s failed to be deleted: %v", apiContainer.ID, err)
|
|
|
|
|
}
|
2015-06-25 10:38:56 -04:00
|
|
|
return fmt.Errorf("Container %s failed to be in running state", apiContainer.ID)
|
2015-02-17 11:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
2018-10-25 02:01:38 -04:00
|
|
|
if !container.State.Running {
|
|
|
|
|
d.Set("exit_code", container.State.ExitCode)
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-20 13:42:36 -04:00
|
|
|
// Read Network Settings
|
|
|
|
|
if container.NetworkSettings != nil {
|
2018-10-25 01:21:48 -04:00
|
|
|
// TODO remove deprecated attributes in next major
|
2015-04-20 13:42:36 -04:00
|
|
|
d.Set("ip_address", container.NetworkSettings.IPAddress)
|
|
|
|
|
d.Set("ip_prefix_length", container.NetworkSettings.IPPrefixLen)
|
|
|
|
|
d.Set("gateway", container.NetworkSettings.Gateway)
|
2018-10-25 01:21:48 -04:00
|
|
|
if container.NetworkSettings != nil && len(container.NetworkSettings.Networks) > 0 {
|
|
|
|
|
// Still support deprecated outputs
|
|
|
|
|
for _, settings := range container.NetworkSettings.Networks {
|
|
|
|
|
d.Set("ip_address", settings.IPAddress)
|
|
|
|
|
d.Set("ip_prefix_length", settings.IPPrefixLen)
|
|
|
|
|
d.Set("gateway", settings.Gateway)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-20 13:42:36 -04:00
|
|
|
d.Set("bridge", container.NetworkSettings.Bridge)
|
2018-10-09 16:32:26 -04:00
|
|
|
if err := d.Set("ports", flattenContainerPorts(container.NetworkSettings.Ports)); err != nil {
|
|
|
|
|
log.Printf("[WARN] failed to set ports from API: %s", err)
|
|
|
|
|
}
|
2018-10-25 01:21:48 -04:00
|
|
|
if err := d.Set("network_data", flattenContainerNetworks(container.NetworkSettings)); err != nil {
|
|
|
|
|
log.Printf("[WARN] failed to set network settings from API: %s", err)
|
|
|
|
|
}
|
2015-04-20 13:42:36 -04:00
|
|
|
}
|
2015-04-16 09:21:14 -04:00
|
|
|
|
2019-11-23 08:42:05 -05:00
|
|
|
// TODO all the other attributes
|
|
|
|
|
d.SetId(container.ID)
|
2020-02-01 10:15:36 -05:00
|
|
|
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)
|
2020-11-15 12:19:19 -05:00
|
|
|
d.Set("security_opts", container.HostConfig.SecurityOpt)
|
2020-02-01 10:15:36 -05:00
|
|
|
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)
|
2021-01-18 03:00:26 -05:00
|
|
|
|
2020-11-11 10:14:49 -05:00
|
|
|
// From what I can tell Init being nullable is only for container creation to allow
|
|
|
|
|
// dockerd to default it to the daemons own default settings. So this != nil
|
|
|
|
|
// check is most likely not ever going to fail. In the event that it does the
|
|
|
|
|
// "init" value will be set to false as there isn't much else we can do about it.
|
|
|
|
|
if container.HostConfig.Init != nil {
|
|
|
|
|
d.Set("init", *container.HostConfig.Init)
|
|
|
|
|
} else {
|
|
|
|
|
d.Set("init", false)
|
|
|
|
|
}
|
2020-02-01 10:15:36 -05:00
|
|
|
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)
|
2020-06-05 11:10:11 -04:00
|
|
|
|
|
|
|
|
// We decided not to set the environment variables and labels
|
|
|
|
|
// because they are taken over from the Docker image and aren't scalar
|
|
|
|
|
// so it's difficult to treat them well.
|
|
|
|
|
// For detail, please see the following URLs.
|
|
|
|
|
// https://github.com/terraform-providers/terraform-provider-docker/issues/242
|
|
|
|
|
// https://github.com/terraform-providers/terraform-provider-docker/pull/269
|
|
|
|
|
|
2020-02-01 10:15:36 -05:00
|
|
|
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
|
|
|
|
|
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)
|
2021-01-18 03:00:26 -05:00
|
|
|
d.Set("tty", container.Config.Tty)
|
|
|
|
|
d.Set("stdin_open", container.Config.OpenStdin)
|
2015-02-17 11:28:33 -05:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func resourceDockerContainerUpdate(d *schema.ResourceData, meta interface{}) error {
|
2020-02-02 00:39:28 -05:00
|
|
|
attrs := []string{
|
|
|
|
|
"restart", "max_retry_count", "cpu_shares", "memory", "cpu_set", "memory_swap",
|
|
|
|
|
}
|
|
|
|
|
for _, attr := range attrs {
|
|
|
|
|
if d.HasChange(attr) {
|
|
|
|
|
|
|
|
|
|
// TODO update ulimits
|
|
|
|
|
// Updating ulimits seems not to work well.
|
|
|
|
|
// It succeeds to run `DockerClient.ContainerUpdate` with `ulimit` but actually `ulimit` aren't changed.
|
|
|
|
|
// https://github.com/terraform-providers/terraform-provider-docker/pull/236#discussion_r373819536
|
|
|
|
|
// ulimits := []*units.Ulimit{}
|
|
|
|
|
// if v, ok := d.GetOk("ulimit"); ok {
|
|
|
|
|
// ulimits = ulimitsToDockerUlimits(v.(*schema.Set))
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
updateConfig := container.UpdateConfig{
|
|
|
|
|
RestartPolicy: container.RestartPolicy{
|
|
|
|
|
Name: d.Get("restart").(string),
|
|
|
|
|
MaximumRetryCount: d.Get("max_retry_count").(int),
|
|
|
|
|
},
|
|
|
|
|
Resources: container.Resources{
|
|
|
|
|
CPUShares: int64(d.Get("cpu_shares").(int)),
|
|
|
|
|
Memory: int64(d.Get("memory").(int)) * 1024 * 1024,
|
|
|
|
|
CpusetCpus: d.Get("cpu_set").(string),
|
|
|
|
|
// Ulimits: ulimits,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ms, ok := d.GetOk("memory_swap"); ok {
|
|
|
|
|
a := int64(ms.(int))
|
|
|
|
|
if a > 0 {
|
|
|
|
|
a = a * 1024 * 1024
|
|
|
|
|
}
|
|
|
|
|
updateConfig.Resources.MemorySwap = a
|
|
|
|
|
}
|
|
|
|
|
client := meta.(*ProviderConfig).DockerClient
|
|
|
|
|
_, err := client.ContainerUpdate(context.Background(), d.Id(), updateConfig)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Unable to update a container: %w", err)
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-02-17 11:28:33 -05:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func resourceDockerContainerDelete(d *schema.ResourceData, meta interface{}) error {
|
2017-11-21 04:14:07 -05:00
|
|
|
client := meta.(*ProviderConfig).DockerClient
|
2015-02-17 11:28:33 -05:00
|
|
|
|
2018-10-25 02:01:38 -04:00
|
|
|
if d.Get("rm").(bool) {
|
|
|
|
|
d.SetId("")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !d.Get("attach").(bool) {
|
|
|
|
|
// Stop the container before removing if destroy_grace_seconds is defined
|
|
|
|
|
if d.Get("destroy_grace_seconds").(int) > 0 {
|
2019-08-29 14:04:50 -04:00
|
|
|
timeout := time.Duration(int32(d.Get("destroy_grace_seconds").(int))) * time.Second
|
|
|
|
|
|
2018-10-25 02:01:38 -04:00
|
|
|
if err := client.ContainerStop(context.Background(), d.Id(), &timeout); err != nil {
|
|
|
|
|
return fmt.Errorf("Error stopping container %s: %s", d.Id(), err)
|
|
|
|
|
}
|
2016-07-11 11:03:02 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
removeOpts := types.ContainerRemoveOptions{
|
2020-10-11 09:25:20 -04:00
|
|
|
RemoveVolumes: d.Get("remove_volumes").(bool),
|
2015-02-17 11:28:33 -05:00
|
|
|
Force: true,
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
if err := client.ContainerRemove(context.Background(), d.Id(), removeOpts); err != nil {
|
2015-02-17 11:28:33 -05:00
|
|
|
return fmt.Errorf("Error deleting container %s: %s", d.Id(), err)
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-25 01:21:48 -04:00
|
|
|
waitOkC, errorC := client.ContainerWait(context.Background(), d.Id(), container.WaitConditionRemoved)
|
|
|
|
|
select {
|
|
|
|
|
case waitOk := <-waitOkC:
|
|
|
|
|
log.Printf("[INFO] Container exited with code [%v]: '%s'", waitOk.StatusCode, d.Id())
|
|
|
|
|
case err := <-errorC:
|
|
|
|
|
if !(strings.Contains(err.Error(), "No such container") || strings.Contains(err.Error(), "is already in progress")) {
|
|
|
|
|
return fmt.Errorf("Error waiting for container removal '%s': %s", d.Id(), err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-17 11:28:33 -05:00
|
|
|
d.SetId("")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-09 16:32:26 -04:00
|
|
|
// TODO extract to structures_container.go
|
2018-10-26 01:24:22 -04:00
|
|
|
type byPortAndProtocol []string
|
|
|
|
|
|
|
|
|
|
func (s byPortAndProtocol) Len() int {
|
|
|
|
|
return len(s)
|
|
|
|
|
}
|
2020-12-02 06:06:39 -05:00
|
|
|
|
2018-10-26 01:24:22 -04:00
|
|
|
func (s byPortAndProtocol) Swap(i, j int) {
|
|
|
|
|
s[i], s[j] = s[j], s[i]
|
|
|
|
|
}
|
2020-12-02 06:06:39 -05:00
|
|
|
|
2018-10-26 01:24:22 -04:00
|
|
|
func (s byPortAndProtocol) Less(i, j int) bool {
|
|
|
|
|
iSplit := strings.Split(string(s[i]), "/")
|
|
|
|
|
iPort, _ := strconv.Atoi(iSplit[0])
|
|
|
|
|
jSplit := strings.Split(string(s[j]), "/")
|
|
|
|
|
jPort, _ := strconv.Atoi(jSplit[0])
|
|
|
|
|
return iPort < jPort
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-16 12:49:57 -04:00
|
|
|
func flattenContainerPorts(in nat.PortMap) []interface{} {
|
2020-12-02 06:06:39 -05:00
|
|
|
out := make([]interface{}, 0)
|
2018-10-26 01:24:22 -04:00
|
|
|
|
|
|
|
|
var internalPortKeys []string
|
|
|
|
|
for portAndProtocolKeys := range in {
|
|
|
|
|
internalPortKeys = append(internalPortKeys, string(portAndProtocolKeys))
|
|
|
|
|
}
|
|
|
|
|
sort.Sort(byPortAndProtocol(internalPortKeys))
|
|
|
|
|
|
|
|
|
|
for _, portKey := range internalPortKeys {
|
2018-10-09 16:32:26 -04:00
|
|
|
m := make(map[string]interface{})
|
2018-10-26 01:24:22 -04:00
|
|
|
|
|
|
|
|
portBindings := in[nat.Port(portKey)]
|
2018-10-09 16:32:26 -04:00
|
|
|
for _, portBinding := range portBindings {
|
2018-10-26 01:24:22 -04:00
|
|
|
portProtocolSplit := strings.Split(string(portKey), "/")
|
2018-10-09 16:32:26 -04:00
|
|
|
convertedInternal, _ := strconv.Atoi(portProtocolSplit[0])
|
|
|
|
|
convertedExternal, _ := strconv.Atoi(portBinding.HostPort)
|
|
|
|
|
m["internal"] = convertedInternal
|
|
|
|
|
m["external"] = convertedExternal
|
|
|
|
|
m["ip"] = portBinding.HostIP
|
|
|
|
|
m["protocol"] = portProtocolSplit[1]
|
|
|
|
|
out = append(out, m)
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-10-16 12:49:57 -04:00
|
|
|
return out
|
2018-10-09 16:32:26 -04:00
|
|
|
}
|
2020-12-02 06:06:39 -05:00
|
|
|
|
2018-10-25 01:21:48 -04:00
|
|
|
func flattenContainerNetworks(in *types.NetworkSettings) []interface{} {
|
2020-12-02 06:06:39 -05:00
|
|
|
out := make([]interface{}, 0)
|
2018-10-25 01:21:48 -04:00
|
|
|
if in == nil || in.Networks == nil || len(in.Networks) == 0 {
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
networks := in.Networks
|
|
|
|
|
for networkName, networkData := range networks {
|
|
|
|
|
m := make(map[string]interface{})
|
|
|
|
|
m["network_name"] = networkName
|
|
|
|
|
m["ip_address"] = networkData.IPAddress
|
|
|
|
|
m["ip_prefix_length"] = networkData.IPPrefixLen
|
|
|
|
|
m["gateway"] = networkData.Gateway
|
2020-05-01 22:29:59 -04:00
|
|
|
m["global_ipv6_address"] = networkData.GlobalIPv6Address
|
|
|
|
|
m["global_ipv6_prefix_length"] = networkData.GlobalIPv6PrefixLen
|
|
|
|
|
m["ipv6_gateway"] = networkData.IPv6Gateway
|
2018-10-25 01:21:48 -04:00
|
|
|
out = append(out, m)
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
2018-10-09 16:32:26 -04:00
|
|
|
|
|
|
|
|
// TODO move to separate flattener file
|
2015-02-17 11:28:33 -05:00
|
|
|
func stringListToStringSlice(stringList []interface{}) []string {
|
|
|
|
|
ret := []string{}
|
|
|
|
|
for _, v := range stringList {
|
2016-04-29 19:42:24 -04:00
|
|
|
if v == nil {
|
|
|
|
|
ret = append(ret, "")
|
|
|
|
|
continue
|
|
|
|
|
}
|
2015-02-17 11:28:33 -05:00
|
|
|
ret = append(ret, v.(string))
|
|
|
|
|
}
|
|
|
|
|
return ret
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func stringSetToStringSlice(stringSet *schema.Set) []string {
|
|
|
|
|
ret := []string{}
|
|
|
|
|
if stringSet == nil {
|
|
|
|
|
return ret
|
|
|
|
|
}
|
|
|
|
|
for _, envVal := range stringSet.List() {
|
|
|
|
|
ret = append(ret, envVal.(string))
|
|
|
|
|
}
|
|
|
|
|
return ret
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-04 12:42:55 -05:00
|
|
|
func mapTypeMapValsToString(typeMap map[string]interface{}) map[string]string {
|
|
|
|
|
mapped := make(map[string]string, len(typeMap))
|
|
|
|
|
for k, v := range typeMap {
|
2015-11-03 15:20:58 -05:00
|
|
|
mapped[k] = v.(string)
|
|
|
|
|
}
|
|
|
|
|
return mapped
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-16 12:00:04 -04:00
|
|
|
// mapTypeMapValsToStringSlice maps a map to a slice with '=': e.g. foo = "bar" -> 'foo=bar'
|
|
|
|
|
func mapTypeMapValsToStringSlice(typeMap map[string]interface{}) []string {
|
2018-08-06 08:21:02 -04:00
|
|
|
mapped := make([]string, 0)
|
2018-05-16 12:00:04 -04:00
|
|
|
for k, v := range typeMap {
|
2018-08-06 08:21:02 -04:00
|
|
|
if len(k) > 0 {
|
|
|
|
|
mapped = append(mapped, k+"="+v.(string))
|
|
|
|
|
}
|
2018-05-16 12:00:04 -04:00
|
|
|
}
|
|
|
|
|
return mapped
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
func fetchDockerContainer(ID string, client *client.Client) (*types.Container, error) {
|
|
|
|
|
apiContainers, err := client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
|
2015-02-17 11:28:33 -05:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("Error fetching container information from Docker: %s\n", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, apiContainer := range apiContainers {
|
2015-12-02 17:27:24 -05:00
|
|
|
if apiContainer.ID == ID {
|
|
|
|
|
return &apiContainer, nil
|
2015-02-17 11:28:33 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-16 12:49:57 -04:00
|
|
|
func portSetToDockerPorts(ports []interface{}) (map[nat.Port]struct{}, map[nat.Port][]nat.PortBinding) {
|
2018-07-03 11:30:53 -04:00
|
|
|
retExposedPorts := map[nat.Port]struct{}{}
|
|
|
|
|
retPortBindings := map[nat.Port][]nat.PortBinding{}
|
2015-02-17 11:28:33 -05:00
|
|
|
|
2018-10-16 12:49:57 -04:00
|
|
|
for _, portInt := range ports {
|
2015-02-17 11:28:33 -05:00
|
|
|
port := portInt.(map[string]interface{})
|
|
|
|
|
internal := port["internal"].(int)
|
|
|
|
|
protocol := port["protocol"].(string)
|
|
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
exposedPort := nat.Port(strconv.Itoa(internal) + "/" + protocol)
|
2015-02-17 11:28:33 -05:00
|
|
|
retExposedPorts[exposedPort] = struct{}{}
|
|
|
|
|
|
2018-10-09 16:32:26 -04:00
|
|
|
portBinding := nat.PortBinding{}
|
2015-02-17 11:28:33 -05:00
|
|
|
|
2018-10-09 16:32:26 -04:00
|
|
|
external, extOk := port["external"].(int)
|
2015-02-17 11:28:33 -05:00
|
|
|
if extOk {
|
2018-10-09 16:32:26 -04:00
|
|
|
portBinding.HostPort = strconv.Itoa(external)
|
2015-02-17 11:28:33 -05:00
|
|
|
}
|
2018-10-09 16:32:26 -04:00
|
|
|
|
|
|
|
|
ip, ipOk := port["ip"].(string)
|
|
|
|
|
if ipOk {
|
|
|
|
|
portBinding.HostIP = ip
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-08 13:01:48 -04:00
|
|
|
if extOk || ipOk {
|
|
|
|
|
retPortBindings[exposedPort] = append(retPortBindings[exposedPort], portBinding)
|
|
|
|
|
}
|
2015-02-17 11:28:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return retExposedPorts, retPortBindings
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
func ulimitsToDockerUlimits(extraUlimits *schema.Set) []*units.Ulimit {
|
|
|
|
|
retExtraUlimits := []*units.Ulimit{}
|
2018-04-20 05:35:49 -04:00
|
|
|
|
|
|
|
|
for _, ulimitInt := range extraUlimits.List() {
|
|
|
|
|
ulimits := ulimitInt.(map[string]interface{})
|
2018-07-03 11:30:53 -04:00
|
|
|
u := &units.Ulimit{
|
2018-04-20 05:35:49 -04:00
|
|
|
Name: ulimits["name"].(string),
|
|
|
|
|
Soft: int64(ulimits["soft"].(int)),
|
|
|
|
|
Hard: int64(ulimits["hard"].(int)),
|
|
|
|
|
}
|
|
|
|
|
retExtraUlimits = append(retExtraUlimits, u)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return retExtraUlimits
|
|
|
|
|
}
|
2020-12-02 06:06:39 -05:00
|
|
|
|
2015-10-09 09:05:43 -04:00
|
|
|
func extraHostsSetToDockerExtraHosts(extraHosts *schema.Set) []string {
|
|
|
|
|
retExtraHosts := []string{}
|
|
|
|
|
|
|
|
|
|
for _, hostInt := range extraHosts.List() {
|
|
|
|
|
host := hostInt.(map[string]interface{})
|
|
|
|
|
ip := host["ip"].(string)
|
|
|
|
|
hostname := host["host"].(string)
|
|
|
|
|
retExtraHosts = append(retExtraHosts, hostname+":"+ip)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return retExtraHosts
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-17 11:28:33 -05:00
|
|
|
func volumeSetToDockerVolumes(volumes *schema.Set) (map[string]struct{}, []string, []string, error) {
|
|
|
|
|
retVolumeMap := map[string]struct{}{}
|
|
|
|
|
retHostConfigBinds := []string{}
|
|
|
|
|
retVolumeFromContainers := []string{}
|
|
|
|
|
|
|
|
|
|
for _, volumeInt := range volumes.List() {
|
|
|
|
|
volume := volumeInt.(map[string]interface{})
|
|
|
|
|
fromContainer := volume["from_container"].(string)
|
|
|
|
|
containerPath := volume["container_path"].(string)
|
2016-01-15 16:59:33 -05:00
|
|
|
volumeName := volume["volume_name"].(string)
|
|
|
|
|
if len(volumeName) == 0 {
|
|
|
|
|
volumeName = volume["host_path"].(string)
|
|
|
|
|
}
|
2015-02-17 11:28:33 -05:00
|
|
|
readOnly := volume["read_only"].(bool)
|
|
|
|
|
|
|
|
|
|
switch {
|
|
|
|
|
case len(fromContainer) == 0 && len(containerPath) == 0:
|
|
|
|
|
return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, errors.New("Volume entry without container path or source container")
|
|
|
|
|
case len(fromContainer) != 0 && len(containerPath) != 0:
|
|
|
|
|
return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, errors.New("Both a container and a path specified in a volume entry")
|
|
|
|
|
case len(fromContainer) != 0:
|
|
|
|
|
retVolumeFromContainers = append(retVolumeFromContainers, fromContainer)
|
2016-01-15 16:59:33 -05:00
|
|
|
case len(volumeName) != 0:
|
2015-02-17 11:28:33 -05:00
|
|
|
readWrite := "rw"
|
|
|
|
|
if readOnly {
|
|
|
|
|
readWrite = "ro"
|
|
|
|
|
}
|
|
|
|
|
retVolumeMap[containerPath] = struct{}{}
|
2016-01-15 16:59:33 -05:00
|
|
|
retHostConfigBinds = append(retHostConfigBinds, volumeName+":"+containerPath+":"+readWrite)
|
2015-02-17 11:28:33 -05:00
|
|
|
default:
|
|
|
|
|
retVolumeMap[containerPath] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, nil
|
|
|
|
|
}
|
2018-04-20 05:14:44 -04:00
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
func deviceSetToDockerDevices(devices *schema.Set) []container.DeviceMapping {
|
|
|
|
|
retDevices := []container.DeviceMapping{}
|
2018-04-20 05:14:44 -04:00
|
|
|
for _, deviceInt := range devices.List() {
|
|
|
|
|
deviceMap := deviceInt.(map[string]interface{})
|
|
|
|
|
hostPath := deviceMap["host_path"].(string)
|
|
|
|
|
containerPath := deviceMap["container_path"].(string)
|
|
|
|
|
permissions := deviceMap["permissions"].(string)
|
|
|
|
|
|
|
|
|
|
switch {
|
|
|
|
|
case len(containerPath) == 0:
|
|
|
|
|
containerPath = hostPath
|
|
|
|
|
fallthrough
|
|
|
|
|
case len(permissions) == 0:
|
|
|
|
|
permissions = "rwm"
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-03 11:30:53 -04:00
|
|
|
device := container.DeviceMapping{
|
2018-04-20 05:14:44 -04:00
|
|
|
PathOnHost: hostPath,
|
|
|
|
|
PathInContainer: containerPath,
|
|
|
|
|
CgroupPermissions: permissions,
|
|
|
|
|
}
|
|
|
|
|
retDevices = append(retDevices, device)
|
|
|
|
|
}
|
|
|
|
|
return retDevices
|
|
|
|
|
}
|
2020-02-01 10:15:36 -05:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|