test-kitchen 1.3.1 → 1.4.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.cane +2 -0
  3. data/.gitignore +4 -0
  4. data/CHANGELOG.md +45 -0
  5. data/Rakefile +15 -0
  6. data/features/kitchen_action_commands.feature +12 -9
  7. data/features/kitchen_defaults.feature +38 -0
  8. data/features/kitchen_init_command.feature +0 -1
  9. data/features/kitchen_list_command.feature +2 -2
  10. data/features/kitchen_login_command.feature +7 -1
  11. data/features/kitchen_test_command.feature +4 -4
  12. data/lib/kitchen.rb +40 -11
  13. data/lib/kitchen/cli.rb +38 -22
  14. data/lib/kitchen/command/list.rb +5 -2
  15. data/lib/kitchen/config.rb +45 -18
  16. data/lib/kitchen/configurable.rb +137 -1
  17. data/lib/kitchen/data_munger.rb +248 -17
  18. data/lib/kitchen/driver.rb +1 -1
  19. data/lib/kitchen/driver/base.rb +1 -83
  20. data/lib/kitchen/driver/dummy.rb +0 -5
  21. data/lib/kitchen/driver/ssh_base.rb +177 -22
  22. data/lib/kitchen/instance.rb +140 -20
  23. data/lib/kitchen/logger.rb +43 -8
  24. data/lib/kitchen/login_command.rb +14 -5
  25. data/lib/kitchen/platform.rb +19 -0
  26. data/lib/kitchen/provisioner.rb +5 -3
  27. data/lib/kitchen/provisioner/base.rb +46 -48
  28. data/lib/kitchen/provisioner/chef/common_sandbox.rb +322 -0
  29. data/lib/kitchen/provisioner/chef_base.rb +179 -286
  30. data/lib/kitchen/provisioner/chef_solo.rb +11 -5
  31. data/lib/kitchen/provisioner/chef_zero.rb +108 -94
  32. data/lib/kitchen/provisioner/dummy.rb +47 -0
  33. data/lib/kitchen/provisioner/shell.rb +45 -12
  34. data/lib/kitchen/rake_tasks.rb +1 -1
  35. data/lib/kitchen/ssh.rb +1 -1
  36. data/lib/kitchen/thor_tasks.rb +1 -1
  37. data/lib/kitchen/transport.rb +54 -0
  38. data/lib/kitchen/transport/base.rb +146 -0
  39. data/lib/kitchen/transport/dummy.rb +75 -0
  40. data/lib/kitchen/transport/ssh.rb +325 -0
  41. data/lib/kitchen/transport/winrm.rb +508 -0
  42. data/lib/kitchen/transport/winrm/command_executor.rb +188 -0
  43. data/lib/kitchen/transport/winrm/file_transporter.rb +454 -0
  44. data/lib/kitchen/transport/winrm/logging.rb +50 -0
  45. data/lib/kitchen/transport/winrm/template.rb +74 -0
  46. data/lib/kitchen/transport/winrm/tmp_zip.rb +187 -0
  47. data/lib/kitchen/verifier.rb +55 -0
  48. data/lib/kitchen/verifier/base.rb +191 -0
  49. data/lib/kitchen/verifier/busser.rb +266 -0
  50. data/lib/kitchen/verifier/dummy.rb +75 -0
  51. data/lib/kitchen/version.rb +1 -1
  52. data/spec/kitchen/cli_spec.rb +56 -0
  53. data/spec/kitchen/config_spec.rb +61 -20
  54. data/spec/kitchen/configurable_spec.rb +327 -1
  55. data/spec/kitchen/data_munger_spec.rb +777 -14
  56. data/spec/kitchen/driver/base_spec.rb +7 -38
  57. data/spec/kitchen/driver/dummy_spec.rb +0 -29
  58. data/spec/kitchen/driver/ssh_base_spec.rb +580 -236
  59. data/spec/kitchen/driver_spec.rb +1 -0
  60. data/spec/kitchen/instance_spec.rb +383 -83
  61. data/spec/kitchen/login_command_spec.rb +29 -10
  62. data/spec/kitchen/platform_spec.rb +58 -2
  63. data/spec/kitchen/provisioner/base_spec.rb +170 -18
  64. data/spec/kitchen/provisioner/chef_base_spec.rb +454 -104
  65. data/spec/kitchen/provisioner/chef_solo_spec.rb +307 -104
  66. data/spec/kitchen/provisioner/chef_zero_spec.rb +561 -230
  67. data/spec/kitchen/provisioner/dummy_spec.rb +91 -0
  68. data/spec/kitchen/provisioner/shell_spec.rb +158 -56
  69. data/spec/kitchen/provisioner_spec.rb +37 -0
  70. data/spec/kitchen/ssh_spec.rb +19 -19
  71. data/spec/kitchen/transport/base_spec.rb +89 -0
  72. data/spec/kitchen/transport/ssh_spec.rb +1147 -0
  73. data/spec/kitchen/transport/winrm/command_executor_spec.rb +400 -0
  74. data/spec/kitchen/transport/winrm/file_transporter_spec.rb +876 -0
  75. data/spec/kitchen/transport/winrm/logging_spec.rb +92 -0
  76. data/spec/kitchen/transport/winrm/template_spec.rb +51 -0
  77. data/spec/kitchen/transport/winrm/tmp_zip_spec.rb +132 -0
  78. data/spec/kitchen/transport/winrm_spec.rb +1069 -0
  79. data/spec/kitchen/transport_spec.rb +112 -0
  80. data/spec/kitchen/verifier/base_spec.rb +310 -0
  81. data/spec/kitchen/verifier/busser_spec.rb +540 -0
  82. data/spec/kitchen/verifier/dummy_spec.rb +91 -0
  83. data/spec/kitchen/verifier_spec.rb +120 -0
  84. data/spec/kitchen_spec.rb +7 -0
  85. data/spec/spec_helper.rb +8 -0
  86. data/spec/support/powershell_max_size_spec.rb +40 -0
  87. data/support/busser_install_command.ps1 +14 -0
  88. data/support/busser_install_command.sh +15 -0
  89. data/support/check_files.ps1.erb +48 -0
  90. data/support/chef_base_init_command.ps1 +18 -0
  91. data/support/chef_base_init_command.sh +2 -0
  92. data/support/chef_base_install_command.ps1 +76 -0
  93. data/support/chef_base_install_command.sh +137 -0
  94. data/support/chef_zero_prepare_command_legacy.ps1 +9 -0
  95. data/support/chef_zero_prepare_command_legacy.sh +10 -0
  96. data/support/decode_files.ps1.erb +61 -0
  97. data/test-kitchen.gemspec +2 -0
  98. metadata +97 -8
  99. data/lib/kitchen/busser.rb +0 -316
  100. data/spec/kitchen/busser_spec.rb +0 -490
  101. data/support/chef_helpers.sh +0 -16
