test-kitchen 2.7.1 → 2.11.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ae69102eac88b3cbb40ad7e39b02fd2a1519e8f9ed9d114fa1c0d4c0ea7319d
4
- data.tar.gz: 48bf850ca5c79fcb0b7af6cc7fce89b05d1c735ad51c80e224285c50581576bd
3
+ metadata.gz: 36e340a7aefc9e4512f05ded9b43003298a3466de705d7c6c6050dd07f433175
4
+ data.tar.gz: 310ae63b637cbd62a88cd5beef8effa437f0d1b2dec6bf9098ba3112e621c8ed
5
5
  SHA512:
6
- metadata.gz: 9164957f96d9e93b04a4a9e32ac475d9b0e239f1e900d02660fa286d4625e312f7d1b47ab5cfa7136b91e514e826f6c915fb9dcc8a56677915714ce9106b6366
7
- data.tar.gz: 3a0968bbe24c08505b12c8bfe3900c450c5f00bc35c3821db18b3535a891256ac9093f2f472a02bcbc7179d0b24f0c87f3a8237e185e87d39b0d741c98629738
6
+ metadata.gz: cc3e9bf0e55d519d757e4339b4e643f038216dedb573225aed9cc1ce2972ad121e7819db643e26e4038ebe5fc05e2bc0a57435588d462bf2d2a4a198cbd0b9b1
7
+ data.tar.gz: 48c9bc01e69691e4096f5ae219638b0d996f069852c3ac14ec143ce583c3f6f610263000e5b24256b3b969314c9903e95b570372c1d3a8d3298d3ba4c65885bd
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
1
  source "https://rubygems.org"
2
+
3
+ # Specify your gem"s dependencies in test-kitchen.gemspec
2
4
  gemspec
3
5
 
4
6
  group :integration do
@@ -8,10 +10,6 @@ group :integration do
8
10
  gem "kitchen-vagrant"
9
11
  end
10
12
 
11
- group :changelog do
12
- gem "github_changelog_generator", "1.15.2"
13
- end
14
-
15
13
  group :debug do
16
14
  gem "pry"
17
15
  gem "pry-byebug"
@@ -19,9 +17,5 @@ group :debug do
19
17
  end
20
18
 
21
19
  group :chefstyle do
22
- gem "chefstyle"
23
- end
24
-
25
- group :docs do
26
- gem "yard"
27
- end
20
+ gem "chefstyle", "1.6.2"
21
+ end
data/Rakefile CHANGED
@@ -41,30 +41,8 @@ end
41
41
  desc "Run all quality tasks"
42
42
  task quality: %i{style stats}
43
43
 
44
- begin
45
- require "yard" unless defined?(YARD)
46
- YARD::Rake::YardocTask.new
47
- rescue LoadError
48
- puts "yard is not available. (sudo) gem install yard to generate yard documentation."
49
- end
50
-
51
44
  task default: %i{test quality}
52
45
 
53
- begin
54
- require "github_changelog_generator/task"
55
- require "kitchen/version"
56
-
57
- GitHubChangelogGenerator::RakeTask.new :changelog do |config|
58
- config.future_release = "v#{Kitchen::VERSION}"
59
- config.enhancement_labels = "enhancement,Enhancement,New Feature,Feature,Improvement".split(",")
60
- config.bug_labels = "bug,Bug".split(",")
61
- config.exclude_labels = %w{Duplicate Question Discussion No_Changelog}
62
- end
63
- rescue LoadError
64
- puts "github_changelog_generator is not available." \
65
- " (sudo) gem install github_changelog_generator to generate changelogs"
66
- end
67
-
68
46
  namespace :docs do
69
47
  desc "Deploy docs"
70
48
  task :deploy do
data/lib/kitchen.rb CHANGED
@@ -16,8 +16,6 @@
16
16
  # limitations under the License.
17
17
 
18
18
  require "pathname" unless defined?(Pathname)
19
- require "thread"
20
-
21
19
  require_relative "kitchen/errors"
22
20
  require_relative "kitchen/logger"
23
21
  require_relative "kitchen/logging"
@@ -67,7 +65,7 @@ module Kitchen
67
65
  #
68
66
  # @return [Pathname] root path of gem
69
67
  def source_root
70
- @source_root ||= Pathname.new(File.expand_path("../../", __FILE__))
68
+ @source_root ||= Pathname.new(File.expand_path("..", __dir__))
71
69
  end
72
70
 
73
71
  # Returns a default logger which emits on standard output.
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright:: Copyright (c) Chef Software Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ module Kitchen
20
+ # Common Dependency Injection wiring for ChefUtils-related modules
21
+ module ChefUtilsWiring
22
+ private
23
+
24
+ def __config
25
+ # this would need to be some kind of Chef::Config looking thing, which probably requires
26
+ # a translation object from t-k config to Chef::Config layout if that ever becomes necessary.
27
+ # this ISNT the t-k config.
28
+ {}
29
+ end
30
+
31
+ def __log
32
+ @logger
33
+ end
34
+
35
+ def __transport_connection
36
+ # this could be wired up to train at some point, but need to be careful because about local vs. remote
37
+ # uses of helpers with test-kitchen, right now we're using it for local.
38
+ end
39
+ end
40
+ end
data/lib/kitchen/cli.rb CHANGED
@@ -16,7 +16,7 @@
16
16
  # limitations under the License.
