kitchen-docker 3.2.4 → 3.3.1

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.release-please-manifest.json +1 -1
  4. data/.rubocop.yml +0 -1
  5. data/.yardopts +11 -0
  6. data/CHANGELOG.md +385 -18
  7. data/CONTRIBUTING.md +203 -0
  8. data/Gemfile +5 -3
  9. data/README.md +418 -519
  10. data/Rakefile +18 -0
  11. data/kitchen-docker.gemspec +3 -2
  12. data/lib/kitchen/docker/container/linux.rb +50 -0
  13. data/lib/kitchen/docker/container/windows.rb +27 -0
  14. data/lib/kitchen/docker/container.rb +34 -0
  15. data/lib/kitchen/docker/docker_version.rb +4 -2
  16. data/lib/kitchen/docker/erb_context.rb +9 -0
  17. data/lib/kitchen/docker/helpers/cli_helper.rb +56 -3
  18. data/lib/kitchen/docker/helpers/container_helper.rb +91 -19
  19. data/lib/kitchen/docker/helpers/dockerfile_helper.rb +54 -0
  20. data/lib/kitchen/docker/helpers/file_helper.rb +8 -0
  21. data/lib/kitchen/docker/helpers/image_helper.rb +28 -0
  22. data/lib/kitchen/docker/helpers/inspec_helper.rb +1 -0
  23. data/lib/kitchen/driver/docker.rb +27 -0
  24. data/lib/kitchen/transport/docker.rb +92 -2
  25. data/spec/cli_helper_spec.rb +312 -0
  26. data/spec/container_helper_spec.rb +178 -0
  27. data/spec/dockerfile_helper_spec.rb +97 -61
  28. data/spec/erb_context_spec.rb +60 -0
  29. data/spec/image_helper_spec.rb +142 -0
  30. data/spec/inspec_helper_spec.rb +14 -11
  31. data/spec/linux_container_spec.rb +194 -0
  32. data/spec/spec_helper.rb +28 -1
  33. data/spec/support/argv.rb +55 -0
  34. data/spec/support/docker_output.rb +117 -0
  35. data/spec/support/harness.rb +70 -0
  36. data/spec/transport_docker_spec.rb +130 -0
  37. data/spec/windows_container_spec.rb +101 -0
  38. metadata +16 -7
  39. data/test/integration/capabilities/disabled/capabilities_drop_spec.rb +0 -24
  40. data/test/integration/default/disabled/default_spec.rb +0 -24
  41. data/test/integration/default/disabled/spec_helper.rb +0 -21
data/Rakefile CHANGED
@@ -16,4 +16,22 @@ rescue LoadError
16
16
  puts "cookstyle/chefstyle is not available. (sudo) gem install cookstyle to do style checking."
17
17
  end
18
18
 
19
+ begin
20
+ require "yard"
21
+
22
+ # Options and the file list live in .yardopts so that a bare `yard` from the
23
+ # command line produces exactly what `rake doc` does.
24
+ YARD::Rake::YardocTask.new(:doc)
25
+
26
+ desc "List anything in lib/ that is still undocumented"
27
+ task :doc_coverage do
28
+ sh "yard stats --list-undoc"
29
+ end
30
+ rescue LoadError
31
+ desc "Generate YARD documentation (not installed)"
32
+ task :doc do
33
+ abort "YARD is not installed. Run: bundle install"
34
+ end
35
+ end
36
+
19
37
  task default: %i{style test}
@@ -3,14 +3,15 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
  require "kitchen/docker/docker_version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = "kitchen-docker"
6
+ spec.name = "kitchen-docker"
7
+ spec.required_ruby_version = ">= 3.1"
7
8
  spec.version = Kitchen::Docker::DOCKER_VERSION
8
9
  spec.authors = ["Sean Porter"]
9
10
  spec.email = ["portertech@gmail.com"]
10
11
  spec.description = %q{A Docker Driver for Test Kitchen}
11
12
  spec.summary = spec.description
12
13
  spec.homepage = "https://github.com/test-kitchen/kitchen-docker"
13
- spec.license = "Apache 2.0"
14
+ spec.license = "Apache-2.0"
14
15
 
15
16
  spec.files = `git ls-files`.split($/)
16
17
  spec.require_paths = ["lib"]
