test-kitchen 1.4.2 → 1.5.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +27 -2
  4. data/CHANGELOG.md +40 -1
  5. data/Gemfile.proxy_tests +6 -0
  6. data/features/kitchen_driver_discover_command.feature +6 -0
  7. data/features/kitchen_init_command.feature +5 -3
  8. data/features/step_definitions/gem_steps.rb +12 -4
  9. data/features/step_definitions/git_steps.rb +1 -1
  10. data/features/step_definitions/output_steps.rb +1 -1
  11. data/features/support/env.rb +13 -9
  12. data/lib/kitchen/cli.rb +3 -0
  13. data/lib/kitchen/command/driver_discover.rb +8 -0
  14. data/lib/kitchen/configurable.rb +32 -0
  15. data/lib/kitchen/driver/ssh_base.rb +18 -4
  16. data/lib/kitchen/generator/driver_create.rb +2 -2
  17. data/lib/kitchen/lazy_hash.rb +20 -0
  18. data/lib/kitchen/provisioner/base.rb +16 -1
  19. data/lib/kitchen/provisioner/chef_base.rb +42 -98
  20. data/lib/kitchen/provisioner/chef_solo.rb +6 -4
  21. data/lib/kitchen/provisioner/chef_zero.rb +7 -5
  22. data/lib/kitchen/transport/winrm.rb +36 -2
  23. data/lib/kitchen/verifier/base.rb +18 -1
  24. data/lib/kitchen/verifier/busser.rb +12 -5
  25. data/lib/kitchen/verifier/shell.rb +101 -0
  26. data/lib/kitchen/version.rb +1 -1
  27. data/spec/kitchen/configurable_spec.rb +211 -2
  28. data/spec/kitchen/driver/ssh_base_spec.rb +131 -8
  29. data/spec/kitchen/lazy_hash_spec.rb +27 -0
  30. data/spec/kitchen/provisioner/base_spec.rb +25 -0
  31. data/spec/kitchen/provisioner/chef_base_spec.rb +133 -252
  32. data/spec/kitchen/provisioner/chef_solo_spec.rb +17 -0
  33. data/spec/kitchen/provisioner/chef_zero_spec.rb +14 -2
  34. data/spec/kitchen/provisioner/shell_spec.rb +60 -12
  35. data/spec/kitchen/transport/winrm_spec.rb +50 -0
  36. data/spec/kitchen/verifier/base_spec.rb +26 -0
  37. data/spec/kitchen/verifier/busser_spec.rb +69 -3
  38. data/spec/kitchen/verifier/shell_spec.rb +157 -0
  39. data/support/busser_install_command.sh +1 -2
  40. data/test-kitchen.gemspec +10 -2
  41. metadata +86 -6
@@ -29,8 +29,8 @@ module Kitchen
29
29
  include Logging
30
30
 
31
31
  default_config :http_proxy, nil
32
-
33
32
  default_config :https_proxy, nil
33
+ default_config :ftp_proxy, nil
34
34
 
35
35
  default_config :root_path do |provisioner|
36
36
  provisioner.windows_os? ? "$env:TEMP\\kitchen" : "/tmp/kitchen"
@@ -44,6 +44,8 @@ module Kitchen
44
44
  provisioner.windows_os? ? nil : "sudo -E"
45
45
  end
46
46
 
47
+ default_config :command_prefix, nil
48
+
47
49
  expand_path_for :test_base_path
48
50
 
49
51
  # Constructs a new provisioner by providing a configuration hash.
@@ -208,6 +210,19 @@ module Kitchen
208
210
  def sudo(script)
209
211
  config[:sudo] ? "#{config[:sudo_command]} #{script}" : script
210
212
  end
213
+
214
+ # Conditionally prefixes a command with a command prefix.
215
+ # This should generally be done after a command has been
216
+ # conditionally prefixed by #sudo as certain platforms, such as
217
+ # Cisco Nexus, require all commands to be run with a prefix to
218
+ # obtain outbound network access.
219
+ #
220
+ # @param command [String] command to be prefixed
221
+ # @return [String] the command, conditionally prefixed with the configured prefix
222
+ # @api private
223
+ def prefix_command(script)
224
+ config[:command_prefix] ? "#{config[:command_prefix]} #{script}" : script
225
+ end
211
226
  end
212
227
  end
213
228
  end
@@ -25,6 +25,9 @@ require "kitchen/provisioner/chef/berkshelf"
25
25
  require "kitchen/provisioner/chef/common_sandbox"
