test-kitchen 1.7.3 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
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