@@ -22,15 +22,28 @@ require_relative "../helpers/dockerfile_helper"
22
22
  module Kitchen
23
23
  module Docker
24
24
  class Container
25
+ # A Linux container, reached over SSH.
26
+ #
27
+ # The generated image installs and runs an SSH server, and Test Kitchen
28
+ # connects to a published port with a generated key. That is why the
29
+ # Dockerfile here is so much larger than the Windows one.
25
30
  class Linux < Kitchen::Docker::Container
26
31
  include Kitchen::Docker::Helpers::DockerfileHelper
27
32
 
33
+ # Serializes SSH key generation across concurrently running instances,
34
+ # which share one key path.
28
35
  MUTEX_FOR_SSH_KEYS = Mutex.new
29
36
 
37
+ # @param config [Hash] the driver configuration
30
38
  def initialize(config)
31
39
  super
32
40
  end
33
41
 
42
+ # Builds the image, runs the container, and publishes its SSH port.
43
+ #
44
+ # @param state [Hash] mutable instance state; gains +ssh_key+, +image_id+,
45
+ # +container_id+, +hostname+, and +port+
46
+ # @return [void]
34
47
  def create(state)
35
48
  super
36
49
 
@@ -44,6 +57,15 @@ module Kitchen
44
57
  state[:port] = container_ssh_port(state)
45
58
  end
46
59
 
60
+ # Runs a command in the container by uploading it as a shell script.
61
+ #
62
+ # The command is written to a temp file and executed with bash rather than
63
+ # passed on the command line, which keeps long converge scripts clear of
64
+ # argument-length and quoting limits.
65
+ #
66
+ # @param command [String] the shell code to run
67
+ # @return [String] the command's combined output
68
+ # @raise [RuntimeError] if the command fails
47
69
  def execute(command)
48
70
  # Create temp script file and upload files to container
49
71
  debug("Executing command on Linux container (Platform: #{@config[:platform]})")
@@ -72,6 +94,12 @@ module Kitchen
72
94
 
73
95
  protected
74
96
 
97
+ # Generates the SSH keypair used to log into Linux containers.
98
+ #
99
+ # Guarded by {MUTEX_FOR_SSH_KEYS} because concurrent instances share one
100
+ # key path and would otherwise write the file while another reads it.
101
+ #
102
+ # @return [void]
75
103
  def generate_keys
76
104
  MUTEX_FOR_SSH_KEYS.synchronize do
77
105
  if !File.exist?(@config[:public_key]) || !File.exist?(@config[:private_key])
@@ -90,6 +118,11 @@ module Kitchen
90
118
  end
91
119
  end
92
120
 
121
+ # Pulls the published port out of `docker port` output.
122
+ #
123
+ # @param output [String] e.g. +"0.0.0.0:32768"+
124
+ # @return [Integer] the host-side port
125
+ # @raise [Kitchen::ActionFailed] if the output cannot be parsed
93
126
  def parse_container_ssh_port(output)
94
127
  _host, port = output.split(":")
95
128
  port.to_i
@@ -97,6 +130,15 @@ module Kitchen
97
130
  raise ActionFailed, "Could not parse Docker port output for container SSH port. #{e}"
98
131
  end
99
132
 
133
+ # The port Test Kitchen should connect to for SSH.
134
+ #
135
+ # On the internal Docker network the container is reached directly, so the
136
+ # unmapped port 22 is correct; otherwise Docker's published mapping is
137
+ # looked up.
138
+ #
139
+ # @param state [Hash] instance state naming the container
140
+ # @return [Integer] the port to connect on
141
+ # @raise [Kitchen::ActionFailed] if no SSH port is mapped
100
142
  def container_ssh_port(state)
101
143
  return 22 if @config[:use_internal_docker_network]
102
144
 
@@ -106,6 +148,14 @@ module Kitchen
106
148
  raise ActionFailed, "Docker reports container has no ssh port mapped. #{e}"
107
149
  end
108
150
 
151
+ # Builds the Dockerfile for a Linux container.
152
+ #
153
+ # A configured +dockerfile+ is used as-is after ERB rendering. Otherwise
154
+ # one is generated: the base image, proxy settings, the platform's own
155
+ # package setup, any +provision_command+ entries, and the generated public
156
+ # key appended to the login user's authorized_keys.
157
+ #
158
+ # @return [String] the Dockerfile contents
109
159
  def dockerfile