26
26
  require "kitchen/provisioner/chef/librarian"
27
27
  require "kitchen/util"
28
+ require "mixlib/install"
29
+ require "chef-config/config"
30
+ require "chef-config/workstation_config_loader"
28
31
 
29
32
  module Kitchen
30
33
 
@@ -40,6 +43,7 @@ module Kitchen
40
43
  default_config :chef_omnibus_install_options, nil
41
44
  default_config :run_list, []
42
45
  default_config :attributes, {}
46
+ default_config :config_path, nil
43
47
  default_config :log_file, nil
44
48
  default_config :cookbook_files_glob, %w[
45
49
  README.* metadata.{json,rb}
@@ -47,18 +51,6 @@ module Kitchen
47
51
  providers/**/* recipes/**/* resources/**/* templates/**/*
48
52
  ].join(",")
49
53
 
50
- default_config :chef_metadata_url do |provisioner|
51
- provisioner.default_windows_chef_metadata_url if provisioner.windows_os?
52
- end
53
-
54
- default_config :chef_omnibus_root do |provisioner|
55
- if provisioner.windows_os?
56
- "$env:systemdrive\\opscode\\chef"
57
- else
58
- "/opt/chef"
59
- end
60
- end
61
-
62
54
  default_config :data_path do |provisioner|
63
55
  provisioner.calculate_path("data")
64
56
  end
@@ -94,29 +86,27 @@ module Kitchen
94
86
  end
95
87
  expand_path_for :encrypted_data_bag_secret_key_path
96
88
 
89
+ # Reads the local Chef::Config object (if present). We do this because
90
+ # we want to start bring Chef config and ChefDK tool config closer
91
+ # together. For example, we want to configure proxy settings in 1
92
+ # location instead of 3 configuration files.
93
+ #
94
+ # @param config [Hash] initial provided configuration
95
+ def initialize(config = {})
96
+ super(config)
97
+
98
+ ChefConfig::WorkstationConfigLoader.new(config[:config_path]).load
99
+ # This exports any proxy config present in the Chef config to
100
+ # appropriate environment variables, which Test Kitchen respects
101
+ ChefConfig::Config.export_proxies
102
+ end
103
+
97
104
  # (see Base#create_sandbox)
98
105
  def create_sandbox
99
106
  super
100
107
  Chef::CommonSandbox.new(config, sandbox_path, instance).populate
101
108
  end
102
109
 
103
- # @return [String] a metadata URL for the Chef Omnitruck API suitable
104
- # for installing a Windows MSI package
105
- def default_windows_chef_metadata_url
106
- version = config[:require_chef_omnibus]
107
- version = "latest" if version == true
108
- base = if config[:chef_omnibus_url] =~ %r{/install.sh$}
109
- "#{File.dirname(config[:chef_omnibus_url])}/"
110
- else
111
- "https://www.chef.io/chef/"
112
- end
113
-
114
- url = "#{base}#{metadata_project_from_options}"
115
- url << "?p=windows&m=x86_64&pv=2008r2" # same pacakge for all versions
116
- url << "&v=#{CGI.escape(version.to_s.downcase)}"
117
- url
118
- end
119
-
120
110
  # (see Base#init_command)
121
111
  def init_command
122
112
  dirs = %w[
@@ -130,7 +120,7 @@ module Kitchen
130
120
  init_command_vars_for_bourne(dirs)
131
121
  end
132
122
 
133
- shell_code_from_file(vars, "chef_base_init_command")
123
+ prefix_command(shell_code_from_file(vars, "chef_base_init_command"))
134
124
  end
135
125
 
136
126
  # (see Base#install_command)
@@ -139,17 +129,33 @@ module Kitchen
139
129
 
140
130
  version = config[:require_chef_omnibus].to_s.downcase
141
131
 
142
- vars = if powershell_shell?
143
- install_command_vars_for_powershell(version)
144
- else
145
- install_command_vars_for_bourne(version)
146
- end
132
+ # Passing "true" to mixlib-install currently breaks the windows metadata_url
133
+ # TODO: remove this line once https://github.com/chef/mixlib-install/pull/22
134
+ # is accepted and released
135
+ version = "" if version == "true"
147
136
 
148
- shell_code_from_file(vars, "chef_base_install_command")
137
+ installer = Mixlib::Install.new(version, powershell_shell?, install_options)
138
+ config[:chef_omnibus_root] = installer.root
139
+ prefix_command(sudo(installer.install_command))
149
140
  end
150
141
 
151
142
  private
152
143
 
144
+ # @return [Hash] an option hash for the install commands
145
+ # @api private
146
+ def install_options
147
+ project = /\s*-P (\w+)\s*/.match(config[:chef_omnibus_install_options])
148
+ {
149
+ :omnibus_url => config[:chef_omnibus_url],
150
+ :project => project.nil? ? nil : project[1],
151
+ :install_flags => config[:chef_omnibus_install_options]
152
+ }.tap do |opts|
153
+ opts[:root] = config[:chef_omnibus_root] if config.key? :chef_omnibus_root
154
+ opts[:http_proxy] = config[:http_proxy] if config.key? :http_proxy
155
+ opts[:https_proxy] = config[:https_proxy] if config.key? :https_proxy
156
+ end
157
+ end
158
+
153
159
  # @return [String] an absolute path to a Berksfile, relative to the
154
160
  # kitchen root
155
161
  # @api private
@@ -251,43 +257,6 @@ module Kitchen
251
257
  ].join("\n")
