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.
data/docker.ps1 ADDED
@@ -0,0 +1,9 @@
1
+ # This script is used to configure the Docker service for Windows builds in Travis CI
2
+ Write-Host "Configuring Docker service to listen on TCP port 2375..."
3
+ $dockerSvcArgs = (Get-WmiObject Win32_Service | ?{$_.Name -eq 'docker'} | Select PathName).PathName
4
+ $dockerSvcArgs = "$dockerSvcArgs -H tcp://0.0.0.0:2375 -H npipe:////./pipe/docker_engine"
5
+ Write-Host "Docker Service Args: $dockerSvcArgs"
6
+
7
+ Get-WmiObject Win32_Service -Filter "Name='docker'" | Invoke-WmiMethod -Name Change -ArgumentList @($null,$null,$null,$null,$null, $dockerSvcArgs) | Out-Null
8
+
9
+ Restart-Service docker -Force -Verbose
@@ -1,20 +1,18 @@
1
- # coding: utf-8
2
1
  lib = File.expand_path('../lib', __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'kitchen/driver/docker_version'
3
+ require 'kitchen/docker/docker_version'
5
4
 
6
5
  Gem::Specification.new do |spec|
7
6
  spec.name = 'kitchen-docker'
8
- spec.version = Kitchen::Driver::DOCKER_VERSION
7
+ spec.version = Kitchen::Docker::DOCKER_VERSION
9
8
  spec.authors = ['Sean Porter']
10
9
  spec.email = ['portertech@gmail.com']
11
10
  spec.description = %q{A Docker Driver for Test Kitchen}
12
11
  spec.summary = spec.description
13
- spec.homepage = 'https://github.com/portertech/kitchen-docker'
12
+ spec.homepage = 'https://github.com/test-kitchen/kitchen-docker'
14
13
  spec.license = 'Apache 2.0'
15
14
 
16
15
  spec.files = `git ls-files`.split($/)
17
- spec.executables = []
18
16
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
17
  spec.require_paths = ['lib']
20
18
 
@@ -36,5 +34,6 @@ Gem::Specification.new do |spec|
36
34
  spec.add_development_dependency 'codecov', '~> 0.0', '>= 0.0.2'
37
35
 
38
36
  # Integration testing gems.
39
- spec.add_development_dependency 'kitchen-inspec', '~> 0.14'
37
+ spec.add_development_dependency 'kitchen-inspec', '~> 2.0'
38
+ spec.add_development_dependency 'train', '>= 2.1', '< 4.0' # validate 4.x when it's released
40
39
  end
@@ -0,0 +1,25 @@
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
+ begin
15
+ require 'docker'
16
+
17
+ # Override API_VERSION constant in docker-api gem to use version 1.24 of the Docker API
18
+ # This override is for the docker-api gem to communicate to the Docker engine on Windows
19
+ module Docker
20
+ VERSION = '0.0.0'
21
+ API_VERSION = '1.24'
22
+ end
23
+ rescue LoadError => e
24
+ logger.debug("[Docker] docker-api gem not found for InSpec verifier. #{e}")
25
+ end
@@ -0,0 +1,75 @@
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_relative 'helpers/cli_helper'
15
+ require_relative 'helpers/container_helper'
16
+ require_relative 'helpers/file_helper'
17
+ require_relative 'helpers/image_helper'
18
+
19
+ module Kitchen
20
+ module Docker
21
+ class Container
22
+ include Kitchen::Docker::Helpers::CliHelper
23
+ include Kitchen::Docker::Helpers::ContainerHelper
24
+ include Kitchen::Docker::Helpers::FileHelper
25
+ include Kitchen::Docker::Helpers::ImageHelper
26
+
27
+ def initialize(config)
28
+ @config = config
29
+ end
30
+
31
+ def create(state)
32
+ if container_exists?(state)
33
+ info("Container ID #{state[:container_id]} already exists.")
34
+ elsif !container_exists?(state) && state[:container_id]
35
+ raise ActionFailed, "Container ID #{state[:container_id]} was found in the kitchen state data, "\
36
+ 'but the container does not exist.'
37
+ end
38
+
39
+ state[:username] = @config[:username]
40
+ end
41
+
42
+ def destroy(state)
43
+ info("[Docker] Destroying Docker container #{state[:container_id]}") if state[:container_id]
44
+ remove_container(state) if container_exists?(state)
45
+
46
+ if @config[:remove_images] && state[:image_id]
47
+ remove_image(state) if image_exists?(state)
48
+ end
49
+ end
50
+
51
+ def hostname(state)
52
+ hostname = 'localhost'
53
+
54
+ if remote_socket?
55
+ hostname = socket_uri.host
56
+ elsif @config[:use_internal_docker_network]
57
+ hostname = container_ip_address(state)
58
+ end
59
+
60
+ hostname
61
+ end
62
+
63
+ def upload(locals, remote)
64
+ files = locals
65
+ files = Array(locals) unless locals.is_a?(Array)
66
+
67
+ files.each do |file|
68
+ copy_file_to_container(@config, file, remote)
69
+ end
70
+
71
+ files
72
+ end
73
+ end
74
+ end
75
+ 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 'base64'
15
+ require 'openssl'
16
+ require 'securerandom'
17
+ require 'shellwords'
18
+
19
+ require_relative '../container'
20
+ require_relative '../helpers/dockerfile_helper'
21
+
22
+ module Kitchen
23
+ module Docker
24
+ class Container
25
+ class Linux < Kitchen::Docker::Container
26
+ include Kitchen::Docker::Helpers::DockerfileHelper
27
+
28
+ MUTEX_FOR_SSH_KEYS = Mutex.new
29
+
30
+ def initialize(config)
31
+ super
32
+ end
33
+
34
+ def create(state)
35
+ super
36
+
37
+ debug('Creating Linux container')
38
+ generate_keys
39
+
40
+ state[:ssh_key] = @config[:private_key]
41
+ state[:image_id] = build_image(state, dockerfile) unless state[:image_id]
42
+ state[:container_id] = run_container(state, 22) unless state[:container_id]
43
+ state[:hostname] = hostname(state)
44
+ state[:port] = container_ssh_port(state)
45
+ end
46
+
47
+ def execute(command)
48
+ # Create temp script file and upload files to container
49
+ debug("Executing command on Linux container (Platform: #{@config[:platform]})")
50
+ filename = "docker-#{::SecureRandom.uuid}.sh"
51
+ temp_file = "./.kitchen/temp/#{filename}"
52
+ create_temp_file(temp_file, command)
53
+
54
+ remote_path = @config[:temp_dir]
55
+ debug("Creating directory #{remote_path} on container")
56
+ create_dir_on_container(@config, remote_path)
57
+
58
+ debug("Uploading temp file #{temp_file} to #{remote_path} on container")
59
+ upload(temp_file, remote_path)
60
+
61
+ debug('Deleting temp file from local filesystem')
62
+ ::File.delete(temp_file)
63
+
64
+ # Replace any environment variables used in the path and execute script file
65
+ debug("Executing temp script #{remote_path}/#{filename} on container")
66
+ remote_path = replace_env_variables(@config, remote_path)
67
+
68
+ container_exec(@config, "/bin/bash #{remote_path}/#{filename}")
69
+ rescue => e
70
+ raise "Failed to execute command on Linux container. #{e}"
71
+ end
72
+
73
+ protected
74
+
75
+ def generate_keys
76
+ MUTEX_FOR_SSH_KEYS.synchronize do
77
+ if !File.exist?(@config[:public_key]) || !File.exist?(@config[:private_key])
78
+ private_key = OpenSSL::PKey::RSA.new(2048)
79
+ blobbed_key = Base64.encode64(private_key.to_blob).gsub("\n", '')
80
+ public_key = "ssh-rsa #{blobbed_key} kitchen_docker_key"
81
+ File.open(@config[:private_key], 'w') do |file|
82
+ file.write(private_key)
83
+ file.chmod(0600)
84
+ end
85
+ File.open(@config[:public_key], 'w') do |file|
86
+ file.write(public_key)
87
+ file.chmod(0600)
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ def parse_container_ssh_port(output)
94
+ _host, port = output.split(':')
95
+ port.to_i
96
+ rescue => e
97
+ raise ActionFailed, "Could not parse Docker port output for container SSH port. #{e}"
98
+ end
99
+
100
+ def container_ssh_port(state)
101
+ return 22 if @config[:use_internal_docker_network]
102
+
103
+ output = docker_command("port #{state[:container_id]} 22/tcp")
104
+ parse_container_ssh_port(output)
105
+ rescue => e
106
+ raise ActionFailed, "Docker reports container has no ssh port mapped. #{e}"
107
+ end
108
+
109
+ def dockerfile
110
+ return dockerfile_template if @config[:dockerfile]
111
+
112
+ from = "FROM #{@config[:image]}"
113
+ platform = dockerfile_platform
114
+ username = @config[:username]
115
+ public_key = IO.read(@config[:public_key]).strip
116
+ homedir = username == 'root' ? '/root' : "/home/#{username}"
117
+ base = dockerfile_base_linux(username, homedir)
118
+
119
+ custom = ''
120
+ Array(@config[:provision_command]).each do |cmd|
121
+ custom << "RUN #{cmd}\n"
122
+ end
123
+
124
+ ssh_key = "RUN echo #{Shellwords.escape(public_key)} >> #{homedir}/.ssh/authorized_keys"
125
+
126
+ # Empty string to ensure the file ends with a newline.
127
+ output = [from, dockerfile_proxy_config, platform, base, custom, ssh_key, ''].join("\n")
128
+ debug('--- Start Dockerfile ---')
129
+ debug(output.strip)
130
+ debug('--- End Dockerfile ---')
131
+ output
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,85 @@
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
+ state[:hostname] = hostname(state)
34
+ end
35
+
36
+ def execute(command)
37
+ # Create temp script file and upload files to container
38
+ debug('Executing command on Windows container')
39
+ filename = "docker-#{::SecureRandom.uuid}.ps1"
40
+ temp_file = ".\\.kitchen\\temp\\#{filename}"
41
+ create_temp_file(temp_file, command)
42
+
43
+ remote_path = @config[:temp_dir].tr('/', '\\')
44
+ debug("Creating directory #{remote_path} on container")
45
+ create_dir_on_container(@config, remote_path)
46
+
47
+ debug("Uploading temp file #{temp_file} to #{remote_path} on container")
48
+ upload(temp_file, remote_path)
49
+
50
+ debug('Deleting temp file from local filesystem')
51
+ ::File.delete(temp_file)
52
+
53
+ # Replace any environment variables used in the path and execute script file
54
+ debug("Executing temp script #{remote_path}\\#{filename} on container")
55
+ remote_path = replace_env_variables(@config, remote_path)
56
+ cmd = build_powershell_command("-File #{remote_path}\\#{filename}")
57
+
58
+ container_exec(@config, cmd)
59
+ rescue => e
60
+ raise "Failed to execute command on Windows container. #{e}"
61
+ end
62
+
63
+ protected
64
+
65
+ def dockerfile
66
+ raise ActionFailed, "Unknown platform '#{@config[:platform]}'" unless @config[:platform] == 'windows'
67
+ return dockerfile_template if @config[:dockerfile]
68
+
69
+ from = "FROM #{@config[:image]}"
70
+
71
+ custom = ''
72
+ Array(@config[:provision_command]).each do |cmd|
73
+ custom << "RUN #{cmd}\n"
74
+ end
75
+
76
+ output = [from, dockerfile_proxy_config, custom, ''].join("\n")
77
+ debug('--- Start Dockerfile ---')
78
+ debug(output.strip)
79
+ debug('--- End Dockerfile ---')
80
+ output
81
+ end
82
+ end
83
+ end
84
+ end
85
+ 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.6.0"
19
+ DOCKER_VERSION = "2.11.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,151 @@
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
+ Array(config[:mount]).each {|mount| cmd << " --mount #{mount}"}
53
+ Array(config[:tmpfs]).each {|tmpfs| cmd << " --tmpfs #{tmpfs}"}
54
+ cmd << " --name #{config[:instance_name]}" if config[:instance_name]
55
+ cmd << ' -P' if config[:publish_all]
56
+ cmd << " -h #{config[:hostname]}" if config[:hostname]
57
+ cmd << " -m #{config[:memory]}" if config[:memory]
58
+ cmd << " -c #{config[:cpu]}" if config[:cpu]
59
+ cmd << " --gpus #{config[:gpus]}" if config[:gpus]
60
+ cmd << " -e http_proxy=#{config[:http_proxy]}" if config[:http_proxy]
61
+ cmd << " -e https_proxy=#{config[:https_proxy]}" if config[:https_proxy]
62
+ cmd << ' --privileged' if config[:privileged]
63
+ cmd << " --isolation #{config[:isolation]}" if config[:isolation]
64
+ Array(config[:cap_add]).each { |cap| cmd << " --cap-add=#{cap}"} if config[:cap_add]
65
+ Array(config[:cap_drop]).each { |cap| cmd << " --cap-drop=#{cap}"} if config[:cap_drop]
66
+ Array(config[:security_opt]).each { |opt| cmd << " --security-opt=#{opt}"} if config[:security_opt]
67
+ extra_run_options = config_to_options(config[:run_options])
68
+ cmd << " #{extra_run_options}" unless extra_run_options.empty?
69
+ cmd << " #{image_id} #{config[:run_command]}"
70
+ logger.debug("build_run_command: #{cmd}")
71
+ cmd
72
+ end
73
+
74
+ def build_exec_command(state, command)
75
+ cmd = 'exec'
76
+ cmd << ' -d' if config[:detach]
77
+ cmd << build_env_variable_args(config[:env_variables]) if config[:env_variables]
78
+ cmd << ' --privileged' if config[:privileged]
79
+ cmd << ' -t' if config[:tty]
80
+ cmd << ' -i' if config[:interactive]
81
+ cmd << " -u #{config[:username]}" if config[:username]
82
+ cmd << " -w #{config[:working_dir]}" if config[:working_dir]
83
+ cmd << " #{state[:container_id]}"
84
+ cmd << " #{command}"
85
+ logger.debug("build_exec_command: #{cmd}")
86
+ cmd
87
+ end
88
+
89
+ def build_copy_command(local_file, remote_file, opts = {})
90
+ cmd = 'cp'
91
+ cmd << ' -a' if opts[:archive]
92
+ cmd << " #{local_file} #{remote_file}"
93
+ cmd
94
+ end
95
+
96
+ def build_powershell_command(args)
97
+ cmd = 'powershell -ExecutionPolicy Bypass -NoLogo '
98
+ cmd << args
99
+ logger.debug("build_powershell_command: #{cmd}")
100
+ cmd
101
+ end
102
+
103
+ def build_env_variable_args(vars)
104
+ raise ActionFailed, 'Environment variables are not of a Hash type' unless vars.is_a?(Hash)
105
+
106
+ args = ''
107
+ vars.each do |k, v|
108
+ args << " -e #{k.to_s.strip}=\"#{v.to_s.strip}\""
109
+ end
110
+
111
+ args
112
+ end
113
+
114
+ def dev_null
115
+ case RbConfig::CONFIG['host_os']
116
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
117
+ 'NUL'
118
+ else
119
+ '/dev/null'
120
+ end
121
+ end
122
+
123
+ def docker_shell_opts(options = {})
124
+ options[:live_stream] = nil if options[:suppress_output]
125
+ options.delete(:suppress_output)
126
+
127
+ options
128
+ end
129
+
130
+ # Convert the config input for `:build_options` or `:run_options` in to a
131
+ # command line string for use with Docker.
132
+ #
133
+ # @since 2.5.0
134
+ # @param config [nil, String, Array, Hash] Config data to convert.
135
+ # @return [String]
136
+ def config_to_options(config)
137
+ case config
138
+ when nil
139
+ ''
140
+ when String
141
+ config
142
+ when Array
143
+ config.map { |c| config_to_options(c) }.join(' ')
144
+ when Hash
145
+ config.map { |k, v| Array(v).map { |c| "--#{k}=#{Shellwords.escape(c)}" }.join(' ') }.join(' ')
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end