110
160
  return dockerfile_template if @config[:dockerfile]
111
161
 
@@ -18,11 +18,25 @@ require_relative "../container"
18
18
  module Kitchen
19
19
  module Docker
20
20
  class Container
21
+ # A Windows container, driven through `docker exec`.
22
+ #
23
+ # There is no SSH server and no key to inject: commands are uploaded as
24
+ # PowerShell scripts and executed in the container directly, so no port
25
+ # is published.
21
26
  class Windows < Kitchen::Docker::Container
27
+ # @param config [Hash] the driver configuration
22
28
  def initialize(config)
23
29
  super
24
30
  end
25
31
 
32
+ # Builds the image and runs the container.
33
+ #
34
+ # No port is published: Windows containers are driven through `docker exec`
35
+ # rather than SSH, so there is nothing to map.
36
+ #
37
+ # @param state [Hash] mutable instance state; gains +username+, +image_id+,
38
+ # +container_id+, and +hostname+
39
+ # @return [void]
26
40
  def create(state)
27
41
  super
28
42
 
@@ -33,6 +47,11 @@ module Kitchen
33
47
  state[:hostname] = hostname(state)
34
48
  end
35
49
 
50
+ # Runs a command in the container by uploading it as a PowerShell script.
51
+ #
52
+ # @param command [String] the PowerShell code to run
53
+ # @return [String] the command's combined output
54
+ # @raise [RuntimeError] if the command fails
36
55
  def execute(command)
37
56
  # Create temp script file and upload files to container
38
57
  debug("Executing command on Windows container")
@@ -62,6 +81,14 @@ module Kitchen
62
81
 
63
82
  protected
64
83
 
84
+ # Builds the Dockerfile for a Windows container.
85
+ #
86
+ # Much shorter than the Linux equivalent: there is no SSH server and no
87
+ # key to inject, so only the base image, proxy settings, and any
88
+ # +provision_command+ entries are emitted.
89
+ #
90
+ # @return [String] the Dockerfile contents
91
+ # @raise [Kitchen::ActionFailed] if the platform is not +windows+
65
92
  def dockerfile
66
93
  raise ActionFailed, "Unknown platform '#{@config[:platform]}'" unless @config[:platform] == "windows"
67
94
  return dockerfile_template if @config[:dockerfile]
@@ -18,16 +18,33 @@ require_relative "helpers/image_helper"
18
18
 
19
19
  module Kitchen
20
20
  module Docker
21
+ # Base class for the container the instance under test runs in.
22
+ #
23
+ # Holds the behaviour that is the same on every platform -- checking
24
+ # whether the container exists, removing it, working out its address, and
25
+ # copying files in. {Linux} and {Windows} add how the image is built and
26
+ # how commands are run, which share almost nothing.
21
27
  class Container
22
28
  include Kitchen::Docker::Helpers::CliHelper
23
29
  include Kitchen::Docker::Helpers::ContainerHelper
24
30
  include Kitchen::Docker::Helpers::FileHelper
25
31
  include Kitchen::Docker::Helpers::ImageHelper
26
32
 
33
+ # @param config [Hash] the driver or transport configuration
27
34
  def initialize(config)
28
35
  @config = config
29
36
  end
30
37
 
38
+ # Checks the container named in state and records the login user.
39
+ #
40
+ # A state file naming a container that no longer exists is an error rather
41
+ # than something to build over, because the stale id usually means the
42
+ # container was removed behind Test Kitchen's back and silently creating a
43
+ # new one would hide that.
44
+ #
45
+ # @param state [Hash] mutable instance state; gains +username+
46
+ # @return [void]
47
+ # @raise [Kitchen::ActionFailed] if state names a container that is gone
31
48
  def create(state)
32
49
  if container_exists?(state)
33
50
  info("Container ID #{state[:container_id]} already exists.")
@@ -39,6 +56,10 @@ module Kitchen
39
56
  state[:username] = @config[:username]
40
57
  end
41
58
 
59
+ # Removes the container, and its image when +remove_images+ is set.
60
+ #
61
+ # @param state [Hash] instance state naming the container
62
+ # @return [void]
42
63
  def destroy(state)
43
64
  info("[Docker] Destroying Docker container #{state[:container_id]}") if state[:container_id]
44
65
  remove_container(state) if container_exists?(state)
