2021-03-18 03:30:54 -04:00
package provider
2015-02-17 11:28:33 -05:00
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"
2021-05-11 02:59:40 -04:00
"errors"
2015-02-17 11:28:33 -05:00
"fmt"
2018-10-09 16:32:26 -04:00
"log"
2025-04-11 06:38:28 -04:00
"math/big"
2019-05-26 05:59:29 -04:00
"os"
2025-04-16 03:12:51 -04: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
2022-07-15 05:15:28 -04:00
"github.com/docker/cli/opts"
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"
2021-03-18 03:30:54 -04:00
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
2025-04-18 06:15:43 -04:00
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
2021-03-18 03:30:54 -04:00
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
2015-02-17 11:28:33 -05:00
)
2021-05-11 02:59:40 -04:00
const (
2022-09-13 09:44:25 -04:00
containerReadRefreshTimeoutMillisecondsDefault = 15000
containerReadRefreshWaitBeforeRefreshes = 100 * time . Millisecond
containerReadRefreshDelay = 100 * time . Millisecond
2021-05-11 02:59:40 -04:00
)
var (
errContainerFailedToBeCreated = errors . New ( "container failed to be created" )
errContainerFailedToBeDeleted = errors . New ( "container failed to be deleted" )
errContainerExitedImmediately = errors . New ( "container exited immediately" )
errContainerFailedToBeInRunningState = errors . New ( "container failed to be in running state" )
2022-11-02 04:19:45 -04:00
errContainerFailedToBeInHealthyState = errors . New ( "container failed to be in healthy state" )
2021-05-11 02:59:40 -04:00
)
// NOTE mavogel: we keep this global var for tracking
// the time in the create and read func
2020-12-02 06:06:39 -05:00
var creationTime time . Time
2015-06-25 10:38:56 -04:00
2021-03-18 03:30:54 -04:00
func resourceDockerContainerCreate ( ctx context . Context , d * schema . ResourceData , meta interface { } ) diag . Diagnostics {
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 )
2022-12-28 05:38:42 -05:00
_ , err = findImage ( ctx , image , client , authConfigs , "" )
2019-05-26 05:42:53 -04:00
if err != nil {
2021-03-18 03:30:54 -04:00
return diag . Errorf ( "Unable to create container with image %s: %s" , image , err )
2015-02-17 11:28:33 -05:00
}
2022-07-11 06:27:47 -04:00
var stopTimeout * int
if v , ok := d . GetOk ( "stop_timeout" ) ; ok {
tmp := v . ( int )
stopTimeout = & tmp
}
2015-02-17 11:28:33 -05:00
2018-07-03 11:30:53 -04:00
config := & container . Config {
2022-07-11 06:27:47 -04:00
Image : image ,
Hostname : d . Get ( "hostname" ) . ( string ) ,
Domainname : d . Get ( "domainname" ) . ( string ) ,
Tty : d . Get ( "tty" ) . ( bool ) ,
OpenStdin : d . Get ( "stdin_open" ) . ( bool ) ,
StopSignal : d . Get ( "stop_signal" ) . ( string ) ,
StopTimeout : stopTimeout ,
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 == "" {
2021-03-18 03:30:54 -04:00
return diag . Errorf ( "values for command may not be empty" )
2016-04-29 19:42:24 -04:00
}
}
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 {
2021-05-26 02:35:46 -04:00
extraHosts = extraHostsSetToContainerExtraHosts ( v . ( * schema . Set ) )
2015-10-09 09:05:43 -04:00
}
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 {
2021-03-18 03:30:54 -04:00
return diag . Errorf ( "Unable to parse volumes: %s" , err )
2015-02-17 11:28:33 -05:00
}
}
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 ) )
}
2025-04-27 10:03:02 -04:00
if rawStartInterval , ok := rawHealthCheck [ "start_interval" ] ; ok {
config . Healthcheck . StartInterval , _ = time . ParseDuration ( rawStartInterval . ( string ) )
}
2018-10-08 09:02:13 -04:00
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 )
}
2025-04-15 12:54:25 -04:00
// QF1003: could use tagged switch on mountType
if mountType == mount . TypeBind { //nolint:staticcheck
2019-05-26 05:59:29 -04:00
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 { } ) )
}
2025-04-24 17:13:01 -04:00
if client . ClientVersion ( ) >= "1.45" {
if value , ok := rawVolumeOptions [ "subpath" ] ; ok {
mountInstance . VolumeOptions . Subpath = value . ( string )
}
} else {
return diag . Errorf ( "Setting VolumeOptions.Subpath requires docker version 1.45 or higher" )
}
2019-05-26 05:59:29 -04:00
}
}
}
} 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 {
2025-04-17 13:22:08 -04:00
Name : container . RestartPolicyMode ( d . Get ( "restart" ) . ( string ) ) ,
2015-10-27 12:08:57 -04:00
MaximumRetryCount : d . Get ( "max_retry_count" ) . ( int ) ,
} ,
2022-07-11 06:27:47 -04:00
Runtime : d . Get ( "runtime" ) . ( string ) ,
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 ) )
}
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
}
2025-04-11 06:38:28 -04:00
if v , ok := d . GetOk ( "cpus" ) ; ok {
if client . ClientVersion ( ) >= "1.28" {
cpu , ok := new ( big . Rat ) . SetString ( v . ( string ) )
if ! ok {
return diag . Errorf ( "Error setting cpus: Failed to parse %v as a rational number" , v . ( string ) )
}
nano := cpu . Mul ( cpu , big . NewRat ( 1e9 , 1 ) )
if ! nano . IsInt ( ) {
return diag . Errorf ( "Error setting cpus: value is too precise" )
}
hostConfig . NanoCPUs = nano . Num ( ) . Int64 ( )
} else {
log . Printf ( "[WARN] Setting CPUs count/quota requires docker version 1.28 or higher" )
}
}
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 ) )
}
2022-07-15 05:15:28 -04:00
if v , ok := d . GetOk ( "gpus" ) ; ok {
if client . ClientVersion ( ) >= "1.40" {
var gpu opts . GpuOpts
err := gpu . Set ( v . ( string ) )
if err != nil {
return diag . Errorf ( "Error setting gpus: %s" , err )
}
hostConfig . DeviceRequests = gpu . Value ( )
} else {
log . Printf ( "[WARN] GPU support requires docker version 1.40 or higher" )
}
}
2019-08-15 11:23:39 -04:00
2022-12-23 04:45:12 -05:00
if v , ok := d . GetOk ( "cgroupns_mode" ) ; ok {
if client . ClientVersion ( ) >= "1.41" {
cgroupnsMode := container . CgroupnsMode ( v . ( string ) )
if ! cgroupnsMode . Valid ( ) {
return diag . Errorf ( "cgroupns_mode: invalid CGROUP mode, must be either 'private', 'host' or empty" )
} else {
hostConfig . CgroupnsMode = cgroupnsMode
}
} else {
log . Printf ( "[WARN] cgroupns_mode requires docker version 1.41 or higher" )
}
}
2020-11-11 10:14:49 -05:00
init := d . Get ( "init" ) . ( bool )
hostConfig . Init = & init
2021-08-04 17:59:48 -04:00
if v , ok := d . GetOk ( "storage_opts" ) ; ok {
hostConfig . StorageOpt = mapTypeMapValsToString ( v . ( map [ string ] interface { } ) )
}
2025-04-17 13:22:08 -04:00
var retContainer container . CreateResponse
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
2021-03-18 03:30:54 -04:00
if retContainer , err = client . ContainerCreate ( ctx , config , hostConfig , networkingConfig , nil , d . Get ( "name" ) . ( string ) ) ; err != nil {
return diag . Errorf ( "Unable to create container: %s" , err )
2015-11-04 15:46:41 -05:00
}
2023-01-13 09:57:26 -05:00
log . Printf ( "[INFO] retContainer %#v" , retContainer )
2015-11-04 15:46:41 -05:00
d . SetId ( retContainer . ID )
2018-10-29 01:36:21 -04:00
// But overwrite them with the future ones, if set
if v , ok := d . GetOk ( "networks_advanced" ) ; ok {
2021-03-18 03:30:54 -04:00
if err := client . NetworkDisconnect ( ctx , "bridge" , retContainer . ID , false ) ; err != nil {
2021-06-22 09:11:32 -04:00
if ! containsIgnorableErrorMessage ( err . Error ( ) , "is not connected to the network bridge" ) {
2021-03-18 03:30:54 -04:00
return diag . Errorf ( "Unable to disconnect the default network: %s" , err )
2018-10-25 01:21:48 -04:00
}
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
2021-03-18 03:30:54 -04:00
if err := client . NetworkConnect ( ctx , networkID , retContainer . ID , endpointConfig ) ; err != nil {
return diag . 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 {
2021-03-18 03:30:54 -04:00
return diag . 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 {
2021-03-18 03:30:54 -04:00
return diag . 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 != "" {
2024-05-08 08:59:49 -04:00
sourceContent , err := os . ReadFile ( source )
2020-02-03 15:44:46 -05:00
if err != nil {
2021-03-18 03:30:54 -04:00
return diag . Errorf ( "could not read file: %s" , err )
2020-02-03 15:44:46 -05:00
}
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 )
2025-04-16 03:12:51 -04:00
permission := upload . ( map [ string ] interface { } ) [ "permissions" ] . ( string )
2016-12-05 06:06:34 -05:00
buf := new ( bytes . Buffer )
tw := tar . NewWriter ( buf )
2025-04-16 03:12:51 -04:00
if permission != "" {
mode , err = strconv . ParseInt ( permission , 8 , 32 )
if err != nil {
return diag . Errorf ( "Error parsing permission: %s" , err )
}
} else if executable {
2020-12-02 06:06:39 -05:00
mode = 0 o744
2018-04-20 05:30:45 -04:00
} else {
2020-12-02 06:06:39 -05:00
mode = 0 o644
2018-04-20 05:30:45 -04:00
}
2016-12-05 06:06:34 -05:00
hdr := & tar . Header {
2021-08-10 05:28:36 -04:00
Name : file ,
Mode : mode ,
Size : int64 ( len ( contentToUpload ) ) ,
ModTime : time . Now ( ) ,
2016-12-05 06:06:34 -05:00
}
if err := tw . WriteHeader ( hdr ) ; err != nil {
2021-03-18 03:30:54 -04:00
return diag . Errorf ( "Error creating tar archive: %s" , err )
2016-12-05 06:06:34 -05:00
}
2019-10-07 17:00:57 -04:00
if _ , err := tw . Write ( [ ] byte ( contentToUpload ) ) ; err != nil {
2021-03-18 03:30:54 -04:00
return diag . Errorf ( "Error creating tar archive: %s" , err )
2016-12-05 06:06:34 -05:00
}
if err := tw . Close ( ) ; err != nil {
2021-03-18 03:30:54 -04:00
return diag . Errorf ( "Error creating tar archive: %s" , err )
2016-12-05 06:06:34 -05:00
}
2018-07-03 11:30:53 -04:00
dstPath := "/"
uploadContent := bytes . NewReader ( buf . Bytes ( ) )
2025-04-17 13:22:08 -04:00
options := container . CopyToContainerOptions { }
2021-03-18 03:30:54 -04:00
if err := client . CopyToContainer ( ctx , retContainer . ID , dstPath , uploadContent , options ) ; err != nil {
return diag . Errorf ( "Unable to upload volume content: %s" , err )
2016-12-05 06:06:34 -05:00
}
}
}
2018-10-08 14:14:01 -04:00
if d . Get ( "start" ) . ( bool ) {
creationTime = time . Now ( )
2025-04-17 13:22:08 -04:00
options := container . StartOptions { }
2021-03-18 03:30:54 -04:00
if err := client . ContainerStart ( ctx , retContainer . ID , options ) ; err != nil {
return diag . Errorf ( "Unable to start container: %s" , err )
2018-10-08 14:14:01 -04:00
}
2022-11-02 04:19:45 -04:00
if d . Get ( "wait" ) . ( bool ) {
waitForHealthyState := func ( result chan <- error ) {
for {
infos , err := client . ContainerInspect ( ctx , retContainer . ID )
if err != nil {
result <- fmt . Errorf ( "error inspecting container state: %s" , err )
}
2025-04-24 16:15:05 -04:00
if infos . ContainerJSONBase == nil || infos . ContainerJSONBase . State == nil || infos . ContainerJSONBase . State . Health == nil { //nolint:staticcheck
result <- fmt . Errorf ( "you have supplied a 'wait' argument, but the container does not have a healthcheck defined. Please remove the 'wait' argument or add a 'healthcheck' attribute" )
break
}
2025-04-15 12:54:25 -04:00
if infos . ContainerJSONBase . State . Health . Status == types . Healthy { //nolint:staticcheck
2022-11-02 04:19:45 -04:00
log . Printf ( "[DEBUG] container state is healthy" )
2025-04-24 16:15:05 -04:00
result <- nil
2022-11-02 04:19:45 -04:00
break
}
log . Printf ( "[DEBUG] waiting for container healthy state" )
time . Sleep ( time . Second )
}
result <- nil
}
ctx , cancel := context . WithTimeout ( ctx , time . Duration ( d . Get ( "wait_timeout" ) . ( int ) ) * time . Second )
defer cancel ( )
result := make ( chan error , 1 )
go waitForHealthyState ( result )
select {
case <- ctx . Done ( ) :
log . Printf ( "[ERROR] Container %s failed to be in healthy state in time" , retContainer . ID )
return diag . FromErr ( errContainerFailedToBeInHealthyState )
case err := <- result :
if err != nil {
return diag . FromErr ( 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
2021-05-11 04:00:02 -04:00
logsRead := make ( chan bool )
2018-10-28 13:41:10 -04:00
if d . Get ( "logs" ) . ( bool ) {
go func ( ) {
2021-05-11 04:00:02 -04:00
defer func ( ) { logsRead <- true } ( )
2025-04-17 13:22:08 -04:00
reader , err := client . ContainerLogs ( ctx , retContainer . ID , container . LogsOptions {
2018-10-28 13:41:10 -04:00
ShowStdout : true ,
ShowStderr : true ,
Follow : true ,
Timestamps : false ,
} )
if err != nil {
log . Panic ( err )
}
2025-04-15 12:54:25 -04:00
defer reader . Close ( ) //nolint:errcheck
2018-10-28 13:41:10 -04:00
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 {
2021-03-18 03:30:54 -04:00
return diag . Errorf ( "Unable to wait container end of execution: %s" , err )
2018-10-25 02:01:38 -04:00
}
2018-10-28 13:41:10 -04:00
case <- attachCh :
if d . Get ( "logs" ) . ( bool ) {
2021-05-11 04:00:02 -04:00
// There is a race condition here.
// If the goroutine does not finish writing into the buffer by this line, we will have no logs.
// Thus, waiting for the goroutine to finish
<- logsRead
2018-10-28 13:41:10 -04:00
d . Set ( "container_logs" , b . String ( ) )
}
2018-10-25 02:01:38 -04:00
}
}
2021-03-18 03:30:54 -04:00
return resourceDockerContainerRead ( ctx , d , meta )
2015-02-17 11:28:33 -05:00
}
2021-03-18 03:30:54 -04:00
func resourceDockerContainerRead ( ctx context . Context , d * schema . ResourceData , meta interface { } ) diag . Diagnostics {
2022-09-13 09:44:25 -04:00
containerReadRefreshTimeoutMilliseconds := d . Get ( "container_read_refresh_timeout_milliseconds" ) . ( int )
// Ensure the timeout can never be 0, the default integer value.
// This also ensures imported resources will get the default of 15 seconds
if containerReadRefreshTimeoutMilliseconds == 0 {
containerReadRefreshTimeoutMilliseconds = containerReadRefreshTimeoutMillisecondsDefault
}
log . Printf ( "[INFO] Waiting for container: '%s' to run: max '%v seconds'" , d . Id ( ) , containerReadRefreshTimeoutMilliseconds / 1000 )
2017-11-21 04:14:07 -05:00
client := meta . ( * ProviderConfig ) . DockerClient
2015-02-17 11:28:33 -05:00
2021-03-18 03:30:54 -04:00
apiContainer , err := fetchDockerContainer ( ctx , d . Id ( ) , client )
2015-02-17 11:28:33 -05:00
if err != nil {
2021-03-18 03:30:54 -04:00
return diag . FromErr ( err )
2015-02-17 11:28:33 -05:00
}
if apiContainer == nil {
// This container doesn't exist anymore
d . SetId ( "" )
return nil
}
2025-04-18 06:15:43 -04:00
stateConf := & retry . StateChangeConf {
2021-05-11 02:59:40 -04:00
Pending : [ ] string { "pending" } ,
Target : [ ] string { "running" } ,
Refresh : resourceDockerContainerReadRefreshFunc ( ctx , d , meta ) ,
2022-09-13 09:44:25 -04:00
Timeout : time . Duration ( containerReadRefreshTimeoutMilliseconds ) * time . Millisecond ,
2021-05-11 02:59:40 -04:00
MinTimeout : containerReadRefreshWaitBeforeRefreshes ,
Delay : containerReadRefreshDelay ,
2015-06-25 10:38:56 -04:00
}
2021-05-11 02:59:40 -04:00
containerRaw , err := stateConf . WaitForStateContext ( ctx )
if err != nil {
if errors . Is ( err , errContainerFailedToBeCreated ) {
2021-03-18 03:30:54 -04:00
return resourceDockerContainerDelete ( ctx , d , meta )
2015-06-25 10:38:56 -04:00
}
2021-05-11 02:59:40 -04:00
if errors . Is ( err , errContainerExitedImmediately ) {
2021-03-18 03:30:54 -04:00
if err := resourceDockerContainerDelete ( ctx , d , meta ) ; err != nil {
2020-12-20 05:04:51 -05:00
log . Printf ( "[ERROR] Container %s failed to be deleted: %v" , apiContainer . ID , err )
2021-05-11 02:59:40 -04:00
return diag . FromErr ( errContainerFailedToBeDeleted )
2020-12-20 05:04:51 -05:00
}
2015-06-25 10:38:56 -04:00
}
2021-05-11 02:59:40 -04:00
return diag . FromErr ( err )
2015-02-17 11:28:33 -05:00
}
2025-04-17 13:22:08 -04:00
container := containerRaw . ( container . InspectResponse )
2021-05-11 02:59:40 -04:00
jsonObj , _ := json . MarshalIndent ( container , "" , "\t" )
log . Printf ( "[DEBUG] Docker container inspect from stateFunc: %s" , jsonObj )
2015-06-25 10:38:56 -04:00
if ! container . State . Running && d . Get ( "must_run" ) . ( bool ) {
2021-03-18 03:30:54 -04:00
if err := resourceDockerContainerDelete ( ctx , d , meta ) ; err != nil {
2021-05-11 02:59:40 -04:00
log . Printf ( "[ERROR] Container %s failed to be deleted: %v" , container . ID , err )
return err
2020-12-20 05:04:51 -05:00
}
2021-05-11 02:59:40 -04:00
log . Printf ( "[ERROR] Container %s failed to be in running state" , container . ID )
return diag . FromErr ( errContainerFailedToBeInRunningState )
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 {
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
2025-04-25 02:49:44 -04:00
// get image value from plan. If it begins with sha256: then we need to use {{ .Image }} value
// If not, we can use Config.Image
// See https://github.com/kreuzwerker/terraform-provider-docker/issues/426#issuecomment-2828954974 for more details
imageValue := d . Get ( "image" ) . ( string )
if strings . HasPrefix ( imageValue , "sha256:" ) {
d . Set ( "image" , container . Image )
} else {
d . Set ( "image" , container . Config . Image )
}
2020-02-01 10:15:36 -05:00
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 ,
} ,
} )
}
2022-07-11 06:27:47 -04:00
d . Set ( "runtime" , container . HostConfig . Runtime )
2020-02-01 10:15:36 -05:00
d . Set ( "mounts" , getDockerContainerMounts ( container ) )
// volumes
d . Set ( "tmpfs" , container . HostConfig . Tmpfs )
2021-04-19 09:33:13 -04:00
if err := d . Set ( "host" , flattenExtraHosts ( container . HostConfig . ExtraHosts ) ) ; err != nil {
log . Printf ( "[WARN] failed to set container hostconfig extrahosts from API: %s" , err )
2021-03-18 03:30:54 -04:00
}
2021-04-19 09:33:13 -04:00
if err = d . Set ( "ulimit" , flattenUlimits ( container . HostConfig . Ulimits ) ) ; err != nil {
log . Printf ( "[WARN] failed to set container hostconfig ulimits from API: %s" , err )
2020-02-01 10:15:36 -05:00
}
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 ( "privileged" , container . HostConfig . Privileged )
2021-04-19 09:33:13 -04:00
if err = d . Set ( "devices" , flattenDevices ( container . HostConfig . Devices ) ) ; err != nil {
log . Printf ( "[WARN] failed to set container hostconfig devices from API: %s" , err )
2020-02-01 10:15:36 -05:00
}
// "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 )
2025-04-11 06:38:28 -04:00
if container . HostConfig . NanoCPUs > 0 {
d . Set ( "cpus" , container . HostConfig . NanoCPUs )
}
2020-02-01 10:15:36 -05:00
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 )
2021-08-04 17:59:48 -04:00
d . Set ( "storage_opts" , container . HostConfig . StorageOpt )
2020-02-01 10:15:36 -05:00
d . Set ( "network_mode" , container . HostConfig . NetworkMode )
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 { } {
2025-04-27 10:03:02 -04:00
"test" : container . Config . Healthcheck . Test ,
"interval" : container . Config . Healthcheck . Interval . String ( ) ,
"timeout" : container . Config . Healthcheck . Timeout . String ( ) ,
"start_period" : container . Config . Healthcheck . StartPeriod . String ( ) ,
"start_interval" : container . Config . Healthcheck . StartInterval . String ( ) ,
"retries" : container . Config . Healthcheck . Retries ,
2020-02-01 10:15:36 -05:00
} ,
} )
}
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 )
2022-07-11 06:27:47 -04:00
d . Set ( "stop_signal" , container . Config . StopSignal )
d . Set ( "stop_timeout" , container . Config . StopTimeout )
2021-05-11 02:59:40 -04:00
2022-07-15 05:15:28 -04:00
if len ( container . HostConfig . DeviceRequests ) > 0 {
// TODO pass the original gpus property string back to the resource
// var gpuOpts opts.GpuOpts
// gpuOpts = opts.GpuOpts{container.HostConfig.DeviceRequests}
d . Set ( "gpus" , "all" )
}
2015-02-17 11:28:33 -05:00
return nil
}
2021-05-11 02:59:40 -04:00
func resourceDockerContainerReadRefreshFunc ( ctx context . Context ,
2025-04-18 06:15:43 -04:00
d * schema . ResourceData , meta interface { } ) retry . StateRefreshFunc {
2021-05-11 02:59:40 -04:00
return func ( ) ( interface { } , string , error ) {
client := meta . ( * ProviderConfig ) . DockerClient
containerID := d . Id ( )
2025-04-17 13:22:08 -04:00
var container container . InspectResponse
2021-05-11 02:59:40 -04:00
container , err := client . ContainerInspect ( ctx , containerID )
if err != nil {
return container , "pending" , err
}
jsonObj , _ := json . MarshalIndent ( container , "" , "\t" )
log . Printf ( "[DEBUG] Docker container inspect: %s" , jsonObj )
if container . State . Running ||
! container . State . Running && ! d . Get ( "must_run" ) . ( bool ) {
log . Printf ( "[DEBUG] Container %s is running: %v" , containerID , container . State . Running )
return container , "running" , nil
}
if creationTime . IsZero ( ) { // We didn't just create it, so don't wait around
log . Printf ( "[DEBUG] Container %s was not created" , containerID )
return container , "pending" , errContainerFailedToBeCreated
}
finishTime , err := time . Parse ( time . RFC3339 , container . State . FinishedAt )
if err != nil {
log . Printf ( "[ERROR] Container %s finish time could not be parsed: %s" , containerID , container . State . FinishedAt )
return container , "pending" , err
}
if finishTime . After ( creationTime ) {
log . Printf ( "[INFO] Container %s exited immediately: started: %v - finished: %v" , containerID , creationTime , finishTime )
return container , "pending" , errContainerExitedImmediately
}
2021-06-21 03:24:02 -04:00
// TODO mavogel wait until all properties are exposed from the API
// dns = []
// dns_opts = []
// dns_search = []
// group_add = []
// id = "9e6d9e987923e2c3a99f17e8781c7ce3515558df0e45f8ab06f6adb2dda0de50"
// log_opts = {}
// name = "nginx"
// sysctls = {}
// tmpfs = {}
2021-05-11 02:59:40 -04:00
return container , "running" , nil
}
}
2021-03-18 03:30:54 -04:00
func resourceDockerContainerUpdate ( ctx context . Context , d * schema . ResourceData , meta interface { } ) diag . Diagnostics {
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 {
2025-04-17 13:22:08 -04:00
Name : container . RestartPolicyMode ( d . Get ( "restart" ) . ( string ) ) ,
2020-02-02 00:39:28 -05:00
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
}
2025-04-15 12:54:25 -04:00
// QF1008: could remove embedded field "Resources" from selector
updateConfig . Resources . MemorySwap = a //nolint:staticcheck
2020-02-02 00:39:28 -05:00
}
client := meta . ( * ProviderConfig ) . DockerClient
2021-03-18 03:30:54 -04:00
_ , err := client . ContainerUpdate ( ctx , d . Id ( ) , updateConfig )
2020-02-02 00:39:28 -05:00
if err != nil {
2021-03-18 03:30:54 -04:00
return diag . Errorf ( "Unable to update a container: %v" , err )
2020-02-02 00:39:28 -05:00
}
break
}
}
2015-02-17 11:28:33 -05:00
return nil
}
2021-03-18 03:30:54 -04:00
func resourceDockerContainerDelete ( ctx context . Context , d * schema . ResourceData , meta interface { } ) diag . Diagnostics {
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 ( "attach" ) . ( bool ) {
// Stop the container before removing if destroy_grace_seconds is defined
2025-04-17 13:22:08 -04:00
var timeout int
2018-10-25 02:01:38 -04:00
if d . Get ( "destroy_grace_seconds" ) . ( int ) > 0 {
2025-04-17 13:22:08 -04:00
timeout = d . Get ( "destroy_grace_seconds" ) . ( int )
2021-12-03 05:38:55 -05:00
}
2019-08-29 14:04:50 -04:00
2021-12-03 05:38:55 -05:00
log . Printf ( "[INFO] Stopping Container '%s' with timeout %v" , d . Id ( ) , timeout )
2025-04-17 13:22:08 -04:00
if err := client . ContainerStop ( ctx , d . Id ( ) , * & container . StopOptions { Timeout : & timeout } ) ; err != nil { //nolint
2021-12-03 05:38:55 -05:00
return diag . Errorf ( "Error stopping container %s: %s" , d . Id ( ) , err )
2016-07-11 11:03:02 -04:00
}
}
2025-04-17 13:22:08 -04:00
removeOpts := container . RemoveOptions {
2020-10-11 09:25:20 -04:00
RemoveVolumes : d . Get ( "remove_volumes" ) . ( bool ) ,
2021-12-03 05:38:55 -05:00
RemoveLinks : d . Get ( "rm" ) . ( bool ) ,
2015-02-17 11:28:33 -05:00
Force : true ,
}
2021-12-03 05:38:55 -05:00
log . Printf ( "[INFO] Removing Container '%s'" , d . Id ( ) )
2021-03-18 03:30:54 -04:00
if err := client . ContainerRemove ( ctx , d . Id ( ) , removeOpts ) ; err != nil {
2021-12-03 05:38:55 -05:00
if ! containsIgnorableErrorMessage ( err . Error ( ) , "No such container" , "is already in progress" ) {
return diag . Errorf ( "Error deleting container %s: %s" , d . Id ( ) , err )
}
}
waitCondition := container . WaitConditionNotRunning
if d . Get ( "rm" ) . ( bool ) {
waitCondition = container . WaitConditionRemoved
2015-02-17 11:28:33 -05:00
}
2021-12-03 05:38:55 -05:00
log . Printf ( "[INFO] Waiting for Container '%s' with condition '%s'" , d . Id ( ) , waitCondition )
waitOkC , errorC := client . ContainerWait ( ctx , d . Id ( ) , waitCondition )
2018-10-25 01:21:48 -04:00
select {
case waitOk := <- waitOkC :
log . Printf ( "[INFO] Container exited with code [%v]: '%s'" , waitOk . StatusCode , d . Id ( ) )
case err := <- errorC :
2021-06-22 09:11:32 -04:00
if ! containsIgnorableErrorMessage ( err . Error ( ) , "No such container" , "is already in progress" ) {
2021-03-18 03:30:54 -04:00
return diag . Errorf ( "Error waiting for container removal '%s': %s" , d . Id ( ) , err )
2018-10-25 01:21:48 -04:00
}
2021-12-03 05:38:55 -05:00
log . Printf ( "[INFO] Waiting for Container '%s' errord: '%s'" , d . Id ( ) , err . Error ( ) )
2018-10-25 01:21:48 -04:00
}
2015-02-17 11:28:33 -05:00
d . SetId ( "" )
return nil
}
2025-04-17 13:22:08 -04:00
func fetchDockerContainer ( ctx context . Context , ID string , client * client . Client ) ( * container . Summary , error ) {
apiContainers , err := client . ContainerList ( ctx , container . ListOptions { All : true } )
2015-02-17 11:28:33 -05:00
if err != nil {
2025-04-15 12:54:25 -04:00
// ST1005: error strings should not end with punctuation or newlines
return nil , fmt . Errorf ( "error fetching container information from Docker: %s\n" , err ) //nolint:staticcheck
2015-02-17 11:28:33 -05:00
}
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
}