@@ -34,6 +34,10 @@ module Kitchen
34
34
  # @return [IO] the log device
35
35
  attr_reader :logdev
36
36
 
37
+ # @return [Boolean] whether logger is configured for
38
+ # overwriting
39
+ attr_reader :log_overwrite
40
+
37
41
  # Constructs a new logger.
38
42
  #
39
43
  # @param options [Hash] configuration for a new logger
@@ -41,6 +45,9 @@ module Kitchen
41
45
  # messages
42
46
  # @option options [Integer] :level the logging severity threshold
43
47
  # (default: `Kitchen::DEFAULT_LOG_LEVEL`)
48
+ # @option options [Boolean] whether to overwrite the log
49
+ # when Test Kitchen runs. Only applies if the :logdev is a String.
50
+ # (default: `Kitchen::DEFAULT_LOG_OVERWRITE`)
44
51
  # @option options [String,IO] :logdev filepath String or IO object to be
45
52
  # used for logging (default: `nil`)
46
53
  # @option options [String] :progname program name to include in log
@@ -49,16 +56,33 @@ module Kitchen
49
56
  # (default: `$stdout`)
50
57
  def initialize(options = {})
51
58
  color = options[:color]
59
+ @log_overwrite = if options[:log_overwrite].nil?
60
+ default_log_overwrite
61
+ else
62
+ options[:log_overwrite]
63
+ end
52
64
 