252
258
  end
253
259
 
254
- # Generates the install command variables for Bourne shell-based
255
- # platforms.
256
- #
257
- # @param version [String] version string
258
- # @return [String] shell variable lines
259
- # @api private
260
- def install_command_vars_for_bourne(version)
261
- install_flags = %w[latest true].include?(version) ? "" : "-v #{CGI.escape(version)}"
262
- if config[:chef_omnibus_install_options]
263
- install_flags << " " << config[:chef_omnibus_install_options]
264
- end
265
-
266
- [
267
- shell_var("chef_omnibus_root", config[:chef_omnibus_root]),
268
- shell_var("chef_omnibus_url", config[:chef_omnibus_url]),
269
- shell_var("install_flags", install_flags.strip),
270
- shell_var("pretty_version", pretty_version(version)),
271
- shell_var("sudo_sh", sudo("sh")),
272
- shell_var("version", version)
273
- ].join("\n")
274
- end
275
-
276
- # Generates the install command variables for PowerShell-based platforms.
277
- #
278
- # @param version [String] version string
279
- # @return [String] shell variable lines
280
- # @api private
281
- def install_command_vars_for_powershell(version)
282
- [
283
- shell_var("chef_metadata_url", config[:chef_metadata_url]),
284
- shell_var("chef_omnibus_root", config[:chef_omnibus_root]),
285
- shell_var("msi", "$env:TEMP\\chef-#{version}.msi"),
286
- shell_var("pretty_version", pretty_version(version)),
287
- shell_var("version", version)
288
- ].join("\n")
289
- end
290
-
291
260
  # Load cookbook dependency resolver code, if required.
292
261
  #
293
262
  # (see Base#load_needed_dependencies!)
@@ -302,31 +271,6 @@ module Kitchen
302
271
  end
303
272
  end
304
273
 
305
- # @return the correct Chef Omnitruck API metadata endpoint, based on
306
- # project type which could live in
307
- # `config[:chef_omnibus_install_options]`
308
- # @api private
309
- def metadata_project_from_options
310
- match = /\s*-P (\w+)\s*/.match(config[:chef_omnibus_install_options])
311
-
312
- if match.nil? || match[1].downcase == "chef"
313
- "metadata"
314
- else
315
- "metadata-#{match[1].downcase}"
316
- end
317
- end
318
-
319
- # @return [String] a pretty/helpful representation of a Chef Omnibus
320
- # package version
321
- # @api private
322
- def pretty_version(version)
323
- case version
324
- when "true" then "install only if missing"
325
- when "latest" then "always install latest version"
326
- else version
327
- end
328
- end
329
-
330
274
  # @return [String] a powershell command to reload the `PATH` environment
331
275
  # variable, only to be used to support old Omnibus Chef packages that
332
276
  # require `PATH` to find the `ruby.exe` binary
@@ -46,7 +46,7 @@ module Kitchen
46
46
  end
47
47
 
48
48
  # (see Base#run_command)
49
- def run_command
49
+ def run_command # rubocop:disable Metrics/AbcSize
50
50
  level = config[:log_level] == :info ? :auto : config[:log_level]
51
51
 
52
52
  cmd = sudo(config[:chef_solo_path]).dup.
@@ -60,10 +60,12 @@ module Kitchen
60
60
  ]
61
61
  args << "--logfile #{config[:log_file]}" if config[:log_file]
62
62
 
