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