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