test-kitchen 1.18.0 → 1.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -0
  3. data/ECOSYSTEM.md +6 -5
  4. data/lib/kitchen/cli.rb +24 -53
  5. data/lib/kitchen/command/doctor.rb +40 -0
  6. data/lib/kitchen/config.rb +6 -0
  7. data/lib/kitchen/configurable.rb +1 -1
  8. data/lib/kitchen/data_munger.rb +2 -0
  9. data/lib/kitchen/driver/base.rb +26 -1
  10. data/lib/kitchen/driver/dummy.rb +1 -0
  11. data/lib/kitchen/driver/exec.rb +71 -0
  12. data/lib/kitchen/driver/proxy.rb +2 -1
  13. data/lib/kitchen/instance.rb +13 -4
  14. data/lib/kitchen/provisioner/base.rb +8 -0
  15. data/lib/kitchen/provisioner/chef_base.rb +3 -1
  16. data/lib/kitchen/provisioner/shell.rb +21 -23
  17. data/lib/kitchen/transport/base.rb +8 -0
  18. data/lib/kitchen/transport/exec.rb +59 -0
  19. data/lib/kitchen/verifier/base.rb +8 -0
  20. data/lib/kitchen/version.rb +1 -1
  21. data/spec/kitchen/config_spec.rb +13 -0
  22. data/spec/kitchen/driver/base_spec.rb +18 -0
  23. data/spec/kitchen/driver/exec_spec.rb +75 -0
  24. data/spec/kitchen/provisioner/chef_base_spec.rb +9 -0
  25. data/spec/kitchen/provisioner/chef_solo_spec.rb +1 -1
  26. data/spec/kitchen/provisioner/chef_zero_spec.rb +1 -1
  27. data/spec/kitchen/provisioner/shell_spec.rb +22 -20
  28. data/spec/kitchen/ssh_spec.rb +0 -29
  29. data/spec/kitchen/transport/exec_spec.rb +79 -0
  30. data/spec/kitchen/transport/ssh_spec.rb +0 -29
  31. data/spec/spec_helper.rb +26 -0
  32. data/support/busser_install_command.sh +10 -3
  33. metadata +9 -8
  34. data/features/kitchen_driver_create_command.feature +0 -64
  35. data/features/kitchen_driver_discover_command.feature +0 -25
  36. data/lib/kitchen/command/driver_discover.rb +0 -102
  37. data/lib/kitchen/generator/driver_create.rb +0 -174
@@ -18,7 +18,7 @@
18
18
  # limitations under the License.
19
19
  #
20
20
 
21
- require "kitchen"
21
+ require "kitchen/driver/ssh_base"
22
22
  require "kitchen/version"
23
23
 
24
24
  module Kitchen
@@ -40,6 +40,7 @@ module Kitchen
40
40
 
41
41
  # (see Base#create)
42
42
  def create(state)
43
+ # TODO: Once this isn't using SSHBase, it should call `super` to support pre_create_command.
43
44
  state[:hostname] = config[:host]
44
45
  reset_instance(state)
45
46
  end
@@ -54,20 +54,20 @@ module Kitchen
54
54
 
55
55
  # @return [Driver::Base] driver object which will manage this instance's
56
56
  # lifecycle actions
57
- attr_reader :driver
57
+ attr_accessor :driver
58
58
 
59
59
  # @return [Provisioner::Base] provisioner object which will the setup
60
60
  # and invocation instructions for configuration management and other
61
61
  # automation tools
62
- attr_reader :provisioner
62
+ attr_accessor :provisioner
63
63
 
64
64
  # @return [Transport::Base] transport object which will communicate with
65
65
  # an instance.
66
- attr_reader :transport
66
+ attr_accessor :transport
67
67
 
68
68
  # @return [Verifier] verifier object for instance to manage the verifier
69
69
  # installation on this instance
70
- attr_reader :verifier
70
+ attr_accessor :verifier
71
71
 
72
72
  # @return [Logger] the logger for this instance
73
73
  attr_reader :logger
@@ -233,6 +233,15 @@ module Kitchen
233
233
  driver.package(state_file.read)
234
234
  end
235
235
 