17
17
 
18
18
  # CI tests fail without an explicit unconditional require of Thor
19
- require "thor" # rubocop:disable ChefRuby/UnlessDefinedRequire
19
+ require "thor" # rubocop:disable Chef/Ruby/UnlessDefinedRequire
20
20
 
21
21
  require_relative "../kitchen"
22
22
  require_relative "generator/init"
@@ -162,7 +162,7 @@ module Kitchen
162
162
  long_desc <<-DESC
163
163
  The instance states are in order: destroy, create, converge, setup, verify, destroy.
164
164
  Change one or more instances from the current state to the #{action} state. Actions for all
165
- intermediate states will be executed. See http://kitchen.ci for further explanation.
165
+ intermediate states will be executed. See https://kitchen.ci/ for further explanation.
166
166
  DESC
167
167
  method_option :concurrency,
168
168
  aliases: "-c",
@@ -15,8 +15,6 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
 
18
- require "thread"
19
-
20
18
  module Kitchen
21
19
  module Command
22
20
  # Base class for CLI commands.
@@ -18,7 +18,7 @@
18
18
  require_relative "../command"
19
19
  require_relative "../diagnostic"
20
20
 
21
- require "yaml" unless defined?(YAML)
21
+ autoload :YAML, "yaml"
22
22
 
23
23
  module Kitchen
24
24
  module Command
@@ -32,9 +32,9 @@ module Kitchen
32
32
 
33
33
  loader = record_failure { load_loader }
34
34
 
35
- puts Kitchen::Diagnostic.new(
35
+ puts YAML.dump(Kitchen::Diagnostic.new(
36
36
  loader: loader, instances: instances, plugins: plugins?
37
- ).read.to_yaml
37
+ ).read)
38
38
  end
39
39
 
40
40
  private
@@ -213,7 +213,7 @@ module Kitchen
213
213
  provisioner: Provisioner::DEFAULT_PLUGIN,
214
214
  verifier: Verifier::DEFAULT_PLUGIN,
215
215
  transport: lambda do |_suite, platform|
216
- platform =~ /^win/i ? "winrm" : Transport::DEFAULT_PLUGIN
216
+ /^win/i.match?(platform) ? "winrm" : Transport::DEFAULT_PLUGIN
217
217
  end,
218
218
  },
219
219
  kitchen_root: kitchen_root,
@@ -244,16 +244,18 @@ module Kitchen
244
244
  # @return [Instance] a new Instance object
245
245
  # @api private
246
246
  def new_instance(suite, platform, index)
247
+ sf = new_state_file(suite, platform)
248
+
247
249
  Instance.new(
248
250
  driver: new_driver(suite, platform),
249
- lifecycle_hooks: new_lifecycle_hooks(suite, platform),
251
+ lifecycle_hooks: new_lifecycle_hooks(suite, platform, sf),
250
252
  logger: new_instance_logger(suite, platform, index),
251
253
  suite: suite,
252
254
  platform: platform,
253
255
  provisioner: new_provisioner(suite, platform),
254
256
  transport: new_transport(suite, platform),
255
257
  verifier: new_verifier(suite, platform),
256
- state_file: new_state_file(suite, platform)
258
+ state_file: sf
257
259
  )
258
260
  end
259
261
 
@@ -284,11 +286,12 @@ module Kitchen
284
286
  #
285
287
  # @param suite [Suite,#name] a Suite
286
288
  # @param platform [Platform,#name] a Platform
289
+ # @param state_file [Kitchen::StateFile] a SateFile
287
290
  # @return [LifecycleHooks] a new LifecycleHooks object
288
291
  # @api private
289
- def new_lifecycle_hooks(suite, platform)
292
+ def new_lifecycle_hooks(suite, platform, state_file)
290
293
  lhdata = data.lifecycle_hooks_data_for(suite.name, platform.name)
291
- LifecycleHooks.new(lhdata)
294
+ LifecycleHooks.new(lhdata, state_file)
292
295
  end
293
296
 
294
297
  # Builds a newly configured Provisioner object, for a given Suite and