@@ -48,6 +69,14 @@ module Kitchen
48
69
  end
49
70
  end
50
71
 
72
+ # Works out the address Test Kitchen should connect to.
73
+ #
74
+ # A remote Docker socket means the container is reachable at the socket's
75
+ # own host; +use_internal_docker_network+ means its container IP; anything
76
+ # else is a published port on localhost.
77
+ #
78
+ # @param state [Hash] instance state naming the container
79
+ # @return [String] a hostname or IP address
51
80
  def hostname(state)
52
81
  hostname = "localhost"
53
82
 
@@ -60,6 +89,11 @@ module Kitchen
60
89
  hostname
61
90
  end
62
91
 
92
+ # Copies local files into the container.
93
+ #
94
+ # @param locals [String, Array<String>] one path or several
95
+ # @param remote [String] destination path inside the container
96
+ # @return [Array<String>] the files copied
63
97
  def upload(locals, remote)
64
98
  files = locals
65
99
  files = Array(locals) unless locals.is_a?(Array)
@@ -13,9 +13,11 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
+ # Test Kitchen's top-level namespace.
16
17
  module Kitchen
18
+ # Everything belonging to the kitchen-docker plugin.
17
19
  module Docker
18
- # Version string for Docker Kitchen driver
19
- DOCKER_VERSION = "3.2.4".freeze
20
+ # The version of the kitchen-docker gem.
21
+ DOCKER_VERSION = "3.3.1".freeze
20
22
  end
21
23
  end
@@ -17,13 +17,22 @@ require "erb" unless defined?(Erb)
17
17
 
18
18
  module Kitchen
19
19
  module Docker
20
+ # Evaluation context for a user-supplied Dockerfile template.
21
+ #
22
+ # Each configuration key becomes an instance variable, so a template can
23
+ # refer to +@image+, +@username+, and the rest.
20
24
  class ERBContext
25
+ # Exposes each config key to the template as an instance variable, so a
26
+ # custom Dockerfile can refer to +@image+, +@username+, and the rest.
27
+ #
28
+ # @param config [Hash] the configuration to expose
21
29
  def initialize(config = {})
22
30
  config.each do |key, value|
23
31
  instance_variable_set("@" + key.to_s, value)
24
32
  end
25
33
  end
26
34
 
35
+ # @return [Binding] a binding for ERB to evaluate the template in
27
36
  def get_binding
28
37
  binding
29
38
  end
@@ -18,14 +18,22 @@ require "kitchen/shell_out"
18
18
 
19
19
  module Kitchen
20
20
  module Docker
21
+ # Mixins shared by the driver, transport, and container classes.
21
22
  module Helpers
22
- # rubocop:disable Metrics/ModuleLength, Style/Documentation
23
+ # rubocop:disable Metrics/ModuleLength
24
+ # Builds and runs docker CLI command lines.
23
25
  module CliHelper
24
26
  include Configurable
25
27
  include Logging
26
28
  include ShellOut
27
29
 
28
30
  # rubocop:disable Metrics/AbcSize
31
+
32
+ # Runs a docker CLI command with the configured connection flags.
33
+ #
34
+ # @param cmd [String] the docker subcommand and its arguments
35
+ # @param options [Hash] shell-out options
36
+ # @return [String] the command's combined stdout and stderr
29
37
  def docker_command(cmd, options = {})
30
38
  docker = config[:binary].dup
31
39
  docker << " -H #{config[:socket]}" if config[:socket]
@@ -39,8 +47,17 @@ module Kitchen
39
47
  end
40
48
  # rubocop:enable Metrics/AbcSize
41
49
 
42
- # Copied from kitchen because we need stderr
43
50
  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
51
+
52
+ # Runs a shell command, returning stderr as well as stdout.
53
+ #
54
+ # Test Kitchen's own +run_command+ discards stderr, but docker writes build
55
+ # progress and image ids there, so this reimplements it to keep both.
56
+ #
57
+ # @param cmd [String] the command to run
58
+ # @param options [Hash] shell-out options
59
+ # @return [String] combined stdout and stderr
60
+ # @raise [Kitchen::ShellCommandFailed] if the command exits non-zero
44
61
  def run_command(cmd, options = {})
45
62
  if options.fetch(:use_sudo, false)
46
63
  cmd = "#{options.fetch(:sudo_command, "sudo -E")} #{cmd}"
