test-kitchen-rsync 3.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +21 -0
  3. data/LICENSE +15 -0
  4. data/Rakefile +53 -0
  5. data/bin/zl-kitchen +11 -0
  6. data/lib/kitchen/base64_stream.rb +48 -0
  7. data/lib/kitchen/chef_utils_wiring.rb +40 -0
  8. data/lib/kitchen/cli.rb +413 -0
  9. data/lib/kitchen/collection.rb +52 -0
  10. data/lib/kitchen/color.rb +63 -0
  11. data/lib/kitchen/command/action.rb +41 -0
  12. data/lib/kitchen/command/console.rb +54 -0
  13. data/lib/kitchen/command/diagnose.rb +84 -0
  14. data/lib/kitchen/command/doctor.rb +39 -0
  15. data/lib/kitchen/command/exec.rb +37 -0
  16. data/lib/kitchen/command/list.rb +148 -0
  17. data/lib/kitchen/command/login.rb +39 -0
  18. data/lib/kitchen/command/package.rb +32 -0
  19. data/lib/kitchen/command/sink.rb +50 -0
  20. data/lib/kitchen/command/test.rb +47 -0
  21. data/lib/kitchen/command.rb +207 -0
  22. data/lib/kitchen/config.rb +344 -0
  23. data/lib/kitchen/configurable.rb +616 -0
  24. data/lib/kitchen/data_munger.rb +1024 -0
  25. data/lib/kitchen/diagnostic.rb +138 -0
  26. data/lib/kitchen/driver/base.rb +133 -0
  27. data/lib/kitchen/driver/dummy.rb +105 -0
  28. data/lib/kitchen/driver/exec.rb +70 -0
  29. data/lib/kitchen/driver/proxy.rb +70 -0
  30. data/lib/kitchen/driver/ssh_base.rb +351 -0
  31. data/lib/kitchen/driver.rb +40 -0
  32. data/lib/kitchen/errors.rb +243 -0
  33. data/lib/kitchen/generator/init.rb +254 -0
  34. data/lib/kitchen/instance.rb +726 -0
  35. data/lib/kitchen/lazy_hash.rb +148 -0
  36. data/lib/kitchen/lifecycle_hook/base.rb +78 -0
  37. data/lib/kitchen/lifecycle_hook/local.rb +53 -0
  38. data/lib/kitchen/lifecycle_hook/remote.rb +39 -0
  39. data/lib/kitchen/lifecycle_hooks.rb +92 -0
  40. data/lib/kitchen/loader/yaml.rb +377 -0
  41. data/lib/kitchen/logger.rb +422 -0
  42. data/lib/kitchen/logging.rb +52 -0
  43. data/lib/kitchen/login_command.rb +49 -0
  44. data/lib/kitchen/metadata_chopper.rb +49 -0
  45. data/lib/kitchen/platform.rb +64 -0
  46. data/lib/kitchen/plugin.rb +76 -0
  47. data/lib/kitchen/plugin_base.rb +60 -0
  48. data/lib/kitchen/provisioner/base.rb +269 -0
  49. data/lib/kitchen/provisioner/chef/berkshelf.rb +116 -0
  50. data/lib/kitchen/provisioner/chef/common_sandbox.rb +350 -0
  51. data/lib/kitchen/provisioner/chef/policyfile.rb +163 -0
  52. data/lib/kitchen/provisioner/chef_apply.rb +121 -0
  53. data/lib/kitchen/provisioner/chef_base.rb +705 -0
  54. data/lib/kitchen/provisioner/chef_infra.rb +167 -0
  55. data/lib/kitchen/provisioner/chef_solo.rb +82 -0
  56. data/lib/kitchen/provisioner/chef_zero.rb +12 -0
  57. data/lib/kitchen/provisioner/dummy.rb +75 -0
  58. data/lib/kitchen/provisioner/shell.rb +157 -0
  59. data/lib/kitchen/provisioner.rb +42 -0
  60. data/lib/kitchen/rake_tasks.rb +80 -0
  61. data/lib/kitchen/shell_out.rb +90 -0
  62. data/lib/kitchen/ssh.rb +289 -0
  63. data/lib/kitchen/state_file.rb +112 -0
  64. data/lib/kitchen/suite.rb +48 -0
  65. data/lib/kitchen/thor_tasks.rb +63 -0
  66. data/lib/kitchen/transport/base.rb +236 -0
  67. data/lib/kitchen/transport/dummy.rb +78 -0
  68. data/lib/kitchen/transport/exec.rb +145 -0
  69. data/lib/kitchen/transport/ssh.rb +579 -0
  70. data/lib/kitchen/transport/winrm.rb +546 -0
  71. data/lib/kitchen/transport.rb +40 -0
  72. data/lib/kitchen/util.rb +229 -0
  73. data/lib/kitchen/verifier/base.rb +243 -0
  74. data/lib/kitchen/verifier/busser.rb +275 -0
  75. data/lib/kitchen/verifier/dummy.rb +75 -0
  76. data/lib/kitchen/verifier/shell.rb +99 -0
  77. data/lib/kitchen/verifier.rb +39 -0
  78. data/lib/kitchen/version.rb +20 -0
  79. data/lib/kitchen/which.rb +26 -0
  80. data/lib/kitchen.rb +152 -0
  81. data/lib/vendor/hash_recursive_merge.rb +79 -0
  82. data/support/busser_install_command.ps1 +14 -0
  83. data/support/busser_install_command.sh +21 -0
  84. data/support/chef-client-fail-if-update-handler.rb +15 -0
  85. data/support/chef_base_init_command.ps1 +18 -0
  86. data/support/chef_base_init_command.sh +1 -0
  87. data/support/chef_base_install_command.ps1 +85 -0
  88. data/support/chef_base_install_command.sh +229 -0
  89. data/support/download_helpers.sh +109 -0
  90. data/support/dummy-validation.pem +27 -0
  91. data/templates/driver/CHANGELOG.md.erb +3 -0
  92. data/templates/driver/Gemfile.erb +3 -0
  93. data/templates/driver/README.md.erb +64 -0
  94. data/templates/driver/Rakefile.erb +21 -0
  95. data/templates/driver/driver.rb.erb +23 -0
  96. data/templates/driver/gemspec.erb +29 -0
  97. data/templates/driver/gitignore.erb +17 -0
  98. data/templates/driver/license_apachev2.erb +15 -0
  99. data/templates/driver/license_lgplv3.erb +16 -0
  100. data/templates/driver/license_mit.erb +22 -0
  101. data/templates/driver/license_reserved.erb +5 -0
  102. data/templates/driver/tailor.erb +4 -0
  103. data/templates/driver/travis.yml.erb +11 -0
  104. data/templates/driver/version.rb.erb +12 -0
  105. data/templates/init/chefignore.erb +2 -0
  106. data/templates/init/kitchen.yml.erb +18 -0
  107. data/test-kitchen.gemspec +52 -0
  108. metadata +528 -0