@@ -0,0 +1,78 @@
1
+ module Kitchen
2
+ class LifecycleHook
3
+ class Base
4
+ # @return [Kitchen::LifecycleHooks]
5
+ attr_reader :lifecycle_hooks
6
+
7
+ # return [String]
8
+ attr_reader :phase
9
+
10
+ # return [Hash]
11
+ attr_reader :hook
12
+
13
+ # @param lifecycle_hooks [Kitchen::LifecycleHooks]
14
+ # @param phase [String]
15
+ # @param hook [Hash]
16
+ def initialize(lifecycle_hooks, phase, hook)
17
+ @lifecycle_hooks = lifecycle_hooks
18
+ @phase = phase
19
+ @hook = hook
20
+ end
21
+
22
+ # return [void]
23
+ def run
24
+ raise NotImplementedError
25
+ end
26
+
27
+ # @return [TrueClass, FalseClass]
28
+ def should_run?
29
+ if !includes.empty?
30
+ includes.include?(platform_name)
31
+ elsif !excludes.empty?
32
+ !excludes.include?(platform_name)
33
+ else
34
+ true
35
+ end
36
+ end
37
+
38
+ # @return [Logger] the lifecycle hooks's logger
39
+ # otherwise
40
+ # @api private
41
+ def logger
42
+ lifecycle_hooks.send(:logger)
43
+ end
44
+
45
+ private
46
+
47
+ # @return [Kitchen::Instance]
48
+ def instance
49
+ lifecycle_hooks.instance
50
+ end
51
+
52
+ # @return [Hash]
53
+ def config
54
+ lifecycle_hooks.send(:config)
55
+ end
56
+
57
+ # @return [Kitchen::StateFile]
58
+ def state_file
59
+ lifecycle_hooks.state_file
60
+ end
61
+
62
+ # @return [Array<String>] names of excluded platforms
63
+ def excludes
64
+ @excludes ||= hook.fetch(:excludes, [])
65
+ end
66
+
67
+ # @return [Array<String>] names of only included platforms
68
+ def includes
69
+ @includes ||= hook.fetch(:includes, [])
70
+ end
71
+
72
+ # @return [String]
73
+ def platform_name
74
+ instance.platform.name
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,53 @@
1
+ require_relative "base"
2
+ require_relative "../shell_out"
3
+ require_relative "../logging"
4
+
5
+ module Kitchen
6
+ class LifecycleHook
7
+ class Local < Base
8
+ include ShellOut
9
+ include Logging
10
+
11
+ # Execute a specific local command hook.
12
+ #
13
+ # @return [void]
14
+ def run
15
+ state = state_file.read
16
+ # set up empty user variable
17
+ user = {}
18
+ # Set up some environment variables with instance info.
19
+ environment = {
20
+ "KITCHEN_INSTANCE_NAME" => instance.name,
21
+ "KITCHEN_SUITE_NAME" => instance.suite.name,
22
+ "KITCHEN_PLATFORM_NAME" => instance.platform.name,
23
+ "KITCHEN_INSTANCE_HOSTNAME" => state[:hostname].to_s,
24
+ }
25
+ # If the user specified env vars too, fix them up because symbol keys
26
+ # make mixlib-shellout sad.
27
+ hook[:environment]&.each do |k, v|
28
+ environment[k.to_s] = v.to_s
29
+ end
30
+
31
+ # add user to user hash for later merging
32
+ user[:user] = hook[:user] if hook[:user]
33
+
34
+ # Default the cwd to the kitchen root and resolve a relative input cwd against that.
35
+ cwd = if hook[:cwd]
36
+ File.expand_path(hook[:cwd], config[:kitchen_root])
37
+ else
38
+ config[:kitchen_root]
39
+ end
40
+ # Build the options for mixlib-shellout.
41
+ opts = {}.merge(user).merge(cwd: cwd, environment: environment)
42
+ run_command(command, opts)
43
+ end
44
+
45
+ private
46
+
47
+ # @return [String]
48
+ def command
49
+ hook.fetch(:local)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,33 @@
1
+ require_relative "base"
2
+ require_relative "../errors"
3
+
4
+ module Kitchen
5
+ class LifecycleHook
6
+ class Remote < Base
7
+ # Execute a specific remote command hook.
8
+ #
9
+ # @return [void]
10
+ def run
11
+ # Check if we're in a state that makes sense to even try.
12
+ unless instance.last_action
13
+ if hook[:skippable]
14
+ # Just not even trying.
15
+ return
16
+ else
17
+ raise UserError, "Cannot use remote lifecycle hooks during phases when the instance is not available"
18
+ end
19
+ end
20
+
21
+ conn = instance.transport.connection(state_file.read)
22
+ conn.execute(command)
23
+ end
24
+
25
+ private
26
+
27
+ # return [String]
28
+ def command
29
+ hook.fetch(:remote)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -15,8 +15,11 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
 
18
+ require_relative "configurable"
18
19
  require_relative "errors"
19
- require_relative "shell_out"
20
+ require_relative "lifecycle_hook/local"
21
+ require_relative "lifecycle_hook/remote"
22
+ require_relative "logging"
20
23
 
21
24
  module Kitchen
22
25
  # A helper object used by {Instance} to coordinate lifecycle hook calls from
@@ -27,10 +30,10 @@ module Kitchen
27
30
  class LifecycleHooks
28
31
  include Configurable
29
32
  include Logging
30
- include ShellOut
31
33
 
32
- def initialize(config)
34
+ def initialize(config, state_file)
33
35
  init_config(config)
36
+ @state_file = state_file
34
37
  end
35
38
 
36
39
  # Run a lifecycle phase with the pre and post hooks.
@@ -40,21 +43,22 @@ module Kitchen
40
43
  # @param block [Proc] Block of code implementing the lifecycle phase.
41
44
  # @return [void]
42
45
  def run_with_hooks(phase, state_file, &block)
43
- run(instance, phase, state_file, :pre)
46
+ run(phase, :pre)
44
47
  yield
45
- run(instance, phase, state_file, :post)
48
+ run(phase, :post)
46
49
  end
47
50
 
51
+ # @return [Kitchen::StateFile]
52
+ attr_reader :state_file
53
+
48
54
  private
49
55
 
50
56
  # Execute a specific lifecycle hook.
