diff --git a/CHANGELOG.md b/CHANGELOG.md index a75e0ee0c..8f59be3ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- Update to work on Chef 18 in unified mode, fixes #1222 [@b-dean](https://github.com/b-dean) + + The following resources are now custom resources: + + - `docker_installation` + - `docker_installation_package` + - `docker_installation_script` + - `docker_installation_tarball` + - `docker_service` + - `docker_service_base` + - `docker_service_manager` + - `docker_service_manager_execute` + - `docker_service_manager_systemd` + + This means their classes are no longer in the `DockerCookbook` module. + ## 10.4.9 - *2023-05-16* ## 10.4.8 - *2023-04-24* diff --git a/libraries/docker_base.rb b/libraries/docker_base.rb deleted file mode 100644 index ff4d793f0..000000000 --- a/libraries/docker_base.rb +++ /dev/null @@ -1,148 +0,0 @@ -module DockerCookbook - class DockerBase < Chef::Resource - require 'docker' - require 'shellwords' - - ################ - # Helper methods - ################ - - def connection - @connection ||= begin - opts = {} - opts[:read_timeout] = read_timeout if read_timeout - opts[:write_timeout] = write_timeout if write_timeout - - if host =~ /^tcp:/ - opts[:scheme] = 'https' if tls || !tls_verify.nil? - opts[:ssl_ca_file] = tls_ca_cert if tls_ca_cert - opts[:client_cert] = tls_client_cert if tls_client_cert - opts[:client_key] = tls_client_key if tls_client_key - end - Docker::Connection.new(host || Docker.url, opts) - end - end - - def with_retries(&_block) - tries = api_retries - begin - yield - # Only catch errors that can be fixed with retries. - rescue Docker::Error::ServerError, # 500 - Docker::Error::UnexpectedResponseError, # 400 - Docker::Error::TimeoutError, - Docker::Error::IOError - tries -= 1 - retry if tries > 0 - raise - end - end - - def call_action(_action) - new_resource.run_action - end - - ######### - # Classes - ######### - - class UnorderedArray < Array - def ==(other) - # If I (desired env) am a subset of the current env, let == return true - other.is_a?(Array) && all? { |val| other.include?(val) } - end - end - - class PartialHash < Hash - def ==(other) - other.is_a?(Hash) && all? { |key, val| other.key?(key) && other[key] == val } - end - end - - ################ - # Type Constants - # - # These will be used when declaring resource property types in the - # docker_service, docker_container, and docker_image resource. - # - ################ - - UnorderedArrayType = property_type( - is: [UnorderedArray, nil], - coerce: proc { |v| v.nil? ? nil : UnorderedArray.new(Array(v)) } - ) unless defined?(UnorderedArrayType) - - PartialHashType = property_type( - is: [PartialHash, nil], - coerce: proc { |v| v.nil? ? nil : PartialHash[v] } - ) unless defined?(PartialHashType) - - ##################### - # Resource properties - ##################### - - property :api_retries, Integer, - default: 3, - desired_state: false - - property :read_timeout, Integer, - default: 60, - desired_state: false - - property :write_timeout, Integer, - desired_state: false - - property :running_wait_time, Integer, - default: 20, - desired_state: false - - property :tls, [TrueClass, FalseClass, nil], - default: lazy { ENV['DOCKER_TLS'] }, - desired_state: false - - property :tls_verify, [TrueClass, FalseClass, nil], - default: lazy { ENV['DOCKER_TLS_VERIFY'] }, - desired_state: false - - property :tls_ca_cert, [String, nil], - default: lazy { ENV['DOCKER_CERT_PATH'] ? "#{ENV['DOCKER_CERT_PATH']}/ca.pem" : nil }, - desired_state: false - - property :tls_server_cert, String, - desired_state: false - - property :tls_server_key, String, - desired_state: false - - property :tls_client_cert, [String, nil], - default: lazy { ENV['DOCKER_CERT_PATH'] ? "#{ENV['DOCKER_CERT_PATH']}/cert.pem" : nil }, - desired_state: false - - property :tls_client_key, [String, nil], - default: lazy { ENV['DOCKER_CERT_PATH'] ? "#{ENV['DOCKER_CERT_PATH']}/key.pem" : nil }, - desired_state: false - - alias_method :tlscacert, :tls_ca_cert - alias_method :tlscert, :tls_server_cert - alias_method :tlskey, :tls_server_key - alias_method :tlsverify, :tls_verify - - action_class do - def parse_registry_host(val) - # example values for val, can be prefixed with http(s):// : - # image (=> Docker Hub) - # organization/image (=> Docker Hub) - # domain.ext/image (=> 3rd party registry) - # domain.ext/.../image (=> 3rd party registry) - # - first_part = val.sub(%r{https?://}, '').split('/').first - - # looks like a host name of a custom docker registry - return first_part if first_part.include?('.') - - # default host - 'index.docker.io' - end - end - end -end diff --git a/libraries/docker_installation_package.rb b/libraries/docker_installation_package.rb deleted file mode 100644 index 75906a439..000000000 --- a/libraries/docker_installation_package.rb +++ /dev/null @@ -1,169 +0,0 @@ -module DockerCookbook - class DockerInstallationPackage < DockerBase - # Chef 18 compatibility - unified_mode false - - resource_name :docker_installation_package - provides :docker_installation_package - - property :setup_docker_repo, [true, false], default: true, desired_state: false - property :repo_channel, String, default: 'stable' - property :package_name, String, default: 'docker-ce', desired_state: false - property :package_version, String, desired_state: false - property :version, String, desired_state: false - property :package_options, String, desired_state: false - - def el7? - return true if platform_family?('rhel') && node['platform_version'].to_i == 7 - false - end - - def fedora? - return true if platform?('fedora') - false - end - - def debuntu? - return true if platform_family?('debian') - false - end - - def debian? - return true if platform?('debian') - false - end - - def ubuntu? - return true if platform?('ubuntu') - false - end - - def stretch? - return true if platform?('debian') && node['platform_version'].to_i == 9 - false - end - - def buster? - return true if platform?('debian') && node['platform_version'].to_i == 10 - false - end - - def bullseye? - return true if platform?('debian') && node['platform_version'].to_i == 11 - false - end - - def bionic? - return true if platform?('ubuntu') && node['platform_version'] == '18.04' - false - end - - def focal? - return true if platform?('ubuntu') && node['platform_version'] == '20.04' - false - end - - def jammy? - return true if platform?('ubuntu') && node['platform_version'] == '22.04' - false - end - - # https://github.com/chef/chef/issues/4103 - def version_string(v) - return if v.nil? - codename = if stretch? # deb 9 - 'stretch' - elsif buster? # deb 10 - 'buster' - elsif bullseye? # deb 11 - 'bullseye' - elsif bionic? # ubuntu 18.04 - 'bionic' - elsif focal? # ubuntu 20.04 - 'focal' - elsif jammy? # ubuntu 22.04 - 'jammy' - end - - # https://github.com/seemethere/docker-ce-packaging/blob/9ba8e36e8588ea75209d813558c8065844c953a0/deb/gen-deb-ver#L16-L20 - test_version = '3' - - if v.to_f < 18.06 && !bionic? - return "#{v}~ce-0~debian" if debian? - return "#{v}~ce-0~ubuntu" if ubuntu? - elsif v.to_f >= 18.09 && debuntu? - return "5:#{v}~#{test_version}-0~debian-#{codename}" if debian? - return "5:#{v}~#{test_version}-0~ubuntu-#{codename}" if ubuntu? - else - return "#{v}~ce~#{test_version}-0~debian" if debian? - return "#{v}~ce~#{test_version}-0~ubuntu" if ubuntu? - v - end - end - - action :create do - if new_resource.setup_docker_repo - if platform_family?('rhel', 'fedora') - arch = node['kernel']['machine'] - platform = - if platform?('fedora') - 'fedora' - # s390x is only available under rhel platform - elsif platform?('redhat') && arch == 's390x' - 'rhel' - else - 'centos' - end - - yum_repository 'Docker' do - baseurl "https://download.docker.com/linux/#{platform}/#{node['platform_version'].to_i}/#{arch}/#{new_resource.repo_channel}" - gpgkey "https://download.docker.com/linux/#{platform}/gpg" - description "Docker #{new_resource.repo_channel.capitalize} repository" - gpgcheck true - enabled true - end - elsif platform_family?('debian') - deb_arch = - case node['kernel']['machine'] - when 'x86_64' - 'amd64' - when 'aarch64' - 'arm64' - when 'armv7l' - 'armhf' - when 'ppc64le' - 'ppc64el' - else - node['kernel']['machine'] - end - - package 'apt-transport-https' - - apt_repository 'Docker' do - components Array(new_resource.repo_channel) - uri "https://download.docker.com/linux/#{node['platform']}" - arch deb_arch - key "https://download.docker.com/linux/#{node['platform']}/gpg" - action :add - end - else - Chef::Log.warn("Cannot setup the Docker repo for platform #{node['platform']}. Skipping.") - end - end - - version = new_resource.package_version || version_string(new_resource.version) - - package new_resource.package_name do - version version - options new_resource.package_options - action :install - end - end - - action :delete do - package new_resource.package_name do - action :remove - end - end - end -end diff --git a/libraries/docker_installation_script.rb b/libraries/docker_installation_script.rb deleted file mode 100644 index 86892fc85..000000000 --- a/libraries/docker_installation_script.rb +++ /dev/null @@ -1,50 +0,0 @@ -module DockerCookbook - class DockerInstallationScript < DockerBase - # Chef 18 compatibility - unified_mode false - - resource_name :docker_installation_script - provides :docker_installation_script - - provides :docker_installation, os: 'linux' - - property :repo, %w(main test experimental), default: 'main', desired_state: false - property :script_url, String, default: lazy { default_script_url }, desired_state: false - - default_action :create - - ######################### - # property helper methods - ######################### - - def default_script_url - case repo - when 'main' - 'https://get.docker.com/' - when 'test' - 'https://test.docker.com/' - when 'experimental' - 'https://experimental.docker.com/' - end - end - - ######### - # Actions - ######### - - action :create do - package 'curl' - - execute 'install docker' do - command "curl -sSL #{new_resource.script_url} | sh" - creates '/usr/bin/docker' - end - end - - action :delete do - package %w(docker-ce docker-engine) do - action :remove - end - end - end -end diff --git a/libraries/docker_installation_tarball.rb b/libraries/docker_installation_tarball.rb deleted file mode 100644 index 8194b0b4d..000000000 --- a/libraries/docker_installation_tarball.rb +++ /dev/null @@ -1,120 +0,0 @@ -module DockerCookbook - class DockerInstallationTarball < DockerBase - # Chef 18 compatibility - unified_mode false - - resource_name :docker_installation_tarball - provides :docker_installation_tarball - - property :checksum, String, default: lazy { default_checksum }, desired_state: false - property :source, String, default: lazy { default_source }, desired_state: false - property :channel, String, default: 'stable', desired_state: false - property :version, String, default: '20.10.11', desired_state: false - - ################## - # Property Helpers - ################## - - def docker_kernel - node['kernel']['name'] - end - - def docker_arch - node['kernel']['machine'] - end - - def default_source - "https://download.docker.com/#{docker_kernel.downcase}/static/#{channel}/#{docker_arch}/#{default_filename(version)}" - end - - def default_filename(version) - # https://download.docker.com/linux/static/stable/x86_64/ - regex = /^(?\d*)\.(?\d*)\./ - semver = regex.match(version) - if semver['major'].to_i >= 19 - "docker-#{version}.tgz" - elsif semver['major'].to_i == 18 && semver['minor'].to_i > 6 - "docker-#{version}.tgz" - else - "docker-#{version}-ce.tgz" - end - end - - def default_checksum - case docker_kernel - when 'Darwin' - case version - when '18.03.1' then 'bbfb9c599a4fdb45523496c2ead191056ff43d6be90cf0e348421dd56bc3dcf0' - when '18.06.3' then 'f7347ef27db9a438b05b8f82cd4c017af5693fe26202d9b3babf750df3e05e0c' - when '18.09.9' then 'ed83a3d51fef2bbcdb19d091ff0690a233aed4bbb47d2f7860d377196e0143a0' - when '19.03.15' then '61672045675798b2075d4790665b74336c03b6d6084036ef22720af60614e50d' - when '20.10.11' then '8f338ba618438fa186d1fa4eae32376cca58f86df2b40b5027c193202fad2acf' - end - when 'Linux' - case version - when '18.03.1' then '0e245c42de8a21799ab11179a4fce43b494ce173a8a2d6567ea6825d6c5265aa' - when '18.06.3' then '346f9394393ee8db5f8bd1e229ee9d90e5b36931bdd754308b2ae68884dd6822' - when '18.09.9' then '82a362af7689038c51573e0fd0554da8703f0d06f4dfe95dd5bda5acf0ae45fb' - when '19.03.15' then '5504d190eef37355231325c176686d51ade6e0cabe2da526d561a38d8611506f' - when '20.10.11' then 'dd6ff72df1edfd61ae55feaa4aadb88634161f0aa06dbaaf291d1be594099ff3' - end - end - end - - ######### - # Actions - ######### - - action :create do - package 'tar' - - # Pull a precompiled binary off the network - remote_file docker_tarball do - source new_resource.source - checksum new_resource.checksum - owner 'root' - group 'root' - mode '0755' - action :create - notifies :run, 'execute[extract tarball]', :immediately - end - - execute 'extract tarball' do - action :nothing - command "tar -xzf #{docker_tarball} --strip-components=1 -C #{docker_bin_prefix}" - end - - group 'docker' do - system true - append true - end - end - - action :delete do - file docker_bin do - action :delete - end - - group 'docker' do - action :delete - end - end - - ################ - # Action Helpers - ################ - action_class do - def docker_bin_prefix - '/usr/bin' - end - - def docker_bin - "#{docker_bin_prefix}/docker" - end - - def docker_tarball - "#{Chef::Config[:file_cache_path]}/docker-#{new_resource.version}.tgz" - end - end - end -end diff --git a/libraries/docker_service.rb b/libraries/docker_service.rb deleted file mode 100644 index 3a6e9c253..000000000 --- a/libraries/docker_service.rb +++ /dev/null @@ -1,127 +0,0 @@ -module DockerCookbook - require_relative 'docker_service_base' - - class DockerService < DockerServiceBase - # Chef 18 compatibility - unified_mode false - - resource_name :docker_service - - # register with the resource resolution system - provides :docker_service - - # installation type and service_manager - property :install_method, %w(script package tarball none auto), default: lazy { docker_install_method }, desired_state: false - property :service_manager, %w(execute systemd auto), default: 'auto', desired_state: false - - # docker_installation_script - property :repo, String, desired_state: false - property :script_url, String, desired_state: false - - # docker_installation_tarball - property :checksum, String, desired_state: false - property :docker_bin, String, desired_state: false - property :source, String, desired_state: false - - # docker_installation_package - property :package_version, String, desired_state: false - property :package_name, String, desired_state: false - property :setup_docker_repo, [true, false], desired_state: false - - # package and tarball - property :version, String, desired_state: false - property :package_options, String, desired_state: false - - ################ - # Helper Methods - ################ - def copy_properties_to(to, *properties) - properties = self.class.properties.keys if properties.empty? - properties.each do |p| - # If the property is set on from, and exists on to, set the - # property on to - if to.class.properties.include?(p) && property_is_set?(p) - to.send(p, send(p)) - end - end - end - - action_class.class_eval do - def validate_install_method - if new_resource.property_is_set?(:version) && - new_resource.install_method != 'package' && - new_resource.install_method != 'tarball' - raise Chef::Exceptions::ValidationFailed, 'Version property only supported for package and tarball installation methods' - end - end - - def installation(&block) - case new_resource.install_method - when 'auto' - install = docker_installation(new_resource.name, &block) - when 'script' - install = docker_installation_script(new_resource.name, &block) - when 'package' - install = docker_installation_package(new_resource.name, &block) - when 'tarball' - install = docker_installation_tarball(new_resource.name, &block) - when 'none' - Chef::Log.info('Skipping Docker installation. Assuming it was handled previously.') - return - end - copy_properties_to(install) - install - end - - def svc_manager(&block) - case new_resource.service_manager - when 'auto' - svc = docker_service_manager(new_resource.name, &block) - when 'execute' - svc = docker_service_manager_execute(new_resource.name, &block) - when 'systemd' - svc = docker_service_manager_systemd(new_resource.name, &block) - end - copy_properties_to(svc) - svc - end - end - - ######### - # Actions - ######### - - action :create do - validate_install_method - - installation do - action :create - notifies :restart, new_resource, :immediately - end - end - - action :delete do - installation do - action :delete - end - end - - action :start do - svc_manager do - action :start - end - end - - action :stop do - svc_manager do - action :stop - end - end - - action :restart do - svc_manager do - action :restart - end - end - end -end diff --git a/libraries/docker_service_base.rb b/libraries/docker_service_base.rb deleted file mode 100644 index 1925848d0..000000000 --- a/libraries/docker_service_base.rb +++ /dev/null @@ -1,124 +0,0 @@ -module DockerCookbook - class DockerServiceBase < DockerBase - ################ - # Helper Methods - ################ - require_relative 'helpers_service' - include DockerHelpers::Service - - ##################### - # resource properties - ##################### - - resource_name :docker_service_base - provides :docker_service_base - - # register with the resource resolution system - provides :docker_service_manager - - # Environment variables to docker service - property :env_vars, Hash - - # daemon management - property :instance, String, name_property: true, desired_state: false - property :auto_restart, [true, false], default: false - property :api_cors_header, String - property :bridge, String - property :bip, [IPV4_ADDR, IPV4_CIDR, IPV6_ADDR, IPV6_CIDR, nil] - property :cluster_store, String - property :cluster_advertise, String - property :cluster_store_opts, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } - property :daemon, [true, false], default: true - property :data_root, String - property :debug, [true, false], default: false - property :dns, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } - property :dns_search, Array - property :exec_driver, ['native', 'lxc', nil] - property :exec_opts, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } - property :fixed_cidr, String - property :fixed_cidr_v6, String - property :group, String, default: 'docker' - property :host, [String, Array], coerce: proc { |v| coerce_host(v) }, desired_state: false - property :icc, [true, false] - property :insecure_registry, [Array, String, nil], coerce: proc { |v| coerce_insecure_registry(v) } - property :ip, [IPV4_ADDR, IPV6_ADDR, nil] - property :ip_forward, [true, false] - property :ipv4_forward, [true, false], default: true - property :ipv6_forward, [true, false], default: true - property :ip_masq, [true, false] - property :iptables, [true, false] - property :ipv6, [true, false] - property :default_ip_address_pool, String - property :log_level, %w(debug info warn error fatal) - property :labels, [String, Array], coerce: proc { |v| coerce_daemon_labels(v) }, desired_state: false - property :log_driver, %w(json-file syslog journald gelf fluentd awslogs splunk etwlogs gcplogs logentries loki-docker none local) - property :log_opts, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } - property :mount_flags, String - property :mtu, String - property :pidfile, String, default: lazy { "/var/run/#{docker_name}.pid" } - property :registry_mirror, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } - property :storage_driver, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } - property :selinux_enabled, [true, false] - property :storage_opts, Array - property :default_ulimit, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } - property :userland_proxy, [true, false] - property :disable_legacy_registry, [true, false] - property :userns_remap, String - property :live_restore, [true, false], default: false - - # These are options specific to systemd configuration such as - # LimitNOFILE or TasksMax that you may wannt to use to customize - # the environment in which Docker runs. - property :systemd_opts, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } - property :systemd_socket_opts, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } - - # These are unvalidated daemon arguments passed in as a string. - property :misc_opts, String - - # environment variables to set before running daemon - property :http_proxy, String - property :https_proxy, String - property :no_proxy, String - property :tmpdir, String - - # logging - property :logfile, String, default: '/var/log/docker.log' - - # docker-wait-ready timeout - property :service_timeout, Integer, default: 20 - - alias_method :label, :labels - alias_method :run_group, :group - alias_method :graph, :data_root - - action_class do - def libexec_dir - return '/usr/libexec/docker' if platform_family?('rhel') - '/usr/lib/docker' - end - - def create_docker_wait_ready - directory libexec_dir do - owner 'root' - group 'root' - mode '0755' - action :create - end - - template "#{libexec_dir}/#{docker_name}-wait-ready" do - source 'default/docker-wait-ready.erb' - owner 'root' - group 'root' - mode '0755' - variables( - docker_cmd: docker_cmd, - libexec_dir: libexec_dir, - service_timeout: new_resource.service_timeout - ) - cookbook 'docker' - action :create - end - end - end - end -end diff --git a/libraries/docker_service_manager_execute.rb b/libraries/docker_service_manager_execute.rb deleted file mode 100644 index e5f369567..000000000 --- a/libraries/docker_service_manager_execute.rb +++ /dev/null @@ -1,58 +0,0 @@ -module DockerCookbook - class DockerServiceManagerExecute < DockerServiceBase - # Chef 18 compatibility - unified_mode false - - resource_name :docker_service_manager_execute - provides :docker_service_manager_execute - - # Start the service - action :start do - # enable ipv4 forwarding - execute 'enable net.ipv4.conf.all.forwarding' do - command '/sbin/sysctl net.ipv4.conf.all.forwarding=1' - not_if '/sbin/sysctl -q -n net.ipv4.conf.all.forwarding | grep ^1$' - action :run - end - - # enable ipv6 forwarding - execute 'enable net.ipv6.conf.all.forwarding' do - command '/sbin/sysctl net.ipv6.conf.all.forwarding=1' - not_if '/sbin/sysctl -q -n net.ipv6.conf.all.forwarding | grep ^1$' - action :run - end - - # Go doesn't support detaching processes natively, so we have - # to manually fork it from the shell with & - # https://github.com/docker/docker/issues/2758 - bash "start docker #{name}" do - code "#{docker_daemon_cmd} >> #{logfile} 2>&1 &" - environment 'HTTP_PROXY' => http_proxy, - 'HTTPS_PROXY' => https_proxy, - 'NO_PROXY' => no_proxy, - 'TMPDIR' => tmpdir - not_if "ps -ef | grep -v grep | grep #{Shellwords.escape(docker_daemon_cmd)}" - action :run - end - - create_docker_wait_ready - - execute 'docker-wait-ready' do - command "#{libexec_dir}/#{docker_name}-wait-ready" - end - end - - action :stop do - execute "stop docker #{name}" do - command "kill `cat #{pidfile}` && while [ -e #{pidfile} ]; do sleep 1; done" - timeout 10 - only_if "#{docker_cmd} ps | head -n 1 | grep ^CONTAINER" - end - end - - action :restart do - action_stop - action_start - end - end -end diff --git a/libraries/docker_service_manager_systemd.rb b/libraries/docker_service_manager_systemd.rb deleted file mode 100644 index 8a0e7dc4f..000000000 --- a/libraries/docker_service_manager_systemd.rb +++ /dev/null @@ -1,146 +0,0 @@ -module DockerCookbook - class DockerServiceManagerSystemd < DockerServiceBase - # Chef 18 compatibility - unified_mode false - - resource_name :docker_service_manager_systemd - provides :docker_service_manager_systemd - - provides :docker_service_manager, os: 'linux' do |_node| - Chef::Platform::ServiceHelpers.service_resource_providers.include?(:systemd) - end - - action :start do - create_docker_wait_ready - - # stock systemd socket file - template "/lib/systemd/system/#{docker_name}.socket" do - source 'systemd/docker.socket.erb' - cookbook 'docker' - owner 'root' - group 'root' - mode '0644' - variables( - config: new_resource, - docker_name: docker_name, - docker_socket: connect_socket - ) - action connect_socket.nil? ? :delete : :create - not_if { docker_name == 'docker' && ::File.exist?('/lib/systemd/system/docker.socket') } - end - - directory '/etc/containerd' - - file '/etc/containerd/config.toml' do - content 'disabled_plugins = ["cri"]' - action :create_if_missing - end - - template '/etc/systemd/system/containerd.service' do - source 'systemd/containerd.service.erb' - cookbook 'docker' - owner 'root' - group 'root' - mode '0644' - variables(service_type: docker_containerd_service_type) - not_if { ::File.exist?('/lib/systemd/system/containerd.service') } - only_if { docker_containerd } - notifies :run, 'execute[systemctl daemon-reload]', :immediately - end - - # stock systemd unit file - # See - https://github.com/docker/docker-ce-packaging/blob/master/systemd/docker.service - template "/lib/systemd/system/#{docker_name}.service" do - source 'systemd/docker.service.erb' - cookbook 'docker' - owner 'root' - group 'root' - mode '0644' - variables( - docker_name: docker_name, - docker_daemon_cmd: docker_daemon_cmd, - docker_socket: connect_socket, - containerd: docker_containerd - ) - not_if { docker_name == 'docker' && ::File.exist?('/lib/systemd/system/docker.service') } - end - - # this overrides the main systemd socket - template "/etc/systemd/system/#{docker_name}.socket" do - source 'systemd/docker.socket-override.erb' - cookbook 'docker' - owner 'root' - group 'root' - mode '0644' - variables( - config: new_resource, - docker_name: docker_name, - docker_socket: connect_socket, - systemd_socket_args: systemd_socket_args - ) - action connect_socket.nil? ? :delete : :create - end - - # this overrides the main systemd service - template "/etc/systemd/system/#{docker_name}.service" do - source 'systemd/docker.service-override.erb' - cookbook 'docker' - owner 'root' - group 'root' - mode '0644' - variables( - config: new_resource, - docker_name: docker_name, - docker_socket: connect_socket, - docker_daemon_cmd: docker_daemon_cmd, - systemd_args: systemd_args, - docker_wait_ready: "#{libexec_dir}/#{docker_name}-wait-ready", - env_vars: new_resource.env_vars, - containerd: docker_containerd - ) - notifies :run, 'execute[systemctl daemon-reload]', :immediately - notifies :run, "execute[systemctl try-restart #{docker_name}]", :immediately - end - - # avoid 'Unit file changed on disk' warning - execute 'systemctl daemon-reload' do - command '/bin/systemctl daemon-reload' - action :nothing - end - - # restart if changes in template resources - execute "systemctl try-restart #{docker_name}" do - command "/bin/systemctl try-restart #{docker_name}" - action :nothing - end - - # service management resource - service docker_name do - provider Chef::Provider::Service::Systemd - supports status: true - action [:enable, :start] - only_if { ::File.exist?("/lib/systemd/system/#{docker_name}.service") } - retries 1 - end - end - - action :stop do - # service management resource - service docker_name do - provider Chef::Provider::Service::Systemd - supports status: true - action [:disable, :stop] - only_if { ::File.exist?("/lib/systemd/system/#{docker_name}.service") } - end - - systemd_unit "#{docker_name}.socket" do - action [:disable, :stop] - end - end - - action :restart do - action_stop - action_start - end - end -end diff --git a/resources/installation_package.rb b/resources/installation_package.rb new file mode 100644 index 000000000..b45ef72db --- /dev/null +++ b/resources/installation_package.rb @@ -0,0 +1,165 @@ +unified_mode true +use 'partial/_base' + +resource_name :docker_installation_package +provides :docker_installation_package + +property :setup_docker_repo, [true, false], default: true, desired_state: false +property :repo_channel, String, default: 'stable' +property :package_name, String, default: 'docker-ce', desired_state: false +property :package_version, String, desired_state: false +property :version, String, desired_state: false +property :package_options, String, desired_state: false + +def el7? + return true if platform_family?('rhel') && node['platform_version'].to_i == 7 + false +end + +def fedora? + return true if platform?('fedora') + false +end + +def debuntu? + return true if platform_family?('debian') + false +end + +def debian? + return true if platform?('debian') + false +end + +def ubuntu? + return true if platform?('ubuntu') + false +end + +def stretch? + return true if platform?('debian') && node['platform_version'].to_i == 9 + false +end + +def buster? + return true if platform?('debian') && node['platform_version'].to_i == 10 + false +end + +def bullseye? + return true if platform?('debian') && node['platform_version'].to_i == 11 + false +end + +def bionic? + return true if platform?('ubuntu') && node['platform_version'] == '18.04' + false +end + +def focal? + return true if platform?('ubuntu') && node['platform_version'] == '20.04' + false +end + +def jammy? + return true if platform?('ubuntu') && node['platform_version'] == '22.04' + false +end + +# https://github.com/chef/chef/issues/4103 +def version_string(v) + return if v.nil? + codename = if stretch? # deb 9 + 'stretch' + elsif buster? # deb 10 + 'buster' + elsif bullseye? # deb 11 + 'bullseye' + elsif bionic? # ubuntu 18.04 + 'bionic' + elsif focal? # ubuntu 20.04 + 'focal' + elsif jammy? # ubuntu 22.04 + 'jammy' + end + + # https://github.com/seemethere/docker-ce-packaging/blob/9ba8e36e8588ea75209d813558c8065844c953a0/deb/gen-deb-ver#L16-L20 + test_version = '3' + + if v.to_f < 18.06 && !bionic? + return "#{v}~ce-0~debian" if debian? + return "#{v}~ce-0~ubuntu" if ubuntu? + elsif v.to_f >= 18.09 && debuntu? + return "5:#{v}~#{test_version}-0~debian-#{codename}" if debian? + return "5:#{v}~#{test_version}-0~ubuntu-#{codename}" if ubuntu? + else + return "#{v}~ce~#{test_version}-0~debian" if debian? + return "#{v}~ce~#{test_version}-0~ubuntu" if ubuntu? + v + end +end + +action :create do + if new_resource.setup_docker_repo + if platform_family?('rhel', 'fedora') + arch = node['kernel']['machine'] + platform = + if platform?('fedora') + 'fedora' + # s390x is only available under rhel platform + elsif platform?('redhat') && arch == 's390x' + 'rhel' + else + 'centos' + end + + yum_repository 'Docker' do + baseurl "https://download.docker.com/linux/#{platform}/#{node['platform_version'].to_i}/#{arch}/#{new_resource.repo_channel}" + gpgkey "https://download.docker.com/linux/#{platform}/gpg" + description "Docker #{new_resource.repo_channel.capitalize} repository" + gpgcheck true + enabled true + end + elsif platform_family?('debian') + deb_arch = + case node['kernel']['machine'] + when 'x86_64' + 'amd64' + when 'aarch64' + 'arm64' + when 'armv7l' + 'armhf' + when 'ppc64le' + 'ppc64el' + else + node['kernel']['machine'] + end + + package 'apt-transport-https' + + apt_repository 'Docker' do + components Array(new_resource.repo_channel) + uri "https://download.docker.com/linux/#{node['platform']}" + arch deb_arch + key "https://download.docker.com/linux/#{node['platform']}/gpg" + action :add + end + else + Chef::Log.warn("Cannot setup the Docker repo for platform #{node['platform']}. Skipping.") + end + end + + version = new_resource.package_version || version_string(new_resource.version) + + package new_resource.package_name do + version version + options new_resource.package_options + action :install + end +end + +action :delete do + package new_resource.package_name do + action :remove + end +end diff --git a/resources/installation_script.rb b/resources/installation_script.rb new file mode 100644 index 000000000..b794a48c8 --- /dev/null +++ b/resources/installation_script.rb @@ -0,0 +1,46 @@ +unified_mode true +use 'partial/_base' + +resource_name :docker_installation_script +provides :docker_installation_script + +provides :docker_installation, os: 'linux' + +property :repo, %w(main test experimental), default: 'main', desired_state: false +property :script_url, String, default: lazy { default_script_url }, desired_state: false + +default_action :create + +######################### +# property helper methods +######################### + +def default_script_url + case repo + when 'main' + 'https://get.docker.com/' + when 'test' + 'https://test.docker.com/' + when 'experimental' + 'https://experimental.docker.com/' + end +end + +######### +# Actions +######### + +action :create do + package 'curl' + + execute 'install docker' do + command "curl -sSL #{new_resource.script_url} | sh" + creates '/usr/bin/docker' + end +end + +action :delete do + package %w(docker-ce docker-engine) do + action :remove + end +end diff --git a/resources/installation_tarball.rb b/resources/installation_tarball.rb new file mode 100644 index 000000000..ec390dfd8 --- /dev/null +++ b/resources/installation_tarball.rb @@ -0,0 +1,116 @@ +unified_mode true +use 'partial/_base' + +resource_name :docker_installation_tarball +provides :docker_installation_tarball + +property :checksum, String, default: lazy { default_checksum }, desired_state: false +property :source, String, default: lazy { default_source }, desired_state: false +property :channel, String, default: 'stable', desired_state: false +property :version, String, default: '20.10.11', desired_state: false + +################## +# Property Helpers +################## + +def docker_kernel + node['kernel']['name'] +end + +def docker_arch + node['kernel']['machine'] +end + +def default_source + "https://download.docker.com/#{docker_kernel.downcase}/static/#{channel}/#{docker_arch}/#{default_filename(version)}" +end + +def default_filename(version) + # https://download.docker.com/linux/static/stable/x86_64/ + regex = /^(?\d*)\.(?\d*)\./ + semver = regex.match(version) + if semver['major'].to_i >= 19 + "docker-#{version}.tgz" + elsif semver['major'].to_i == 18 && semver['minor'].to_i > 6 + "docker-#{version}.tgz" + else + "docker-#{version}-ce.tgz" + end +end + +def default_checksum + case docker_kernel + when 'Darwin' + case version + when '18.03.1' then 'bbfb9c599a4fdb45523496c2ead191056ff43d6be90cf0e348421dd56bc3dcf0' + when '18.06.3' then 'f7347ef27db9a438b05b8f82cd4c017af5693fe26202d9b3babf750df3e05e0c' + when '18.09.9' then 'ed83a3d51fef2bbcdb19d091ff0690a233aed4bbb47d2f7860d377196e0143a0' + when '19.03.15' then '61672045675798b2075d4790665b74336c03b6d6084036ef22720af60614e50d' + when '20.10.11' then '8f338ba618438fa186d1fa4eae32376cca58f86df2b40b5027c193202fad2acf' + end + when 'Linux' + case version + when '18.03.1' then '0e245c42de8a21799ab11179a4fce43b494ce173a8a2d6567ea6825d6c5265aa' + when '18.06.3' then '346f9394393ee8db5f8bd1e229ee9d90e5b36931bdd754308b2ae68884dd6822' + when '18.09.9' then '82a362af7689038c51573e0fd0554da8703f0d06f4dfe95dd5bda5acf0ae45fb' + when '19.03.15' then '5504d190eef37355231325c176686d51ade6e0cabe2da526d561a38d8611506f' + when '20.10.11' then 'dd6ff72df1edfd61ae55feaa4aadb88634161f0aa06dbaaf291d1be594099ff3' + end + end +end + +######### +# Actions +######### + +action :create do + package 'tar' + + # Pull a precompiled binary off the network + remote_file docker_tarball do + source new_resource.source + checksum new_resource.checksum + owner 'root' + group 'root' + mode '0755' + action :create + notifies :run, 'execute[extract tarball]', :immediately + end + + execute 'extract tarball' do + action :nothing + command "tar -xzf #{docker_tarball} --strip-components=1 -C #{docker_bin_prefix}" + end + + group 'docker' do + system true + append true + end +end + +action :delete do + file docker_bin do + action :delete + end + + group 'docker' do + action :delete + end +end + +################ +# Action Helpers +################ +action_class do + def docker_bin_prefix + '/usr/bin' + end + + def docker_bin + "#{docker_bin_prefix}/docker" + end + + def docker_tarball + "#{Chef::Config[:file_cache_path]}/docker-#{new_resource.version}.tgz" + end +end diff --git a/resources/partial/_service_base.rb b/resources/partial/_service_base.rb new file mode 100644 index 000000000..77b29c3d8 --- /dev/null +++ b/resources/partial/_service_base.rb @@ -0,0 +1,113 @@ +################ +# Helper Methods +################ +include DockerCookbook::DockerHelpers::Service + +##################### +# resource properties +##################### + +# Environment variables to docker service +property :env_vars, Hash + +# daemon management +property :instance, String, name_property: true, desired_state: false +property :auto_restart, [true, false], default: false +property :api_cors_header, String +property :bridge, String +property :bip, [IPV4_ADDR, IPV4_CIDR, IPV6_ADDR, IPV6_CIDR, nil] +property :cluster_store, String +property :cluster_advertise, String +property :cluster_store_opts, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } +property :daemon, [true, false], default: true +property :data_root, String +property :debug, [true, false], default: false +property :dns, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } +property :dns_search, Array +property :exec_driver, ['native', 'lxc', nil] +property :exec_opts, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } +property :fixed_cidr, String +property :fixed_cidr_v6, String +property :group, String, default: 'docker' +property :host, [String, Array], coerce: proc { |v| coerce_host(v) }, desired_state: false +property :icc, [true, false] +property :insecure_registry, [Array, String, nil], coerce: proc { |v| coerce_insecure_registry(v) } +property :ip, [IPV4_ADDR, IPV6_ADDR, nil] +property :ip_forward, [true, false] +property :ipv4_forward, [true, false], default: true +property :ipv6_forward, [true, false], default: true +property :ip_masq, [true, false] +property :iptables, [true, false] +property :ipv6, [true, false] +property :default_ip_address_pool, String +property :log_level, %w(debug info warn error fatal) +property :labels, [String, Array], coerce: proc { |v| coerce_daemon_labels(v) }, desired_state: false +property :log_driver, %w(json-file syslog journald gelf fluentd awslogs splunk etwlogs gcplogs logentries loki-docker none local) +property :log_opts, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } +property :mount_flags, String +property :mtu, String +property :pidfile, String, default: lazy { "/var/run/#{docker_name}.pid" } +property :registry_mirror, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } +property :storage_driver, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } +property :selinux_enabled, [true, false] +property :storage_opts, Array +property :default_ulimit, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } +property :userland_proxy, [true, false] +property :disable_legacy_registry, [true, false] +property :userns_remap, String +property :live_restore, [true, false], default: false + +# These are options specific to systemd configuration such as +# LimitNOFILE or TasksMax that you may wannt to use to customize +# the environment in which Docker runs. +property :systemd_opts, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } +property :systemd_socket_opts, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } + +# These are unvalidated daemon arguments passed in as a string. +property :misc_opts, String + +# environment variables to set before running daemon +property :http_proxy, String +property :https_proxy, String +property :no_proxy, String +property :tmpdir, String + +# logging +property :logfile, String, default: '/var/log/docker.log' + +# docker-wait-ready timeout +property :service_timeout, Integer, default: 20 + +alias_method :label, :labels +alias_method :run_group, :group +alias_method :graph, :data_root + +action_class do + def libexec_dir + return '/usr/libexec/docker' if platform_family?('rhel') + '/usr/lib/docker' + end + + def create_docker_wait_ready + directory libexec_dir do + owner 'root' + group 'root' + mode '0755' + action :create + end + + template "#{libexec_dir}/#{docker_name}-wait-ready" do + source 'default/docker-wait-ready.erb' + owner 'root' + group 'root' + mode '0755' + variables( + docker_cmd: docker_cmd, + libexec_dir: libexec_dir, + service_timeout: new_resource.service_timeout + ) + cookbook 'docker' + action :create + end + end +end diff --git a/resources/service.rb b/resources/service.rb new file mode 100644 index 000000000..3d4a823c0 --- /dev/null +++ b/resources/service.rb @@ -0,0 +1,120 @@ +unified_mode true +use 'partial/_base' +use 'partial/_service_base' + +resource_name :docker_service + +# register with the resource resolution system +provides :docker_service + +# installation type and service_manager +property :install_method, %w(script package tarball none auto), default: lazy { docker_install_method }, desired_state: false +property :service_manager, %w(execute systemd auto), default: 'auto', desired_state: false + +# docker_installation_script +property :repo, String, desired_state: false +property :script_url, String, desired_state: false + +# docker_installation_tarball +property :checksum, String, desired_state: false +property :docker_bin, String, desired_state: false +property :source, String, desired_state: false + +# docker_installation_package +property :package_version, String, desired_state: false +property :package_name, String, desired_state: false +property :setup_docker_repo, [true, false], desired_state: false + +# package and tarball +property :version, String, desired_state: false +property :package_options, String, desired_state: false + +action_class do + def validate_install_method + if new_resource.property_is_set?(:version) && + new_resource.install_method != 'package' && + new_resource.install_method != 'tarball' + raise Chef::Exceptions::ValidationFailed, 'Version property only supported for package and tarball installation methods' + end + end + + def property_intersection(src, dest) + src.class.properties.keys.intersection(dest.class.properties.keys) + end + + def installation(&block) + b = proc { + copy_properties_from(new_resource, *property_intersection(new_resource, self), exclude: [:install_method]) + instance_exec(&block) + } + + case new_resource.install_method + when 'auto' + install = docker_installation(new_resource.name, &b) + when 'script' + install = docker_installation_script(new_resource.name, &b) + when 'package' + install = docker_installation_package(new_resource.name, &b) + when 'tarball' + install = docker_installation_tarball(new_resource.name, &b) + when 'none' + Chef::Log.info('Skipping Docker installation. Assuming it was handled previously.') + return + end + install + end + + def svc_manager(&block) + b = proc { + copy_properties_from(new_resource, exclude: [:service_manager, :install_method]) + instance_exec(&block) + } + + case new_resource.service_manager + when 'auto' + svc = docker_service_manager(new_resource.name, &b) + when 'execute' + svc = docker_service_manager_execute(new_resource.name, &b) + when 'systemd' + svc = docker_service_manager_systemd(new_resource.name, &b) + end + svc + end +end + +######### +# Actions +######### + +action :create do + validate_install_method + + installation do + action :create + notifies :restart, new_resource, :immediately + end +end + +action :delete do + installation do + action :delete + end +end + +action :start do + svc_manager do + action :start + end +end + +action :stop do + svc_manager do + action :stop + end +end + +action :restart do + svc_manager do + action :restart + end +end diff --git a/resources/service_base.rb b/resources/service_base.rb new file mode 100644 index 000000000..bbb16445f --- /dev/null +++ b/resources/service_base.rb @@ -0,0 +1,7 @@ +unified_mode true +use 'partial/_base' +use 'partial/_service_base' + +resource_name :docker_service_base +provides :docker_service_base +provides :docker_service_manager diff --git a/resources/service_manager_execute.rb b/resources/service_manager_execute.rb new file mode 100644 index 000000000..f387f7e93 --- /dev/null +++ b/resources/service_manager_execute.rb @@ -0,0 +1,55 @@ +unified_mode true +use 'partial/_base' +use 'partial/_service_base' + +resource_name :docker_service_manager_execute +provides :docker_service_manager_execute + +# Start the service +action :start do + # enable ipv4 forwarding + execute 'enable net.ipv4.conf.all.forwarding' do + command '/sbin/sysctl net.ipv4.conf.all.forwarding=1' + not_if '/sbin/sysctl -q -n net.ipv4.conf.all.forwarding | grep ^1$' + action :run + end + + # enable ipv6 forwarding + execute 'enable net.ipv6.conf.all.forwarding' do + command '/sbin/sysctl net.ipv6.conf.all.forwarding=1' + not_if '/sbin/sysctl -q -n net.ipv6.conf.all.forwarding | grep ^1$' + action :run + end + + # Go doesn't support detaching processes natively, so we have + # to manually fork it from the shell with & + # https://github.com/docker/docker/issues/2758 + bash "start docker #{name}" do + code "#{docker_daemon_cmd} >> #{logfile} 2>&1 &" + environment 'HTTP_PROXY' => http_proxy, + 'HTTPS_PROXY' => https_proxy, + 'NO_PROXY' => no_proxy, + 'TMPDIR' => tmpdir + not_if "ps -ef | grep -v grep | grep #{Shellwords.escape(docker_daemon_cmd)}" + action :run + end + + create_docker_wait_ready + + execute 'docker-wait-ready' do + command "#{libexec_dir}/#{docker_name}-wait-ready" + end +end + +action :stop do + execute "stop docker #{name}" do + command "kill `cat #{pidfile}` && while [ -e #{pidfile} ]; do sleep 1; done" + timeout 10 + only_if "#{docker_cmd} ps | head -n 1 | grep ^CONTAINER" + end +end + +action :restart do + action_stop + action_start +end diff --git a/resources/service_manager_systemd.rb b/resources/service_manager_systemd.rb new file mode 100644 index 000000000..80a6db9e6 --- /dev/null +++ b/resources/service_manager_systemd.rb @@ -0,0 +1,143 @@ +unified_mode true +use 'partial/_base' +use 'partial/_service_base' + +resource_name :docker_service_manager_systemd +provides :docker_service_manager_systemd + +provides :docker_service_manager, os: 'linux' do |_node| + Chef::Platform::ServiceHelpers.service_resource_providers.include?(:systemd) +end + +action :start do + create_docker_wait_ready + + # stock systemd socket file + template "/lib/systemd/system/#{docker_name}.socket" do + source 'systemd/docker.socket.erb' + cookbook 'docker' + owner 'root' + group 'root' + mode '0644' + variables( + config: new_resource, + docker_name: docker_name, + docker_socket: connect_socket + ) + action connect_socket.nil? ? :delete : :create + not_if { docker_name == 'docker' && ::File.exist?('/lib/systemd/system/docker.socket') } + end + + directory '/etc/containerd' + + file '/etc/containerd/config.toml' do + content 'disabled_plugins = ["cri"]' + action :create_if_missing + end + + template '/etc/systemd/system/containerd.service' do + source 'systemd/containerd.service.erb' + cookbook 'docker' + owner 'root' + group 'root' + mode '0644' + variables(service_type: docker_containerd_service_type) + not_if { ::File.exist?('/lib/systemd/system/containerd.service') } + only_if { docker_containerd } + notifies :run, 'execute[systemctl daemon-reload]', :immediately + end + + # stock systemd unit file + # See - https://github.com/docker/docker-ce-packaging/blob/master/systemd/docker.service + template "/lib/systemd/system/#{docker_name}.service" do + source 'systemd/docker.service.erb' + cookbook 'docker' + owner 'root' + group 'root' + mode '0644' + variables( + docker_name: docker_name, + docker_daemon_cmd: docker_daemon_cmd, + docker_socket: connect_socket, + containerd: docker_containerd + ) + not_if { docker_name == 'docker' && ::File.exist?('/lib/systemd/system/docker.service') } + end + + # this overrides the main systemd socket + template "/etc/systemd/system/#{docker_name}.socket" do + source 'systemd/docker.socket-override.erb' + cookbook 'docker' + owner 'root' + group 'root' + mode '0644' + variables( + config: new_resource, + docker_name: docker_name, + docker_socket: connect_socket, + systemd_socket_args: systemd_socket_args + ) + action connect_socket.nil? ? :delete : :create + end + + # this overrides the main systemd service + template "/etc/systemd/system/#{docker_name}.service" do + source 'systemd/docker.service-override.erb' + cookbook 'docker' + owner 'root' + group 'root' + mode '0644' + variables( + config: new_resource, + docker_name: docker_name, + docker_socket: connect_socket, + docker_daemon_cmd: docker_daemon_cmd, + systemd_args: systemd_args, + docker_wait_ready: "#{libexec_dir}/#{docker_name}-wait-ready", + env_vars: new_resource.env_vars, + containerd: docker_containerd + ) + notifies :run, 'execute[systemctl daemon-reload]', :immediately + notifies :run, "execute[systemctl try-restart #{docker_name}]", :immediately + end + + # avoid 'Unit file changed on disk' warning + execute 'systemctl daemon-reload' do + command '/bin/systemctl daemon-reload' + action :nothing + end + + # restart if changes in template resources + execute "systemctl try-restart #{docker_name}" do + command "/bin/systemctl try-restart #{docker_name}" + action :nothing + end + + # service management resource + service docker_name do + provider Chef::Provider::Service::Systemd + supports status: true + action [:enable, :start] + only_if { ::File.exist?("/lib/systemd/system/#{docker_name}.service") } + retries 1 + end +end + +action :stop do + # service management resource + service docker_name do + provider Chef::Provider::Service::Systemd + supports status: true + action [:disable, :stop] + only_if { ::File.exist?("/lib/systemd/system/#{docker_name}.service") } + end + + systemd_unit "#{docker_name}.socket" do + action [:disable, :stop] + end +end + +action :restart do + action_stop + action_start +end