kitchen-docker 2.6.0 → 2.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,70 @@
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 =~ /image id|build successful|successfully built|writing image/i
30
+ img_id = line.split(/\s+/).last
31
+ return img_id
32
+ end
33
+ end
34
+ raise ActionFailed, 'Could not parse Docker build output for image ID'
35
+ end
36
+
37
+ def remove_image(state)
38
+ image_id = state[:image_id]
39
+ docker_command("rmi #{image_id}")
40
+ end
41
+
42
+ def build_image(state, dockerfile)
43
+ cmd = 'build'
44
+ cmd << ' --no-cache' unless config[:use_cache]
45
+ extra_build_options = config_to_options(config[:build_options])
46
+ cmd << " #{extra_build_options}" unless extra_build_options.empty?
47
+ dockerfile_contents = dockerfile
48
+ build_context = config[:build_context] ? '.' : '-'
49
+ file = Tempfile.new('Dockerfile-kitchen', Dir.pwd)
50
+ output = begin
51
+ file.write(dockerfile)
52
+ file.close
53
+ docker_command("#{cmd} -f #{Shellwords.escape(dockerfile_path(file))} #{build_context}",
54
+ input: dockerfile_contents,
55
+ environment: { DOCKER_BUILDKIT: '0' })
56
+ ensure
57
+ file.close unless file.closed?
58
+ file.unlink
59
+ end
60
+
61
+ parse_image_id(output)
62
+ end
63
+
64
+ def image_exists?(state)
65
+ state[:image_id] && !!docker_command("inspect --type=image #{state[:image_id]}") rescue false
66
+ end
67
+ end
68
+ end
69
+ end
70
+ 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