kitchen-docker 2.9.0 → 2.10.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,84 @@
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 'securerandom'
15
+
16
+ require_relative '../container'
17
+
18
+ module Kitchen
19
+ module Docker
20
+ class Container
21
+ class Windows < Kitchen::Docker::Container
22
+ def initialize(config)
23
+ super
24
+ end
25
+
26
+ def create(state)
27
+ super
28
+
29
+ debug('Creating Windows container')
30
+ state[:username] = @config[:username]
31
+ state[:image_id] = build_image(state, dockerfile) unless state[:image_id]
32
+ state[:container_id] = run_container(state) unless state[:container_id]
33
+ end
34
+
35
+ def execute(command)
36
+ # Create temp script file and upload files to container
37
+ debug('Executing command on Windows container')
38
+ filename = "docker-#{::SecureRandom.uuid}.ps1"
39
+ temp_file = ".\\.kitchen\\temp\\#{filename}"
40
+ create_temp_file(temp_file, command)
41
+
42
+ remote_path = @config[:temp_dir].tr('/', '\\')
43
+ debug("Creating directory #{remote_path} on container")
44
+ create_dir_on_container(@config, remote_path)
45
+
46
+ debug("Uploading temp file #{temp_file} to #{remote_path} on container")
47
+ upload(temp_file, remote_path)
48
+
49
+ debug('Deleting temp file from local filesystem')
50
+ ::File.delete(temp_file)
51
+
52
+ # Replace any environment variables used in the path and execute script file
53
+ debug("Executing temp script #{remote_path}\\#{filename} on container")
54
+ remote_path = replace_env_variables(@config, remote_path)
55
+ cmd = build_powershell_command("-File #{remote_path}\\#{filename}")
56
+
57
+ container_exec(@config, cmd)
58
+ rescue => e
59
+ raise "Failed to execute command on Windows container. #{e}"
60
+ end
61
+
62
+ protected
63
+
64
+ def dockerfile
65
+ raise ActionFailed, "Unknown platform '#{@config[:platform]}'" unless @config[:platform] == 'windows'
66
+ return dockerfile_template if @config[:dockerfile]
67
+
68
+ from = "FROM #{@config[:image]}"
69
+
70
+ custom = ''
71
+ Array(@config[:provision_command]).each do |cmd|
72
+ custom << "RUN #{cmd}\n"
73
+ end
74
+
75
+ output = [from, dockerfile_proxy_config, custom, ''].join("\n")
76
+ debug('--- Start Dockerfile ---')
77
+ debug(output.strip)
78
+ debug('--- End Dockerfile ---')
79
+ output
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -1,4 +1,3 @@
1
- # -*- encoding: utf-8 -*-
2
1
  #
3
2
  # Copyright (C) 2014, Sean Porter
4
3
  #
@@ -15,10 +14,8 @@
15
14
  # limitations under the License.
16
15
 
17
16
  module Kitchen
18
-
19
- module Driver
20
-
17
+ module Docker
21
18
  # Version string for Docker Kitchen driver
22
- DOCKER_VERSION = "2.9.0"
19
+ DOCKER_VERSION = "2.10.0"
23
20
  end
24
21
  end
@@ -1,4 +1,3 @@
1
- # -*- encoding: utf-8 -*-
2
1
  #
3
2
  # Copyright (C) 2014, Sean Porter
4
3
  #
@@ -17,10 +16,8 @@
17
16
  require 'erb'
18
17
 
19
18
  module Kitchen
20
-
21
- module Driver
22
-
23
- class DockerERBContext
19
+ module Docker
20
+ class ERBContext
24
21
  def initialize(config={})
25
22
  config.each do |key, value|
26
23
  instance_variable_set('@' + key.to_s, value)