@@ -62,6 +79,12 @@ module Kitchen
62
79
  # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
63
80
 
64
81
  # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/AbcSize
82
+
83
+ # Builds the `docker run` command line from the configuration.
84
+ #
85
+ # @param image_id [String] the image to run
86
+ # @param transport_port [Integer, nil] container port to publish, if any
87
+ # @return [String] the docker subcommand and its arguments
65
88
  def build_run_command(image_id, transport_port = nil)
66
89
  cmd = "run -d"
67
90
  cmd << " -i" if config[:interactive]
@@ -100,6 +123,12 @@ module Kitchen
100
123
  # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/AbcSize
101
124
 
102
125
  # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/AbcSize
126
+
127
+ # Builds a `docker exec` command line from the configuration.
128
+ #
129
+ # @param state [Hash] instance state naming the container
130
+ # @param command [String] the command to run inside it
131
+ # @return [String] the docker subcommand and its arguments
103
132
  def build_exec_command(state, command)
104
133
  cmd = "exec"
105
134
  cmd << " -d" if config[:detach]
@@ -116,6 +145,12 @@ module Kitchen
116
145
  end
117
146
  # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/AbcSize
118
147
 
148
+ # Builds a `docker cp` command line.
149
+ #
150
+ # @param local_file [String] source path
151
+ # @param remote_file [String] destination, as +container:path+
152
+ # @param opts [Hash] +:archive+ to preserve ownership and mode
153
+ # @return [String] the docker subcommand and its arguments
119
154
  def build_copy_command(local_file, remote_file, opts = {})
120
155
  cmd = "cp"
121
156
  cmd << " -a" if opts[:archive]
@@ -123,6 +158,10 @@ module Kitchen
123
158
  cmd
124
159
  end
125
160
 
161
+ # Wraps PowerShell code so it can be run through `docker exec`.
162
+ #
163
+ # @param args [String] the PowerShell arguments
164
+ # @return [String] the full powershell invocation
126
165
  def build_powershell_command(args)
127
166
  cmd = "powershell -ExecutionPolicy Bypass -NoLogo "
128
167
  cmd << args
@@ -130,6 +169,11 @@ module Kitchen
130
169
  cmd
131
170
  end
132
171
 
172
+ # Turns a hash of environment variables into `-e` flags.
173
+ #
174
+ # @param vars [Hash] variable names to values
175
+ # @return [String] the flags, each preceded by a space
176
+ # @raise [Kitchen::ActionFailed] if given something other than a Hash
133
177
  def build_env_variable_args(vars)
134
178
  raise ActionFailed, "Environment variables are not of a Hash type" unless vars.is_a?(Hash)
135
179
 
@@ -141,6 +185,8 @@ module Kitchen
141
185
  args
142
186
  end
143
187
 
188
+ # @return [String] the platform's null device, +NUL+ on Windows and
189
+ # +/dev/null+ everywhere else
144
190
  def dev_null
145
191
  case RbConfig::CONFIG["host_os"]
146
192
  when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
@@ -150,6 +196,13 @@ module Kitchen
150
196
  end
151
197
  end
152
198
 
199
+ # Normalizes shell-out options for a docker command.
200
+ #
201
+ # Translates +:suppress_output+ into silencing the live stream, and removes
202
+ # it, since Mixlib::ShellOut would reject the unknown key.
203
+ #
204
+ # @param options [Hash] the options to normalize
205
+ # @return [Hash] options Mixlib::ShellOut accepts
153
206
  def docker_shell_opts(options = {})
154
207
  options[:live_stream] = nil if options[:suppress_output]
155
208
  options.delete(:suppress_output)
@@ -178,7 +231,7 @@ module Kitchen
178
231
  end
179
232
  # rubocop:enable Metrics/CyclomaticComplexity
180
233
  end
181
- # rubocop:enable Metrics/ModuleLength, Style/Documentation
234
+ # rubocop:enable Metrics/ModuleLength
182
235
  end
183
236
  end
184
237
  end
@@ -24,12 +24,22 @@ require_relative "cli_helper"
24
24
 
25
25
  module Kitchen
26
26
  module Docker
27
+ # Mixins shared by the driver, transport, and container classes.
27
28
  module Helpers