63
- wrap_shell_code(
64
- [cmd, *args].join(" ").
65
- tap { |str| str.insert(0, reload_ps1_path) if windows_os? }
63
+ prefix_command(
64
+ wrap_shell_code(
65
+ [cmd, *args].join(" ").
66
+ tap { |str| str.insert(0, reload_ps1_path) if windows_os? }
66
67
  )
68
+ )
67
69
  end
68
70
 
69
71
  private
@@ -66,16 +66,18 @@ module Kitchen
66
66
  shell_var("gem", sudo(gem_bin))
67
67
  ].join("\n").concat("\n")
68
68
 
69
- shell_code_from_file(vars, "chef_zero_prepare_command_legacy")
69
+ prefix_command(shell_code_from_file(vars, "chef_zero_prepare_command_legacy"))
70
70
  end
71
71
 
72
72
  # (see Base#run_command)
73
73
  def run_command
74
74
  cmd = modern? ? local_mode_command : shim_command
75
75
 
76
- wrap_shell_code(
77
- [cmd, *chef_client_args].join(" ").
78
- tap { |str| str.insert(0, reload_ps1_path) if windows_os? }
76
+ prefix_command(
77
+ wrap_shell_code(
78
+ [cmd, *chef_client_args].join(" ").
79
+ tap { |str| str.insert(0, reload_ps1_path) if windows_os? }
80
+ )
79
81
  )
80
82
  end
81
83
 
@@ -111,7 +113,7 @@ module Kitchen
111
113
  # @return [Array<String>] an array of command line arguments
112
114
  # @api private
113
115
  def chef_client_args
114
- level = config[:log_level] == :info ? :auto : config[:log_level]
116
+ level = config[:log_level] == :info ? :warn : config[:log_level]
115
117
  args = [
116
118
  "--config #{remote_path_join(config[:root_path], "client.rb")}",
117
119
  "--log_level #{level}",
@@ -87,6 +87,10 @@ module Kitchen
87
87
  def execute(command)
88
88
  return if command.nil?
89
89
  logger.debug("[WinRM] #{self} (#{command})")
90
+
91
+ if command.length > MAX_COMMAND_SIZE
92
+ command = run_from_file_command(command)
93
+ end
90
94
  exit_code, stderr = execute_with_exit_code(command)
91
95
 
92
96
  if logger.debug? && exit_code == 0
@@ -134,6 +138,11 @@ module Kitchen
134
138
 
135
139
  PING_COMMAND = "Write-Host '[WinRM] Established\n'".freeze
136
140
 
141
+ # Maximum string to send to the transport to execute. WinRM has an 8000 character
142
+ # command line limit. The original command string is coverted to a base 64 encoded
143
+ # UTF-16 string which will double the string size.
144
+ MAX_COMMAND_SIZE = 3000
145
+
137
146
  RESCUE_EXCEPTIONS_ON_ESTABLISH = lambda do
138
147
  [
139
148
  Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED,
@@ -375,11 +384,36 @@ module Kitchen
375
384
  def to_s
376
385
  "#{winrm_transport}::#{endpoint}<#{options.inspect}>"
377
386
  end
387
+
388
+ # takes a long (greater than 3000 characters) command and saves it to a
389
+ # file and uploads it to the test instance.
390
+ #
391
+ # @param command [String] a long command to be saved and uploaded
392
+ # @return [String] a command that executes the uploaded script
393
+ # @api private
394
+ def run_from_file_command(command)
395
+ temp_dir = Dir.mktmpdir("kitchen-long-script")
396
+ begin
397
+ script_name = "#{instance_name}-long_script.ps1"
398
+ script_path = File.join(temp_dir, script_name)
399
+
400
+ File.open(script_path, "wb") do |file|
401
+ file.write(command)
402
+ end
403
+
404
+ target_path = File.join("$env:TEMP", "kitchen")
405
+ upload(script_path, target_path)
406
+
407
+ %{& "#{File.join(target_path, script_name)}"}
408
+ ensure
409
+ FileUtils.rmtree(temp_dir)
410
+ end
411
+ end
378
412
  end
379
413
 
380
414
  private
381
415
 
382
- WINRM_TRANSPORT_SPEC_VERSION = "~> 1.0".freeze
416
+ WINRM_TRANSPORT_SPEC_VERSION = ["~> 1.0", ">= 1.0.3"].freeze
383
417
 
384
418
  # Builds the hash of options needed by the Connection object on
385
419
  # construction.
@@ -429,7 +463,7 @@ module Kitchen
429
463
  spec_version = WINRM_TRANSPORT_SPEC_VERSION.dup
430
464
  logger.debug("Winrm Transport requested," \
431
465
  " loading WinRM::Transport gem (#{spec_version})")
432
- gem "winrm-transport", spec_version
466
+ gem "winrm-transport", *spec_version
433
467
  first_load = require "winrm/transport/version"
434
468
  load_winrm_transport!
435
469
 
@@ -33,8 +33,8 @@ module Kitchen
33
33
  include Logging
34
34
 
35
35
  default_config :http_proxy, nil
36
-
37
36
  default_config :https_proxy, nil
37
+ default_config :ftp_proxy, nil
38
38
 
39
39
  default_config :root_path do |verifier|
40
40
  verifier.windows_os? ? "$env:TEMP\\verifier" : "/tmp/verifier"
@@ -44,10 +44,14 @@ module Kitchen
44
44
  verifier.windows_os? ? nil : true
45
45
  end
46
46
 
47
+ default_config :chef_omnibus_root, "/opt/chef"
48
+
47
49
  default_config :sudo_command do |verifier|
48
50
  verifier.windows_os? ? nil : "sudo -E"
49
51
  end
50
52
 
53
+ default_config :command_prefix, nil
54
+
51
55
  default_config(:suite_name) { |busser| busser.instance.suite.name }
52
56
 
53
57
  # Creates a new Verifier object using the provided configuration data
@@ -213,6 +217,19 @@ module Kitchen
213
217
  def sudo(script)
214
218
  config[:sudo] ? "#{config[:sudo_command]} #{script}" : script
215
219
  end
220
+
221
+ # Conditionally prefixes a command with a command prefix.
222
+ # This should generally be done after a command has been
223
+ # conditionally prefixed by #sudo as certain platforms, such as
224
+ # Cisco Nexus, require all commands to be run with a prefix to
225
+ # obtain outbound network access.
226
+ #
227
+ # @param command [String] command to be prefixed
228
+ # @return [String] the command, conditionally prefixed with the configured prefix
229
+ # @api private
230
+ def prefix_command(script)
231
+ config[:command_prefix] ? "#{config[:command_prefix]} #{script}" : script
232
+ end
216
233
  end
217
234
  end
218
235
  end
@@ -46,7 +46,7 @@ module Kitchen
46
46
  if verifier.windows_os?
47
47
  "$env:systemdrive\\opscode\\chef\\embedded\\bin"
48
48
  else
49
- "/opt/chef/embedded/bin"
49
+ verifier.remote_path_join(%W[#{verifier[:chef_omnibus_root]} embedded bin])
50
50
  end
51
51
  end
52
52
 
@@ -76,7 +76,7 @@ module Kitchen
76
76
  cmd = sudo(config[:busser_bin]).dup.
77
77
  tap { |str| str.insert(0, "& ") if powershell_shell? }
78
78
 
79
- wrap_shell_code(Util.outdent!(<<-CMD))
79
+ prefix_command(wrap_shell_code(Util.outdent!(<<-CMD)))
80
80
  #{busser_env}
81
81
 
82
82
  #{cmd} suite cleanup
@@ -89,7 +89,7 @@ module Kitchen
89
89
 
90
90
  vars = install_command_vars
91
91
 
92
- shell_code_from_file(vars, "busser_install_command")
92
+ prefix_command(shell_code_from_file(vars, "busser_install_command"))
93
93
  end
94
94
 
95
95
  # (see Base#run_command)
@@ -99,7 +99,7 @@ module Kitchen
99
99
  cmd = sudo(config[:busser_bin]).dup.
100
100
  tap { |str| str.insert(0, "& ") if powershell_shell? }
101
101
 
102
- wrap_shell_code(Util.outdent!(<<-CMD))
102
+ prefix_command(wrap_shell_code(Util.outdent!(<<-CMD)))
103
103
  #{busser_env}
104
104
 
105
105
  #{cmd} test
@@ -173,9 +173,16 @@ module Kitchen
173
173
  gem, version = config[:version].split("@")
174
174
  gem, version = "busser", gem if gem =~ /^\d+\.\d+\.\d+/
175
175
 
176
+ root = config[:root_path]
177
+ gem_bin = remote_path_join(root, "bin")
178
+
179
+ # We don't want the gems to be installed in the home directory,
180
+ # this will force the bindir and the gem install location both
181
+ # to be under /tmp/verifier
176
182
  args = gem
177
183
  args += " --version #{version}" if version
178
- args += " --no-rdoc --no-ri"
184
+ args += " --no-rdoc --no-ri --no-format-executable -n #{gem_bin}"
185
+ args += " --no-user-install"
179
186
  args
180
187
  end
181
188