53
- @loggers = []
54
- @loggers << @logdev = logdev_logger(options[:logdev]) if options[:logdev]
55
- @loggers << stdout_logger(options[:stdout], color) if options[:stdout]
56
- @loggers << stdout_logger($stdout, color) if @loggers.empty?
65
+ @logdev = logdev_logger(options[:logdev], log_overwrite) if options[:logdev]
66
+
67
+ populate_loggers(color, options)
57
68
 
69
+ # These setters cannot be called until @loggers are populated because
70
+ # they are delegated
58
71
  self.progname = options[:progname] || "Kitchen"
59
72
  self.level = options[:level] || default_log_level
60
73
  end
61
74
 
75
+ # Pulled out for Rubocop complexity issues
76
+ #
77
+ # @api private
78
+ def populate_loggers(color, options)
79
+ @loggers = []
80
+ @loggers << logdev unless logdev.nil?
81
+ @loggers << stdout_logger(options[:stdout], color) if options[:stdout]
82
+ @loggers << stdout_logger($stdout, color) if @loggers.empty?
83
+ end
84
+ private :populate_loggers
85
+
62
86
  class << self
63
87
 
64
88
  private
@@ -252,6 +276,12 @@ module Kitchen
252
276
  Util.to_logger_level(Kitchen::DEFAULT_LOG_LEVEL)
253
277
  end
254
278
 
279
+ # @return [Boolean] whether to overwrite logs by default
280
+ # @api private
281
+ def default_log_overwrite
282
+ Kitchen::DEFAULT_LOG_OVERWRITE
283
+ end
284
+
255
285
  # Construct a new standard out logger.
256
286
  #
257
287
  # @param stdout [IO] the IO object that represents stdout (or similar)
@@ -275,21 +305,26 @@ module Kitchen
275
305
  # Construct a new logdev logger.
276
306
  #
277
307
  # @param filepath_or_logdev [String,IO] a filepath String or IO object
308
+ # @param log_overwrite [Boolean] apply log overwriting
309
+ # if filepath_or_logdev is a file path
278
310
  # @return [LogdevLogger] a new logger
279
311
  # @api private
280
- def logdev_logger(filepath_or_logdev)
281
- LogdevLogger.new(resolve_logdev(filepath_or_logdev))
312
+ def logdev_logger(filepath_or_logdev, log_overwrite)
313
+ LogdevLogger.new(resolve_logdev(filepath_or_logdev, log_overwrite))
282
314
  end
283
315
 
284
316
  # Return an IO object from a filepath String or the IO object itself.
285
317
  #
286
318
  # @param filepath_or_logdev [String,IO] a filepath String or IO object
319
+ # @param log_overwrite [Boolean] apply log overwriting
320
+ # if filepath_or_logdev is a file path
287
321
  # @return [IO] an IO object
288
322
  # @api private
289
- def resolve_logdev(filepath_or_logdev)
323
+ def resolve_logdev(filepath_or_logdev, log_overwrite)
290
324
  if filepath_or_logdev.is_a? String
325
+ mode = log_overwrite ? "wb" : "ab"
291
326
  FileUtils.mkdir_p(File.dirname(filepath_or_logdev))
292
- file = File.open(File.expand_path(filepath_or_logdev), "ab")
327
+ file = File.open(File.expand_path(filepath_or_logdev), mode)
293
328
  file.sync = true
294
329
  file
295
330
  else
@@ -24,20 +24,29 @@ module Kitchen
24
24
  # @author Fletcher Nichol <fnichol@nichol.ca>