236
+ # Check system and configuration for common errors.
237
+ #
238
+ def doctor_action
239
+ banner "The doctor is in"
240
+ [driver, provisioner, transport, verifier].any? do |obj|
241
+ obj.doctor(state_file.read)
242
+ end
243
+ end
244
+
236
245
  # Returns a Hash of configuration and other useful diagnostic information.
237
246
  #
238
247
  # @return [Hash] a diagnostic hash
@@ -85,6 +85,14 @@ module Kitchen
85
85
  cleanup_sandbox
86
86
  end
87
87
 
88
+ # Check system and configuration for common errors.
89
+ #
90
+ # @param state [Hash] mutable instance state
91
+ # @returns [Boolean] Return true if a problem is found.
92
+ def doctor(state)
93
+ false
94
+ end
95
+
88
96
  # Generates a command string which will install and configure the
89
97
  # provisioner software on an instance. If no work is required, then `nil`
90
98
  # will be returned.
@@ -49,7 +49,9 @@ module Kitchen
49
49
  default_config :attributes, {}
50
50
  default_config :config_path, nil
51
51
  default_config :log_file, nil
52
- default_config :log_level, "auto"
52
+ default_config :log_level do |provisioner|
53
+ provisioner[:debug] ? "debug" : "auto"
54
+ end
53
55
  default_config :profile_ruby, false
54
56
  # The older policyfile_zero used `policyfile` so support it for compat.
55
57
  default_config :policyfile, nil
@@ -16,6 +16,8 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
 
19
+ require "shellwords"
20
+
19
21
  require "kitchen/provisioner/base"
20
22
  require "kitchen/version"
21
23
 
@@ -35,6 +37,12 @@ module Kitchen
35
37
  end
36
38
  expand_path_for :script
37
39
 
40
+ # Run a single command instead of managing and running a script.
41
+ default_config :command, nil
42
+
43
+ # Add extra arguments to the converge script.
44
+ default_config :arguments, []
45
+
38
46
  default_config :data_path do |provisioner|
39
47
  provisioner.calculate_path("data")
40
48
  end
@@ -49,6 +57,7 @@ module Kitchen
49
57
 
50
58
  # (see Base#init_command)
51
59
  def init_command
60
+ return nil if config[:command]
52
61
  root = config[:root_path]
53
62
  data = remote_path_join(root, "data")
54
63
 
@@ -65,18 +74,24 @@ module Kitchen
65
74
  "#{sudo('rm')} -rf #{data} ; mkdir -p #{root}"
66
75
  end
67
76
 
68
- wrap_shell_code(code)
77
+ prefix_command(wrap_shell_code(code))
69
78
  end
70
79
 
71
80
  # (see Base#run_command)
72
81
  def run_command
82
+ return prefix_command(wrap_shell_code(config[:command])) if config[:command]
83
+ return unless config[:script]
73
84
  script = remote_path_join(
74
85
  config[:root_path],
75
86
  File.basename(config[:script])
76
87
  )
77
88
 
78
- if config[:arguments]
79
- script.concat(" ").concat(config[:arguments])
89
+ if config[:arguments] && !config[:arguments].empty?
90
+ if config[:arguments].is_a?(Array)
91
+ script = Shellwords.join([script] + config[:arguments])
92
+ else
93
+ script.concat(" ").concat(config[:arguments].to_s)
94
+ end
80
95
  end
81
96
 
82
97
  code = powershell_shell? ? %{& "#{script}"} : sudo(script)
@@ -111,30 +126,13 @@ module Kitchen
111
126
  if config[:script]
112
127
  debug("Using script from #{config[:script]}")
113
128
  FileUtils.cp_r(config[:script], sandbox_path)
129
+ FileUtils.chmod(0755,
130
+ File.join(sandbox_path, File.basename(config[:script])))
114
131
  else
115
- prepare_stubbed_script
132
+ info("No provisioner script file specified, skipping")
116
133
  end
117
-
118
- FileUtils.chmod(0755,
119
- File.join(sandbox_path, File.basename(config[:script])))
120
134
  end
121
135
 
