kitchen-docker 2.9.0 → 2.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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