25
25
  class LoginCommand
26
26
 
27
- # @return [Array] array of login command arguments
28
- attr_reader :cmd_array
27
+ # @return [String] login command
28
+ attr_reader :command
29
+
30
+ # @return [Array] array of arguments to the command
31
+ attr_reader :arguments
29
32
 
30
33
  # @return [Hash] options hash, passed to `Kernel#exec`
31
34
  attr_reader :options
32
35
 
33
36
  # Constructs a new LoginCommand instance.
34
37
  #
35
- # @param cmd_array [Array] array of login command arguments
38
+ # @param command [String] command
39
+ # @param arguments [Array] array of arguments to the command
36
40
  # @param options [Hash] options hash, passed to `Kernel#exec`
37
41
  # @see http://www.ruby-doc.org/core-2.1.2/Kernel.html#method-i-exec
38
- def initialize(cmd_array, options = {})
39
- @cmd_array = Array(cmd_array)
42
+ def initialize(command, arguments, options = {})
43
+ @command = command
44
+ @arguments = Array(arguments)
40
45
  @options = options
41
46
  end
47
+
48
+ def exec_args
49
+ [command, *arguments, options]
50
+ end
42
51
  end
43
52
  end
@@ -28,6 +28,12 @@ module Kitchen
28
28
  # @return [String] logical name of this platform
29
29
  attr_reader :name
30
30
 
31
+ # @return [String] operating system type hint (default: `"unix"`)
32
+ attr_reader :os_type
33
+
34
+ # @return [String] shell command flavor hint (default: `"bourne"`)
35
+ attr_reader :shell_type
36
+
31
37
  # Constructs a new platform.
32
38
  #
33
39
  # @param [Hash] options configuration for a new platform
@@ -37,6 +43,19 @@ module Kitchen
37
43
  @name = options.fetch(:name) do
38
44
  raise ClientError, "Platform#new requires option :name"
39
45
  end
46
+ @os_type = options.fetch(:os_type) do
47
+ @name.downcase =~ /^win/ ? "windows" : "unix"
48
+ end
49
+ @shell_type = options.fetch(:shell_type) do
50
+ @name.downcase =~ /^win/ ? "powershell" : "bourne"
51
+ end
52
+ end
53
+
54
+ # Returns a Hash of configuration and other useful diagnostic information.
55
+ #
56
+ # @return [Hash] a diagnostic hash
57
+ def diagnose
58
+ { :os_type => os_type, :shell_type => shell_type }
40
59
  end
41
60
  end
42
61
  end
@@ -34,14 +34,16 @@ module Kitchen
34
34
  #
35
35
  # @param plugin [String] a provisioner plugin type, to be constantized
36
36
  # @param config [Hash] a configuration hash to initialize the provisioner
37
- # @return [Provisioner::Base] a driver instance
37
+ # @return [Provisioner::Base] a provisioner instance
38
38
  # @raise [ClientError] if a provisioner instance could not be created
39
39
  def self.for_plugin(plugin, config)
40
- require("kitchen/provisioner/#{plugin}")
40
+ first_load = require("kitchen/provisioner/#{plugin}")
41
41
 
42
42
  str_const = Thor::Util.camel_case(plugin)
43
43
  klass = const_get(str_const)
44
- klass.new(config)
44
+ object = klass.new(config)
45
+ object.verify_dependencies if first_load
46
+ object
45
47
  rescue LoadError, NameError
46
48
  raise ClientError,
47
49
  "Could not load the '#{plugin}' provisioner from the load path." \
@@ -28,8 +28,17 @@ module Kitchen
28
28
  include Configurable
29
29
  include Logging
30
30
 
31
- default_config :root_path, "/tmp/kitchen"
32
- default_config :sudo, true
31
+ default_config :http_proxy, nil
32
+
33
+ default_config :https_proxy, nil
34
+
35
+ default_config :root_path do |provisioner|
36
+ provisioner.windows_os? ? "$env:TEMP\\kitchen" : "/tmp/kitchen"
37
+ end
38
+
39
+ default_config :sudo do |provisioner|
40
+ provisioner.windows_os? ? nil : true
41
+ end
33
42
 