51
57
  #
52
- # @param instance [Instance] The instance object to run against.
53
58
  # @param phase [String] Lifecycle phase which is being executed.
54
- # @param state_file [StateFile] Instance state file object.
55
59
  # @param hook_timing [Symbol] `:pre` or `:post` to indicate which hook to run.
56
60
  # @return [void]
57
- def run(instance, phase, state_file, hook_timing)
61
+ def run(phase, hook_timing)
58
62
  # Yes this has to be a symbol because of how data munger works.
59
63
  hook_key = :"#{hook_timing}_#{phase}"
60
64
  # No hooks? We're outta here.
@@ -65,80 +69,24 @@ module Kitchen
65
69
  # Coerce the common case of a bare string to be a local command. This
66
70
  # is to match the behavior of the old `pre_create_command` semi-hook.
67
71
  hook = { local: hook } if hook.is_a?(String)
68
- if hook.include?(:local)
69
- # Local command execution on the workstation.
70
- run_local_hook(instance, state_file, hook)
71
- elsif hook.include?(:remote)
72
- # Remote command execution on the test instance.
73
- run_remote_hook(instance, state_file, hook)
74
- else
75
- raise UserError, "Unknown lifecycle hook target #{hook.inspect}"
76
- end
72
+ hook = generate_hook(phase, hook)
73
+ hook.run if hook.should_run?
77
74
  end
78
75
  end
79
76
 
80
- # Execute a specific local command hook.
81
- #
82
- # @param instance [Instance] The instance object to run against.
83
- # @param state_file [StateFile] Instance state file object.
84
- # @param hook [Hash] Hook configration to use.
85
- # @return [void]
86
- def run_local_hook(instance, state_file, hook)
87
- cmd = hook.fetch(:local)
88
- state = state_file.read
89
- # set up empty user variable
90
- user = {}
91
- # Set up some environment variables with instance info.
92
- environment = {
93
- "KITCHEN_INSTANCE_NAME" => instance.name,
94
- "KITCHEN_SUITE_NAME" => instance.suite.name,
95
- "KITCHEN_PLATFORM_NAME" => instance.platform.name,
96
- "KITCHEN_INSTANCE_HOSTNAME" => state[:hostname].to_s,
97
- }
98
- # If the user specified env vars too, fix them up because symbol keys
99
- # make mixlib-shellout sad.
100
- if hook[:environment]
101
- hook[:environment].each do |k, v|
102
- environment[k.to_s] = v.to_s
103
- end
104
- end
105
-
106
- # add user to user hash for later merging
107
- if hook[:user]
108
- user[:user] = hook[:user]
77
+ # @param phase [String]
78
+ # @param hook [Hash]
79
+ # @return [Kitchen::LifecycleHook::Local, Kitchen::LifecycleHook::Remote]
80
+ def generate_hook(phase, hook)
81
+ if hook.include?(:local)
82
+ # Local command execution on the workstation.
83
+ Kitchen::LifecycleHook::Local.new(self, phase, hook)
84
+ elsif hook.include?(:remote)
85
+ # Remote command execution on the test instance.
86
+ Kitchen::LifecycleHook::Remote.new(self, phase, hook)
87
+ else
88
+ raise UserError, "Unknown lifecycle hook target #{hook.inspect}"
109
89
  end
110
-
111
- # Default the cwd to the kitchen root and resolve a relative input cwd against that.
112
- cwd = if hook[:cwd]
113
- File.expand_path(hook[:cwd], config[:kitchen_root])
114
- else
115
- config[:kitchen_root]
116
- end
117
- # Build the options for mixlib-shellout.
118
- opts = {}.merge(user).merge(cwd: cwd, environment: environment)
119
- run_command(cmd, opts)
120
- end
121
-
122
- # Execute a specific remote command hook.
123
- #
124
- # @param instance [Instance] The instance object to run against.
125
- # @param state_file [StateFile] Instance state file object.
126
- # @param hook [Hash] Hook configration to use.
127
- # @return [void]
128
- def run_remote_hook(instance, state_file, hook)
129
- # Check if we're in a state that makes sense to even try.
130
- unless instance.last_action
131
- if hook[:skippable]
132
- # Just not even trying.
133
- return
134
- else
135
- raise UserError, "Cannot use remote lifecycle hooks during phases when the instance is not available"
136
- end
137
- end
138
-
139
- cmd = hook.fetch(:remote)
140
- conn = instance.transport.connection(state_file.read)
141
- conn.execute(cmd)
142
90
  end
143
91
  end
144
92
  end
@@ -17,6 +17,7 @@
17
17
 
18
18
  require "erb" unless defined?(Erb)
19
19
  require_relative "../../vendor/hash_recursive_merge"
20
+ require "psych" unless defined?(Psych)
20
21
  require "yaml" unless defined?(YAML)
21
22
 
22
23
  module Kitchen
@@ -51,6 +51,7 @@ module Kitchen
51
51
 
52
52
  default_config :command_prefix, nil
53
53
 
54
+ default_config :uploads, {}
54
55
  default_config :downloads, {}
55
56
 
56
57
  expand_path_for :test_base_path
@@ -72,6 +73,10 @@ module Kitchen
72
73
  sandbox_dirs = Util.list_directory(sandbox_path)