28
- # rubocop:disable Metrics/ModuleLength, Style/Documentation
29
+ # rubocop:disable Metrics/ModuleLength
30
+ # Operations against a running container: exec, copy, inspect, remove.
29
31
  module ContainerHelper
30
32
  include Configurable
31
33
  include Kitchen::Docker::Helpers::CliHelper
32
34
 
35
+ # Pulls the container id out of `docker run` output.
36
+ #
37
+ # Docker prints ids in short (12) or full (64) hex form; anything else
38
+ # means the output was not an id at all.
39
+ #
40
+ # @param output [String] the command output
41
+ # @return [String] the container id
42
+ # @raise [Kitchen::ActionFailed] if no id could be parsed
33
43
  def parse_container_id(output)
34
44
  container_id = output.chomp
35
45
 
@@ -40,28 +50,49 @@ module Kitchen
40
50
  container_id
41
51
  end
42
52
 
53
+ # Renders the configured Dockerfile through ERB.
54
+ #
55
+ # @return [String] the rendered Dockerfile
43
56
  def dockerfile_template
44
57
  template = IO.read(File.expand_path(config[:dockerfile]))
45
58
  context = Kitchen::Docker::ERBContext.new(config.to_hash)
46
59
  ERB.new(template).result(context.get_binding)
47
60
  end
48
61
 
62
+ # @return [Boolean] whether the configured socket is a TCP one, meaning
63
+ # the daemon is not on this machine
49
64
  def remote_socket?
50
65
  config[:socket] ? socket_uri.scheme == "tcp" : false
51
66
  end
52
67
 
68
+ # @return [URI] the configured Docker socket
53
69
  def socket_uri
54
70
  URI.parse(config[:socket])
55
71
  end
56
72
 
73
+ # The path to pass to `docker build -f`.
74
+ #
75
+ # With a build context the path has to be relative to it; without one
76
+ # docker reads the Dockerfile from stdin and the absolute path is fine.
77
+ #
78
+ # @param file [File] the temp Dockerfile
79
+ # @return [String] the path to use
57
80
  def dockerfile_path(file)
58
81
  config[:build_context] ? Pathname.new(file.path).relative_path_from(Pathname.pwd).to_s : file.path
59
82
  end
60
83
 
84
+ # @param state [Hash] instance state naming the container
85
+ # @return [Boolean] whether the container is present and running
61
86
  def container_exists?(state)
62
87
  state[:container_id] && !!docker_command("top #{state[:container_id]}") rescue false
63
88
  end
64
89
 
90
+ # Runs a command inside the container.
91
+ #
92
+ # @param state [Hash] instance state naming the container
93
+ # @param command [String] the command to run
94
+ # @return [String] the command's combined output
95
+ # @raise [RuntimeError] if the command fails
65
96
  def container_exec(state, command)
66
97
  cmd = build_exec_command(state, command)
67
98
  docker_command(cmd)
@@ -69,6 +100,13 @@ module Kitchen
69
100
  raise "Failed to execute command on Docker container. #{e}"
70
101
  end
71
102
 
103
+ # Creates a directory inside the container, on Linux or Windows.
104
+ #
105
+ # @param state [Hash] instance state naming the container
106
+ # @param path [String] the directory to create; environment variable
107
+ # references are expanded first
108
+ # @return [String] the command's combined output
109
+ # @raise [RuntimeError] if the directory cannot be created
72
110
  def create_dir_on_container(state, path)
73
111
  path = replace_env_variables(state, path)
74
112
  cmd = "mkdir -p #{path}"
@@ -84,6 +122,13 @@ module Kitchen
84
122
  raise "Failed to create directory #{path} on container. #{e}"
85
123
  end
86
124
 
125
+ # Copies a local file into the container.
126
+ #
127
+ # @param state [Hash] instance state naming the container
128
+ # @param local_file [String] source path
129
+ # @param remote_file [String] destination path inside the container
130
+ # @return [String] the command's combined output
131
+ # @raise [RuntimeError] if the copy fails
87
132
  def copy_file_to_container(state, local_file, remote_file)
88
133
  debug("Copying local file #{local_file} to #{remote_file} on container")
89
134
 
@@ -97,6 +142,11 @@ module Kitchen
97
142
  end
98
143
 
99
144
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
145
+
146
+ # Reads the container's environment.
147
+ #
148
+ # @param state [Hash] instance state naming the container
149
+ # @return [Hash] variable names to values
100
150
  def container_env_variables(state)