@@ -0,0 +1,705 @@
1
+ #
2
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
3
+ #
4
+ # Copyright (C) 2013, Fletcher Nichol
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require "fileutils" unless defined?(FileUtils)
19
+ require "pathname" unless defined?(Pathname)
20
+ require "json" unless defined?(JSON)
21
+ require "cgi" unless defined?(CGI)
22
+
23
+ require_relative "chef/policyfile"
24
+ require_relative "chef/berkshelf"
25
+ require_relative "chef/common_sandbox"
26
+ require_relative "../util"
27
+ module LicenseAcceptance
28
+ autoload :Acceptor, "license_acceptance/acceptor"
29
+ end
30
+
31
+ begin
32
+ require "chef-config/config"
33
+ require "chef-config/workstation_config_loader"
34
+ rescue LoadError # rubocop:disable Lint/HandleExceptions
35
+ # This space left intentionally blank.
36
+ end
37
+
38
+ module Kitchen
39
+ module Provisioner
40
+ # Common implementation details for Chef-related provisioners.
41
+ #
42
+ # @author Fletcher Nichol <fnichol@nichol.ca>
43
+ class ChefBase < Base
44
+ default_config :require_chef_omnibus, true
45
+ default_config :chef_omnibus_url, "https://omnitruck.chef.io/install.sh"
46
+ default_config :chef_omnibus_install_options, nil
47
+ default_config :chef_license, nil
48
+ default_config :run_list, []
49
+ default_config :policy_group, nil
50
+ default_config :attributes, {}
51
+ default_config :config_path, nil
52
+ default_config :log_file, nil
53
+ default_config :log_level do |provisioner|
54
+ provisioner[:debug] ? "debug" : "auto"
55
+ end
56
+ default_config :profile_ruby, false
57
+ # The older policyfile_zero used `policyfile` so support it for compat.
58
+ default_config :policyfile, nil
59
+ # Will try to autodetect by searching for `Policyfile.rb` if not set.
60
+ # If set, will error if the file doesn't exist.
61
+ default_config :policyfile_path, nil
62
+ # Will try to autodetect by searching for `Berksfile` if not set.
63
+ # If set, will error if the file doesn't exist.
64
+ default_config :berksfile_path, nil
65
+ # If set to true (which is the default from `chef generate`), try to update
66
+ # backend cookbook downloader on every kitchen run.
67
+ default_config :always_update_cookbooks, true
68
+ default_config :cookbook_files_glob, %w(
69
+ README.* VERSION metadata.{json,rb} attributes.rb recipe.rb
70
+ attributes/**/* definitions/**/* files/**/* libraries/**/*
71
+ providers/**/* recipes/**/* resources/**/* templates/**/*
72
+ ohai/**/*
73
+ ).join(",")
74
+ # to ease upgrades, allow the user to turn deprecation warnings into errors
75
+ default_config :deprecations_as_errors, false
76
+
77
+ # Override the default from Base so reboot handling works by default for Chef.
78
+ default_config :retry_on_exit_code, [35, 213]
79
+
80
+ default_config :multiple_converge, 1
81
+
82
+ default_config :enforce_idempotency, false
83
+
84
+ default_config :data_path do |provisioner|
85
+ provisioner.calculate_path("data")
86
+ end
87
+ expand_path_for :data_path
88
+
89
+ default_config :data_bags_path do |provisioner|
90
+ provisioner.calculate_path("data_bags")
91
+ end
92
+ expand_path_for :data_bags_path
93
+
94
+ default_config :environments_path do |provisioner|
95
+ provisioner.calculate_path("environments")
96
+ end
97
+ expand_path_for :environments_path
98
+
99
+ default_config :nodes_path do |provisioner|
100
+ provisioner.calculate_path("nodes")
101
+ end
102
+ expand_path_for :nodes_path
103
+
104
+ default_config :roles_path do |provisioner|
105
+ provisioner.calculate_path("roles")
106
+ end
107
+ expand_path_for :roles_path
108
+
109
+ default_config :clients_path do |provisioner|
110
+ provisioner.calculate_path("clients")
111
+ end
112
+ expand_path_for :clients_path
113
+
114
+ default_config :encrypted_data_bag_secret_key_path do |provisioner|
115
+ provisioner.calculate_path("encrypted_data_bag_secret_key", type: :file)
116
+ end
117
+ expand_path_for :encrypted_data_bag_secret_key_path
118
+
119
+ #
120
+ # New configuration options per RFC 091
121
+ # https://github.com/chef/chef-rfc/blob/master/rfc091-deprecate-kitchen-settings.md
122
+ #
123
+
124
+ # Setting product_name to nil. It is currently the pivot point
125
+ # between the two install paths (Mixlib::Install::ScriptGenerator and Mixlib::Install)
126
+ default_config :product_name
127
+
128
+ default_config :product_version, :latest
129
+
130
+ default_config :channel, :stable
131
+
132
+ default_config :install_strategy, "once"
133
+
134
+ default_config :platform
135
+
136
+ default_config :platform_version
137
+
138
+ default_config :architecture
139
+
140
+ default_config :download_url
141
+
142
+ default_config :checksum
143
+
144
+ deprecate_config_for :require_chef_omnibus do |provisioner|
145
+ case
146
+ when provisioner[:require_chef_omnibus] == false
147
+ Util.outdent!(<<-MSG)
148
+ The 'require_chef_omnibus' attribute with value of 'false' will
149
+ change to use the new 'install_strategy' attribute with a value of 'skip'.
150
+
151
+ Note: 'product_name' must be set in order to use 'install_strategy'.
152
+ Although this seems counterintuitive, it is necessary until
153
+ 'product_name' replaces 'require_chef_omnibus' as the default.
154
+
155
+ # New Usage #
156
+ provisioner:
157
+ product_name: <chef or chef-workstation>
158
+ install_strategy: skip
159
+ MSG
160
+ when provisioner[:require_chef_omnibus].to_s.match?(/\d/)
161
+ Util.outdent!(<<-MSG)
162
+ The 'require_chef_omnibus' attribute with version values will change
163
+ to use the new 'product_version' attribute.
164
+
165
+ Note: 'product_name' must be set in order to use 'product_version'
166
+ until 'product_name' replaces 'require_chef_omnibus' as the default.
167
+
168
+ # New Usage #
169
+ provisioner:
170
+ product_name: <chef or chef-workstation>
171
+ product_version: #{provisioner[:require_chef_omnibus]}
172
+ MSG
173
+ when provisioner[:require_chef_omnibus] == "latest"
174
+ Util.outdent!(<<-MSG)
175
+ The 'require_chef_omnibus' attribute with value of 'latest' will change
176
+ to use the new 'install_strategy' attribute with a value of 'always'.
177
+
178
+ Note: 'product_name' must be set in order to use 'install_strategy'
179
+ until 'product_name' replaces 'require_chef_omnibus' as the default.
180
+
181
+ # New Usage #
182
+ provisioner:
183
+ product_name: <chef or chef-workstation>
184
+ install_strategy: always
185
+ MSG
186
+ end
187
+ end
188
+
189
+ deprecate_config_for :chef_omnibus_url, Util.outdent!(<<-MSG)
190
+ Changing the 'chef_omnibus_url' attribute breaks existing functionality. It will
191
+ be removed in a future version.
192
+ MSG
193
+
194
+ deprecate_config_for :chef_omnibus_install_options, Util.outdent!(<<-MSG)
195
+ The 'chef_omnibus_install_options' attribute will be replaced by using
196
+ 'product_name' and 'channel' attributes.
197
+
198
+ Note: 'product_name' must be set in order to use 'channel'
199
+ until 'product_name' replaces 'require_chef_omnibus' as the default.
200
+
201
+ # Deprecated Example #
202
+ provisioner:
203
+ chef_omnibus_install_options: -P chef-workstation -c current
204
+
205
+ # New Usage #
206
+ provisioner:
207
+ product_name: chef-workstation
208
+ channel: current
209
+ MSG
210
+
211
+ deprecate_config_for :install_msi_url, Util.outdent!(<<-MSG)
212
+ The 'install_msi_url' will be relaced by the 'download_url' attribute.
213
+ 'download_url' will be applied to Bourne and Powershell download scripts.
214
+
215
+ Note: 'product_name' must be set in order to use 'download_url'
216
+ until 'product_name' replaces 'require_chef_omnibus' as the default.
217
+
218
+ # New Usage #
219
+ provisioner:
220
+ product_name: <chef or chef-workstation>
221
+ download_url: http://direct-download-url
222
+ MSG
223
+
224
+ deprecate_config_for :chef_metadata_url, Util.outdent!(<<-MSG)
225
+ The 'chef_metadata_url' will be removed. The Windows metadata URL will be
226
+ fully managed by using attribute settings.
227
+ MSG
228
+
229
+ # Reads the local Chef::Config object (if present). We do this because
230
+ # we want to start bring Chef config and Chef Workstation config closer
231
+ # together. For example, we want to configure proxy settings in 1
232
+ # location instead of 3 configuration files.
233
+ #
234
+ # @param config [Hash] initial provided configuration
235
+ def initialize(config = {})
236
+ super(config)
237
+
238
+ if defined?(ChefConfig::WorkstationConfigLoader)
239
+ ChefConfig::WorkstationConfigLoader.new(config[:config_path]).load
240
+ end
241
+ # This exports any proxy config present in the Chef config to
242
+ # appropriate environment variables, which Test Kitchen respects
243
+ ChefConfig::Config.export_proxies if defined?(ChefConfig::Config.export_proxies)
244
+ end
245
+
246
+ def doctor(state)
247
+ deprecated_config.each do |attr, msg|
248
+ info("**** #{attr} deprecated\n#{msg}")
249
+ end
250
+ end
251
+
252
+ # gives us the product version from either require_chef_omnibus or product_version
253
+ # If the non-default (true) value of require_chef_omnibus is present use that
254
+ # otherwise use config[:product_version] which defaults to :latest and is the actual
255
+ # default for chef provisioners
256
+ #
257
+ # @return [String,Symbol,NilClass] version or nil if not applicable
258
+ def product_version
259
+ case config[:require_chef_omnibus]
260
+ when FalseClass
261
+ nil
262
+ when TrueClass
263
+ config[:product_version]
264
+ else
265
+ config[:require_chef_omnibus]
266
+ end
267
+ end
268
+
269
+ # If the user has policyfiles we shell out to the `chef` executable, so need to ensure they have
270
+ # accepted the Chef Workstation license. Otherwise they just need the Chef Infra license.
271
+ #
272
+ # @return [String] license id to prompt for acceptance
273
+ def license_acceptance_id
274
+ case
275
+ when File.exist?(policyfile)
276
+ "chef-workstation"
277
+ when config[:product_name]
278
+ config[:product_name]
279
+ else
280
+ "chef"
281
+ end
282
+ end
283
+
284
+ # (see Base#check_license)
285
+ def check_license
286
+ name = license_acceptance_id
287
+ version = product_version
288
+ debug("Checking if we need to prompt for license acceptance on product: #{name} version: #{version}.")
289
+
290
+ acceptor = LicenseAcceptance::Acceptor.new(logger: Kitchen.logger, provided: config[:chef_license])
291
+ if acceptor.license_required?(name, version)
292
+ debug("License acceptance required for #{name} version: #{version}. Prompting")
293
+ license_id = acceptor.id_from_mixlib(name)
294
+ begin
295
+ acceptor.check_and_persist(license_id, version.to_s)
296
+ rescue LicenseAcceptance::LicenseNotAcceptedError => e
297
+ error("Cannot converge without accepting the #{e.product.pretty_name} License. Set it in your kitchen.yml or using the CHEF_LICENSE environment variable")
298
+ raise
299
+ end
300
+ config[:chef_license] ||= acceptor.acceptance_value
301
+ end
302
+ end
303
+
304
+ # (see Base#create_sandbox)
305
+ def create_sandbox
306
+ super
307
+ sanity_check_sandbox_options!
308
+ Chef::CommonSandbox.new(config, sandbox_path, instance).populate
309
+ end
310
+
311
+ # (see Base#init_command)
312
+ def init_command
313
+ dirs = %w{
314
+ cookbooks data data_bags environments roles clients
315
+ encrypted_data_bag_secret
316
+ }.sort.map { |dir| remote_path_join(config[:root_path], dir) }
317
+
318
+ vars = if powershell_shell?
319
+ init_command_vars_for_powershell(dirs)
320
+ else
321
+ init_command_vars_for_bourne(dirs)
322
+ end
323
+
324
+ prefix_command(shell_code_from_file(vars, "chef_base_init_command"))
325
+ end
326
+
327
+ # (see Base#install_command)
328
+ def install_command
329
+ return unless config[:require_chef_omnibus] || config[:product_name]
330
+ return if config[:product_name] && config[:install_strategy] == "skip"
331
+
332
+ prefix_command(install_script_contents)
333
+ end
334
+
335
+ private
336
+
337
+ def last_exit_code
338
+ "; exit $LastExitCode" if powershell_shell?
339
+ end
340
+
341
+ # @return [Hash] an option hash for the install commands
342
+ # @api private
343
+ def install_options
344
+ add_omnibus_directory_option if instance.driver.cache_directory
345
+ project = /\s*-P (\w+)\s*/.match(config[:chef_omnibus_install_options])
346
+ {
347
+ omnibus_url: config[:chef_omnibus_url],
348
+ project: project.nil? ? nil : project[1],
349
+ install_flags: config[:chef_omnibus_install_options],
350
+ sudo_command: sudo_command,
351
+ }.tap do |opts|
352
+ opts[:root] = config[:chef_omnibus_root] if config.key? :chef_omnibus_root
353
+ %i{install_msi_url http_proxy https_proxy}.each do |key|
354
+ opts[key] = config[key] if config.key? key
355
+ end
356
+ end
357
+ end
358
+
359
+ # Verify if the "omnibus_dir_option" has already been passed, if so we
360
+ # don't use the @driver.cache_directory
361
+ #
362
+ # @api private
363
+ def add_omnibus_directory_option
364
+ cache_dir_option = "#{omnibus_dir_option} #{instance.driver.cache_directory}"
365
+ if config[:chef_omnibus_install_options].nil?
366
+ config[:chef_omnibus_install_options] = cache_dir_option
367
+ elsif config[:chef_omnibus_install_options].match(/\s*#{omnibus_dir_option}\s*/).nil?
368
+ config[:chef_omnibus_install_options] << " " << cache_dir_option
369
+ end
370
+ end
371
+
372
+ # @return [String] an absolute path to a Policyfile, relative to the
373
+ # kitchen root
374
+ # @api private
375
+ def policyfile
376
+ policyfile_basename = config[:policyfile_path] || config[:policyfile] || "Policyfile.rb"
377
+ File.expand_path(policyfile_basename, config[:kitchen_root])
378
+ end
379
+
380
+ # @return [String] an absolute path to a Berksfile, relative to the
381
+ # kitchen root
382
+ # @api private
383
+ def berksfile
384
+ berksfile_basename = config[:berksfile_path] || config[:berksfile] || "Berksfile"
385
+ File.expand_path(berksfile_basename, config[:kitchen_root])
386
+ end
387
+
388
+ # Generates a Hash with default values for a solo.rb or client.rb Chef
389
+ # configuration file.
390
+ #
391
+ # @return [Hash] a configuration hash
392
+ # @api private
393
+ def default_config_rb # rubocop:disable Metrics/MethodLength
394
+ root = config[:root_path].gsub("$env:TEMP", "\#{ENV['TEMP']\}")
395
+
396
+ config_rb = {
397
+ node_name: instance.name,
398
+ checksum_path: remote_path_join(root, "checksums"),
399
+ file_cache_path: remote_path_join(root, "cache"),
400
+ file_backup_path: remote_path_join(root, "backup"),
401
+ cookbook_path: [
402
+ remote_path_join(root, "cookbooks"),
403
+ remote_path_join(root, "site-cookbooks"),
404
+ ],
405
+ data_bag_path: remote_path_join(root, "data_bags"),
406
+ environment_path: remote_path_join(root, "environments"),
407
+ node_path: remote_path_join(root, "nodes"),
408
+ role_path: remote_path_join(root, "roles"),
409
+ client_path: remote_path_join(root, "clients"),
410
+ user_path: remote_path_join(root, "users"),
411
+ validation_key: remote_path_join(root, "validation.pem"),
412
+ client_key: remote_path_join(root, "client.pem"),
413
+ chef_server_url: "http://127.0.0.1:8889",
414
+ encrypted_data_bag_secret: remote_path_join(
415
+ root, "encrypted_data_bag_secret"
416
+ ),
417
+ treat_deprecation_warnings_as_errors: config[:deprecations_as_errors],
418
+ }
419
+ config_rb[:chef_license] = config[:chef_license] unless config[:chef_license].nil?
420
+ config_rb
421
+ end
422
+
423
+ # Generates a rendered client.rb/solo.rb/knife.rb formatted file as a
424
+ # String.
425
+ #
426
+ # @param data [Hash] a key/value pair hash of configuration
427
+ # @return [String] a rendered Chef config file as a String
428
+ # @api private
429
+ def format_config_file(data)
430
+ data.each.map do |attr, value|
431
+ [attr, format_value(value)].join(" ")
432
+ end.join("\n")
433
+ end
434
+
435
+ # Converts a Ruby object to a String interpretation suitable for writing
436
+ # out to a client.rb/solo.rb/knife.rb file.
437
+ #
438
+ # @param obj [Object] an object
439
+ # @return [String] a string representation
440
+ # @api private
441
+ def format_value(obj)
442
+ if obj.is_a?(String) && obj =~ /^:/
443
+ obj
444
+ elsif obj.is_a?(String)
445
+ %{"#{obj.gsub(/\\/, "\\\\\\\\")}"}
446
+ elsif obj.is_a?(Array)
447
+ %{[#{obj.map { |i| format_value(i) }.join(", ")}]}
448
+ else
449
+ obj.inspect
450
+ end
451
+ end
452
+
453
+ # Generates the init command variables for Bourne shell-based platforms.
454
+ #
455
+ # @param dirs [Array<String>] directories
456
+ # @return [String] shell variable lines
457
+ # @api private
458
+ def init_command_vars_for_bourne(dirs)
459
+ [
460
+ shell_var("sudo_rm", sudo("rm")),
461
+ shell_var("dirs", dirs.join(" ")),
462
+ shell_var("root_path", config[:root_path]),
463
+ ].join("\n")
464
+ end
465
+
466
+ # Generates the init command variables for PowerShell-based platforms.
467
+ #
468
+ # @param dirs [Array<String>] directories
469
+ # @return [String] shell variable lines
470
+ # @api private
471
+ def init_command_vars_for_powershell(dirs)
472
+ [
473
+ %{$dirs = @(#{dirs.map { |d| %{"#{d}"} }.join(", ")})},
474
+ shell_var("root_path", config[:root_path]),
475
+ ].join("\n")
476
+ end
477
+
478
+ # Load cookbook dependency resolver code, if required.
479
+ #
480
+ # (see Base#load_needed_dependencies!)
481
+ def load_needed_dependencies!
482
+ super
483
+ if File.exist?(policyfile)
484
+ debug("Policyfile found at #{policyfile}, using Policyfile to resolve cookbook dependencies")
485
+ Chef::Policyfile.load!(logger: logger)
486
+ elsif File.exist?(berksfile)
487
+ debug("Berksfile found at #{berksfile}, using Berkshelf to resolve cookbook dependencies")
488
+ Chef::Berkshelf.load!(logger: logger)
489
+ end
490
+ end
491
+
492
+ # @return [String] contents of the install script
493
+ # @api private
494
+ def install_script_contents
495
+ # by default require_chef_omnibus is set to true. Check config[:product_name] first
496
+ # so that we can use it if configured.
497
+ if config[:product_name]
498
+ script_for_product
499
+ elsif config[:require_chef_omnibus]
500
+ script_for_omnibus_version
501
+ end
502
+ end
503
+
504
+ # @return [String] contents of product based install script
505
+ # @api private
506
+ def script_for_product
507
+ require "mixlib/install"
508
+ installer = Mixlib::Install.new({
509
+ product_name: config[:product_name],
510
+ product_version: config[:product_version],
511
+ channel: config[:channel].to_sym,
512
+ install_command_options: {
513
+ install_strategy: config[:install_strategy],
514
+ },
515
+ }.tap do |opts|
516
+ opts[:shell_type] = :ps1 if powershell_shell?
517
+ %i{platform platform_version architecture}.each do |key|
518
+ opts[key] = config[key] if config[key]
519
+ end
520
+
521
+ unless windows_os?
522
+ # omnitruck installer does not currently support a tmp dir option on windows
523
+ opts[:install_command_options][:tmp_dir] = config[:root_path]
524
+ opts[:install_command_options]["TMPDIR"] = config[:root_path]
525
+ end
526
+
527
+ if config[:download_url]
528
+ opts[:install_command_options][:download_url_override] = config[:download_url]
529
+ opts[:install_command_options][:checksum] = config[:checksum] if config[:checksum]
530
+ end
531
+
532
+ if instance.driver.cache_directory
533
+ download_dir_option = windows_os? ? :download_directory : :cmdline_dl_dir
534
+ opts[:install_command_options][download_dir_option] = instance.driver.cache_directory
535
+ end
536
+
537
+ proxies = {}.tap do |prox|
538
+ %i{http_proxy https_proxy ftp_proxy no_proxy}.each do |key|
539
+ prox[key] = config[key] if config[key]
540
+ end
541
+
542
+ # install.ps1 only supports http_proxy
543
+ prox.delete_if { |p| %i{https_proxy ftp_proxy no_proxy}.include?(p) } if powershell_shell?
544
+ end
545
+ opts[:install_command_options].merge!(proxies)
546
+ end)
547
+ config[:chef_omnibus_root] = installer.root
548
+ if powershell_shell?
549
+ installer.install_command
550
+ else
551
+ install_from_file(installer.install_command)
552
+ end
553
+ end
554
+
555
+ # @return [String] Correct option per platform to specify the the
556
+ # cache directory
557
+ # @api private
558
+ def omnibus_dir_option
559
+ windows_os? ? "-download_directory" : "-d"
560
+ end
561
+
562
+ def install_from_file(command)
563
+ install_file = "#{config[:root_path]}/chef-installer.sh"
564
+ script = []
565
+ script << "mkdir -p #{config[:root_path]}"
566
+ script << "if [ $? -ne 0 ]; then"
567
+ script << " echo Kitchen config setting root_path: '#{config[:root_path]}' not creatable by regular user "
568
+ script << " exit 1"
569
+ script << "fi"
570
+ script << "cat > #{install_file} <<\"EOL\""
571
+ script << command
572
+ script << "EOL"
573
+ script << "chmod +x #{install_file}"
574
+ script << sudo(install_file)
575
+ script.join("\n")
576
+ end
577
+
578
+ # @return [String] contents of version based install script
579
+ # @api private
580
+ def script_for_omnibus_version
581
+ require "mixlib/install/script_generator"
582
+ installer = Mixlib::Install::ScriptGenerator.new(
583
+ config[:require_chef_omnibus], powershell_shell?, install_options
584
+ )
585
+ config[:chef_omnibus_root] = installer.root
586
+ sudo(installer.install_command)
587
+ end
588
+
589
+ # Hook used in subclasses to indicate support for policyfiles.
590
+ #
591
+ # @abstract
592
+ # @return [Boolean]
593
+ # @api private
594
+ def supports_policyfile?
595
+ false
596
+ end
597
+
598
+ # @return [void]
599
+ # @raise [UserError]
600
+ # @api private
601
+ def sanity_check_sandbox_options!
602
+ if (config[:policyfile_path] || config[:policyfile]) && !File.exist?(policyfile)
603
+ raise UserError, "policyfile_path set in config "\
604
+ "(#{config[:policyfile_path]} could not be found. " \
605
+ "Expected to find it at full path #{policyfile}."
606
+ end
607
+ if config[:berksfile_path] && !File.exist?(berksfile)
608
+ raise UserError, "berksfile_path set in config "\
609
+ "(#{config[:berksfile_path]} could not be found. " \
610
+ "Expected to find it at full path #{berksfile}."
611
+ end
612
+ if File.exist?(policyfile) && !supports_policyfile?
613
+ raise UserError, "policyfile detected, but provisioner " \
614
+ "#{self.class.name} doesn't support Policyfiles. " \
615
+ "Either use a different provisioner, or delete/rename " \
616
+ "#{policyfile}."
617
+ end
618
+ end
619
+
620
+ # Writes a configuration file to the sandbox directory.
621
+ # @api private
622
+ def prepare_config_rb
623
+ data = default_config_rb.merge(config[config_filename.tr(".", "_").to_sym])
624
+ data = data.merge(named_run_list: config[:named_run_list]) if config[:named_run_list]
625
+
626
+ info("Preparing #{config_filename}")
627
+ debug("Creating #{config_filename} from #{data.inspect}")
628
+
629
+ File.open(File.join(sandbox_path, config_filename), "wb") do |file|
630
+ file.write(format_config_file(data))
631
+ end
632
+
633
+ prepare_config_idempotency_check(data) if config[:enforce_idempotency]
634
+ end
635
+
636
+ # Writes a configuration file to the sandbox directory
637
+ # to check for idempotency of the run.
638
+ # @api private
639
+ def prepare_config_idempotency_check(data)
640
+ handler_filename = "chef-client-fail-if-update-handler.rb"
641
+ source = File.join(
642
+ File.dirname(__FILE__), %w{.. .. .. support }, handler_filename
643
+ )
644
+ FileUtils.cp(source, File.join(sandbox_path, handler_filename))
645
+ File.open(File.join(sandbox_path, "client_no_updated_resources.rb"), "wb") do |file|
646
+ file.write(format_config_file(data))
647
+ file.write("\n\n")
648
+ file.write("handler_file = File.join(File.dirname(__FILE__), '#{handler_filename}')\n")
649
+ file.write "Chef::Config.from_file(handler_file)\n"
650
+ end
651
+ end
652
+
653
+ # Returns an Array of command line arguments for the chef client.
654
+ #
655
+ # @return [Array<String>] an array of command line arguments
656
+ # @api private
657
+ def chef_args(_config_filename)
658
+ raise "You must override in sub classes!"
659
+ end
660
+
661
+ # Returns a filename for the configuration file
662
+ # defaults to client.rb
663
+ #
664
+ # @return [String] a filename
665
+ # @api private
666
+ def config_filename
667
+ "client.rb"
668
+ end
669
+
670
+ # Gives the command used to run chef
671
+ # @api private
672
+ def chef_cmd(base_cmd)
673
+ if windows_os?
674
+ separator = [
675
+ "; if ($LastExitCode -ne 0) { ",
676
+ "throw \"Command failed with exit code $LastExitCode.\" } ;",
677
+ ].join
678
+ else
679
+ separator = " && "
680
+ end
681
+ chef_cmds(base_cmd).join(separator)
682
+ end
683
+
684
+ # Gives an array of command
685
+ # @api private
686
+ def chef_cmds(base_cmd)
687
+ cmd = prefix_command(wrap_shell_code(
688
+ [base_cmd, *chef_args(config_filename), last_exit_code].join(" ")
689
+ .tap { |str| str.insert(0, reload_ps1_path) if windows_os? }
690
+ ))
691
+
692
+ cmds = [cmd].cycle(config[:multiple_converge].to_i).to_a
693
+
694
+ if config[:enforce_idempotency]
695
+ idempotent_cmd = prefix_command(wrap_shell_code(
696
+ [base_cmd, *chef_args("client_no_updated_resources.rb"), last_exit_code].join(" ")
697
+ .tap { |str| str.insert(0, reload_ps1_path) if windows_os? }
698
+ ))
699
+ cmds[-1] = idempotent_cmd
700
+ end
701
+ cmds
702
+ end
703
+ end
704
+ end
705
+ end