@@ -0,0 +1,147 @@
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 'kitchen/logging'
17
+ require 'kitchen/shell_out'
18
+
19
+ module Kitchen
20
+ module Docker
21
+ module Helpers
22
+ module CliHelper
23
+ include Configurable
24
+ include Logging
25
+ include ShellOut
26
+
27
+ def docker_command(cmd, options={})
28
+ docker = config[:binary].dup
29
+ docker << " -H #{config[:socket]}" if config[:socket]
30
+ docker << ' --tls' if config[:tls]
31
+ docker << ' --tlsverify' if config[:tls_verify]
32
+ docker << " --tlscacert=#{config[:tls_cacert]}" if config[:tls_cacert]
33
+ docker << " --tlscert=#{config[:tls_cert]}" if config[:tls_cert]
34
+ docker << " --tlskey=#{config[:tls_key]}" if config[:tls_key]
35
+ logger.debug("docker_command: #{docker} #{cmd} shell_opts: #{docker_shell_opts(options)}")
36
+ run_command("#{docker} #{cmd}", docker_shell_opts(options))
37
+ end
38
+
39
+ def build_run_command(image_id, transport_port = nil)
40
+ cmd = 'run -d'
41
+ cmd << ' -i' if config[:interactive]
42
+ cmd << ' -t' if config[:tty]
43
+ cmd << build_env_variable_args(config[:env_variables]) if config[:env_variables]
44
+ cmd << " -p #{transport_port}" unless transport_port.nil?
45
+ Array(config[:forward]).each { |port| cmd << " -p #{port}" }
46
+ Array(config[:dns]).each { |dns| cmd << " --dns #{dns}" }
47
+ Array(config[:add_host]).each { |host, ip| cmd << " --add-host=#{host}:#{ip}" }
48
+ Array(config[:volume]).each { |volume| cmd << " -v #{volume}" }
49
+ Array(config[:volumes_from]).each { |container| cmd << " --volumes-from #{container}" }
50
+ Array(config[:links]).each { |link| cmd << " --link #{link}" }
51
+ Array(config[:devices]).each { |device| cmd << " --device #{device}" }
52
+ cmd << " --name #{config[:instance_name]}" if config[:instance_name]
53
+ cmd << ' -P' if config[:publish_all]
54
+ cmd << " -h #{config[:hostname]}" if config[:hostname]
55
+ cmd << " -m #{config[:memory]}" if config[:memory]
56
+ cmd << " -c #{config[:cpu]}" if config[:cpu]
57
+ cmd << " -e http_proxy=#{config[:http_proxy]}" if config[:http_proxy]
58
+ cmd << " -e https_proxy=#{config[:https_proxy]}" if config[:https_proxy]
59
+ cmd << ' --privileged' if config[:privileged]
60
+ Array(config[:cap_add]).each { |cap| cmd << " --cap-add=#{cap}"} if config[:cap_add]
61
+ Array(config[:cap_drop]).each { |cap| cmd << " --cap-drop=#{cap}"} if config[:cap_drop]
62
+ Array(config[:security_opt]).each { |opt| cmd << " --security-opt=#{opt}"} if config[:security_opt]
63
+ extra_run_options = config_to_options(config[:run_options])
64
+ cmd << " #{extra_run_options}" unless extra_run_options.empty?
65
+ cmd << " #{image_id} #{config[:run_command]}"
66
+ logger.debug("build_run_command: #{cmd}")
67
+ cmd
68
+ end
69
+
70
+ def build_exec_command(state, command)
71
+ cmd = 'exec'
72
+ cmd << ' -d' if config[:detach]
73
+ cmd << build_env_variable_args(config[:env_variables]) if config[:env_variables]
74
+ cmd << ' --privileged' if config[:privileged]
75
+ cmd << ' -t' if config[:tty]
76
+ cmd << ' -i' if config[:interactive]
77
+ cmd << " -u #{config[:username]}" if config[:username]
78
+ cmd << " -w #{config[:working_dir]}" if config[:working_dir]
79
+ cmd << " #{state[:container_id]}"
80
+ cmd << " #{command}"
81
+ logger.debug("build_exec_command: #{cmd}")
82
+ cmd
83
+ end
84
+
85
+ def build_copy_command(local_file, remote_file, opts = {})
86
+ cmd = 'cp'
87
+ cmd << ' -a' if opts[:archive]
88
+ cmd << " #{local_file} #{remote_file}"
89
+ cmd
90
+ end
91
+
92
+ def build_powershell_command(args)
93
+ cmd = 'powershell -ExecutionPolicy Bypass -NoLogo '
94
+ cmd << args
95
+ logger.debug("build_powershell_command: #{cmd}")
96
+ cmd
97
+ end
98
+
99
+ def build_env_variable_args(vars)
100
+ raise ActionFailed, 'Environment variables are not of a Hash type' unless vars.is_a?(Hash)
101
+
102
+ args = ''
103
+ vars.each do |k, v|
104
+ args << " -e #{k.to_s.strip}=\"#{v.to_s.strip}\""
105
+ end
106
+
107
+ args
108
+ end
109
+
110
+ def dev_null
111
+ case RbConfig::CONFIG['host_os']
112
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
113
+ 'NUL'
114
+ else
115
+ '/dev/null'
116
+ end
117
+ end
118
+
119
+ def docker_shell_opts(options = {})
120
+ options[:live_stream] = nil if options[:suppress_output]
121
+ options.delete(:suppress_output)
122
+
123
+ options
124
+ end
125
+
126
+ # Convert the config input for `:build_options` or `:run_options` in to a
127
+ # command line string for use with Docker.
128
+ #
129
+ # @since 2.5.0
130
+ # @param config [nil, String, Array, Hash] Config data to convert.
131
+ # @return [String]
132
+ def config_to_options(config)
133
+ case config
134
+ when nil
135
+ ''
136
+ when String
137
+ config
138
+ when Array
139
+ config.map { |c| config_to_options(c) }.join(' ')
140
+ when Hash
141
+ config.map { |k, v| Array(v).map { |c| "--#{k}=#{Shellwords.escape(c)}" }.join(' ') }.join(' ')
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -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,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