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,352 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
3
|
+
#
|
|
4
|
+
# Copyright (C) 2015, 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 "json" unless defined?(JSON)
|
|
19
|
+
|
|
20
|
+
module Kitchen
|
|
21
|
+
module Provisioner
|
|
22
|
+
module Chef
|
|
23
|
+
# Internal object to manage common sandbox preparation for
|
|
24
|
+
# Chef-related provisioners.
|
|
25
|
+
#
|
|
26
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
27
|
+
# @api private
|
|
28
|
+
class CommonSandbox
|
|
29
|
+
include Logging
|
|
30
|
+
|
|
31
|
+
# Constructs a new object, taking config, a sandbox path, and an
|
|
32
|
+
# instance.
|
|
33
|
+
#
|
|
34
|
+
# @param config [Hash] configuration hash
|
|
35
|
+
# @param sandbox_path [String] path to local sandbox directory
|
|
36
|
+
# @param instance [Instance] an instance
|
|
37
|
+
def initialize(config, sandbox_path, instance)
|
|
38
|
+
@config = config
|
|
39
|
+
@sandbox_path = sandbox_path
|
|
40
|
+
@instance = instance
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Populate the sandbox.
|
|
44
|
+
def populate
|
|
45
|
+
prepare_json
|
|
46
|
+
prepare_cache
|
|
47
|
+
prepare_cookbooks
|
|
48
|
+
prepare(:data)
|
|
49
|
+
prepare(:data_bags)
|
|
50
|
+
prepare(:environments)
|
|
51
|
+
prepare(:nodes)
|
|
52
|
+
prepare(:roles)
|
|
53
|
+
prepare(:clients)
|
|
54
|
+
prepare(
|
|
55
|
+
:secret,
|
|
56
|
+
type: :file,
|
|
57
|
+
dest_name: "encrypted_data_bag_secret",
|
|
58
|
+
key_name: :encrypted_data_bag_secret_key_path
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
# @return [Hash] configuration hash
|
|
65
|
+
# @api private
|
|
66
|
+
attr_reader :config
|
|
67
|
+
|
|
68
|
+
# @return [Instance] an instance
|
|
69
|
+
# @api private
|
|
70
|
+
attr_reader :instance
|
|
71
|
+
|
|
72
|
+
# @return [String] path to local sandbox directory
|
|
73
|
+
# @api private
|
|
74
|
+
attr_reader :sandbox_path
|
|
75
|
+
|
|
76
|
+
# @return [String] name of the policy_group, nil results in "local"
|
|
77
|
+
# @api private
|
|
78
|
+
attr_reader :policy_group
|
|
79
|
+
|
|
80
|
+
# Generates a list of all files in the cookbooks directory in the
|
|
81
|
+
# sandbox path.
|
|
82
|
+
#
|
|
83
|
+
# @return [Array<String>] an array of absolute paths to files
|
|
84
|
+
# @api private
|
|
85
|
+
def all_files_in_cookbooks
|
|
86
|
+
Util.list_directory(tmpbooks_dir, include_dot: true, recurse: true)
|
|
87
|
+
.select { |fn| File.file?(fn) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @return [String] an absolute path to a Policyfile, relative to the
|
|
91
|
+
# kitchen root
|
|
92
|
+
# @api private
|
|
93
|
+
def policyfile
|
|
94
|
+
basename = config[:policyfile_path] || config[:policyfile] || "Policyfile.rb"
|
|
95
|
+
File.expand_path(basename, config[:kitchen_root])
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @return [String] an absolute path to a Berksfile, relative to the
|
|
99
|
+
# kitchen root
|
|
100
|
+
# @api private
|
|
101
|
+
def berksfile
|
|
102
|
+
basename = config[:berksfile_path] || "Berksfile"
|
|
103
|
+
File.expand_path(basename, config[:kitchen_root])
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @return [String] an absolute path to a cookbooks/ directory, relative
|
|
107
|
+
# to the kitchen root
|
|
108
|
+
# @api private
|
|
109
|
+
def cookbooks_dir
|
|
110
|
+
File.join(config[:kitchen_root], "cookbooks")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Copies a cookbooks/ directory into the sandbox path.
|
|
114
|
+
#
|
|
115
|
+
# @api private
|
|
116
|
+
def cp_cookbooks
|
|
117
|
+
info("Preparing cookbooks from project directory")
|
|
118
|
+
debug("Using cookbooks from #{cookbooks_dir}")
|
|
119
|
+
|
|
120
|
+
FileUtils.mkdir_p(tmpbooks_dir)
|
|
121
|
+
FileUtils.cp_r(File.join(cookbooks_dir, "."), tmpbooks_dir)
|
|
122
|
+
|
|
123
|
+
cp_site_cookbooks if File.directory?(site_cookbooks_dir)
|
|
124
|
+
cp_this_cookbook if File.exist?(metadata_rb)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Copies a site-cookbooks/ directory into the sandbox path.
|
|
128
|
+
#
|
|
129
|
+
# @api private
|
|
130
|
+
def cp_site_cookbooks
|
|
131
|
+
info("Preparing site-cookbooks from project directory")
|
|
132
|
+
debug("Using cookbooks from #{site_cookbooks_dir}")
|
|
133
|
+
|
|
134
|
+
FileUtils.mkdir_p(tmpsitebooks_dir)
|
|
135
|
+
FileUtils.cp_r(File.join(site_cookbooks_dir, "."), tmpsitebooks_dir)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Copies the current project, assumed to be a Chef cookbook into the
|
|
139
|
+
# sandbox path.
|
|
140
|
+
#
|
|
141
|
+
# @api private
|
|
142
|
+
def cp_this_cookbook
|
|
143
|
+
info("Preparing current project directory as a cookbook")
|
|
144
|
+
debug("Using metadata.rb from #{metadata_rb}")
|
|
145
|
+
|
|
146
|
+
cb_name = MetadataChopper.extract(metadata_rb).first || raise(UserError,
|
|
147
|
+
"The metadata.rb does not define the 'name' key." \
|
|
148
|
+
" Please add: `name '<cookbook_name>'` to metadata.rb and retry")
|
|
149
|
+
|
|
150
|
+
cb_path = File.join(tmpbooks_dir, cb_name)
|
|
151
|
+
|
|
152
|
+
glob = Util.list_directory(config[:kitchen_root])
|
|
153
|
+
|
|
154
|
+
FileUtils.mkdir_p(cb_path)
|
|
155
|
+
FileUtils.cp_r(glob, cb_path)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Removes all non-cookbook files in the sandbox path.
|
|
159
|
+
#
|
|
160
|
+
# @api private
|
|
161
|
+
def filter_only_cookbook_files
|
|
162
|
+
info("Removing non-cookbook files before transfer")
|
|
163
|
+
FileUtils.rm(all_files_in_cookbooks - only_cookbook_files)
|
|
164
|
+
Util.list_directory(tmpbooks_dir, recurse: true)
|
|
165
|
+
.reverse_each { |fn| FileUtils.rmdir(fn) if File.directory?(fn) && Dir.empty?(fn) }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# @return [Logger] the instance's logger or Test Kitchen's common
|
|
169
|
+
# logger otherwise
|
|
170
|
+
# @api private
|
|
171
|
+
def logger
|
|
172
|
+
instance ? instance.logger : Kitchen.logger
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Creates a minimal, no-op cookbook in the sandbox path.
|
|
176
|
+
#
|
|
177
|
+
# @api private
|
|
178
|
+
def make_fake_cookbook
|
|
179
|
+
info("Policyfile, Berksfile, cookbooks/, or metadata.rb not found " \
|
|
180
|
+
"so Chef Infra Client will run, but do nothing. Is this intended?")
|
|
181
|
+
name = File.basename(config[:kitchen_root])
|
|
182
|
+
fake_cb = File.join(tmpbooks_dir, name)
|
|
183
|
+
FileUtils.mkdir_p(fake_cb)
|
|
184
|
+
File.open(File.join(fake_cb, "metadata.rb"), "wb") do |file|
|
|
185
|
+
file.write(%{name "#{name}"\n})
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# @return [String] an absolute path to a metadata.rb, relative to the
|
|
190
|
+
# kitchen root
|
|
191
|
+
# @api private
|
|
192
|
+
def metadata_rb
|
|
193
|
+
File.join(config[:kitchen_root], "metadata.rb")
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Generates a list of all typical cookbook files needed in a Chef run,
|
|
197
|
+
# located in the cookbooks directory in the sandbox path.
|
|
198
|
+
#
|
|
199
|
+
# @return [Array<String>] an array of absolute paths to files
|
|
200
|
+
# @api private
|
|
201
|
+
def only_cookbook_files
|
|
202
|
+
glob = File.join("*", "{#{config[:cookbook_files_glob]}}")
|
|
203
|
+
Util.safe_glob(tmpbooks_dir, glob, File::FNM_DOTMATCH)
|
|
204
|
+
.select { |fn| File.file?(fn) && ! %w{. ..}.include?(fn) }
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Prepares a generic Chef component source directory or file for
|
|
208
|
+
# inclusion in the sandbox path. These components might includes nodes,
|
|
209
|
+
# roles, etc.
|
|
210
|
+
#
|
|
211
|
+
# @param component [Symbol,String] a component name such as `:node`
|
|
212
|
+
# @param opts [Hash] optional configuration
|
|
213
|
+
# @option opts [Symbol] :type whether the component is a directory or
|
|
214
|
+
# file (default: `:directory`)
|
|
215
|
+
# @option opts [Symbol] :key_name the key name in the config hash from
|
|
216
|
+
# which to pull the source path (default: `"#{component}_path"`)
|
|
217
|
+
# @option opts [String] :dest_name the destination file or directory
|
|
218
|
+
# basename in the sandbox path (default: `component.to_s`)
|
|
219
|
+
# @api private
|
|
220
|
+
def prepare(component, opts = {})
|
|
221
|
+
opts = { type: :directory }.merge(opts)
|
|
222
|
+
key_name = opts.fetch(:key_name, "#{component}_path")
|
|
223
|
+
src = config[key_name.to_sym]
|
|
224
|
+
return if src.nil?
|
|
225
|
+
|
|
226
|
+
info("Preparing #{component}")
|
|
227
|
+
debug("Using #{component} from #{src}")
|
|
228
|
+
|
|
229
|
+
dest = File.join(sandbox_path, opts.fetch(:dest_name, component.to_s))
|
|
230
|
+
|
|
231
|
+
case opts[:type]
|
|
232
|
+
when :directory
|
|
233
|
+
FileUtils.mkdir_p(dest)
|
|
234
|
+
Array(src).each { |dir| FileUtils.cp_r(Util.list_directory(dir), dest) }
|
|
235
|
+
when :file
|
|
236
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
|
237
|
+
Array(src).each { |file| FileUtils.cp_r(file, dest) }
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Prepares a cache directory for inclusion in the sandbox path.
|
|
242
|
+
#
|
|
243
|
+
# @api private
|
|
244
|
+
def prepare_cache
|
|
245
|
+
FileUtils.mkdir_p(File.join(sandbox_path, "cache"))
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Prepares Chef cookbooks for inclusion in the sandbox path.
|
|
249
|
+
#
|
|
250
|
+
# @api private
|
|
251
|
+
# rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
|
252
|
+
def prepare_cookbooks
|
|
253
|
+
if File.exist?(policyfile)
|
|
254
|
+
resolve_with_policyfile
|
|
255
|
+
elsif File.exist?(berksfile)
|
|
256
|
+
resolve_with_berkshelf
|
|
257
|
+
elsif File.directory?(cookbooks_dir)
|
|
258
|
+
cp_cookbooks
|
|
259
|
+
elsif File.exist?(metadata_rb)
|
|
260
|
+
cp_this_cookbook
|
|
261
|
+
else
|
|
262
|
+
make_fake_cookbook
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
filter_only_cookbook_files
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Prepares a Chef JSON file, sometimes called a dna.json or
|
|
269
|
+
# first-boot.json, for inclusion in the sandbox path.
|
|
270
|
+
#
|
|
271
|
+
# @api private
|
|
272
|
+
def prepare_json
|
|
273
|
+
dna = if File.exist?(policyfile)
|
|
274
|
+
update_dna_for_policyfile
|
|
275
|
+
else
|
|
276
|
+
config[:attributes].merge(run_list: config[:run_list])
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
info("Preparing dna.json")
|
|
280
|
+
debug("Creating dna.json from #{dna.inspect}")
|
|
281
|
+
|
|
282
|
+
File.open(File.join(sandbox_path, "dna.json"), "wb") do |file|
|
|
283
|
+
file.write(dna.to_json)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def update_dna_for_policyfile
|
|
288
|
+
policy = Chef::Policyfile.new(
|
|
289
|
+
policyfile, sandbox_path,
|
|
290
|
+
logger:,
|
|
291
|
+
always_update: config[:always_update_cookbooks],
|
|
292
|
+
policy_group:,
|
|
293
|
+
license: config[:chef_license]
|
|
294
|
+
)
|
|
295
|
+
Kitchen.mutex.synchronize do
|
|
296
|
+
policy.compile
|
|
297
|
+
end
|
|
298
|
+
policy_name = JSON.parse(File.read(policy.lockfile))["name"]
|
|
299
|
+
policy_group = config[:policy_group] || "local"
|
|
300
|
+
config[:attributes].merge(policy_name:, policy_group:)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Performs a Policyfile cookbook resolution inside a common mutex.
|
|
304
|
+
#
|
|
305
|
+
# @api private
|
|
306
|
+
def resolve_with_policyfile
|
|
307
|
+
Kitchen.mutex.synchronize do
|
|
308
|
+
Chef::Policyfile.new(
|
|
309
|
+
policyfile, sandbox_path,
|
|
310
|
+
logger:,
|
|
311
|
+
always_update: config[:always_update_cookbooks],
|
|
312
|
+
policy_group: config[:policy_group],
|
|
313
|
+
license: config[:chef_license]
|
|
314
|
+
).resolve
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Performs a Berkshelf cookbook resolution inside a common mutex.
|
|
319
|
+
#
|
|
320
|
+
# @api private
|
|
321
|
+
def resolve_with_berkshelf
|
|
322
|
+
Kitchen.mutex.synchronize do
|
|
323
|
+
Chef::Berkshelf.new(berksfile, tmpbooks_dir,
|
|
324
|
+
logger:,
|
|
325
|
+
always_update: config[:always_update_cookbooks]).resolve
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# @return [String] an absolute path to a site-cookbooks/ directory,
|
|
330
|
+
# relative to the kitchen root
|
|
331
|
+
# @api private
|
|
332
|
+
def site_cookbooks_dir
|
|
333
|
+
File.join(config[:kitchen_root], "site-cookbooks")
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# @return [String] an absolute path to a cookbooks/ directory in the
|
|
337
|
+
# sandbox path
|
|
338
|
+
# @api private
|
|
339
|
+
def tmpbooks_dir
|
|
340
|
+
File.join(sandbox_path, "cookbooks")
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# @return [String] an absolute path to a site cookbooks directory in the
|
|
344
|
+
# sandbox path
|
|
345
|
+
# @api private
|
|
346
|
+
def tmpsitebooks_dir
|
|
347
|
+
File.join(sandbox_path, "cookbooks")
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
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 "shellwords" unless defined?(Shellwords)
|
|
19
|
+
require "rbconfig" unless defined?(RbConfig)
|
|
20
|
+
|
|
21
|
+
require "kitchen/errors"
|
|
22
|
+
require "kitchen/logging"
|
|
23
|
+
require "kitchen/shell_out"
|
|
24
|
+
require "kitchen/which"
|
|
25
|
+
|
|
26
|
+
module Kitchen
|
|
27
|
+
module Provisioner
|
|
28
|
+
module Chef
|
|
29
|
+
# Chef cookbook resolver that uses Policyfiles to calculate dependencies.
|
|
30
|
+
#
|
|
31
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
|
32
|
+
class Policyfile
|
|
33
|
+
include Logging
|
|
34
|
+
include ShellOut
|
|
35
|
+
include Which
|
|
36
|
+
|
|
37
|
+
# Creates a new cookbook resolver.
|
|
38
|
+
#
|
|
39
|
+
# @param policyfile [String] path to a Policyfile
|
|
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, license: nil, logger: Kitchen.logger, always_update: false, policy_group: nil)
|
|
45
|
+
@policyfile = policyfile
|
|
46
|
+
@path = path
|
|
47
|
+
@logger = logger
|
|
48
|
+
@always_update = always_update
|
|
49
|
+
@policy_group = policy_group
|
|
50
|
+
@license = license
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Loads the library code required to use the resolver.
|
|
54
|
+
#
|
|
55
|
+
# @param logger [Kitchen::Logger] a logger to use for output, defaults
|
|
56
|
+
# to `Kitchen.logger`
|
|
57
|
+
def self.load!(logger: Kitchen.logger)
|
|
58
|
+
# intentionally left blank
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Performs the cookbook resolution and vendors the resulting cookbooks
|
|
62
|
+
# in the desired path.
|
|
63
|
+
def resolve
|
|
64
|
+
if policy_group
|
|
65
|
+
info("Exporting cookbook dependencies from Policyfile #{path} with policy_group #{policy_group} using `#{cli_path} export`...")
|
|
66
|
+
run_command("#{cli_path} export #{escape_path(policyfile)} #{escape_path(path)} --policy_group #{policy_group} --force #{chef_license(license)}")
|
|
67
|
+
else
|
|
68
|
+
info("Exporting cookbook dependencies from Policyfile #{path} using `#{cli_path} export`...")
|
|
69
|
+
run_command("#{cli_path} export #{escape_path(policyfile)} #{escape_path(path)} --force #{chef_license(license)}")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Runs `chef install` to determine the correct cookbook set and
|
|
74
|
+
# generate the policyfile lock.
|
|
75
|
+
def compile
|
|
76
|
+
if File.exist?(lockfile)
|
|
77
|
+
info("Installing cookbooks for Policyfile #{policyfile} using `#{cli_path} install`")
|
|
78
|
+
else
|
|
79
|
+
info("Policy lock file doesn't exist, running `#{cli_path} install` for Policyfile #{policyfile}...")
|
|
80
|
+
end
|
|
81
|
+
run_command("#{cli_path} install #{escape_path(policyfile)} #{chef_license(license)}")
|
|
82
|
+
|
|
83
|
+
if always_update
|
|
84
|
+
info("Updating policy lock using `#{cli_path} update`")
|
|
85
|
+
run_command("#{cli_path} update #{escape_path(policyfile)} #{chef_license(license)}")
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Return the path to the lockfile corresponding to this policyfile.
|
|
90
|
+
#
|
|
91
|
+
# @return [String]
|
|
92
|
+
def lockfile
|
|
93
|
+
policyfile.gsub(/\.rb\Z/, ".lock.json")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
# @return [String] path to a Policyfile
|
|
99
|
+
# @api private
|
|
100
|
+
attr_reader :policyfile
|
|
101
|
+
|
|
102
|
+
# @return [String] path in which to vendor the resulting cookbooks
|
|
103
|
+
# @api private
|
|
104
|
+
attr_reader :path
|
|
105
|
+
|
|
106
|
+
# @return [Kitchen::Logger] a logger to use for output
|
|
107
|
+
# @api private
|
|
108
|
+
attr_reader :logger
|
|
109
|
+
|
|
110
|
+
# @return [Boolean] If true, always update cookbooks in the policy.
|
|
111
|
+
# @api private
|
|
112
|
+
attr_reader :always_update
|
|
113
|
+
|
|
114
|
+
# @return [String] name of the policy_group, nil results in "local"
|
|
115
|
+
# @api private
|
|
116
|
+
attr_reader :policy_group
|
|
117
|
+
|
|
118
|
+
# @return [String] name of the chef_license
|
|
119
|
+
# @api private
|
|
120
|
+
attr_reader :license
|
|
121
|
+
|
|
122
|
+
# Escape spaces in a path in way that works with both Sh (Unix) and
|
|
123
|
+
# Windows.
|
|
124
|
+
#
|
|
125
|
+
# @param path [String] Path to escape
|
|
126
|
+
# @return [String]
|
|
127
|
+
# @api private
|
|
128
|
+
def escape_path(path)
|
|
129
|
+
if /mswin|mingw/.match?(RbConfig::CONFIG["host_os"])
|
|
130
|
+
# I know what you're thinking: "just use Shellwords.escape". That
|
|
131
|
+
# method produces incorrect results on Windows with certain input
|
|
132
|
+
# which would be a metacharacter in Sh but is not for one or more of
|
|
133
|
+
# Windows command line parsing libraries. This covers the 99% case of
|
|
134
|
+
# spaces in the path without breaking other stuff.
|
|
135
|
+
if /[ \t\n\v"]/.match?(path)
|
|
136
|
+
"\"#{path.gsub(/[ \t\n\v\"\\]/) { |m| "\\" + m[0] }}\""
|
|
137
|
+
else
|
|
138
|
+
path
|
|
139
|
+
end
|
|
140
|
+
else
|
|
141
|
+
Shellwords.escape(path)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Find the `chef` or `chef-cli` commands in the path or raise `chef` is present in
|
|
146
|
+
# ChefDK / Workstation releases, but is no longer shipped in any gems now that we
|
|
147
|
+
# use a Go based wrapper for the `chef` command in Workstation. The Ruby CLI has been
|
|
148
|
+
# renamed `chef-cli` under the hood and is shipped in the `chef-cli` gem.
|
|
149
|
+
#
|
|
150
|
+
# @api private
|
|
151
|
+
# @returns [String]
|
|
152
|
+
def cli_path
|
|
153
|
+
@cli_path ||= which("chef-cli") || which("chef") || no_cli_found_error
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# @api private
|
|
157
|
+
def no_cli_found_error
|
|
158
|
+
@logger.fatal("The `chef` or `chef-cli` executables cannot be found in your " \
|
|
159
|
+
"PATH. Ensure you have installed Chef Workstation " \
|
|
160
|
+
"from https://www.chef.io/downloads/ and that your PATH " \
|
|
161
|
+
"setting includes the path to the `chef` or `chef-cli` commands.")
|
|
162
|
+
raise UserError, "Could not find the chef or chef-cli executables in your PATH."
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Return `"--chef-license #{license}"` when `license` is not nil or empty and the empty string otherwise.
|
|
166
|
+
def chef_license(license)
|
|
167
|
+
(license.nil? || license.empty?) ? "" : "--chef-license #{license}"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Author:: SAWANOBORI Yukihiko <sawanoboriyu@higanworks.com>)
|
|
3
|
+
#
|
|
4
|
+
# Copyright (C) 2015, HiganWorks LLC
|
|
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
|
+
# Usage:
|
|
19
|
+
#
|
|
20
|
+
# puts your recipes to` apply/` directory.
|
|
21
|
+
#
|
|
22
|
+
# An example of .kitchen.yml.
|
|
23
|
+
#
|
|
24
|
+
# ---
|
|
25
|
+
# driver:
|
|
26
|
+
# name: vagrant
|
|
27
|
+
#
|
|
28
|
+
# provisioner:
|
|
29
|
+
# name: chef_apply
|
|
30
|
+
#
|
|
31
|
+
# platforms:
|
|
32
|
+
# - name: ubuntu-24.04
|
|
33
|
+
# - name: almalinux-10
|
|
34
|
+
#
|
|
35
|
+
# suites:
|
|
36
|
+
# - name: default
|
|
37
|
+
# run_list:
|
|
38
|
+
# - recipe1
|
|
39
|
+
# - recipe2
|
|
40
|
+
#
|
|
41
|
+
#
|
|
42
|
+
# The chef-apply runs twice below.
|
|
43
|
+
#
|
|
44
|
+
# chef-apply apply/recipe1.rb
|
|
45
|
+
# chef-apply apply/recipe2.rb
|
|
46
|
+
|
|
47
|
+
require_relative "chef_base"
|
|
48
|
+
|
|
49
|
+
module Kitchen
|
|
50
|
+
module Provisioner
|
|
51
|
+
# Chef Apply provisioner.
|
|
52
|
+
#
|
|
53
|
+
# @author SAWANOBORI Yukihiko <sawanoboriyu@higanworks.com>)
|
|
54
|
+
class ChefApply < ChefBase
|
|
55
|
+
kitchen_provisioner_api_version 2
|
|
56
|
+
|
|
57
|
+
plugin_version Kitchen::VERSION
|
|
58
|
+
|
|
59
|
+
default_config :chef_apply_path do |provisioner|
|
|
60
|
+
provisioner
|
|
61
|
+
.remote_path_join(%W{#{provisioner[:chef_omnibus_root]} bin chef-apply})
|
|
62
|
+
.tap { |path| path.concat(".bat") if provisioner.windows_os? }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
default_config :apply_path do |provisioner|
|
|
66
|
+
provisioner.calculate_path("apply")
|
|
67
|
+
end
|
|
68
|
+
expand_path_for :apply_path
|
|
69
|
+
|
|
70
|
+
# (see ChefBase#create_sandbox)
|
|
71
|
+
def create_sandbox
|
|
72
|
+
@sandbox_path = Dir.mktmpdir("#{instance.name}-sandbox-")
|
|
73
|
+
File.chmod(0755, sandbox_path)
|
|
74
|
+
info("Preparing files for transfer")
|
|
75
|
+
debug("Creating local sandbox in #{sandbox_path}")
|
|
76
|
+
|
|
77
|
+
prepare_json
|
|
78
|
+
prepare(:apply)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# (see ChefBase#init_command)
|
|
82
|
+
def init_command
|
|
83
|
+
dirs = %w{
|
|
84
|
+
apply
|
|
85
|
+
}.sort.map { |dir| remote_path_join(config[:root_path], dir) }
|
|
86
|
+
|
|
87
|
+
vars = if powershell_shell?
|
|
88
|
+
init_command_vars_for_powershell(dirs)
|
|
89
|
+
else
|
|
90
|
+
init_command_vars_for_bourne(dirs)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
prefix_command(shell_code_from_file(vars, "chef_base_init_command"))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# (see ChefSolo#run_command)
|
|
97
|
+
def run_command
|
|
98
|
+
level = config[:log_level]
|
|
99
|
+
lines = []
|
|
100
|
+
config[:run_list].map do |recipe|
|
|
101
|
+
cmd = sudo(config[:chef_apply_path]).dup
|
|
102
|
+
.tap { |str| str.insert(0, "& ") if powershell_shell? }
|
|
103
|
+
args = [
|
|
104
|
+
"apply/#{recipe}.rb",
|
|
105
|
+
"--log_level #{level}",
|
|
106
|
+
"--no-color",
|
|
107
|
+
]
|
|
108
|
+
args << "--logfile #{config[:log_file]}" if config[:log_file]
|
|
109
|
+
args << "--chef-license #{config[:chef_license]}" if config[:chef_license]
|
|
110
|
+
|
|
111
|
+
lines << wrap_shell_code(
|
|
112
|
+
[cmd, *args].join(" ")
|
|
113
|
+
.tap { |str| str.insert(0, reload_ps1_path) if windows_os? }
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
prefix_command(lines.join("\n"))
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|