122
- # Creates a minimal, no-op script in the sandbox path.
123
- #
124
- # @api private
125
- def prepare_stubbed_script
126
- base = powershell_shell? ? "bootstrap.ps1" : "bootstrap.sh"
127
- config[:script] = File.join(sandbox_path, base)
128
- info("#{File.basename(config[:script])} not found " \
129
- "so Kitchen will run a stubbed script. Is this intended?")
130
- File.open(config[:script], "wb") do |file|
131
- if powershell_shell?
132
- file.write(%{Write-Host "NO BOOTSTRAP SCRIPT PRESENT`n"\n})
133
- else
134
- file.write(%{#!/bin/sh\necho "NO BOOTSTRAP SCRIPT PRESENT"\n})
135
- end
136
- end
137
- end
138
136
  end
139
137
  end
140
138
  end
@@ -64,6 +64,14 @@ module Kitchen
64
64
  raise ClientError, "#{self.class}#connection must be implemented"
65
65
  end
66
66
 
67
+ # Check system and configuration for common errors.
68
+ #
69
+ # @param state [Hash] mutable instance state
70
+ # @returns [Boolean] Return true if a problem is found.
71
+ def doctor(state)
72
+ false
73
+ end
74
+
67
75
  # Closes the connection, if it is still active.
68
76
  #
69
77
  # @return [void]
@@ -0,0 +1,59 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require "fileutils"
16
+
17
+ require "kitchen/shell_out"
18
+ require "kitchen/transport/base"
19
+ require "kitchen/version"
20
+
21
+ module Kitchen
22
+ module Transport
23
+ # Exec transport for Kitchen. This transport runs all commands locally.
24
+ #
25
+ # @since 1.19
26
+ class Exec < Kitchen::Transport::Base
27
+ kitchen_transport_api_version 1
28
+
29
+ plugin_version Kitchen::VERSION
30
+
31
+ def connection(state, &block)
32
+ options = config.to_hash.merge(state)
33
+ Kitchen::Transport::Exec::Connection.new(options, &block)
34
+ end
35
+
36
+ # Fake connection which just does local operations.
37
+ class Connection < Kitchen::Transport::Base::Connection
38
+ include ShellOut
39
+
40
+ # (see Base#execute)
41
+ def execute(command)
42
+ return if command.nil?
43
+ run_command(command)
44
+ end
45
+
46
+ # "Upload" the files by copying them locally.
47
+ #
48
+ # @see Base#upload
49
+ def upload(locals, remote)
50
+ FileUtils.mkdir_p(remote)
51
+ Array(locals).each do |local|
52
+ FileUtils.cp_r(local, remote)
53
+ end
54
+ end
55
+
56
+ end
57
+ end
58
+ end
59
+ end
@@ -82,6 +82,14 @@ module Kitchen
82
82
  cleanup_sandbox
83
83
  end
84
84
 
85
+ # Check system and configuration for common errors.
86
+ #
87
+ # @param state [Hash] mutable instance state
88
+ # @returns [Boolean] Return true if a problem is found.
89
+ def doctor(state)
90
+ false
91
+ end
92
+
85
93
  # Deletes the sandbox path. Without calling this method, the sandbox path
86
94
  # will persist after the process terminates. In other words, cleanup is
87
95
  # explicit. This method is safe to call multiple times.
@@ -17,5 +17,5 @@
17
17
  # limitations under the License.
18
18
 
19
19
  module Kitchen
20
- VERSION = "1.18.0".freeze
20
+ VERSION = "1.19.0".freeze
21
21
  end
@@ -63,6 +63,7 @@ describe Kitchen::Config do
63
63
  log_level: :debug,
64
64
  log_overwrite: false,
65
65
  colorize: false,
66
+ debug: true,
66
67
  }
67
68
  end
68
69
 
@@ -166,6 +167,18 @@ describe Kitchen::Config do
166
167
  end
167
168
  end
168
169
 
170
+ describe "#debug" do
171
+ it "returns its debug" do
172
+ config.debug.must_equal true
173
+ end
174
+
175
+ it "uses false by default" do
176
+ opts.delete(:debug)
177
+
178
+ config.debug.must_equal false
179
+ end
180
+ end
181
+
169
182
  describe "#platforms" do
170
183
  before do
171
184
  Kitchen::DataMunger.stubs(:new).returns(munger)
@@ -88,6 +88,24 @@ describe Kitchen::Driver::Base do
88
88
  end
89
89
  end
90
90
 
91
+ describe ".pre_create_command" do
92
+ it "run command" do
93
+ Kitchen::Driver::Base.any_instance.stubs(:run_command).returns(true)
94
+
95
+ config[:pre_create_command] = "echo works 2&>1 > /dev/null"
96
+ driver.expects(:run_command).with("echo works 2&>1 > /dev/null")
97
+ driver.send(:pre_create_command)
98
+ end
99
+
100
+ it "error if cannot run" do
101
+ class ShellCommandFailed < Kitchen::ShellOut::ShellCommandFailed; end
102
+ Kitchen::Driver::Base.any_instance.stubs(:run_command).raises(ShellCommandFailed, "Expected process to exit with [0], but received '1'")
103
+
104
+ config[:pre_create_command] = "touch /this/dir/does/not/exist 2&>1 > /dev/null"
105
+ proc { driver.send(:pre_create_command) }.must_raise Kitchen::ActionFailed
106
+ end
107
+ end
108
+
91
109
  describe ".no_parallel_for" do
92
110
  it "registers no serial actions when none are declared" do
93
111
  Kitchen::Driver::Speedy.serial_actions.must_be_nil
@@ -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
+
15
+ require_relative "../../spec_helper"
16
+
17
+ require "kitchen/driver/exec"
18
+
19
+ describe Kitchen::Driver::Exec do
20
+ let(:logged_output) { StringIO.new }
21
+ let(:logger) { Logger.new(logged_output) }
22
+ let(:state) { Hash.new }
23
+
24
+ let(:config) do
25
+ { reset_command: "mulligan" }
26
+ end
27
+
28
+ let(:instance) do
29
+ stub(name: "coolbeans", logger: logger, to_str: "instance", :"transport=" => nil)
30
+ end
31
+
32
+ let(:driver) do
33
+ Kitchen::Driver::Exec.new(config).finalize_config!(instance)
34
+ end
35
+
36
+ it "plugin_version is set to Kitchen::VERSION" do
37
+ driver.diagnose_plugin[:version].must_equal Kitchen::VERSION
38
+ end
39
+
40
+ it "sets the transport to exec" do
41
+ instance.expects(:"transport=").with { |v| v.is_a?(Kitchen::Transport::Exec) }
42
+ driver
43
+ end
44
+
45
+ describe "#create" do
46
+ it "runs the reset command" do
47
+ driver.expects(:run_command).with("mulligan")
48
+
49
+ driver.create(state)
50
+ end
51
+
52
+ it "skips the reset command if :reset_command is falsey" do
53
+ config[:reset_command] = false
54
+ driver.expects(:run_command).never
55
+
56
+ driver.create(state)
57
+ end
58
+ end
59
+
60
+ describe "#destroy" do
61
+ it "calls the reset command" do
62
+ driver.expects(:run_command).with("mulligan")
63
+
64
+ driver.destroy(state)
65
+ end
66
+
67
+ it "skips reset command if :reset_command is falsey" do
68
+ config[:reset_command] = false
69
+ driver.expects(:run_command).never
70
+
71
+ driver.destroy(state)
72
+ end
73
+ end
74
+
75
+ end
@@ -90,6 +90,15 @@ describe Kitchen::Provisioner::ChefBase do
90
90
  provisioner[:attributes].must_equal Hash.new
91
91
  end
92
92
 
93
+ it ":log_level defaults to auto" do
94
+ provisioner[:log_level].must_equal "auto"
95
+ end
96
+
97
+ it ":log_level is debug when in debug mode" do
98
+ config[:debug] = true
99
+ provisioner[:log_level].must_equal "debug"
100
+ end
101
+
93
102
  it ":log_file defaults to nil" do
94
103
  provisioner[:log_file].must_be_nil
95
104
  end
@@ -285,7 +285,7 @@ describe Kitchen::Provisioner::ChefSolo do
285
285
  config[:enforce_idempotency] = true
286
286
  provisioner.create_sandbox
287
287
 
288
- file_no_updated_resources.join.must_match /handler_file =.*chef-client-fail-if-update-handler.rb/
288
+ file_no_updated_resources.join.must_match(/handler_file =.*chef-client-fail-if-update-handler.rb/)
289
289
  end
290
290
  end
291
291
 
@@ -310,7 +310,7 @@ describe Kitchen::Provisioner::ChefZero do
310
310
  config[:enforce_idempotency] = true
311
311
  provisioner.create_sandbox
312
312
 
313
- file_no_updated_resources.join.must_match /handler_file =.*chef-client-fail-if-update-handler.rb/
313
+ file_no_updated_resources.join.must_match(/handler_file =.*chef-client-fail-if-update-handler.rb/)
314
314
  end
315
315
  end
316
316
 
@@ -163,6 +163,12 @@ describe Kitchen::Provisioner::Shell do
163
163
 
164
164
  cmd.must_match regexify("mkdir -p /root/path", :partial_line)
165
165
  end
166
+
167
+ it "respects command" do
168
+ config[:command] = "asdf"
169
+
170
+ cmd.must_be_nil
171
+ end
166
172
  end
167
173
 
168
174
  describe "for powershell shells on windows os types" do
@@ -319,6 +325,12 @@ describe Kitchen::Provisioner::Shell do
319
325
  config[:arguments] = "--php 70 --mysql 57"
320
326
  cmd.must_match(/--php 70 --mysql 57/)
321
327
  end
328
+
329
+ it "respects command" do
330
+ config[:command] = "dothingy.rb"
331
+
332
+ cmd.must_match regexify("dothingy.rb", :partial_line)
333
+ end
322
334
  end
323
335
 
324
336
  describe "for powershell shells on windows os types" do
@@ -469,6 +481,10 @@ describe Kitchen::Provisioner::Shell do
469
481
  describe "with no :script file" do
470
482
  before { config[:script] = nil }
471
483
 
484
+ it "has no run command" do
485
+ provisioner.run_command.must_be_nil
486
+ end
487
+
472
488
  describe "for bourne shells" do
473
489
  before { platform.stubs(:shell_type).returns("bourne") }
474
490
 
@@ -482,20 +498,13 @@ describe Kitchen::Provisioner::Shell do
482
498
  provisioner.create_sandbox
483
499
 
484
500
  logged_output.string.must_match info_line(
485
- "bootstrap.sh not found so Kitchen will run a stubbed script. " \
486
- "Is this intended?")
501
+ "No provisioner script file specified, skipping")
487
502
  end