34
43
  expand_path_for :test_base_path
35
44
 
@@ -40,26 +49,27 @@ module Kitchen
40
49
  init_config(config)
41
50
  end
42
51
 
43
- # Performs any final configuration required for the provisioner to do its
44
- # work. A reference to an Instance is required as configuration dependant
45
- # data may need access through an Instance. This also acts as a hook
46
- # point where the object may wish to perform other last minute checks,
47
- # valiations, or configuration expansions.
48
- #
49
- # @param instance [Instance] an associated instance
50
- # @return [self] itself, used for chaining
51
- # @raise [ClientError] if instance parameter is nil
52
- def finalize_config!(instance)
53
- super
54
- load_needed_dependencies!
55
- self
56
- end
57
-
58
- # Returns the name of this driver, suitable for display in a CLI.
59
- #
60
- # @return [String] name of this driver
61
- def name
62
- self.class.name.split("::").last
52
+ # Runs the provisioner on the instance.
53
+ #
54
+ # @param state [Hash] mutable instance state
55
+ # @raise [ActionFailed] if the action could not be completed
56
+ def call(state)
57
+ create_sandbox
58
+ sandbox_dirs = Dir.glob(File.join(sandbox_path, "*"))
59
+
60
+ instance.transport.connection(state) do |conn|
61
+ conn.execute(install_command)
62
+ conn.execute(init_command)
63
+ info("Transferring files to #{instance.to_str}")
64
+ conn.upload(sandbox_dirs, config[:root_path])
65
+ debug("Transfer complete")
66
+ conn.execute(prepare_command)
67
+ conn.execute(run_command)
68
+ end
69
+ rescue Kitchen::Transport::TransportFailed => ex
70
+ raise ActionFailed, ex.message
71
+ ensure
72
+ cleanup_sandbox
63
73
  end
64
74
 
65
75
  # Generates a command string which will install and configure the
@@ -145,34 +155,22 @@ module Kitchen
145
155
 
146
156
  private
147
157
 
148
- # Loads any required third party Ruby libraries or runs any shell out
149
- # commands to prepare the provisioner. This method will be called in the
150
- # context of the main thread of execution and so does not necessarily
151
- # have to be thread safe.
152
- #
153
- # **Note:** any subclasses overriding this method would be well advised
154
- # to call super when overriding this method, for example:
158
+ # Builds a complete command given a variables String preamble and a file
159
+ # containing shell code.
155
160
  #
156
- # @example overriding `#load_needed_dependencies!`
157
- #
158
- # class MyProvisioner < Kitchen::Provisioner::Base
159
- # def load_needed_dependencies!
160
- # super
161
- # # any further work
162
- # end
163
- # end
164
- #
165
- # @raise [ClientError] if any library loading fails or any of the
166
- # dependency requirements cannot be satisfied
167
- # @api private
168
- def load_needed_dependencies!
169
- end
170
-
171
- # @return [Logger] the instance's logger or Test Kitchen's common logger
172
- # otherwise
161
+ # @param vars [String] shell variables, as a String
162
+ # @param file [String] file basename (without extension) containing
163
+ # shell code
164
+ # @return [String] command
173
165
  # @api private
174
- def logger
175
- instance ? instance.logger : Kitchen.logger
166
+ def shell_code_from_file(vars, file)
167
+ src_file = File.join(
168
+ File.dirname(__FILE__),
169
+ %w[.. .. .. support],
170
+ file + (powershell_shell? ? ".ps1" : ".sh")
171
+ )
172
+
173
+ wrap_shell_code([vars, "", IO.read(src_file)].join("\n"))
176
174
  end
177
175
 
178
176
  # Conditionally prefixes a command with a sudo command.