73
74
 
74
75
  instance.transport.connection(state) do |conn|
76
+ config[:uploads].to_h.each do |locals, remote|
77
+ debug("Uploading #{Array(locals).join(", ")} to #{remote}")
78
+ conn.upload(locals.to_s, remote)
79
+ end
75
80
  conn.execute(install_command)
76
81
  conn.execute(init_command)
77
82
  info("Transferring files to #{instance.to_str}")
@@ -21,6 +21,7 @@ require "rbconfig" unless defined?(RbConfig)
21
21
  require_relative "../../errors"
22
22
  require_relative "../../logging"
23
23
  require_relative "../../shell_out"
24
+ require_relative "../../which"
24
25
 
25
26
  module Kitchen
26
27
  module Provisioner
@@ -31,6 +32,7 @@ module Kitchen
31
32
  class Policyfile
32
33
  include Logging
33
34
  include ShellOut
35
+ include Which
34
36
 
35
37
  # Creates a new cookbook resolver.
36
38
  #
@@ -40,9 +42,9 @@ module Kitchen
40
42
  # @param logger [Kitchen::Logger] a logger to use for output, defaults
41
43
  # to `Kitchen.logger`
42
44
  def initialize(policyfile, path, logger: Kitchen.logger, always_update: false)
43
- @policyfile = policyfile
44
- @path = path
45
- @logger = logger
45
+ @policyfile = policyfile
46
+ @path = path
47
+ @logger = logger
46
48
  @always_update = always_update
47
49
  end
48
50
 
@@ -51,30 +53,29 @@ module Kitchen
51
53
  # @param logger [Kitchen::Logger] a logger to use for output, defaults
52
54
  # to `Kitchen.logger`
53
55
  def self.load!(logger: Kitchen.logger)
54
- detect_chef_command!(logger)
56
+ # intentionally left blank
55
57
  end
56
58
 
57
59
  # Performs the cookbook resolution and vendors the resulting cookbooks
58
60
  # in the desired path.
59
61
  def resolve
60
- info("Exporting cookbook dependencies from Policyfile #{path}...")
61
- run_command("chef export #{escape_path(policyfile)} #{escape_path(path)} --force")
62
+ info("Exporting cookbook dependencies from Policyfile #{path} using `#{cli_path} export`...")
63
+ run_command("#{cli_path} export #{escape_path(policyfile)} #{escape_path(path)} --force")
62
64
  end
63
65
 
64
66
  # Runs `chef install` to determine the correct cookbook set and
65
67
  # generate the policyfile lock.
66
68
  def compile
67
69
  if File.exist?(lockfile)
68
- info("Installing cookbooks for Policyfile #{policyfile} using `chef install`")
70
+ info("Installing cookbooks for Policyfile #{policyfile} using `#{cli_path} install`")
69
71
  else
70
- info("Policy lock file doesn't exist, running `chef install` for "\
71
- "Policyfile #{policyfile}...")
72
+ info("Policy lock file doesn't exist, running `#{cli_path} install` for Policyfile #{policyfile}...")
72
73
  end
73
- run_command("chef install #{escape_path(policyfile)}")
74
+ run_command("#{cli_path} install #{escape_path(policyfile)}")
74
75
 
75
76
  if always_update
76
- info("Updating policy lock using `chef update`")
77
- run_command("chef update #{escape_path(policyfile)}")
77
+ info("Updating policy lock using `#{cli_path} update`")
78
+ run_command("#{cli_path} update #{escape_path(policyfile)}")
78
79
  end
79
80
  end
80
81
 
@@ -110,13 +111,13 @@ module Kitchen
110
111
  # @return [String]
111
112
  # @api private
112
113
  def escape_path(path)
113
- if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
114
+ if /mswin|mingw/.match?(RbConfig::CONFIG["host_os"])
114
115
  # I know what you're thinking: "just use Shellwords.escape". That
115
116
  # method produces incorrect results on Windows with certain input
116
117
  # which would be a metacharacter in Sh but is not for one or more of
117
118
  # Windows command line parsing libraries. This covers the 99% case of
118
119
  # spaces in the path without breaking other stuff.