488
503
 
489
- it "creates a file in the sandbox directory" do
504
+ it "does not create a file in the sandbox directory" do
490
505
  provisioner.create_sandbox
491
506
 
492
- sandbox_path("bootstrap.sh").file?.must_equal true
493
- unless running_tests_on_windows?
494
- # Windows doesn't have the concept of executable
495
- sandbox_path("bootstrap.sh").executable?.must_equal true
496
- end
497
- IO.read(sandbox_path("bootstrap.sh"))
498
- .must_match(/NO BOOTSTRAP SCRIPT PRESENT/)
507
+ sandbox_path("bootstrap.sh").file?.must_equal false
499
508
  end
500
509
  end
501
510
 
@@ -512,20 +521,13 @@ describe Kitchen::Provisioner::Shell do
512
521
  provisioner.create_sandbox
513
522
 
514
523
  logged_output.string.must_match info_line(
515
- "bootstrap.ps1 not found so Kitchen will run a stubbed script. " \
516
- "Is this intended?")
524
+ "No provisioner script file specified, skipping")
517
525
  end
518
526
 
519
- it "creates a file in the sandbox directory" do
527
+ it "does not create a file in the sandbox directory" do
520
528
  provisioner.create_sandbox
521
529
 
522
- sandbox_path("bootstrap.ps1").file?.must_equal true
523
- unless running_tests_on_windows?
524
- # Windows doesn't have the concept of executable
525
- sandbox_path("bootstrap.ps1").executable?.must_equal true
526
- end
527
- IO.read(sandbox_path("bootstrap.ps1"))
528
- .must_match(/Write-Host "NO BOOTSTRAP SCRIPT PRESENT`n"/)
530
+ sandbox_path("bootstrap.ps1").file?.must_equal false
529
531
  end
530
532
  end
531
533
  end