101
151
  # Retrieves all environment variables from inside container
102
152
  vars = {}
@@ -116,6 +166,14 @@ module Kitchen
116
166
  end
117
167
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
118
168
 
169
+ # Expands a container-side environment variable reference in a path.
170
+ #
171
+ # Handles both +$env:TEMP+ and +$TEMP+ forms. The value has to be read from
172
+ # inside the container, since the workstation's environment is unrelated.
173
+ #
174
+ # @param state [Hash] instance state naming the container
175
+ # @param str [String] the string to expand
176
+ # @return [String] the expanded string
119
177
  def replace_env_variables(state, str)
120
178
  if str.include?("$env:")
121
179
  key = str[/\$env:(.*?)(\\|$)/, 1]
@@ -130,12 +188,20 @@ module Kitchen
130
188
  str
131
189
  end
132
190
 
191
+ # Runs the container and returns its id.
192
+ #
193
+ # @param state [Hash] instance state naming the image
194
+ # @param transport_port [Integer, nil] container port to publish, if any
195
+ # @return [String] the new container's id
133
196
  def run_container(state, transport_port = nil)
134
197
  cmd = build_run_command(state[:image_id], transport_port)
135
198
  output = docker_command(cmd)
136
199
  parse_container_id(output)
137
200
  end
138
201
 
202
+ # @param state [Hash] instance state naming the container
203
+ # @return [String] the container's address on the Docker network
204
+ # @raise [Kitchen::ActionFailed] if it cannot be determined
139
205
  def container_ip_address(state)
140
206
  cmd = "inspect --format '{{ .NetworkSettings.IPAddress }}'"
141
207
  cmd << " #{state[:container_id]}"
@@ -144,35 +210,41 @@ module Kitchen
144
210
  raise ActionFailed, "Error getting internal IP of Docker container"
145
211
  end
146
212
 
213
+ # Stops and removes the container.
214
+ #
215
+ # @param state [Hash] instance state naming the container
216
+ # @return [void]
147
217
  def remove_container(state)
148
218
  container_id = state[:container_id]
149
219
  docker_command("stop -t 0 #{container_id}")
150
220
  docker_command("rm #{container_id}")
151
221
  end
152
222
 
153
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
223
+ # Dockerfile ENV lines carrying the configured proxy settings.
224
+ #
225
+ # Each is emitted in both lower and upper case, because different tools
226
+ # inside the image read different spellings.
227
+ #
228
+ # @return [String] the ENV lines, empty when no proxy is configured
154
229
  def dockerfile_proxy_config
155
- env_variables = ""
156
- if config[:http_proxy]
157
- env_variables << "ENV http_proxy=#{config[:http_proxy]}\n"
158
- env_variables << "ENV HTTP_PROXY=#{config[:http_proxy]}\n"
159
- end
160
-
161
- if config[:https_proxy]
162
- env_variables << "ENV https_proxy=#{config[:https_proxy]}\n"
163
- env_variables << "ENV HTTPS_PROXY=#{config[:https_proxy]}\n"
164
- end
230
+ %i{http_proxy https_proxy no_proxy}.map do |proxy_type|
231
+ proxy_env_vars(proxy_type)
232
+ end.join
233
+ end
165
234
 
166
- if config[:no_proxy]
167
- env_variables << "ENV no_proxy=#{config[:no_proxy]}\n"
168
- env_variables << "ENV NO_PROXY=#{config[:no_proxy]}\n"
169
- end
235
+ # ENV lines for one proxy setting, in both spellings.
236
+ #
237
+ # @param proxy_type [Symbol] +:http_proxy+, +:https_proxy+, or
238
+ # +:no_proxy+
239
+ # @return [String] two ENV lines, or empty when that proxy is unset
240
+ def proxy_env_vars(proxy_type)
241
+ return "" unless config[proxy_type]
170
242
 
171
- env_variables
243
+ value = config[proxy_type]
244
+ "ENV #{proxy_type}=#{value}\nENV #{proxy_type.upcase}=#{value}\n"
172
245
  end
173
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
174
246
  end
175
- # rubocop:enable Metrics/ModuleLength, Style/Documentation
247
+ # rubocop:enable Metrics/ModuleLength
176
248
  end
177
249
  end
178
250
  end