@@ -0,0 +1,322 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2015, 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
+ module Kitchen
20
+
21
+ module Provisioner
22
+
23
+ module Chef
24
+
25
+ # Internal object to manage common sandbox preparation for
26
+ # Chef-related provisioners.
27
+ #
28
+ # @author Fletcher Nichol <fnichol@nichol.ca>
29
+ # @api private
30
+ class CommonSandbox
31
+
32
+ include Logging
33
+
34
+ # Constructs a new object, taking config, a sandbox path, and an
35
+ # instance.
36
+ #
37
+ # @param config [Hash] configuration hash
38
+ # @param sandbox_path [String] path to local sandbox directory
39
+ # @param instance [Instance] an instance
40
+ def initialize(config, sandbox_path, instance)
41
+ @config = config
42
+ @sandbox_path = sandbox_path
43
+ @instance = instance
44
+ end
45
+
46
+ # Populate the sandbox.
47
+ def populate
48
+ prepare_json
49
+ prepare_cache
50
+ prepare_cookbooks
51
+ prepare(:data)
52
+ prepare(:data_bags)
53
+ prepare(:environments)
54
+ prepare(:nodes)
55
+ prepare(:roles)
56
+ prepare(:clients)
57
+ prepare(
58
+ :secret,
59
+ :type => :file,
60
+ :dest_name => "encrypted_data_bag_secret",
61
+ :key_name => :encrypted_data_bag_secret_key_path
62
+ )
63
+ end
64
+
65
+ private
66
+
67
+ # @return [Hash] configuration hash
68
+ # @api private
69
+ attr_reader :config
70
+
71
+ # @return [Instance] an instance
72
+ # @api private
73
+ attr_reader :instance
74
+
75
+ # @return [String] path to local sandbox directory
76
+ # @api private
77
+ attr_reader :sandbox_path
78
+
79
+ # Generates a list of all files in the cookbooks directory in the
80
+ # sandbox path.
81
+ #
82
+ # @return [Array<String>] an array of absolute paths to files
83
+ # @api private
84
+ def all_files_in_cookbooks
85
+ Dir.glob(File.join(tmpbooks_dir, "**/*"), File::FNM_DOTMATCH).
86
+ select { |fn| File.file?(fn) && ! %w[. ..].include?(fn) }
87
+ end
88
+
89
+ # @return [String] an absolute path to a Berksfile, relative to the
90
+ # kitchen root
91
+ # @api private
92
+ def berksfile
93
+ File.join(config[:kitchen_root], "Berksfile")
94
+ end
95
+
96
+ # @return [String] an absolute path to a Cheffile, relative to the
97
+ # kitchen root
98
+ # @api private
99
+ def cheffile
100
+ File.join(config[:kitchen_root], "Cheffile")
101
+ end
102
+
103
+ # @return [String] an absolute path to a cookbooks/ directory, relative
104
+ # to the kitchen root
105
+ # @api private
106
+ def cookbooks_dir
107
+ File.join(config[:kitchen_root], "cookbooks")
108
+ end
109
+
110
+ # Copies a cookbooks/ directory into the sandbox path.
111
+ #
112
+ # @api private
113
+ def cp_cookbooks
114
+ info("Preparing cookbooks from project directory")
115
+ debug("Using cookbooks from #{cookbooks_dir}")
116
+
117
+ FileUtils.mkdir_p(tmpbooks_dir)
118
+ FileUtils.cp_r(File.join(cookbooks_dir, "."), tmpbooks_dir)
119
+
120
+ cp_site_cookbooks if File.directory?(site_cookbooks_dir)
121
+ cp_this_cookbook if File.exist?(metadata_rb)
122
+ end
123
+
124
+ # Copies a site-cookbooks/ directory into the sandbox path.
125
+ #
126
+ # @api private
127
+ def cp_site_cookbooks
128
+ info("Preparing site-cookbooks from project directory")
129
+ debug("Using cookbooks from #{site_cookbooks_dir}")
130
+
131
+ FileUtils.mkdir_p(tmpsitebooks_dir)
132
+ FileUtils.cp_r(File.join(site_cookbooks_dir, "."), tmpsitebooks_dir)
133
+ end
134
+
135
+ # Copies the current project, assumed to be a Chef cookbook into the
136
+ # sandbox path.
137
+ #
138
+ # @api private
139
+ def cp_this_cookbook
140
+ info("Preparing current project directory as a cookbook")
141
+ debug("Using metadata.rb from #{metadata_rb}")
142
+
143
+ cb_name = MetadataChopper.extract(metadata_rb).first || raise(UserError,
144
+ "The metadata.rb does not define the 'name' key." \
145
+ " Please add: `name '<cookbook_name>'` to metadata.rb and retry")
146
+
147
+ cb_path = File.join(tmpbooks_dir, cb_name)
148
+
149
+ glob = Dir.glob("#{config[:kitchen_root]}/**")
150
+
151
+ FileUtils.mkdir_p(cb_path)
152
+ FileUtils.cp_r(glob, cb_path)
153
+ end
154
+
155
+ # Removes all non-cookbook files in the sandbox path.
156
+ #
157
+ # @api private
158
+ def filter_only_cookbook_files
159
+ info("Removing non-cookbook files before transfer")
160
+ FileUtils.rm(all_files_in_cookbooks - only_cookbook_files)
161
+ Dir.glob(File.join(tmpbooks_dir, "**/"), File::FNM_PATHNAME).
162
+ reverse_each { |fn| FileUtils.rmdir(fn) if Dir.entries(fn).size == 2 }
163
+ end
164
+
165
+ # @return [Logger] the instance's logger or Test Kitchen's common
166
+ # logger otherwise
167
+ # @api private
168
+ def logger
169
+ instance ? instance.logger : Kitchen.logger
170
+ end
171
+
172
+ # Creates a minimal, no-op cookbook in the sandbox path.
173
+ #
174
+ # @api private
175
+ def make_fake_cookbook
176
+ info("Berksfile, Cheffile, cookbooks/, or metadata.rb not found " \
177
+ "so Chef will run with effectively no cookbooks. Is this intended?")
178
+ name = File.basename(config[:kitchen_root])
179
+ fake_cb = File.join(tmpbooks_dir, name)
180
+ FileUtils.mkdir_p(fake_cb)
181
+ File.open(File.join(fake_cb, "metadata.rb"), "wb") do |file|
182
+ file.write(%{name "#{name}"\n})
183
+ end
184
+ end
185
+
186
+ # @return [String] an absolute path to a metadata.rb, relative to the
187
+ # kitchen root
188
+ # @api private
189
+ def metadata_rb
190
+ File.join(config[:kitchen_root], "metadata.rb")
191
+ end
192
+
193
+ # Generates a list of all typical cookbook files needed in a Chef run,
194
+ # located in the cookbooks directory in the sandbox path.
195
+ #
196
+ # @return [Array<String>] an array of absolute paths to files
197
+ # @api private
198
+ def only_cookbook_files
199
+ glob = File.join(tmpbooks_dir, "*", "{#{config[:cookbook_files_glob]}}")
200
+
201
+ Dir.glob(glob, File::FNM_DOTMATCH).
202
+ select { |fn| File.file?(fn) && ! %w[. ..].include?(fn) }
203
+ end
204
+
205
+ # Prepares a generic Chef component source directory or file for
206
+ # inclusion in the sandbox path. These components might includes nodes,
207
+ # roles, etc.
208
+ #
209
+ # @param component [Symbol,String] a component name such as `:node`
210
+ # @param opts [Hash] optional configuration
211
+ # @option opts [Symbol] :type whether the component is a directory or
212
+ # file (default: `:directory`)
213
+ # @option opts [Symbol] :key_name the key name in the config hash from
214
+ # which to pull the source path (default: `"#{component}_path"`)
215
+ # @option opts [String] :dest_name the destination file or directory
216
+ # basename in the sandbox path (default: `component.to_s`)
217
+ # @api private
218
+ def prepare(component, opts = {})
219
+ opts = { :type => :directory }.merge(opts)
220
+ key_name = opts.fetch(:key_name, "#{component}_path")
221
+ src = config[key_name.to_sym]
222
+ return if src.nil?
223
+
224
+ info("Preparing #{component}")
225
+ debug("Using #{component} from #{src}")
226
+
227
+ dest = File.join(sandbox_path, opts.fetch(:dest_name, component.to_s))
228
+
229
+ case opts[:type]
230
+ when :directory
231
+ FileUtils.mkdir_p(dest)
232
+ FileUtils.cp_r(Dir.glob("#{src}/*"), dest)
233
+ when :file
234
+ FileUtils.mkdir_p(File.dirname(dest))
235
+ FileUtils.cp_r(src, dest)
236
+ end
237
+ end
238
+
239
+ # Prepares a cache directory for inclusion in the sandbox path.
240
+ #
241
+ # @api private
242
+ def prepare_cache
243
+ FileUtils.mkdir_p(File.join(sandbox_path, "cache"))
244
+ end
245
+
246
+ # Prepares Chef cookbooks for inclusion in the sandbox path.
247
+ #
248
+ # @api private
249
+ def prepare_cookbooks
250
+ if File.exist?(berksfile)
251
+ resolve_with_berkshelf
252
+ elsif File.exist?(cheffile)
253
+ resolve_with_librarian
254
+ cp_site_cookbooks if File.directory?(site_cookbooks_dir)
255
+ elsif File.directory?(cookbooks_dir)
256
+ cp_cookbooks
257
+ elsif File.exist?(metadata_rb)
258
+ cp_this_cookbook
259
+ else
260
+ make_fake_cookbook
261
+ end
262
+
263
+ filter_only_cookbook_files
264
+ end
265
+
266
+ # Prepares a Chef JSON file, sometimes called a dna.json or
267
+ # first-boot.json, for inclusion in the sandbox path.
268
+ #
269
+ # @api private
270
+ def prepare_json
271
+ dna = config[:attributes].merge(:run_list => config[:run_list])
272
+
273
+ info("Preparing dna.json")
274
+ debug("Creating dna.json from #{dna.inspect}")
275
+
276
+ File.open(File.join(sandbox_path, "dna.json"), "wb") do |file|
277
+ file.write(dna.to_json)
278
+ end
279
+ end
280
+
281
+ # Performs a Berkshelf cookbook resolution inside a common mutex.
282
+ #
283
+ # @api private
284
+ def resolve_with_berkshelf
285
+ Kitchen.mutex.synchronize do
286
+ Chef::Berkshelf.new(berksfile, tmpbooks_dir, logger).resolve
287
+ end
288
+ end
289
+
290
+ # Performs a Librarin-Chef cookbook resolution inside a common mutex.
291
+ #
292
+ # @api private
293
+ def resolve_with_librarian
294
+ Kitchen.mutex.synchronize do
295
+ Chef::Librarian.new(cheffile, tmpbooks_dir, logger).resolve
296
+ end
297
+ end
298
+
299
+ # @return [String] an absolute path to a site-cookbooks/ directory,
300
+ # relative to the kitchen root
301
+ # @api private
302
+ def site_cookbooks_dir
303
+ File.join(config[:kitchen_root], "site-cookbooks")
304
+ end
305
+
306
+ # @return [String] an absolute path to a cookbooks/ directory in the
307
+ # sandbox path
308
+ # @api private
309
+ def tmpbooks_dir
310
+ File.join(sandbox_path, "cookbooks")
311
+ end
312
+
313
+ # @return [String] an absolute path to a site cookbooks directory in the
314
+ # sandbox path
315
+ # @api private
316
+ def tmpsitebooks_dir
317
+ File.join(sandbox_path, "cookbooks")
318
+ end
319
+ end
320
+ end
321
+ end
322
+ end