test-kitchen 1.7.3 → 1.8.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
  SHA1:
3
- metadata.gz: d0e37a0923ae4e4813b068aef196a6edc377c1a8
4
- data.tar.gz: 76b01133768705977dc35a50790be3269e1715f3
3
+ metadata.gz: 2ad48e5953da8eb9d58bd3a8e2933450d669caa8
4
+ data.tar.gz: 9d661157b18854c43bb1ce3094cb95888b9e0f95
5
5
  SHA512:
6
- metadata.gz: f93c6bbc69350a1350313a87a2980972e3ab96c99a7d20035490040da2ceab98606359137cae50c6edc5bbe02b36668801534e2ce464f30d16d375518d01a365
7
- data.tar.gz: 7d8628a749047cfc683b90998e23b2a9ac67019acc0f18be5073e3fb20e5a378f3e7df2a35a2937625bce620e8d64505b6a0f74a918b29225c15eca6e85f190a
6
+ metadata.gz: 61f25a72986488f05691bbbcb1189e08bac9a6953c52bbc93980c0f7caa283ac80a197da1a296e273217d99f43115f1cecfc136ccc43804eefc985a97e170894
7
+ data.tar.gz: 344538d21ba1ce73e9c892bb895ad59f31a078eb661552ef8661d6c878f5d4f187eee6eb5f13756eb81fd894f2ee632a1a360fa87a9452fb8f06f3a4bb5ba3dd
data/.kitchen.ci.yml CHANGED
@@ -13,6 +13,9 @@ provisioner:
13
13
  platforms:
14
14
  - name: ubuntu-14.04
15
15
  - name: windows-2012R2
16
+ transport:
17
+ name: winrm
18
+ elevated: true
16
19
 
17
20
  verifier:
18
21
  name: inspec
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Change Log
2
2
 
