2017-07-13 01:15:48 +02:00
module DockerCookbook
class DockerContainer < DockerBase
resource_name :docker_container
property :container_name , String , name_property : true
property :repo , String , default : lazy { container_name }
property :tag , String , default : 'latest'
2019-02-17 07:02:05 +01:00
property :command , [ Array , String , nil ] , coerce : proc { | v | v . is_a? ( String ) ? :: Shellwords . shellwords ( v ) : v }
property :attach_stderr , [ TrueClass , FalseClass ] , default : false , desired_state : false
property :attach_stdin , [ TrueClass , FalseClass ] , default : false , desired_state : false
property :attach_stdout , [ TrueClass , FalseClass ] , default : false , desired_state : false
property :autoremove , [ TrueClass , FalseClass ] , default : false , desired_state : false
property :cap_add , [ Array , nil ] , coerce : proc { | v | Array ( v ) . empty? ? nil : Array ( v ) }
property :cap_drop , [ Array , nil ] , coerce : proc { | v | Array ( v ) . empty? ? nil : Array ( v ) }
2017-07-13 01:15:48 +02:00
property :cgroup_parent , String , default : ''
2019-02-17 07:02:05 +01:00
property :cpu_shares , Integer , default : 0
2017-07-13 01:15:48 +02:00
property :cpuset_cpus , String , default : ''
2019-02-17 07:02:05 +01:00
property :detach , [ TrueClass , FalseClass ] , default : true , desired_state : false
2017-07-13 01:15:48 +02:00
property :devices , Array , default : [ ]
property :dns , Array , default : [ ]
property :dns_search , Array , default : [ ]
property :domain_name , String , default : ''
2019-02-17 07:02:05 +01:00
property :entrypoint , [ Array , String , nil ] , coerce : proc { | v | v . is_a? ( String ) ? :: Shellwords . shellwords ( v ) : v }
2017-07-13 01:15:48 +02:00
property :env , UnorderedArrayType , default : [ ]
2019-02-17 07:02:05 +01:00
property :env_file , [ Array , String ] , coerce : proc { | v | coerce_env_file ( v ) } , default : [ ] , desired_state : false
property :extra_hosts , [ Array , nil ] , coerce : proc { | v | Array ( v ) . empty? ? nil : Array ( v ) }
2017-07-13 01:15:48 +02:00
property :exposed_ports , PartialHashType , default : { }
2019-02-17 07:02:05 +01:00
property :force , [ TrueClass , FalseClass ] , default : false , desired_state : false
property :health_check , Hash , default : { }
property :host , [ String , nil ] , default : lazy { ENV [ 'DOCKER_HOST' ] } , desired_state : false
2017-07-13 01:15:48 +02:00
property :hostname , String
property :ipc_mode , String , default : ''
2019-02-17 07:02:05 +01:00
property :kernel_memory , [ String , Integer ] , coerce : proc { | v | coerce_to_bytes ( v ) } , default : 0
2017-07-13 01:15:48 +02:00
property :labels , [ String , Array , Hash ] , default : { } , coerce : proc { | v | coerce_labels ( v ) }
property :links , UnorderedArrayType , coerce : proc { | v | coerce_links ( v ) }
property :log_driver , %w( json-file syslog journald gelf fluentd awslogs splunk etwlogs gcplogs none ) , default : 'json-file' , desired_state : false
property :log_opts , [ Hash , nil ] , coerce : proc { | v | coerce_log_opts ( v ) } , desired_state : false
2019-02-17 07:02:05 +01:00
property :init , [ TrueClass , FalseClass , nil ]
2017-07-13 01:15:48 +02:00
property :ip_address , String
property :mac_address , String
2019-02-17 07:02:05 +01:00
property :memory , [ String , Integer ] , coerce : proc { | v | coerce_to_bytes ( v ) } , default : 0
property :memory_swap , [ String , Integer ] , coerce : proc { | v | coerce_to_bytes ( v ) } , default : 0
property :memory_swappiness , Integer , default : 0
property :memory_reservation , Integer , coerce : proc { | v | coerce_to_bytes ( v ) } , default : 0
property :network_disabled , [ TrueClass , FalseClass ] , default : false
property :network_mode , String , default : 'bridge'
property :network_aliases , [ String , Array ] , default : [ ] , coerce : proc { | v | Array ( v ) }
property :oom_kill_disable , [ TrueClass , FalseClass ] , default : false
property :oom_score_adj , Integer , default : - 500
property :open_stdin , [ TrueClass , FalseClass ] , default : false , desired_state : false
property :outfile , String
2017-07-13 01:15:48 +02:00
property :port_bindings , PartialHashType , default : { }
property :pid_mode , String , default : ''
2019-02-17 07:02:05 +01:00
property :privileged , [ TrueClass , FalseClass ] , default : false
property :publish_all_ports , [ TrueClass , FalseClass ] , default : false
property :remove_volumes , [ TrueClass , FalseClass ] , default : false
2017-07-13 01:15:48 +02:00
property :restart_maximum_retry_count , Integer , default : 0
property :restart_policy , String
2019-02-17 07:02:05 +01:00
property :runtime , String , default : 'runc'
property :ro_rootfs , [ TrueClass , FalseClass ] , default : false
property :security_opt , [ String , Array ] , coerce : proc { | v | v . nil? ? nil : Array ( v ) }
property :shm_size , [ String , Integer ] , default : '64m' , coerce : proc { | v | coerce_to_bytes ( v ) }
2017-07-13 01:15:48 +02:00
property :signal , String , default : 'SIGTERM'
2019-02-17 07:02:05 +01:00
property :stdin_once , [ TrueClass , FalseClass ] , default : false , desired_state : false
2017-07-13 01:15:48 +02:00
property :sysctls , Hash , default : { }
2019-02-17 07:02:05 +01:00
property :timeout , Integer , desired_state : false
property :tty , [ TrueClass , FalseClass ] , default : false
2017-07-13 01:15:48 +02:00
property :ulimits , [ Array , nil ] , coerce : proc { | v | coerce_ulimits ( v ) }
property :user , String , default : ''
property :userns_mode , String , default : ''
property :uts_mode , String , default : ''
property :volumes , PartialHashType , default : { } , coerce : proc { | v | coerce_volumes ( v ) }
2019-02-17 07:02:05 +01:00
property :volumes_from , [ String , Array ] , coerce : proc { | v | v . nil? ? nil : Array ( v ) }
2017-07-13 01:15:48 +02:00
property :volume_driver , String
2019-02-17 07:02:05 +01:00
property :working_dir , String , default : ''
2017-07-13 01:15:48 +02:00
# Used to store the bind property since binds is an alias to volumes
property :volumes_binds , Array
# Used to store the state of the Docker container
property :container , Docker :: Container , desired_state : false
2019-02-17 07:02:05 +01:00
# Used to store the state of the Docker container create options
property :create_options , Hash , default : { } , desired_state : false
2017-07-13 01:15:48 +02:00
# Used by :stop action. If the container takes longer than this
2019-02-17 07:02:05 +01:00
# many seconds to stop, kill it instead. A nil value (the default) means
2017-07-13 01:15:48 +02:00
# never kill the container.
2019-02-17 07:02:05 +01:00
property :kill_after , [ Integer , NilClass ] , default : nil , desired_state : false
alias_method :cmd , :command
alias_method :additional_host , :extra_hosts
alias_method :rm , :autoremove
alias_method :remove_automatically , :autoremove
alias_method :host_name , :hostname
alias_method :domainname , :domain_name
alias_method :dnssearch , :dns_search
alias_method :restart_maximum_retries , :restart_maximum_retry_count
alias_method :volume , :volumes
alias_method :binds , :volumes
alias_method :volume_from , :volumes_from
alias_method :destination , :outfile
alias_method :workdir , :working_dir
###################
# Property helpers
###################
def coerce_labels ( v )
case v
when Hash , nil
v
else
Array ( v ) . each_with_object ( { } ) do | label , h |
parts = label . split ( ':' )
h [ parts [ 0 ] ] = parts [ 1 .. - 1 ] . join ( ':' )
end
end
end
def coerce_links ( v )
case v
when DockerBase :: UnorderedArray , nil
v
else
return nil if v . empty?
# Parse docker input of /source:/container_name/dest into source:dest
DockerBase :: UnorderedArray . new ( Array ( v ) ) . map! do | link |
if link =~ %r{ ^/(?<source>.+):/ #{ name } /(?<dest>.+) }
link = " #{ Regexp . last_match [ :source ] } : #{ Regexp . last_match [ :dest ] } "
end
link
end
end
end
def to_bytes ( v )
n = v . to_i
u = v . gsub ( / \ d / , '' ) . upcase
multiplier = case u
when 'B'
1
when 'K'
1024 ** 1
when 'M'
1024 ** 2
when 'G'
1024 ** 3
when 'T'
1024 ** 4
when 'P'
1024 ** 5
when 'E'
1024 ** 6
when 'Z'
1024 ** 7
when 'Y'
1024 ** 8
else
1
end
n * multiplier
end
def coerce_to_bytes ( v )
case v
when Integer , nil
v
else
to_bytes ( v )
end
end
def coerce_log_opts ( v )
case v
when Hash , nil
v
else
Array ( v ) . each_with_object ( { } ) do | log_opt , memo |
key , value = log_opt . split ( '=' , 2 )
memo [ key ] = value
end
end
end
def coerce_ulimits ( v )
return v if v . nil?
Array ( v ) . map do | u |
u = " #{ u [ 'Name' ] } = #{ u [ 'Soft' ] } : #{ u [ 'Hard' ] } " if u . is_a? ( Hash )
u
end
end
def coerce_volumes ( v )
case v
when DockerBase :: PartialHash , nil
v
when Hash
DockerBase :: PartialHash [ v ]
else
b = [ ]
v = Array ( v ) . to_a # in case v.is_A?(Chef::Node::ImmutableArray)
v . delete_if do | x |
parts = x . split ( ':' )
b << x if parts . length > 1
end
b = nil if b . empty?
volumes_binds b
return DockerBase :: PartialHash . new if v . empty?
v . each_with_object ( DockerBase :: PartialHash . new ) { | volume , h | h [ volume ] = { } }
end
end
def state
# Always return the latest state, see #510
Docker :: Container . get ( container_name , { } , connection ) . info [ 'State' ]
rescue StandardError
{ }
end
def wait_running_state ( v )
tries = running_wait_time
tries . times do
return if state [ 'Running' ] == v
sleep 1
end
return if state [ 'Running' ] == v
# Container failed to reach correct state: Throw an error
desired_state_str = v ? 'running' : 'not running'
raise Docker :: Error :: TimeoutError , " Container #{ container_name } failed to change to #{ desired_state_str } state after #{ tries } seconds "
end
def port ( v = nil )
return @port if v . nil?
exposed_ports coerce_exposed_ports ( v )
port_bindings coerce_port_bindings ( v )
@port = v
@port
end
def parse_port ( v )
_ , protocol = v . split ( '/' )
parts = v . split ( ':' )
case parts . length
when 3
host_ip = parts [ 0 ]
host_port = parts [ 1 ] . split ( '-' )
container_port = parts [ 2 ] . split ( '-' )
when 2
host_ip = '0.0.0.0'
host_port = parts [ 0 ] . split ( '-' )
container_port = parts [ 1 ] . split ( '-' )
when 1
host_ip = ''
host_port = [ '' ]
container_port = parts [ 0 ] . split ( '-' )
end
host_port . map! ( & :to_i ) unless host_port == [ '' ]
container_port . map! ( & :to_i )
if host_port . count > 1
Chef :: Log . fatal ( " FATAL: Invalid port range! #{ host_port } " ) if host_port [ 0 ] > host_port [ 1 ]
host_port = ( host_port [ 0 ] .. host_port [ 1 ] ) . to_a
end
if container_port . count > 1
Chef :: Log . fatal ( " FATAL: Invalid port range! #{ container_port } " ) if container_port [ 0 ] > container_port [ 1 ]
container_port = ( container_port [ 0 ] .. container_port [ 1 ] ) . to_a
end
Chef :: Log . fatal ( 'FATAL: Port range size does not match!' ) if host_port . count > 1 && host_port . count != container_port . count
# qualify the port-binding protocol even when it is implicitly tcp #427.
protocol = 'tcp' if protocol . nil?
Array ( container_port ) . map . with_index do | _ , i |
{
'host_ip' = > host_ip ,
'host_port' = > host_port [ i ] . to_s ,
'container_port' = > " #{ container_port [ i ] } / #{ protocol } " ,
}
end
end
def coerce_exposed_ports ( v )
case v
when Hash , nil
v
else
x = Array ( v ) . map { | a | parse_port ( a ) }
x . flatten!
x . each_with_object ( { } ) do | y , h |
h [ y [ 'container_port' ] ] = { }
end
end
end
def coerce_port_bindings ( v )
case v
when Hash , nil
v
else
x = Array ( v ) . map { | a | parse_port ( a ) }
x . flatten!
x . each_with_object ( { } ) do | y , h |
h [ y [ 'container_port' ] ] = [ ] unless h [ y [ 'container_port' ] ]
h [ y [ 'container_port' ] ] << {
'HostIp' = > y [ 'host_ip' ] ,
'HostPort' = > y [ 'host_port' ] ,
}
end
end
end
def coerce_env_file ( v )
return v if v . empty?
Array ( v ) . map { | f | :: File . readlines ( f ) . map ( & :strip ) } . flatten
end
# log_driver and log_opts really handle this
def log_config ( value = Chef :: NOT_PASSED )
if value != Chef :: NOT_PASSED
@log_config = value
log_driver value [ 'Type' ]
log_opts value [ 'Config' ]
end
return @log_config if defined? ( @log_config )
def_logcfg = { }
def_logcfg [ 'Type' ] = log_driver if property_is_set? ( :log_driver )
def_logcfg [ 'Config' ] = log_opts if property_is_set? ( :log_opts )
def_logcfg = nil if def_logcfg . empty?
def_logcfg
end
# TODO: test image property in serverspec and kitchen, not only in rspec
# for full specs of image parsing, see spec/helpers_container_spec.rb
#
# If you say: `repo 'blah'`
# Image will be: `blah:latest`
#
# If you say: `repo 'blah'; tag '3.1'`
# Image will be: `blah:3.1`
#
# If you say: `image 'blah'`
# Repo will be: `blah`
# Tag will be: `latest`
#
# If you say: `image 'blah:3.1'`
# Repo will be: `blah`
# Tag will be: `3.1`
#
# If you say: `image 'repo/blah'`
# Repo will be: `repo/blah`
# Tag will be: `latest`
#
# If you say: `image 'repo/blah:3.1'`
# Repo will be: `repo/blah`
# Tag will be: `3.1`
#
# If you say: `image 'repo:1337/blah'`
# Repo will be: `repo:1337/blah`
# Tag will be: `latest'
#
# If you say: `image 'repo:1337/blah:3.1'`
# Repo will be: `repo:1337/blah`
# Tag will be: `3.1`
#
def image ( image = nil )
if image
if image . include? ( '/' )
# pathological case, a ':' may be present which starts the 'port'
# part of the image name and not a tag. example: 'host:1337/blah'
# fortunately, tags are only found in the 'basename' part of image
# so we can split on '/' and rebuild once the tag has been parsed.
dirname , _ , basename = image . rpartition ( '/' )
r , t = basename . split ( ':' , 2 )
r = [ dirname , r ] . join ( '/' )
else
# normal case, the ':' starts the tag part
r , t = image . split ( ':' , 2 )
end
repo r
tag t if t
end
" #{ repo } : #{ tag } "
end
def to_shellwords ( command )
command . is_a? ( String ) ? :: Shellwords . shellwords ( command ) : command
end
######################
2017-07-13 01:15:48 +02:00
# Load Current Value
2019-02-17 07:02:05 +01:00
######################
def to_snake_case ( name )
# ExposedPorts -> _exposed_ports
name = name . gsub ( / [A-Z] / ) { | x | " _ #{ x . downcase } " }
# _exposed_ports -> exposed_ports
name = name [ 1 .. - 1 ] if name . start_with? ( '_' )
name
end
2017-07-13 01:15:48 +02:00
load_current_value do
# Grab the container and assign the container property
begin
with_retries { container Docker :: Container . get ( container_name , { } , connection ) }
rescue Docker :: Error :: NotFoundError
current_value_does_not_exist!
end
# Go through everything in the container and set corresponding properties:
# c.info['Config']['ExposedPorts'] -> exposed_ports
( container . info [ 'Config' ] . to_a + container . info [ 'HostConfig' ] . to_a ) . each do | key , value |
next if value . nil? || key == 'RestartPolicy' || key == 'Binds' || key == 'ReadonlyRootfs'
# Image => image
# Set exposed_ports = ExposedPorts (etc.)
property_name = to_snake_case ( key )
public_send ( property_name , value ) if respond_to? ( property_name )
end
# load container specific labels (without engine/image ones)
load_container_labels
# these are a special case for us because our names differ from theirs
restart_policy container . info [ 'HostConfig' ] [ 'RestartPolicy' ] [ 'Name' ]
restart_maximum_retry_count container . info [ 'HostConfig' ] [ 'RestartPolicy' ] [ 'MaximumRetryCount' ]
volumes_binds container . info [ 'HostConfig' ] [ 'Binds' ]
ro_rootfs container . info [ 'HostConfig' ] [ 'ReadonlyRootfs' ]
2019-02-17 07:02:05 +01:00
ip_address ip_address_from_container_networks ( container ) unless ip_address_from_container_networks ( container ) . nil?
end
# Gets the ip address from the existing container
# current docker api of 1.16 does not have ['NetworkSettings']['Networks']
# For docker > 1.21 - use ['NetworkSettings']['Networks']
#
# @param container [Docker::Container] A container object
# @returns [String] An ip_address
def ip_address_from_container_networks ( container )
# We use the first value in 'Networks'
# We can't assume it will be 'bridged'
# It might also not match the new_resource value
if container . info [ 'NetworkSettings' ] &&
container . info [ 'NetworkSettings' ] [ 'Networks' ] &&
container . info [ 'NetworkSettings' ] [ 'Networks' ] . values [ 0 ] &&
container . info [ 'NetworkSettings' ] [ 'Networks' ] . values [ 0 ] [ 'IPAMConfig' ] &&
container . info [ 'NetworkSettings' ] [ 'Networks' ] . values [ 0 ] [ 'IPAMConfig' ] [ 'IPv4Address' ]
# Return the ip address listed
container . info [ 'NetworkSettings' ] [ 'Networks' ] . values [ 0 ] [ 'IPAMConfig' ] [ 'IPv4Address' ]
end
2017-07-13 01:15:48 +02:00
end
#########
# Actions
#########
# Super handy visual reference!
# http://gliderlabs.com/images/docker_events.png
# Loads container specific labels excluding those of engine or image.
# This insures idempotency.
def load_container_labels
image_labels = Docker :: Image . get ( container . info [ 'Image' ] , { } , connection ) . info [ 'Config' ] [ 'Labels' ] || { }
engine_labels = Docker . info ( connection ) [ 'Labels' ] || { }
labels = ( container . info [ 'Config' ] [ 'Labels' ] || { } ) . reject do | key , val |
image_labels . any? { | k , v | k == key && v == val } ||
engine_labels . any? { | k , v | k == key && v == val }
end
public_send ( :labels , labels )
end
2019-02-17 07:02:05 +01:00
action :run do
validate_container_create
call_action ( :create )
call_action ( :start )
call_action ( :delete ) if new_resource . autoremove
2017-07-13 01:15:48 +02:00
end
action :create do
validate_container_create
converge_if_changed do
action_delete
with_retries do
config = {
2019-02-17 07:02:05 +01:00
'name' = > new_resource . container_name ,
'Image' = > " #{ new_resource . repo } : #{ new_resource . tag } " ,
'Labels' = > new_resource . labels ,
'Cmd' = > to_shellwords ( new_resource . command ) ,
'AttachStderr' = > new_resource . attach_stderr ,
'AttachStdin' = > new_resource . attach_stdin ,
'AttachStdout' = > new_resource . attach_stdout ,
'Domainname' = > new_resource . domain_name ,
'Entrypoint' = > to_shellwords ( new_resource . entrypoint ) ,
'Env' = > new_resource . env + new_resource . env_file ,
'ExposedPorts' = > new_resource . exposed_ports ,
2017-07-13 01:15:48 +02:00
'Hostname' = > parsed_hostname ,
2019-02-17 07:02:05 +01:00
'MacAddress' = > new_resource . mac_address ,
'NetworkDisabled' = > new_resource . network_disabled ,
'OpenStdin' = > new_resource . open_stdin ,
'StdinOnce' = > new_resource . stdin_once ,
'Tty' = > new_resource . tty ,
'User' = > new_resource . user ,
'Volumes' = > new_resource . volumes ,
'WorkingDir' = > new_resource . working_dir ,
2017-07-13 01:15:48 +02:00
'HostConfig' = > {
2019-02-17 07:02:05 +01:00
'Binds' = > new_resource . volumes_binds ,
'CapAdd' = > new_resource . cap_add ,
'CapDrop' = > new_resource . cap_drop ,
'CgroupParent' = > new_resource . cgroup_parent ,
'CpuShares' = > new_resource . cpu_shares ,
'CpusetCpus' = > new_resource . cpuset_cpus ,
'Devices' = > new_resource . devices ,
'Dns' = > new_resource . dns ,
'DnsSearch' = > new_resource . dns_search ,
'ExtraHosts' = > new_resource . extra_hosts ,
'IpcMode' = > new_resource . ipc_mode ,
'Init' = > new_resource . init ,
'KernelMemory' = > new_resource . kernel_memory ,
'Links' = > new_resource . links ,
2017-07-13 01:15:48 +02:00
'LogConfig' = > log_config ,
2019-02-17 07:02:05 +01:00
'Memory' = > new_resource . memory ,
'MemorySwap' = > new_resource . memory_swap ,
'MemorySwappiness' = > new_resource . memory_swappiness ,
'MemoryReservation' = > new_resource . memory_reservation ,
'NetworkMode' = > new_resource . network_mode ,
'OomKillDisable' = > new_resource . oom_kill_disable ,
'OomScoreAdj' = > new_resource . oom_score_adj ,
'Privileged' = > new_resource . privileged ,
'PidMode' = > new_resource . pid_mode ,
'PortBindings' = > new_resource . port_bindings ,
'PublishAllPorts' = > new_resource . publish_all_ports ,
2017-07-13 01:15:48 +02:00
'RestartPolicy' = > {
2019-02-17 07:02:05 +01:00
'Name' = > new_resource . restart_policy ,
'MaximumRetryCount' = > new_resource . restart_maximum_retry_count ,
2017-07-13 01:15:48 +02:00
} ,
2019-02-17 07:02:05 +01:00
'ReadonlyRootfs' = > new_resource . ro_rootfs ,
'Runtime' = > new_resource . runtime ,
'SecurityOpt' = > new_resource . security_opt ,
'ShmSize' = > new_resource . shm_size ,
'Sysctls' = > new_resource . sysctls ,
2017-07-13 01:15:48 +02:00
'Ulimits' = > ulimits_to_hash ,
2019-02-17 07:02:05 +01:00
'UsernsMode' = > new_resource . userns_mode ,
'UTSMode' = > new_resource . uts_mode ,
'VolumesFrom' = > new_resource . volumes_from ,
'VolumeDriver' = > new_resource . volume_driver ,
2017-07-13 01:15:48 +02:00
} ,
}
net_config = {
'NetworkingConfig' = > {
'EndpointsConfig' = > {
2019-02-17 07:02:05 +01:00
new_resource . network_mode = > {
2017-07-13 01:15:48 +02:00
'IPAMConfig' = > {
2019-02-17 07:02:05 +01:00
'IPv4Address' = > new_resource . ip_address ,
2017-07-13 01:15:48 +02:00
} ,
2019-02-17 07:02:05 +01:00
'Aliases' = > new_resource . network_aliases ,
2017-07-13 01:15:48 +02:00
} ,
} ,
} ,
2019-02-17 07:02:05 +01:00
} if new_resource . network_mode
2017-07-13 01:15:48 +02:00
config . merge! net_config
2019-02-17 07:02:05 +01:00
# Remove any options not supported in windows
if platform? ( 'windows' )
config [ 'HostConfig' ] . delete ( 'MemorySwappiness' )
end
unless new_resource . health_check . empty?
config [ 'Healthcheck' ] = new_resource . health_check
end
# Store the state of the options and create the container
new_resource . create_options = config
2017-07-13 01:15:48 +02:00
Docker :: Container . create ( config , connection )
end
end
end
action :start do
return if state [ 'Restarting' ]
return if state [ 'Running' ]
2019-02-17 07:02:05 +01:00
converge_by " starting #{ new_resource . container_name } " do
2017-07-13 01:15:48 +02:00
with_retries do
2019-02-17 07:02:05 +01:00
current_resource . container . start
unless new_resource . detach
new_resource . timeout ? current_resource . container . wait ( new_resource . timeout ) : current_resource . container . wait
end
2017-07-13 01:15:48 +02:00
end
2019-02-17 07:02:05 +01:00
wait_running_state ( true ) if new_resource . detach
2017-07-13 01:15:48 +02:00
end
end
action :stop do
return unless state [ 'Running' ]
2019-02-17 07:02:05 +01:00
kill_after_str = " (will kill after #{ new_resource . kill_after } s) " if new_resource . kill_after
converge_by " stopping #{ new_resource . container_name } #{ kill_after_str } " do
2017-07-13 01:15:48 +02:00
begin
with_retries do
2019-02-17 07:02:05 +01:00
current_resource . container . stop! ( 'timeout' = > new_resource . kill_after )
2017-07-13 01:15:48 +02:00
wait_running_state ( false )
end
rescue Docker :: Error :: TimeoutError
2019-02-17 07:02:05 +01:00
raise Docker :: Error :: TimeoutError , " Container failed to stop, consider adding kill_after to the container #{ new_resource . container_name } "
2017-07-13 01:15:48 +02:00
end
end
end
action :kill do
return unless state [ 'Running' ]
2019-02-17 07:02:05 +01:00
converge_by " killing #{ new_resource . container_name } " do
with_retries { current_resource . container . kill ( signal : new_resource . signal ) }
2017-07-13 01:15:48 +02:00
end
end
action :run_if_missing do
return if current_resource
call_action ( :run )
end
action :pause do
return if state [ 'Paused' ]
2019-02-17 07:02:05 +01:00
converge_by " pausing #{ new_resource . container_name } " do
with_retries { current_resource . container . pause }
2017-07-13 01:15:48 +02:00
end
end
action :unpause do
return if current_resource && ! state [ 'Paused' ]
2019-02-17 07:02:05 +01:00
converge_by " unpausing #{ new_resource . container_name } " do
with_retries { current_resource . container . unpause }
2017-07-13 01:15:48 +02:00
end
end
action :restart do
2019-02-17 07:02:05 +01:00
kill_after_str = " (will kill after #{ new_resource . kill_after } s) " if new_resource . kill_after != - 1
converge_by " restarting #{ new_resource . container_name } #{ kill_after_str } " do
current_resource ? current_resource . container . restart ( 'timeout' = > new_resource . kill_after ) : call_action ( :run )
2017-07-13 01:15:48 +02:00
end
end
action :reload do
2019-02-17 07:02:05 +01:00
converge_by " reloading #{ new_resource . container_name } " do
with_retries { current_resource . container . kill ( signal : 'SIGHUP' ) }
2017-07-13 01:15:48 +02:00
end
end
action :redeploy do
validate_container_create
# never start containers resulting from a previous action :create #432
should_create = state [ 'Running' ] == false && state [ 'StartedAt' ] == '0001-01-01T00:00:00Z'
call_action ( :delete )
call_action ( should_create ? :create : :run )
end
action :delete do
return unless current_resource
call_action ( :unpause )
call_action ( :stop )
2019-02-17 07:02:05 +01:00
converge_by " deleting #{ new_resource . container_name } " do
with_retries { current_resource . container . delete ( force : new_resource . force , v : new_resource . remove_volumes ) }
2017-07-13 01:15:48 +02:00
end
end
action :remove do
call_action ( :delete )
end
action :commit do
2019-02-17 07:02:05 +01:00
converge_by " committing #{ new_resource . container_name } " do
2017-07-13 01:15:48 +02:00
with_retries do
2019-02-17 07:02:05 +01:00
new_image = current_resource . container . commit
new_image . tag ( 'repo' = > new_resource . repo , 'tag' = > new_resource . tag , 'force' = > new_resource . force )
2017-07-13 01:15:48 +02:00
end
end
end
action :export do
2019-02-17 07:02:05 +01:00
raise " Please set outfile property on #{ new_resource . container_name } " if new_resource . outfile . nil?
converge_by " exporting #{ new_resource . container_name } " do
2017-07-13 01:15:48 +02:00
with_retries do
2019-02-17 07:02:05 +01:00
:: File . open ( new_resource . outfile , 'w' ) { | f | current_resource . container . export { | chunk | f . write ( chunk ) } }
end
end
end
declare_action_class . class_eval do
def validate_container_create
if new_resource . property_is_set? ( :restart_policy ) &&
new_resource . restart_policy != 'no' &&
new_resource . restart_policy != 'always' &&
new_resource . restart_policy != 'unless-stopped' &&
new_resource . restart_policy != 'on-failure'
raise Chef :: Exceptions :: ValidationFailed , 'restart_policy must be either no, always, unless-stopped, or on-failure.'
end
if new_resource . autoremove == true && ( new_resource . property_is_set? ( :restart_policy ) && restart_policy != 'no' )
raise Chef :: Exceptions :: ValidationFailed , 'Conflicting options restart_policy and autoremove.'
end
if new_resource . detach == true &&
(
2019-10-26 10:44:08 +02:00
new_resource . attach_stderr == true ||
new_resource . attach_stdin == true ||
new_resource . attach_stdout == true ||
new_resource . stdin_once == true
2019-02-17 07:02:05 +01:00
)
raise Chef :: Exceptions :: ValidationFailed , 'Conflicting options detach, attach_stderr, attach_stdin, attach_stdout, stdin_once.'
end
if new_resource . network_mode == 'host' &&
(
2019-10-26 10:44:08 +02:00
! ( new_resource . hostname . nil? || new_resource . hostname . empty? ) ||
! ( new_resource . mac_address . nil? || new_resource . mac_address . empty? )
2019-02-17 07:02:05 +01:00
)
raise Chef :: Exceptions :: ValidationFailed , 'Cannot specify hostname or mac_address when network_mode is host.'
end
if new_resource . network_mode == 'container' &&
(
2019-10-26 10:44:08 +02:00
! ( new_resource . hostname . nil? || new_resource . hostname . empty? ) ||
! ( new_resource . dns . nil? || new_resource . dns . empty? ) ||
! ( new_resource . dns_search . nil? || new_resource . dns_search . empty? ) ||
! ( new_resource . mac_address . nil? || new_resource . mac_address . empty? ) ||
! ( new_resource . extra_hosts . nil? || new_resource . extra_hosts . empty? ) ||
! ( new_resource . exposed_ports . nil? || new_resource . exposed_ports . empty? ) ||
! ( new_resource . port_bindings . nil? || new_resource . port_bindings . empty? ) ||
! ( new_resource . publish_all_ports . nil? || new_resource . publish_all_ports . empty? ) ||
! new_resource . port . nil?
2019-02-17 07:02:05 +01:00
)
raise Chef :: Exceptions :: ValidationFailed , 'Cannot specify hostname, dns, dns_search, mac_address, extra_hosts, exposed_ports, port_bindings, publish_all_ports, port when network_mode is container.'
end
end
def parsed_hostname
return nil if new_resource . network_mode == 'host'
new_resource . hostname
end
def call_action ( action )
send ( " action_ #{ action } " )
load_current_resource
end
def state
current_resource ? current_resource . state : { }
end
def ulimits_to_hash
return nil if new_resource . ulimits . nil?
new_resource . ulimits . map do | u |
name = u . split ( '=' ) [ 0 ]
soft = u . split ( '=' ) [ 1 ] . split ( ':' ) [ 0 ]
hard = u . split ( '=' ) [ 1 ] . split ( ':' ) [ 1 ]
{ 'Name' = > name , 'Soft' = > soft . to_i , 'Hard' = > hard . to_i }
2017-07-13 01:15:48 +02:00
end
end
end
end
end