119
- if path =~ /[ \t\n\v"]/
120
+ if /[ \t\n\v"]/.match?(path)
120
121
  "\"#{path.gsub(/[ \t\n\v\"\\]/) { |m| '\\' + m[0] }}\""
121
122
  else
122
123
  path
@@ -126,33 +127,24 @@ module Kitchen
126
127
  end
127
128
  end
128
129
 
129
- class << self
130
- private
131
-
132
- # Ensure the `chef` command is in the path.
133
- #
134
- # @param logger [Kitchen::Logger] the logger to use
135
- # @raise [UserError] if the `chef` command is not in the PATH
136
- # @api private
137
- def detect_chef_command!(logger)
138
- unless ENV["PATH"].split(File::PATH_SEPARATOR).any? do |path|
139
- if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
140
- # Windows could have different extentions: BAT, EXE or NONE
141
- %w{chef chef.exe chef.bat}.each do |bin|
142
- File.exist?(File.join(path, bin))
143
- end
144
- else
145
- File.exist?(File.join(path, "chef"))
146
- end
147
- end
148
- logger.fatal("The `chef` executable cannot be found in your " \
149
- "PATH. Ensure you have installed ChefDK or Chef Workstation " \
150
- "from https://downloads.chef.io and that your PATH " \
151
- "setting includes the path to the `chef` command.")
152
- raise UserError,
153
- "Could not find the chef executable in your PATH."
154
- end
155
- end
130
+ # Find the `chef` or `chef-cli` commands in the path or raise `chef` is present in
131
+ # ChefDK / Workstation releases, but is no longer shipped in any gems now that we
132
+ # use a Go based wrapper for the `chef` command in Workstation. The Ruby CLI has been
133
+ # renamed `chef-cli` under the hood and is shipped in the `chef-cli` gem.
134
+ #
135
+ # @api private
136
+ # @returns [String]
137
+ def cli_path
138
+ @cli_path ||= which("chef-cli") || which("chef") || no_cli_found_error
139
+ end
140
+
141
+ # @api private
142
+ def no_cli_found_error
143
+ @logger.fatal("The `chef` or `chef-cli` executables cannot be found in your " \
144
+ "PATH. Ensure you have installed Chef Workstation " \
145
+ "from https://downloads.chef.io and that your PATH " \
146
+ "setting includes the path to the `chef` or `chef-cli` commands.")
147
+ raise UserError, "Could not find the chef or chef-cli executables in your PATH."
156
148
  end
157
149
 
158
150
  end
@@ -156,7 +156,7 @@ module Kitchen
156
156
  product_name: <chef or chef-workstation>
157
157
  install_strategy: skip
158
158
  MSG
159
- when provisioner[:require_chef_omnibus].to_s.match(/\d/)
159
+ when provisioner[:require_chef_omnibus].to_s.match?(/\d/)
160
160
  Util.outdent!(<<-MSG)
161
161
  The 'require_chef_omnibus' attribute with version values will change
162
162
  to use the new 'product_version' attribute.
@@ -28,7 +28,7 @@ module Kitchen
28
28
  plugin_version Kitchen::VERSION
29
29
 
30
30
  def connection(state, &block)
31
- options = config.to_hash.merge(state)
31
+ options = connection_options(config.to_hash.merge(state))
32
32
  Kitchen::Transport::Exec::Connection.new(options, &block)
33
33
  end
34
34
 
@@ -40,19 +40,105 @@ module Kitchen
40
40
  def execute(command)
41
41
  return if command.nil?
42
42
 
43
- run_command(command)
43
+ if host_os_windows?
44
+ run_command(run_from_file_command(command))
45
+ close
46
+ else
47
+ run_command(command)
48
+ end
49
+ end
50
+
51
+ def close
52
+ if host_os_windows?
53
+ FileUtils.remove(exec_script_file)
54
+ end
44
55
  end
45
56
 
46
57
  # "Upload" the files by copying them locally.
47
58
  #
48
59
  # @see Base#upload
49
60
  def upload(locals, remote)
50
- FileUtils.mkdir_p(remote)
61
+ # evaluate $env:temp on Windows
62
+ real_remote = remote.to_s == "\$env:TEMP\\kitchen" ? kitchen_temp : remote
63
+ FileUtils.mkdir_p(real_remote)
51
64
  Array(locals).each do |local|
52
- FileUtils.cp_r(local, remote)
65
+ FileUtils.cp_r(local, real_remote)
66
+ end
67
+ end
68
+
69
+ # (see Base#init_options)
70
+ def init_options(options)
71
+ super
72
+ @instance_name = @options.delete(:instance_name)
73
+ @kitchen_root = @options.delete(:kitchen_root)
74
+ end
75
+
76
+ private
77
+
78
+ # @return [String] display name for the associated instance
79
+ # @api private
80
+ attr_reader :instance_name
81
+
82
+ # @return [String] local path to the root of the project
83
+ # @api private
84
+ attr_reader :kitchen_root
85
+
86
+ # Takes a long command and saves it to a file and uploads it to
87
+ # the test instance. Windows has cli character limits.
88
+ #
89
+ # @param command [String] a long command to be saved and uploaded
90
+ # @return [String] a command that executes the uploaded script
91
+ # @api private
92
+ def run_from_file_command(command)
93
+ if logger.debug?
94
+ debug("Creating exec script for #{instance_name} (#{exec_script_file})")
95
+ debug("Executing #{exec_script_file}")
96
+ end
97
+ File.open(exec_script_file, "wb") { |file| file.write(command) }
98
+ %{powershell -file "#{exec_script_file}"}
99
+ end
100
+
101
+ # @return [String] evaluated $env:temp variable
102
+ # @api private
103
+ def kitchen_temp
104
+ "#{ENV["temp"]}/kitchen"
105
+ end
106
+
107
+ # @return [String] name of script using instance name
108
+ # @api private
109
+ def exec_script_name
110
+ "#{instance_name}-exec-script.ps1"
111
+ end
112
+
113
+ # @return [String] file path for exec script to be run
114
+ # @api private
115
+ def exec_script_file
116
+ File.join(kitchen_root, ".kitchen", exec_script_name)
117
+ end
118
+
119
+ def host_os_windows?
120
+ case RbConfig::CONFIG["host_os"]
121
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
122
+ true
123
+ else
124
+ false
53
125
  end
54
126
  end
127
+ end
128
+
129
+ private
55
130
 
131
+ # Builds the hash of options needed by the Connection object on construction.
132
+ #
133
+ # @param data [Hash] merged configuration and mutable state data
134
+ # @return [Hash] hash of connection options
135
+ # @api private
136
+ def connection_options(data)
137
+ opts = {
138
+ instance_name: instance.name,
139
+ kitchen_root: Dir.pwd,
140
+ }
141
+ opts
56
142
  end
57
143
  end
58
144
  end
@@ -195,18 +195,16 @@ module Kitchen
195
195
  FileUtils.mkdir_p(File.dirname(local))
196
196
 
197
197
  Array(remotes).each do |file|
198
+ logger.debug("Attempting to download '#{file}' as file")
199
+ session.scp.download!(file, local)
200
+ rescue Net::SCP::Error
198
201
  begin
199
- logger.debug("Attempting to download '#{file}' as file")
200
- session.scp.download!(file, local)
202
+ logger.debug("Attempting to download '#{file}' as directory")
203
+ session.scp.download!(file, local, recursive: true)
201
204
  rescue Net::SCP::Error
202
- begin
203
- logger.debug("Attempting to download '#{file}' as directory")
204
- session.scp.download!(file, local, recursive: true)
205
- rescue Net::SCP::Error
206
- logger.warn(
207
- "SCP download failed for file or directory '#{file}', perhaps it does not exist?"
208
- )
209
- end
205
+ logger.warn(
206
+ "SCP download failed for file or directory '#{file}', perhaps it does not exist?"
207
+ )
210
208
  end
211
209
  end
212
210
  rescue Net::SSH::Exception => ex
@@ -308,16 +308,20 @@ module Kitchen
308
308
 
309
309
  # Builds a `LoginCommand` for use by Linux-based platforms.
310
310
  #
311
- # TODO: determine whether or not `desktop` exists
312
- #
313
311
  # @return [LoginCommand] a login command
314
312
  # @api private
315
313
  def login_command_for_linux
316
- args = %W{-u #{options[:user]}}
317
- args += %W{-p #{options[:password]}} if options.key?(:password)
318
- args += %W{#{URI.parse(options[:endpoint]).host}:#{rdp_port}}
314
+ xfreerdp = Util.command_exists? "xfreerdp"
315
+ unless xfreerdp
316
+ raise WinrmFailed, "xfreerdp binary not found. Please install freerdp2-x11 on Debian-based systems or freerdp on Redhat-based systems."
317
+ end
318
+
319
+ args = %W{/u:#{options[:user]}}
320
+ args += %W{/p:#{options[:password]}} if options.key?(:password)
321
+ args += %W{/v:#{URI.parse(options[:endpoint]).host}:#{rdp_port}}
322
+ args += %W{/cert-tofu} # always accept certificate
319
323
 
320
- LoginCommand.new("rdesktop", args)
324
+ LoginCommand.new(xfreerdp, args)
321
325
  end
322
326
 
323
327
  # Builds a `LoginCommand` for use by Mac-based platforms.
data/lib/kitchen/util.rb CHANGED
@@ -110,7 +110,7 @@ module Kitchen
110
110
  def self.wrap_command(cmd)
111
111
  cmd = "false" if cmd.nil?
112
112
  cmd = "true" if cmd.to_s.empty?
113
- cmd = cmd.sub(/\n\Z/, "") if cmd =~ /\n\Z/
113
+ cmd = cmd.sub(/\n\Z/, "") if /\n\Z/.match?(cmd)
114
114
 
115
115
  "sh -c '\n#{cmd}\n'"
116
116
  end
@@ -215,5 +215,15 @@ module Kitchen
215
215
  def self.snake_case(a_string)
216
216
  Thor::Util.snake_case(a_string)
217
217
  end
218
+
219
+ # Check if a cmd exists on the PATH
220
+ def self.command_exists?(cmd)
221
+ paths = ENV["PATH"].split(File::PATH_SEPARATOR) + [ "/bin", "/usr/bin", "/sbin", "/usr/sbin" ]
222
+ paths.each do |path|
223
+ filename = File.join(path, cmd)
224
+ return filename if File.executable?(filename)
225
+ end
226
+ false
227
+ end
218
228
  end
219
229
  end
@@ -168,7 +168,7 @@ module Kitchen
168
168
  # @api private
169
169
  def gem_install_args
170
170
  gem, version = config[:version].split("@")
171
- if gem =~ /^\d+\.\d+\.\d+/
171
+ if /^\d+\.\d+\.\d+/.match?(gem)
172
172
  version = gem
173
173
  gem = "busser"
174
174
  end
@@ -88,6 +88,7 @@ module Kitchen
88
88
  env_state[:environment]["KITCHEN_INSTANCE"] = instance.name
89
89
  env_state[:environment]["KITCHEN_PLATFORM"] = instance.platform.name
90
90
  env_state[:environment]["KITCHEN_SUITE"] = instance.suite.name
91
+ env_state[:environment]["KITCHEN_USERNAME"] = instance.transport[:username] if instance.respond_to?(:transport)
91
92
  state.each_pair do |key, value|
92
93
  env_state[:environment]["KITCHEN_" + key.to_s.upcase] = value.to_s
93
94
  end
@@ -16,5 +16,5 @@
16
16
  # limitations under the License.
17
17
 
18
18
  module Kitchen
19
- VERSION = "2.7.1".freeze
19
+ VERSION = "2.11.0".freeze
20
20
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Copyright:: Copyright (c) Chef Software Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require "chef-utils/dsl/which" unless defined?(ChefUtils::DSL::Which)
19
+ require_relative "chef_utils_wiring" unless defined?(Ohai::Mixin::ChefUtilsWiring)
20
+
21
+ module Kitchen
22
+ module Which
23
+ include ChefUtils::DSL::Which
24
+ include ChefUtilsWiring
25
+ end
26
+ end
data/test-kitchen.gemspec CHANGED
@@ -1,4 +1,4 @@
1
- lib = File.expand_path("../lib", __FILE__)
1
+ lib = File.expand_path("lib", __dir__)
2
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
  require "kitchen/version"
4
4
  require "English"
@@ -20,7 +20,7 @@ Gem::Specification.new do |gem|
20
20
  gem.executables = %w{kitchen}
21
21
  gem.require_paths = ["lib"]
22
22
 
23
- gem.required_ruby_version = ">= 2.4"
23
+ gem.required_ruby_version = ">= 2.5"
24
24
 
25
25
  gem.add_dependency "mixlib-shellout", ">= 1.2", "< 4.0"
26
26
  gem.add_dependency "net-scp", ">= 1.1", "< 4.0" # pinning until we can confirm 4+ works
@@ -33,6 +33,7 @@ Gem::Specification.new do |gem|
33
33
  gem.add_dependency "winrm", "~> 2.0"
34
34
  gem.add_dependency "winrm-elevated", "~> 1.0"
35
35
  gem.add_dependency "winrm-fs", "~> 1.1"
36
+ gem.add_dependency "chef-utils", ">= 16.4.35"
36
37
  # Required to run the Chef provisioner local license check for remote systems
37
38
  # TK is not under Chef EULA
38
39
  gem.add_dependency "license-acceptance", ">= 1.0.11", "< 3.0" # pinning until we can confirm 3+ works
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test-kitchen
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.1
4
+ version: 2.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fletcher Nichol
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-15 00:00:00.000000000 Z
11
+ date: 2021-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mixlib-shellout
@@ -194,6 +194,20 @@ dependencies:
194
194
  - - "~>"
195
195
  - !ruby/object:Gem::Version
196
196
  version: '1.1'
197
+ - !ruby/object:Gem::Dependency
198
+ name: chef-utils
199
+ requirement: !ruby/object:Gem::Requirement
200
+ requirements:
201
+ - - ">="
202
+ - !ruby/object:Gem::Version
203
+ version: 16.4.35
204
+ type: :runtime
205
+ prerelease: false
206
+ version_requirements: !ruby/object:Gem::Requirement
207
+ requirements:
208
+ - - ">="
209
+ - !ruby/object:Gem::Version
210
+ version: 16.4.35
197
211
  - !ruby/object:Gem::Dependency
198
212
  name: license-acceptance
199
213
  requirement: !ruby/object:Gem::Requirement
@@ -387,6 +401,7 @@ files:
387
401
  - bin/kitchen
388
402
  - lib/kitchen.rb
389
403
  - lib/kitchen/base64_stream.rb
404
+ - lib/kitchen/chef_utils_wiring.rb
390
405
  - lib/kitchen/cli.rb
391
406
  - lib/kitchen/collection.rb
392
407
  - lib/kitchen/color.rb
@@ -415,6 +430,9 @@ files:
415
430
  - lib/kitchen/generator/init.rb
416
431
  - lib/kitchen/instance.rb
417
432
  - lib/kitchen/lazy_hash.rb
433
+ - lib/kitchen/lifecycle_hook/base.rb
434
+ - lib/kitchen/lifecycle_hook/local.rb
435
+ - lib/kitchen/lifecycle_hook/remote.rb
418
436
  - lib/kitchen/lifecycle_hooks.rb
419
437
  - lib/kitchen/loader/yaml.rb
420
438
  - lib/kitchen/logger.rb
@@ -454,6 +472,7 @@ files:
454
472
  - lib/kitchen/verifier/dummy.rb
455
473
  - lib/kitchen/verifier/shell.rb
456
474
  - lib/kitchen/version.rb
475
+ - lib/kitchen/which.rb
457
476
  - lib/vendor/hash_recursive_merge.rb
458
477
  - support/busser_install_command.ps1
459
478
  - support/busser_install_command.sh
@@ -493,14 +512,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
493
512
  requirements:
494
513
  - - ">="
495
514
  - !ruby/object:Gem::Version
496
- version: '2.4'
515
+ version: '2.5'
497
516
  required_rubygems_version: !ruby/object:Gem::Requirement
498
517
  requirements:
499
518
  - - ">="
500
519
  - !ruby/object:Gem::Version
501
520
  version: '0'
502
521
  requirements: []
503
- rubygems_version: 3.1.2
522
+ rubygems_version: 3.1.4
504
523
  signing_key:
505
524
  specification_version: 4
506
525
  summary: Test Kitchen is an integration tool for developing and testing infrastructure