3
+ ## [1.8.0](https://github.com/test-kitchen/test-kitchen/tree/1.8.0) (2016-05-05)
4
+ [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.7.3...1.8.0)
5
+
6
+ **Implemented enhancements:**
7
+
8
+ - Add native policyfile resolution support [\#1014](https://github.com/test-kitchen/test-kitchen/pull/1014) ([danielsdeleo](https://github.com/danielsdeleo))
9
+ - Provide the option to run all winrm commands through a scheduled task [\#1012](https://github.com/test-kitchen/test-kitchen/pull/1012) ([mwrock](https://github.com/mwrock))
10
+
3
11
  ## [1.7.3](https://github.com/test-kitchen/test-kitchen/tree/1.7.3) (2016-04-13)
4
12
  [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.7.2...1.7.3)
5
13
 
@@ -624,10 +624,11 @@ module Kitchen
624
624
  # Destructively moves key Chef configuration key/value pairs from being
625
625
  # directly under a suite or platform into a `:provisioner` sub-hash.
626
626
  #
627
- # There are two key Chef configuration key/value pairs:
627
+ # There are three key Chef configuration key/value pairs:
628
628
  #
629
629
  # 1. `:attributes`
630
630
  # 2. `:run_list`
631
+ # 3. `:named_run_list`
631
632
  #
632
633
  # This method converts the following:
633
634
  #
@@ -678,11 +679,13 @@ module Kitchen
678
679
  data.fetch(:suites, []).each do |suite|
679
680
  move_chef_data_to_provisioner_at!(suite, :attributes)
680
681
  move_chef_data_to_provisioner_at!(suite, :run_list)
682
+ move_chef_data_to_provisioner_at!(suite, :named_run_list)
681
683
  end
682
684
 
683
685
  data.fetch(:platforms, []).each do |platform|
684
686
  move_chef_data_to_provisioner_at!(platform, :attributes)
685
687
  move_chef_data_to_provisioner_at!(platform, :run_list)
688
+ move_chef_data_to_provisioner_at!(platform, :named_run_list)
686
689
  end
687
690
  end
688
691
 
@@ -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 "json"
20
+
19
21
  module Kitchen
20
22
 
21
23
  module Provisioner
@@ -86,6 +88,14 @@ module Kitchen
86
88
  select { |fn| File.file?(fn) && ! %w[. ..].include?(fn) }
87
89
  end
88
90
 
91
+ # @return [String] an absolute path to a Policyfile, relative to the
92
+ # kitchen root
93
+ # @api private
94
+ def policyfile
95
+ basename = config[:policyfile_path] || "Policyfile.rb"
96
+ File.join(config[:kitchen_root], basename)
97
+ end
98
+
89
99
  # @return [String] an absolute path to a Berksfile, relative to the
90
100
  # kitchen root
91
101
  # @api private
@@ -246,8 +256,11 @@ module Kitchen
246
256
  # Prepares Chef cookbooks for inclusion in the sandbox path.
247
257
  #
248
258
  # @api private
259
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
249
260
  def prepare_cookbooks
250
- if File.exist?(berksfile)
261
+ if File.exist?(policyfile)
262
+ resolve_with_policyfile
263
+ elsif File.exist?(berksfile)
251
264
  resolve_with_berkshelf
252
265
  elsif File.exist?(cheffile)
253
266
  resolve_with_librarian
@@ -268,7 +281,11 @@ module Kitchen
268
281
  #
269
282
  # @api private
270
283
  def prepare_json
271
- dna = config[:attributes].merge(:run_list => config[:run_list])
284
+ dna = if File.exist?(policyfile)
285
+ update_dna_for_policyfile
286
+ else
287
+ config[:attributes].merge(:run_list => config[:run_list])
288
+ end
272
289
 
273
290
  info("Preparing dna.json")
274
291
  debug("Creating dna.json from #{dna.inspect}")
@@ -278,6 +295,32 @@ module Kitchen
278
295
  end
279
296
  end
280
297
 
298
+ def update_dna_for_policyfile
299
+ if !config[:run_list].nil? && !config[:run_list].empty?
300
+ warn("You must set your run_list in your policyfile instead of "\
301
+ "kitchen config. The run_list your config will be ignored.")
302
+ warn("Ignored run_list: #{config[:run_list].inspect}")
303
+ end
304
+ policylock = policyfile.gsub(/\.rb\Z/, ".lock.json")
305
+ unless File.exist?(policylock)
306
+ Kitchen.mutex.synchronize do
307
+ Chef::Policyfile.new(policyfile, sandbox_path, logger).compile
308
+ end
309
+ end
310
+ policy_name = JSON.parse(IO.read(policylock))["name"]
311
+ policy_group = "local"
312
+ config[:attributes].merge(:policy_name => policy_name, :policy_group => policy_group)
313
+ end
314
+
315
+ # Performs a Policyfile cookbook resolution inside a common mutex.
316
+ #
317
+ # @api private
318
+ def resolve_with_policyfile
319
+ Kitchen.mutex.synchronize do
320
+ Chef::Policyfile.new(policyfile, sandbox_path, logger).resolve
321
+ end
322
+ end
323
+
281
324
  # Performs a Berkshelf cookbook resolution inside a common mutex.
282
325
  #
283
326
  # @api private
@@ -316,6 +359,7 @@ module Kitchen
316
359
  def tmpsitebooks_dir
317
360
  File.join(sandbox_path, "cookbooks")
318
361
  end
362
+
319
363
  end
320
364
  end
321
365
  end
@@ -0,0 +1,107 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2013, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require "kitchen/errors"
20
+ require "kitchen/logging"
21
+ require "kitchen/shell_out"
22
+
23
+ module Kitchen
24
+
25
+ module Provisioner
26
+
27
+ module Chef
28
+
29
+ # Chef cookbook resolver that uses Policyfiles to calculate dependencies.
30
+ #
31
+ # @author Fletcher Nichol <fnichol@nichol.ca>
32
+ class Policyfile
33
+
34
+ include Logging
35
+ include ShellOut
36
+
37
+ # Creates a new cookbook resolver.
38
+ #
39
+ # @param berksfile [String] path to a Berksfile
40
+ # @param path [String] path in which to vendor the resulting
41
+ # cookbooks
42
+ # @param logger [Kitchen::Logger] a logger to use for output, defaults
43
+ # to `Kitchen.logger`
44
+ def initialize(policyfile, path, logger = Kitchen.logger)
45
+ @policyfile = policyfile
46
+ @path = path
47
+ @logger = logger
48
+ end
49
+
50
+ # Loads the library code required to use the resolver.
51
+ #
52
+ # @param logger [Kitchen::Logger] a logger to use for output, defaults
53
+ # to `Kitchen.logger`
54
+ def self.load!(logger = Kitchen.logger)
55
+ detect_chef_command!(logger)
56
+ end
57
+
58
+ # Performs the cookbook resolution and vendors the resulting cookbooks
59
+ # in the desired path.
60
+ def resolve
61
+ info("Exporting cookbook dependencies from Policyfile #{path}...")
62
+ run_command("chef export #{policyfile} #{path} --force")
63
+ end
64
+
65
+ # Runs `chef install` to determine the correct cookbook set and
66
+ # generate the policyfile lock.
67
+ def compile
68
+ info("Policy lock file doesn't exist, running `chef install` for "\
69
+ "Policyfile #{policyfile}...")
70
+ run_command("chef install #{policyfile}")
71
+ end
72
+
73
+ private
74
+
75
+ # @return [String] path to a Berksfile
76
+ # @api private
77
+ attr_reader :policyfile
78
+
79
+ # @return [String] path in which to vendor the resulting cookbooks
80
+ # @api private
81
+ attr_reader :path
82
+
83
+ # @return [Kitchen::Logger] a logger to use for output
84
+ # @api private
85
+ attr_reader :logger
86
+
87
+ # Ensure the `chef` command is in the path.
88
+ #
89
+ # @param logger [Kitchen::Logger] the logger to use
90
+ # @raise [UserError] if the `chef` command is not in the PATH
91
+ # @api private
92
+ def self.detect_chef_command!(logger)
93
+ unless ENV["PATH"].split(File::PATH_SEPARATOR).any? { |p|
94
+ File.exist?(File.join(p, "chef"))
95
+ }
96
+ logger.fatal("The `chef` executable cannot be found in your " \
97
+ "PATH. Ensure you have installed ChefDK from " \
98
+ "https://downloads.chef.io and that your PATH " \
99
+ "setting includes the path to the `chef` comand.")
100
+ raise UserError,
101
+ "Could not find the chef executable in your PATH."
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -21,6 +21,7 @@ require "pathname"
21
21
  require "json"
22
22
  require "cgi"
23
23
 
24
+ require "kitchen/provisioner/chef/policyfile"
24
25
  require "kitchen/provisioner/chef/berkshelf"
25
26
  require "kitchen/provisioner/chef/common_sandbox"
26
27
  require "kitchen/provisioner/chef/librarian"
@@ -53,6 +54,9 @@ module Kitchen
53
54
  default_config :log_file, nil
54
55
  default_config :log_level, "auto"
55
56
  default_config :profile_ruby, false
57
+ # Will try to autodetect by searching for `Policyfile.rb` if not set.
58
+ # If set, will error if the file doesn't exist.
59
+ default_config :policyfile_path, nil
56
60
  default_config :cookbook_files_glob, %w[
57
61
  README.* metadata.{json,rb}
58
62
  attributes/**/* definitions/**/* files/**/* libraries/**/*
@@ -114,6 +118,7 @@ module Kitchen
114
118
  # (see Base#create_sandbox)
115
119
  def create_sandbox
116
120
  super
121
+ sanity_check_sandbox_options!
117
122
  Chef::CommonSandbox.new(config, sandbox_path, instance).populate
118
123
  end
119
124
 
@@ -158,6 +163,14 @@ module Kitchen
158
163
  end
159
164
  end
160
165
 
166
+ # @return [String] an absolute path to a Policyfile, relative to the
167
+ # kitchen root
168
+ # @api private
169
+ def policyfile
170
+ policyfile_basename = config[:policyfile_path] || "Policyfile.rb"
171
+ File.join(config[:kitchen_root], policyfile_basename)
172
+ end
173
+
161
174
  # @return [String] an absolute path to a Berksfile, relative to the
162
175
  # kitchen root
163
176
  # @api private
@@ -264,7 +277,10 @@ module Kitchen
264
277
  # (see Base#load_needed_dependencies!)
265
278
  def load_needed_dependencies!
266
279
  super
267
- if File.exist?(berksfile)
280
+ if File.exist?(policyfile)
281
+ debug("Policyfile found at #{policyfile}, using Policyfile to resolve dependencies")
282
+ Chef::Policyfile.load!(logger)
283
+ elsif File.exist?(berksfile)
268
284
  debug("Berksfile found at #{berksfile}, loading Berkshelf")
269
285
  Chef::Berkshelf.load!(logger)
270
286
  elsif File.exist?(cheffile)
@@ -325,6 +341,27 @@ module Kitchen
325
341
  config[:chef_omnibus_root] = installer.root
326
342
  installer.install_command
327
343
  end
344
+
345
+ def supports_policyfile?
346
+ false
347
+ end
348
+
349
+ # @return [void]
350
+ # @raise [UserError]
351
+ # @api private
352
+ def sanity_check_sandbox_options!
353
+ if config[:policyfile_path] && !File.exist?(policyfile)
354
+ raise UserError, "policyfile_path set in config "\
355
+ "(#{config[:policyfile_path]} could not be found. " \
356
+ "Expected to find it at full path #{policyfile} " \
357
+ end
358
+ if File.exist?(policyfile) && !supports_policyfile?
359
+ raise UserError, "policyfile detected, but provisioner " \
360
+ "#{self.class.name} doesn't support policyfiles. " \
361
+ "Either use a different provisioner, or delete/rename " \
362
+ "#{policyfile}"
363
+ end
364
+ end
328
365
  end
329
366
  end
330
367
  end
@@ -32,6 +32,7 @@ module Kitchen
32
32
  plugin_version Kitchen::VERSION
33
33
 
34
34
  default_config :client_rb, {}
35
+ default_config :named_run_list, {}
35
36
  default_config :json_attributes, true
36
37
  default_config :chef_zero_host, nil
37
38
  default_config :chef_zero_port, 8889
@@ -207,6 +208,7 @@ module Kitchen
207
208
  # @api private
208
209
  def prepare_client_rb
209
210
  data = default_config_rb.merge(config[:client_rb])
211
+ data = data.merge(:named_run_list => config[:named_run_list]) if config[:named_run_list]
210
212
 
211
213
  info("Preparing client.rb")
212
214
  debug("Creating client.rb from #{data.inspect}")
@@ -240,6 +242,14 @@ module Kitchen
240
242
 
241
243
  "#{chef_client_zero_env}\n#{sudo(ruby)} #{shim}"
242
244
  end
245
+
246
+ # This provisioner supports policyfiles, so override the default (which
247
+ # is false)
248
+ # @return [true] always returns true
249
+ # @api private
250
+ def supports_policyfile?
251
+ true
252
+ end
243
253
  end
244
254
  end
245
255
  end
@@ -42,6 +42,7 @@ module Kitchen
42
42
 
43
43
  default_config :username, "administrator"
44
44
  default_config :password, nil
45
+ default_config :elevated, false
45
46
  default_config :rdp_port, 3389
46
47
  default_config :connection_retries, 5
47
48
  default_config :connection_retry_sleep, 1
@@ -185,6 +186,10 @@ module Kitchen
185
186
  # @api private
186
187
  attr_reader :winrm_transport
187
188
 
189
+ # @return [Boolean] whether to use winrm-elevated for running commands
190
+ # @api private
191
+ attr_reader :elevated
192
+
188
193
  # Writes an RDP document to the local file system.
189
194
  #
190
195
  # @param opts [Hash] file options
@@ -217,13 +222,30 @@ module Kitchen
217
222
  # script and the standard error stream
218
223
  # @api private
219
224
  def execute_with_exit_code(command)
220
- response = session.run_powershell_script(command) do |stdout, _|
221
- logger << stdout if stdout
225
+ if elevated
226
+ unless options[:elevated_username] == options[:user]
227
+ command = "$env:temp='#{unelevated_temp_dir}';#{command}"
228
+ end
229
+ response = elevated_runner.powershell_elevated(
230
+ command,
231
+ options[:elevated_username],
232
+ options[:elevated_password]
233
+ ) do |stdout, _|
234
+ logger << stdout if stdout
235
+ end
236
+ else
237
+ response = session.run_powershell_script(command) do |stdout, _|
238
+ logger << stdout if stdout
239
+ end
222
240
  end
223
241
 
224
242
  [response[:exitcode], response.stderr]
225
243
  end
226
244
 
245
+ def unelevated_temp_dir
246
+ @unelevated_temp_dir ||= session.run_powershell_script("$env:temp").stdout.chomp
247
+ end
248
+
227
249
  # @return [Winrm::FileTransporter] a file transporter
228
250
  # @api private
229
251
  def file_transporter
@@ -241,6 +263,7 @@ module Kitchen
241
263
  @connection_retries = @options.delete(:connection_retries)
242
264
  @connection_retry_sleep = @options.delete(:connection_retry_sleep)
243
265
  @max_wait_until_ready = @options.delete(:max_wait_until_ready)
266
+ @elevated = @options.delete(:elevated)
244
267
  end
245
268
 
246
269
  # Logs formatted standard error output at the warning level.
@@ -309,16 +332,33 @@ module Kitchen
309
332
  # @return [Winrm::CommandExecutor] the command executor session
310
333
  # @api private
311
334
  def session(retry_options = {})
312
- @session ||= begin
335
+ @session ||= service(retry_options).create_executor
336
+ end
337
+
338
+ # Creates the elevated runner for running elevated commands
339
+ #
340
+ # @return [Winrm::Elevated::Runner] the elevated runner
341
+ # @api private
342
+ def elevated_runner
343
+ @elevated_runner ||= WinRM::Elevated::Runner.new(session)
344
+ end
345
+
346
+ # Creates a winrm web service instance
347
+ #
348
+ # @param retry_options [Hash] retry options for the initial connection
349
+ # @return [Winrm::WinRMWebService] the winrm web service
350
+ # @api private
351
+ def service(retry_options = {})
352
+ @service ||= begin
313
353
  opts = {
314
354
  :retry_limit => connection_retries.to_i,
315
355
  :retry_delay => connection_retry_sleep.to_i
316
356
  }.merge(retry_options)
317
357
 
318
358
  service_args = [endpoint, winrm_transport, options.merge(opts)]
319
- @service = ::WinRM::WinRMWebService.new(*service_args)
320
- @service.logger = logger
321
- @service.create_executor
359
+ svc = ::WinRM::WinRMWebService.new(*service_args)
360
+ svc.logger = logger
361
+ svc
322
362
  end
323
363
  end
324
364
 
@@ -360,6 +400,7 @@ module Kitchen
360
400
 
361
401
  WINRM_SPEC_VERSION = ["~> 1.6"].freeze
362
402
  WINRM_FS_SPEC_VERSION = ["~> 0.4.1"].freeze
403
+ WINRM_ELEVATED_SPEC_VERSION = ["~> 0.4.0"].freeze
363
404
 
364
405
  # Builds the hash of options needed by the Connection object on
365
406
  # construction.
@@ -368,6 +409,9 @@ module Kitchen
368
409
  # @return [Hash] hash of connection options
369
410
  # @api private
370
411
  def connection_options(data)
412
+ elevated_password = data[:password]
413
+ elevated_password = data[:elevated_password] if data.key?(:elevated_password)
414
+
371
415
  opts = {
372
416
  :instance_name => instance.name,
373
417
  :kitchen_root => data[:kitchen_root],
@@ -379,7 +423,10 @@ module Kitchen
379
423
  :connection_retries => data[:connection_retries],
380
424
  :connection_retry_sleep => data[:connection_retry_sleep],
381
425
  :max_wait_until_ready => data[:max_wait_until_ready],
382
- :winrm_transport => data[:winrm_transport]
426
+ :winrm_transport => data[:winrm_transport],
427
+ :elevated => data[:elevated],
428
+ :elevated_username => data[:elevated_username] || data[:username],
429
+ :elevated_password => elevated_password
383
430
  }
384
431
  opts.merge!(additional_transport_args(opts[:winrm_transport]))
385
432
  opts
@@ -424,6 +471,7 @@ module Kitchen
424
471
  super
425
472
  load_with_rescue!("winrm", WINRM_SPEC_VERSION.dup)
426
473
  load_with_rescue!("winrm-fs", WINRM_FS_SPEC_VERSION.dup)
474
+ load_with_rescue!("winrm-elevated", WINRM_ELEVATED_SPEC_VERSION.dup) if config[:elevated]
427
475
  end
428
476
 
429
477
  def load_with_rescue!(gem_name, spec_version)
@@ -17,5 +17,5 @@
17
17
  # limitations under the License.
18
18
 
19
19
  module Kitchen
20
- VERSION = "1.7.3"
20
+ VERSION = "1.8.0"
21
21
  end
@@ -480,6 +480,23 @@ module Kitchen # rubocop:disable Metrics/ModuleLength
480
480
  )
481
481
  end
482
482
 
483
+ it "moves named_run_list into provisioner" do
484
+ DataMunger.new(
485
+ {
486
+ :provisioner => "chefy",
487
+ :suites => [
488
+ {
489
+ :name => "sweet",
490
+ :named_run_list => "other_run_list"
491
+ }
492
+ ]
493
+ },
494
+ {}
495
+ ).provisioner_data_for("sweet", "plat").must_equal(
496
+ :name => "chefy",
497
+ :named_run_list => "other_run_list"
498
+ )
499
+ end
483
500
  it "maintains run_list in provisioner" do
484
501
  DataMunger.new(
485
502
  {
@@ -536,6 +553,23 @@ module Kitchen # rubocop:disable Metrics/ModuleLength
536
553
  )
537
554
  end
538
555
 
556
+ it "merge provisioner into named_run_list if provisioner exists" do
557
+ DataMunger.new(
558
+ {
559
+ :suites => [
560
+ {
561
+ :name => "sweet",
562
+ :named_run_list => "other_run_list",
563
+ :provisioner => "chefy"
564
+ }
565
+ ]
566
+ },
567
+ {}
568
+ ).provisioner_data_for("sweet", "plat").must_equal(
569
+ :name => "chefy",
570
+ :named_run_list => "other_run_list"
571
+ )
572
+ end
539
573
  it "drops nil run_list" do
540
574
  DataMunger.new(
541
575
  {
@@ -609,6 +643,23 @@ module Kitchen # rubocop:disable Metrics/ModuleLength
609
643
  )
610
644
  end
611
645
 
646
+ it "moves named_run_list into provisioner" do
647
+ DataMunger.new(
648
+ {
649
+ :provisioner => "chefy",
650
+ :platforms => [
651
+ {
652
+ :name => "plat",
653
+ :named_run_list => "other_run_list"
654
+ }
655
+ ]
656
+ },
657
+ {}
658
+ ).provisioner_data_for("sweet", "plat").must_equal(
659
+ :name => "chefy",
660
+ :named_run_list => "other_run_list"
661
+ )
662
+ end
612
663
  it "maintains run_list in provisioner" do
613
664
  DataMunger.new(
614
665
  {
@@ -665,6 +716,23 @@ module Kitchen # rubocop:disable Metrics/ModuleLength
665
716
  )
666
717
  end
667
718
 
719
+ it "merge provisioner into named_run_list if provisioner exists" do
720
+ DataMunger.new(
721
+ {
722
+ :platforms => [
723
+ {
724
+ :name => "plat",
725
+ :named_run_list => "other_run_list",
726
+ :provisioner => "chefy"
727
+ }
728
+ ]
729
+ },
730
+ {}
731
+ ).provisioner_data_for("sweet", "plat").must_equal(
732
+ :name => "chefy",
733
+ :named_run_list => "other_run_list"
734
+ )
735
+ end
668
736
  it "drops nil run_list" do
669
737
  DataMunger.new(
670
738
  {
@@ -893,6 +893,170 @@ describe Kitchen::Provisioner::ChefBase do
893
893
  end
894
894
  end
895
895
 
896
+ describe "with a Policyfile under kitchen_root" do
897
+
898
+ let(:resolver) { stub(:resolve => true) }
899
+
900
+ describe "with the default name `Policyfile.rb`" do
901
+ before do
902
+ File.open("#{kitchen_root}/Policyfile.rb", "wb") do |file|
903
+ file.write(<<-POLICYFILE)
904
+ name 'wat'
905
+ run_list 'wat'
906
+ cookbook 'wat'
907
+ POLICYFILE
908
+ end
909
+ File.open("#{kitchen_root}/Policyfile.lock.json", "wb") do |file|
910
+ file.write(<<-POLICYFILE)
911
+ {
912
+ "name": "wat"
913
+ }
914
+ POLICYFILE
915
+ end
916
+ Kitchen::Provisioner::Chef::Policyfile.stubs(:new).returns(resolver)
917
+ end
918
+
919
+ describe "when the chef executable is not in the PATH" do
920
+ it "raises a UserError" do
921
+ Kitchen::Provisioner::Chef::Policyfile.stubs(:detect_chef_command!).with do
922
+ raise Kitchen::UserError, "Load failed"
923
+ end
924
+ proc { provisioner }.must_raise Kitchen::UserError
925
+ end
926
+ end
927
+
928
+ describe "when using a provisoner that doesn't support policyfiles" do
929
+ # This is be the default, provisioners must opt-in.
930
+ it "raises a UserError" do
931
+ proc { provisioner.create_sandbox }.must_raise Kitchen::UserError
932
+ end
933
+ end
934
+
935
+ describe "when the chef executable is in the PATH" do
936
+
937
+ before do
938
+ Kitchen::Provisioner::Chef::Policyfile.stubs(:load!)
939
+ provisioner.stubs(:supports_policyfile?).returns(true)
940
+ end
941
+
942
+ it "logs on debug that it autodetected the policyfile" do
943
+ provisioner
944
+
945
+ logged_output.string.must_match debug_line(
946
+ "Policyfile found at #{kitchen_root}/Policyfile.rb, "\
947
+ "using Policyfile to resolve dependencies")
948
+ end
949
+
950
+ it "uses uses the policyfile to resolve dependencies" do
951
+ resolver.expects(:resolve)
952
+
953
+ provisioner.create_sandbox
954
+ end
955
+
956
+ it "uses Kitchen.mutex for resolving" do
957
+ Kitchen.mutex.expects(:synchronize)
958
+
959
+ provisioner.create_sandbox
960
+ end
961
+
962
+ it "injects policyfile configuration into the dna.json" do
963
+ provisioner.create_sandbox
964
+
965
+ dna_json_file = File.join(provisioner.sandbox_path, "dna.json")
966
+ dna_json_data = JSON.parse(IO.read(dna_json_file))
967
+
968
+ expected = {
969
+ "policy_name" => "wat",
970
+ "policy_group" => "local"
971
+ }
972
+
973
+ dna_json_data.must_equal(expected)
974
+ end
975
+ end
976
+ end
977
+ describe "with a custom policyfile_path" do
978
+
979
+ let(:config) do
980
+ {
981
+ :policyfile_path => "foo-policy.rb",
982
+ :test_base_path => "/basist",
983
+ :kitchen_root => "/rooty"
984
+ }
985
+ end
986
+
987
+ before do
988
+ Kitchen::Provisioner::Chef::Policyfile.stubs(:load!)
989
+ Kitchen::Provisioner::Chef::Policyfile.stubs(:new).returns(resolver)
990
+ provisioner.stubs(:supports_policyfile?).returns(true)
991
+ end
992
+
993
+ describe "when the policyfile exists" do
994
+
995
+ let(:policyfile_path) { "#{kitchen_root}/foo-policy.rb" }
996
+ let(:policyfile_lock_path) { "#{kitchen_root}/foo-policy.lock.json" }
997
+
998
+ before do
999
+ File.open(policyfile_path, "wb") do |file|
1000
+ file.write(<<-POLICYFILE)
1001
+ name 'wat'
1002
+ run_list 'wat'
1003
+ cookbook 'wat'
1004
+ POLICYFILE
1005
+ end
1006
+ File.open(policyfile_lock_path, "wb") do |file|
1007
+ file.write(<<-POLICYFILE)
1008
+ {
1009
+ "name": "wat"
1010
+ }
1011
+ POLICYFILE
1012
+ end
1013
+ end
1014
+
1015
+ it "uses uses the policyfile to resolve dependencies" do
1016
+ Kitchen::Provisioner::Chef::Policyfile.stubs(:load!)
1017
+ resolver.expects(:resolve)
1018
+
1019
+ provisioner.create_sandbox
1020
+ end
1021
+
1022
+ it "passes the correct path to the policyfile resolver" do
1023
+ Kitchen::Provisioner::Chef::Policyfile.
1024
+ expects(:new).
1025
+ with(policyfile_path, instance_of(String), anything).
1026
+ returns(resolver)
1027
+
1028
+ Kitchen::Provisioner::Chef::Policyfile.stubs(:load!)
1029
+ resolver.expects(:resolve)
1030
+
1031
+ provisioner.create_sandbox
1032
+ end
1033
+ end
1034
+ describe "when the policyfile doesn't exist" do
1035
+
1036
+ it "raises a UserError" do
1037
+ proc { provisioner.create_sandbox }.must_raise Kitchen::UserError
1038
+ end
1039
+
1040
+ end
1041
+ describe "when the policyfile lock doesn't exist" do
1042
+ before do
1043
+ File.open("#{kitchen_root}/Policyfile.rb", "wb") do |file|
1044
+ file.write(<<-POLICYFILE)
1045
+ name 'wat'
1046
+ run_list 'wat'
1047
+ cookbook 'wat'
1048
+ POLICYFILE
1049
+ end
1050
+
1051
+ it "runs `chef install` to generate the lock" do
1052
+ resolver.expects(:compile)
1053
+ provisioner.create_sandbox
1054
+ end
1055
+ end
1056
+ end
1057
+ end
1058
+ end
1059
+
896
1060
  describe "with a Berksfile under kitchen_root" do
897
1061
 
898
1062
  let(:resolver) { stub(:resolve => true) }
@@ -21,6 +21,7 @@ require_relative "../../spec_helper"
21
21
  require "kitchen/transport/winrm"
22
22
  require "winrm"
23
23
  require "winrm-fs"
24
+ require "winrm-elevated"
24
25
 
25
26
  module Kitchen
26
27
 
@@ -108,6 +109,10 @@ describe Kitchen::Transport::Winrm do
108
109
  it "sets :winrm_transport to :negotiate" do
109
110
  transport[:winrm_transport].must_equal :negotiate
110
111
  end
112
+
113
+ it "sets :elevated to false" do
114
+ transport[:elevated].must_equal false
115
+ end
111
116
  end
112
117
 
113
118
  describe "#connection" do
@@ -326,6 +331,59 @@ describe Kitchen::Transport::Winrm do
326
331
  make_connection
327
332
  end
328
333
 
334
+ it "sets elevated_username from user by default" do
335
+ config[:username] = "user"
336
+
337
+ klass.expects(:new).with do |hash|
338
+ hash[:elevated_username] == "user"
339
+ end
340
+
341
+ make_connection
342
+ end
343
+
344
+ it "sets elevated_username from overriden elevated_username" do
345
+ config[:username] = "user"
346
+ config[:elevated_username] = "elevated_user"
347
+
348
+ klass.expects(:new).with do |hash|
349
+ hash[:elevated_username] == "elevated_user"
350
+ end
351
+
352
+ make_connection
353
+ end
354
+
355
+ it "sets elevated_password from user by default" do
356
+ config[:password] = "pass"
357
+
358
+ klass.expects(:new).with do |hash|
359
+ hash[:elevated_password] == "pass"
360
+ end
361
+
362
+ make_connection
363
+ end
364
+
365
+ it "sets elevated_password from overriden elevated_password" do
366
+ config[:password] = "pass"
367
+ config[:elevated_password] = "elevated_pass"
368
+
369
+ klass.expects(:new).with do |hash|
370
+ hash[:elevated_password] == "elevated_pass"
371
+ end
372
+
373
+ make_connection
374
+ end
375
+
376
+ it "sets elevated_password to nil if overriden elevated_password is nil" do
377
+ config[:password] = "pass"
378
+ config[:elevated_password] = nil
379
+
380
+ klass.expects(:new).with do |hash|
381
+ hash[:elevated_password].nil?
382
+ end
383
+
384
+ make_connection
385
+ end
386
+
329
387
  describe "when negotiate is set in config" do
330
388
  before do
331
389
  config[:winrm_transport] = "negotiate"
@@ -406,6 +464,31 @@ describe Kitchen::Transport::Winrm do
406
464
  end
407
465
 
408
466
  describe "#load_needed_dependencies" do
467
+ describe "winrm-elevated" do
468
+ let(:transport) { Kitchen::Transport::Winrm.new(config) }
469
+
470
+ before do
471
+ transport.stubs(:require).with("winrm")
472
+ transport.stubs(:require).with("winrm-fs")
473
+ end
474
+
475
+ describe "elevated is false" do
476
+ it "does not require winrm-elevated" do
477
+ transport.expects(:require).with("winrm-elevated").never
478
+ transport.finalize_config!(instance)
479
+ end
480
+ end
481
+
482
+ describe "elevated is true" do
483
+ before { config[:elevated] = true }
484
+
485
+ it "does requires winrm-elevated" do
486
+ transport.expects(:require).with("winrm-elevated")
487
+ transport.finalize_config!(instance)
488
+ end
489
+ end
490
+ end
491
+
409
492
  describe "winrm-fs" do
410
493
  before do
411
494
  # force loading of winrm-fs to get the version constant
@@ -656,6 +739,76 @@ describe Kitchen::Transport::Winrm::Connection do
656
739
  end
657
740
  end
658
741
 
742
+ describe "elevated command" do
743
+ let(:response) do
744
+ o = WinRM::Output.new
745
+ o[:exitcode] = 0
746
+ o[:data].concat([
747
+ { :stdout => "ok\r\n" },
748
+ { :stderr => "congrats\r\n" }
749
+ ])
750
+ o
751
+ end
752
+ let(:env_temp_response) do
753
+ o = WinRM::Output.new
754
+ o[:exitcode] = 0
755
+ o[:data].concat([
756
+ { :stdout => "temp_dir" }
757
+ ])
758
+ o
759
+ end
760
+ let(:elevated_runner) do
761
+ r = mock("elevated_runner")
762
+ r.responds_like_instance_of(WinRM::Elevated::Runner)
763
+ r
764
+ end
765
+
766
+ before do
767
+ options[:elevated] = true
768
+ WinRM::Elevated::Runner.stubs(:new).with(executor).returns(elevated_runner)
769
+ end
770
+
771
+ describe "elevated user is not login user" do
772
+ before do
773
+ options[:elevated_username] = "username"
774
+ options[:elevated_password] = "password"
775
+ executor.expects(:run_powershell_script).
776
+ with("$env:temp").returns(env_temp_response)
777
+ elevated_runner.expects(:powershell_elevated).
778
+ with(
779
+ "$env:temp='temp_dir';doit",
780
+ options[:elevated_username],
781
+ options[:elevated_password]
782
+ ).yields("ok\n", nil).returns(response)
783
+ end
784
+
785
+ it "logger captures stdout" do
786
+ connection.execute("doit")
787
+
788
+ logged_output.string.must_match(/^ok$/)
789
+ end
790
+ end
791
+
792
+ describe "elevator user is login user" do
793
+ before do
794
+ options[:elevated_username] = options[:user]
795
+ options[:elevated_password] = options[:pass]
796
+ elevated_runner.expects(:powershell_elevated).
797
+ with(
798
+ "doit",
799
+ options[:elevated_username],
800
+ options[:elevated_password]
801
+ ).yields("ok\n", nil).returns(response)
802
+ end
803
+
804
+ it "logger captures stdout" do
805
+ connection.execute("doit")
806
+
807
+ logged_output.string.must_match(/^ok$/)
808
+ end
809
+ end
810
+ end
811
+
659
812
  describe "long command" do
660
813
  let(:command) { %{Write-Host "#{"a" * 4000}"} }
661
814
 
data/test-kitchen.gemspec CHANGED
@@ -34,6 +34,7 @@ Gem::Specification.new do |gem|
34
34
  gem.add_development_dependency "pry-byebug"
35
35
  gem.add_development_dependency "pry-stack_explorer"
36
36
  gem.add_development_dependency "winrm", "~> 1.6"
37
+ gem.add_development_dependency "winrm-elevated", "~> 0.4.0"
37
38
  gem.add_development_dependency "winrm-fs", "~> 0.4.1"
38
39
 
39
40
  gem.add_development_dependency "bundler", "~> 1.3"
data/testing_windows.md CHANGED
@@ -9,6 +9,7 @@ Ensure that the cookbook's root directory includes a `Gemfile` that includes you
9
9
  gem 'test-kitchen', git: 'https://github.com/mwrock/test-kitchen', branch: 'winrm-fs'
10
10
  gem 'winrm', '~> 1.6'
11
11
  gem 'winrm-fs', '~> 0.4.1'
12
+ gem 'winrm-elevated', '~> 0.4.0'
12
13
  ```
13
14
  The above would target the `winrm-fs` branch in mwrock's test-kitchen repo.
14
15
 
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: 1.7.3
4
+ version: 1.8.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: 2016-04-13 00:00:00.000000000 Z
11
+ date: 2016-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mixlib-shellout
@@ -168,6 +168,20 @@ dependencies:
168
168
  - - "~>"
169
169
  - !ruby/object:Gem::Version
170
170
  version: '1.6'
171
+ - !ruby/object:Gem::Dependency
172
+ name: winrm-elevated
173
+ requirement: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - "~>"
176
+ - !ruby/object:Gem::Version
177
+ version: 0.4.0
178
+ type: :development
179
+ prerelease: false
180
+ version_requirements: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - "~>"
183
+ - !ruby/object:Gem::Version
184
+ version: 0.4.0
171
185
  - !ruby/object:Gem::Dependency
172
186
  name: winrm-fs
173
187
  requirement: !ruby/object:Gem::Requirement
@@ -465,6 +479,7 @@ files:
465
479
  - lib/kitchen/provisioner/chef/berkshelf.rb
466
480
  - lib/kitchen/provisioner/chef/common_sandbox.rb
467
481
  - lib/kitchen/provisioner/chef/librarian.rb
482
+ - lib/kitchen/provisioner/chef/policyfile.rb
468
483
  - lib/kitchen/provisioner/chef_apply.rb
469
484
  - lib/kitchen/provisioner/chef_base.rb
470
485
  - lib/kitchen/provisioner/chef_solo.rb
@@ -587,7 +602,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
587
602
  version: '0'
588
603
  requirements: []
589
604
  rubyforge_project:
590
- rubygems_version: 2.5.2
605
+ rubygems_version: 2.6.3
591
606
  signing_key:
592
607
  specification_version: 4
593
608
  summary: Test Kitchen is an integration tool for developing and testing infrastructure