kitchen-docker 2.8.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,172 @@
1
+ #
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+
14
+ require 'erb'
15
+ require 'json'
16
+ require 'shellwords'
17
+ require 'tempfile'
18
+ require 'uri'
19
+
20
+ require 'kitchen'
21
+ require 'kitchen/configurable'
22
+ require_relative '../erb_context'
23
+ require_relative 'cli_helper'
24
+
25
+ module Kitchen
26
+ module Docker
27
+ module Helpers
28
+ module ContainerHelper
29
+ include Configurable
30
+ include Kitchen::Docker::Helpers::CliHelper
31
+
32
+ def parse_container_id(output)
33
+ container_id = output.chomp
34
+
35
+ unless [12, 64].include?(container_id.size)
36
+ raise ActionFailed, 'Could not parse Docker run output for container ID'
37
+ end
38
+
39
+ container_id
40
+ end
41
+
42
+ def dockerfile_template
43
+ template = IO.read(File.expand_path(config[:dockerfile]))
44
+ context = Kitchen::Docker::ERBContext.new(config.to_hash)
45
+ ERB.new(template).result(context.get_binding)
46
+ end
47
+
48
+ def remote_socket?
49
+ config[:socket] ? socket_uri.scheme == 'tcp' : false
50
+ end
51
+
52
+ def socket_uri
53
+ URI.parse(config[:socket])
54
+ end
55
+
56
+ def dockerfile_path(file)
57
+ config[:build_context] ? Pathname.new(file.path).relative_path_from(Pathname.pwd).to_s : file.path
58
+ end
59
+
60
+ def container_exists?(state)
61
+ state[:container_id] && !!docker_command("top #{state[:container_id]}") rescue false
62
+ end
63
+
64
+ def container_exec(state, command)
65
+ cmd = build_exec_command(state, command)
66
+ docker_command(cmd)
67
+ rescue => e
68
+ raise "Failed to execute command on Docker container. #{e}"
69
+ end
70
+
71
+ def create_dir_on_container(state, path)
72
+ path = replace_env_variables(state, path)
73
+ cmd = "mkdir -p #{path}"
74
+
75
+ if state[:platform].include?('windows')
76
+ psh = "-Command if(-not (Test-Path \'#{path}\')) { New-Item -Path \'#{path}\' -Force }"
77
+ cmd = build_powershell_command(psh)
78
+ end
79
+
80
+ cmd = build_exec_command(state, cmd)
81
+ docker_command(cmd)
82
+ rescue => e
83
+ raise "Failed to create directory #{path} on container. #{e}"
84
+ end
85
+
86
+ def copy_file_to_container(state, local_file, remote_file)
87
+ debug("Copying local file #{local_file} to #{remote_file} on container")
88
+
89
+ remote_file = replace_env_variables(state, remote_file)
90
+
91
+ remote_file = "#{state[:container_id]}:#{remote_file}"
92
+ cmd = build_copy_command(local_file, remote_file)
93
+ docker_command(cmd)
94
+ rescue => e
95
+ raise "Failed to copy file #{local_file} to container. #{e}"
96
+ end
97
+
98
+ def container_env_variables(state)
99
+ # Retrieves all environment variables from inside container
100
+ vars = {}
101
+
102
+ if state[:platform].include?('windows')
103
+ cmd = build_powershell_command('-Command [System.Environment]::GetEnvironmentVariables() ^| ConvertTo-Json')
104
+ cmd = build_exec_command(state, cmd)
105
+ stdout = docker_command(cmd, suppress_output: !logger.debug?).strip
106
+ vars = ::JSON.parse(stdout)
107
+ else
108
+ cmd = build_exec_command(state, 'printenv')
109
+ stdout = docker_command(cmd, suppress_output: !logger.debug?).strip
110
+ stdout.split("\n").each { |line| vars[line.split('=')[0]] = line.split('=')[1] }
111
+ end
112
+
113
+ vars
114
+ end
115
+
116
+ def replace_env_variables(state, str)
117
+ if str.include?('$env:')
118
+ key = str[/\$env:(.*?)(\\|$)/, 1]
119
+ value = container_env_variables(state)[key].to_s.strip
120
+ str = str.gsub("$env:#{key}", value)
121
+ elsif str.include?('$')
122
+ key = str[/\$(.*?)(\/|$)/, 1]
123
+ value = container_env_variables(state)[key].to_s.strip
124
+ str = str.gsub("$#{key}", value)
125
+ end
126
+
127
+ str
128
+ end
129
+
130
+ def run_container(state, transport_port = nil)
131
+ cmd = build_run_command(state[:image_id], transport_port)
132
+ output = docker_command(cmd)
133
+ parse_container_id(output)
134
+ end
135
+
136
+ def container_ip_address(state)
137
+ cmd = "inspect --format '{{ .NetworkSettings.IPAddress }}'"
138
+ cmd << " #{state[:container_id]}"
139
+ docker_command(cmd).strip
140
+ rescue
141
+ raise ActionFailed, 'Error getting internal IP of Docker container'
142
+ end
143
+
144
+ def remove_container(state)
145
+ container_id = state[:container_id]
146
+ docker_command("stop -t 0 #{container_id}")
147
+ docker_command("rm #{container_id}")
148
+ end
149
+
150
+ def dockerfile_proxy_config
151
+ env_variables = ''
152
+ if config[:http_proxy]
153
+ env_variables << "ENV http_proxy #{config[:http_proxy]}\n"
154
+ env_variables << "ENV HTTP_PROXY #{config[:http_proxy]}\n"
155
+ end
156
+
157
+ if config[:https_proxy]
158
+ env_variables << "ENV https_proxy #{config[:https_proxy]}\n"
159
+ env_variables << "ENV HTTPS_PROXY #{config[:https_proxy]}\n"
160
+ end
161
+
162
+ if config[:no_proxy]
163
+ env_variables << "ENV no_proxy #{config[:no_proxy]}\n"
164
+ env_variables << "ENV NO_PROXY #{config[:no_proxy]}\n"
165
+ end
166
+
167
+ env_variables
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,136 @@
1
+ #
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+
14
+ require 'kitchen'
15
+ require 'kitchen/configurable'
16
+
17
+ module Kitchen
18
+ module Docker
19
+ module Helpers
20
+ module DockerfileHelper
21
+ include Configurable
22
+
23
+ def dockerfile_platform
24
+ case config[:platform]
25
+ when 'arch'
26
+ arch_platform
27
+ when 'debian', 'ubuntu'
28
+ debian_platform
29
+ when 'fedora'
30
+ fedora_platform
31
+ when 'gentoo'
32
+ gentoo_platform
33
+ when 'gentoo-paludis'
34
+ gentoo_paludis_platform
35
+ when 'opensuse/tumbleweed', 'opensuse/leap', 'opensuse', 'sles'
36
+ opensuse_platform
37
+ when 'rhel', 'centos', 'oraclelinux', 'amazonlinux', 'almalinux', 'rockylinux'
38
+ rhel_platform
39
+ else
40
+ raise ActionFailed, "Unknown platform '#{config[:platform]}'"
41
+ end
42
+ end
43
+
44
+ def arch_platform
45
+ # See https://bugs.archlinux.org/task/47052 for why we
46
+ # blank out limits.conf.
47
+ <<-CODE
48
+ RUN pacman --noconfirm -Sy archlinux-keyring
49
+ RUN pacman-db-upgrade
50
+ RUN pacman --noconfirm -Syu openssl openssh sudo curl
51
+ RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -A -t rsa -f /etc/ssh/ssh_host_rsa_key
52
+ RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -A -t dsa -f /etc/ssh/ssh_host_dsa_key
53
+ RUN echo >/etc/security/limits.conf
54
+ CODE
55
+ end
56
+
57
+ def debian_platform
58
+ disable_upstart = <<-CODE
59
+ RUN [ ! -f "/sbin/initctl" ] || dpkg-divert --local --rename --add /sbin/initctl \
60
+ && ln -sf /bin/true /sbin/initctl
61
+ CODE
62
+ packages = <<-CODE
63
+ ENV DEBIAN_FRONTEND noninteractive
64
+ ENV container docker
65
+ RUN apt-get update
66
+ RUN apt-get install -y sudo openssh-server curl lsb-release
67
+ CODE
68
+ config[:disable_upstart] ? disable_upstart + packages : packages
69
+ end
70
+
71
+ def fedora_platform
72
+ <<-CODE
73
+ ENV container docker
74
+ RUN dnf clean all
75
+ RUN dnf install -y sudo openssh-server openssh-clients which curl
76
+ RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N ''
77
+ RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key -N ''
78
+ CODE
79
+ end
80
+
81
+ def gentoo_platform
82
+ <<-CODE
83
+ RUN emerge-webrsync
84
+ RUN emerge --quiet --noreplace net-misc/openssh app-admin/sudo
85
+ RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -A -t rsa -f /etc/ssh/ssh_host_rsa_key
86
+ RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -A -t dsa -f /etc/ssh/ssh_host_dsa_key
87
+ CODE
88
+ end
89
+
90
+ def gentoo_paludis_platform
91
+ <<-CODE
92
+ RUN cave sync
93
+ RUN cave resolve -zx net-misc/openssh app-admin/sudo
94
+ RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -A -t rsa -f /etc/ssh/ssh_host_rsa_key
95
+ RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -A -t dsa -f /etc/ssh/ssh_host_dsa_key
96
+ CODE
97
+ end
98
+
99
+ def opensuse_platform
100
+ <<-CODE
101
+ ENV container docker
102
+ RUN zypper install -y sudo openssh which curl
103
+ RUN /usr/sbin/sshd-gen-keys-start
104
+ CODE
105
+ end
106
+
107
+ def rhel_platform
108
+ <<-CODE
109
+ ENV container docker
110
+ RUN yum clean all
111
+ RUN yum install -y sudo openssh-server openssh-clients which curl
112
+ RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N ''
113
+ RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key -N ''
114
+ CODE
115
+ end
116
+
117
+ def dockerfile_base_linux(username, homedir)
118
+ <<-CODE
119
+ RUN if ! getent passwd #{username}; then \
120
+ useradd -d #{homedir} -m -s /bin/bash -p '*' #{username}; \
121
+ fi
122
+ RUN echo "#{username} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/#{username}
123
+ RUN echo "Defaults !requiretty" >> /etc/sudoers.d/#{username}
124
+ RUN mkdir -p #{homedir}/.ssh
125
+ RUN chown -R #{username} #{homedir}/.ssh
126
+ RUN chmod 0700 #{homedir}/.ssh
127
+ RUN touch #{homedir}/.ssh/authorized_keys
128
+ RUN chown #{username} #{homedir}/.ssh/authorized_keys
129
+ RUN chmod 0600 #{homedir}/.ssh/authorized_keys
130
+ RUN mkdir -p /run/sshd
131
+ CODE
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,40 @@
1
+ #
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+
14
+ require 'fileutils'
15
+
16
+ module Kitchen
17
+ module Docker
18
+ module Helpers
19
+ module FileHelper
20
+ def create_temp_file(file, contents)
21
+ debug("[Docker] Creating temp file #{file}")
22
+ debug('[Docker] --- Start Temp File Contents ---')
23
+ debug(contents)
24
+ debug('[Docker] --- End Temp File Contents ---')
25
+
26
+ begin
27
+ path = ::File.dirname(file)
28
+ ::FileUtils.mkdir_p(path) unless ::Dir.exist?(path)
29
+ file = ::File.open(file, 'w')
30
+ file.write(contents)
31
+ rescue IOError => e
32
+ raise "Failed to write temp file. Error Details: #{e}"
33
+ ensure
34
+ file.close unless file.nil?
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,76 @@
1
+ #
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+
14
+ require 'kitchen'
15
+ require 'kitchen/configurable'
16
+ require_relative 'cli_helper'
17
+ require_relative 'container_helper'
18
+
19
+ module Kitchen
20
+ module Docker
21
+ module Helpers
22
+ module ImageHelper
23
+ include Configurable
24
+ include Kitchen::Docker::Helpers::CliHelper
25
+ include Kitchen::Docker::Helpers::ContainerHelper
26
+
27
+ def parse_image_id(output)
28
+ output.each_line do |line|
29
+ if line =~ /writing image sha256:[[:xdigit:]]{64} done/i
30
+ img_id = line[/writing image (sha256:[[:xdigit:]]{64}) done/i,1]
31
+ return img_id
32
+ end
33
+ if line =~ /image id|build successful|successfully built/i
34
+ img_id = line.split(/\s+/).last
35
+ return img_id
36
+ end
37
+ end
38
+ raise ActionFailed, 'Could not parse Docker build output for image ID'
39
+ end
40
+
41
+ def remove_image(state)
42
+ image_id = state[:image_id]
43
+ docker_command("rmi #{image_id}")
44
+ end
45
+
46
+ def build_image(state, dockerfile)
47
+ cmd = 'build'
48
+ cmd << ' --no-cache' unless config[:use_cache]
49
+ cmd << " --platform=#{config[:docker_platform]}" if config[:docker_platform]
50
+ extra_build_options = config_to_options(config[:build_options])
51
+ cmd << " #{extra_build_options}" unless extra_build_options.empty?
52
+ dockerfile_contents = dockerfile
53
+ file = Tempfile.new('Dockerfile-kitchen', Dir.pwd)
54
+ cmd << " -f #{Shellwords.escape(dockerfile_path(file))}" if config[:build_context]
55
+ build_context = config[:build_context] ? '.' : '-'
56
+ output = begin
57
+ file.write(dockerfile)
58
+ file.close
59
+ docker_command("#{cmd} #{build_context}",
60
+ input: dockerfile_contents,
61
+ environment: { BUILDKIT_PROGRESS: 'plain' })
62
+ ensure
63
+ file.close unless file.closed?
64
+ file.unlink
65
+ end
66
+
67
+ parse_image_id(output)
68
+ end
69
+
70
+ def image_exists?(state)
71
+ state[:image_id] && !!docker_command("inspect --type=image #{state[:image_id]}") rescue false
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,40 @@
1
+ #
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+
14
+ # This helper should be removed when the kitchen-inspec gem has been updated to include these runner options
15
+ begin
16
+ require 'kitchen/verifier/inspec'
17
+
18
+ # Add runner options for Docker transport for kitchen-inspec gem
19
+ module Kitchen
20
+ module Docker
21
+ module Helpers
22
+ module InspecHelper
23
+ Kitchen::Verifier::Inspec.class_eval do
24
+ def runner_options_for_docker(config_data)
25
+ opts = {
26
+ 'backend' => 'docker',
27
+ 'logger' => logger,
28
+ 'host' => config_data[:container_id],
29
+ }
30
+ logger.debug "Connect to Container: #{opts['host']}"
31
+ opts
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ rescue LoadError => e
39
+ logger.debug("[Docker] kitchen-inspec gem not found for InSpec verifier. #{e}")
40
+ end