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,525 @@
|
|
|
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 "fileutils" unless defined?(FileUtils)
|
|
18
|
+
require "pathname" unless defined?(Pathname)
|
|
19
|
+
require "json" unless defined?(JSON)
|
|
20
|
+
require "cgi" unless defined?(CGI)
|
|
21
|
+
require "kitchen/util"
|
|
22
|
+
|
|
23
|
+
require_relative "cinc/policyfile"
|
|
24
|
+
require_relative "cinc/berkshelf"
|
|
25
|
+
require_relative "cinc/common_sandbox"
|
|
26
|
+
|
|
27
|
+
begin
|
|
28
|
+
require "chef-config/config"
|
|
29
|
+
require "chef-config/workstation_config_loader"
|
|
30
|
+
rescue LoadError # rubocop:disable Lint/HandleExceptions
|
|
31
|
+
# This space left intentionally blank.
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
module Kitchen
|
|
35
|
+
module Provisioner
|
|
36
|
+
# Common implementation details for Cinc-related provisioners.
|
|
37
|
+
#
|
|
38
|
+
# @author Cinc Project
|
|
39
|
+
class CincBase < Base
|
|
40
|
+
default_config :run_list, []
|
|
41
|
+
default_config :policy_group, nil
|
|
42
|
+
default_config :attributes, {}
|
|
43
|
+
default_config :config_path, nil
|
|
44
|
+
default_config :log_file, nil
|
|
45
|
+
default_config :log_level do |provisioner|
|
|
46
|
+
provisioner[:debug] ? "debug" : "auto"
|
|
47
|
+
end
|
|
48
|
+
default_config :profile_ruby, false
|
|
49
|
+
# The older policyfile_zero used `policyfile` so support it for compat.
|
|
50
|
+
default_config :policyfile, nil
|
|
51
|
+
# Will try to autodetect by searching for `Policyfile.rb` if not set.
|
|
52
|
+
# If set, will error if the file doesn't exist.
|
|
53
|
+
default_config :policyfile_path, nil
|
|
54
|
+
# Will try to autodetect by searching for `Berksfile` if not set.
|
|
55
|
+
# If set, will error if the file doesn't exist.
|
|
56
|
+
default_config :berksfile_path, nil
|
|
57
|
+
# If set to true (which is the default from `chef generate`), try to update
|
|
58
|
+
# backend cookbook downloader on every kitchen run.
|
|
59
|
+
default_config :always_update_cookbooks, true
|
|
60
|
+
default_config :cookbook_files_glob, %w(
|
|
61
|
+
README.* VERSION metadata.{json,rb} attributes.rb recipe.rb
|
|
62
|
+
attributes/**/* definitions/**/* files/**/* libraries/**/*
|
|
63
|
+
providers/**/* recipes/**/* resources/**/* templates/**/*
|
|
64
|
+
ohai/**/* compliance/**/*
|
|
65
|
+
).join(",")
|
|
66
|
+
# to ease upgrades, allow the user to turn deprecation warnings into errors
|
|
67
|
+
default_config :deprecations_as_errors, false
|
|
68
|
+
|
|
69
|
+
# Override the default from Base so reboot handling works by default for Cinc.
|
|
70
|
+
default_config :retry_on_exit_code, [35, 213]
|
|
71
|
+
|
|
72
|
+
default_config :multiple_converge, 1
|
|
73
|
+
|
|
74
|
+
default_config :enforce_idempotency, false
|
|
75
|
+
|
|
76
|
+
default_config :data_path do |provisioner|
|
|
77
|
+
provisioner.calculate_path("data")
|
|
78
|
+
end
|
|
79
|
+
expand_path_for :data_path
|
|
80
|
+
|
|
81
|
+
default_config :data_bags_path do |provisioner|
|
|
82
|
+
provisioner.calculate_path("data_bags")
|
|
83
|
+
end
|
|
84
|
+
expand_path_for :data_bags_path
|
|
85
|
+
|
|
86
|
+
default_config :environments_path do |provisioner|
|
|
87
|
+
provisioner.calculate_path("environments")
|
|
88
|
+
end
|
|
89
|
+
expand_path_for :environments_path
|
|
90
|
+
|
|
91
|
+
default_config :nodes_path do |provisioner|
|
|
92
|
+
provisioner.calculate_path("nodes")
|
|
93
|
+
end
|
|
94
|
+
expand_path_for :nodes_path
|
|
95
|
+
|
|
96
|
+
default_config :roles_path do |provisioner|
|
|
97
|
+
provisioner.calculate_path("roles")
|
|
98
|
+
end
|
|
99
|
+
expand_path_for :roles_path
|
|
100
|
+
|
|
101
|
+
default_config :clients_path do |provisioner|
|
|
102
|
+
provisioner.calculate_path("clients")
|
|
103
|
+
end
|
|
104
|
+
expand_path_for :clients_path
|
|
105
|
+
|
|
106
|
+
default_config :encrypted_data_bag_secret_key_path do |provisioner|
|
|
107
|
+
provisioner.calculate_path("encrypted_data_bag_secret_key", type: :file)
|
|
108
|
+
end
|
|
109
|
+
expand_path_for :encrypted_data_bag_secret_key_path
|
|
110
|
+
|
|
111
|
+
#
|
|
112
|
+
# Modern configuration options (RFC 091 equivalent for Cinc)
|
|
113
|
+
#
|
|
114
|
+
|
|
115
|
+
# Default product_name to "cinc" so that the modern Mixlib::Install
|
|
116
|
+
# code path is used automatically.
|
|
117
|
+
default_config :product_name, "cinc"
|
|
118
|
+
|
|
119
|
+
default_config :product_version, :latest
|
|
120
|
+
|
|
121
|
+
default_config :channel, :stable
|
|
122
|
+
|
|
123
|
+
default_config :install_strategy, "once"
|
|
124
|
+
|
|
125
|
+
default_config :platform
|
|
126
|
+
|
|
127
|
+
default_config :platform_version
|
|
128
|
+
|
|
129
|
+
default_config :architecture
|
|
130
|
+
|
|
131
|
+
default_config :download_url
|
|
132
|
+
|
|
133
|
+
default_config :checksum
|
|
134
|
+
|
|
135
|
+
# Reads the local Chef::Config object (if present). We do this because
|
|
136
|
+
# we want to start bringing Cinc config and Cinc Workstation config closer
|
|
137
|
+
# together. For example, we want to configure proxy settings in 1
|
|
138
|
+
# location instead of 3 configuration files.
|
|
139
|
+
#
|
|
140
|
+
# @param config [Hash] initial provided configuration
|
|
141
|
+
def initialize(config = {})
|
|
142
|
+
super(config)
|
|
143
|
+
|
|
144
|
+
if defined?(ChefConfig::WorkstationConfigLoader)
|
|
145
|
+
ChefConfig::WorkstationConfigLoader.new(config[:config_path]).load
|
|
146
|
+
end
|
|
147
|
+
# This exports any proxy config present in the config to
|
|
148
|
+
# appropriate environment variables, which Test Kitchen respects
|
|
149
|
+
ChefConfig::Config.export_proxies if defined?(ChefConfig::Config.export_proxies)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# (see Base#create_sandbox)
|
|
153
|
+
def create_sandbox
|
|
154
|
+
super
|
|
155
|
+
sanity_check_sandbox_options!
|
|
156
|
+
Cinc::CommonSandbox.new(config, sandbox_path, instance).populate
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# (see Base#init_command)
|
|
160
|
+
def init_command
|
|
161
|
+
dirs = %w{
|
|
162
|
+
cookbooks data data_bags environments roles clients
|
|
163
|
+
encrypted_data_bag_secret
|
|
164
|
+
}.sort.map { |dir| remote_path_join(config[:root_path], dir) }
|
|
165
|
+
|
|
166
|
+
vars = if powershell_shell?
|
|
167
|
+
init_command_vars_for_powershell(dirs)
|
|
168
|
+
else
|
|
169
|
+
init_command_vars_for_bourne(dirs)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
prefix_command(shell_code_from_file(vars, "cinc_base_init_command"))
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# (see Base#install_command)
|
|
176
|
+
def install_command
|
|
177
|
+
return unless config[:product_name]
|
|
178
|
+
return if config[:install_strategy] == "skip"
|
|
179
|
+
|
|
180
|
+
prefix_command(install_script_contents)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
private
|
|
184
|
+
|
|
185
|
+
def last_exit_code
|
|
186
|
+
"; exit $LastExitCode" if powershell_shell?
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# @return [String] an absolute path to a Policyfile, relative to the
|
|
190
|
+
# kitchen root
|
|
191
|
+
# @api private
|
|
192
|
+
def policyfile
|
|
193
|
+
policyfile_basename = config[:policyfile_path] || config[:policyfile] || "Policyfile.rb"
|
|
194
|
+
File.expand_path(policyfile_basename, config[:kitchen_root])
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# @return [String] an absolute path to a Berksfile, relative to the
|
|
198
|
+
# kitchen root
|
|
199
|
+
# @api private
|
|
200
|
+
def berksfile
|
|
201
|
+
berksfile_basename = config[:berksfile_path] || config[:berksfile] || "Berksfile"
|
|
202
|
+
File.expand_path(berksfile_basename, config[:kitchen_root])
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Generates a Hash with default values for a solo.rb or client.rb
|
|
206
|
+
# configuration file.
|
|
207
|
+
#
|
|
208
|
+
# @return [Hash] a configuration hash
|
|
209
|
+
# @api private
|
|
210
|
+
def default_config_rb
|
|
211
|
+
root = config[:root_path].gsub("$env:TEMP", "\#{ENV['TEMP']}")
|
|
212
|
+
|
|
213
|
+
{
|
|
214
|
+
node_name: instance.name,
|
|
215
|
+
checksum_path: remote_path_join(root, "checksums"),
|
|
216
|
+
file_cache_path: remote_path_join(root, "cache"),
|
|
217
|
+
file_backup_path: remote_path_join(root, "backup"),
|
|
218
|
+
cookbook_path: [
|
|
219
|
+
remote_path_join(root, "cookbooks"),
|
|
220
|
+
remote_path_join(root, "site-cookbooks"),
|
|
221
|
+
],
|
|
222
|
+
data_bag_path: remote_path_join(root, "data_bags"),
|
|
223
|
+
environment_path: remote_path_join(root, "environments"),
|
|
224
|
+
node_path: remote_path_join(root, "nodes"),
|
|
225
|
+
role_path: remote_path_join(root, "roles"),
|
|
226
|
+
client_path: remote_path_join(root, "clients"),
|
|
227
|
+
user_path: remote_path_join(root, "users"),
|
|
228
|
+
validation_key: remote_path_join(root, "validation.pem"),
|
|
229
|
+
client_key: remote_path_join(root, "client.pem"),
|
|
230
|
+
chef_server_url: "http://127.0.0.1:8889",
|
|
231
|
+
encrypted_data_bag_secret: remote_path_join(
|
|
232
|
+
root, "encrypted_data_bag_secret"
|
|
233
|
+
),
|
|
234
|
+
treat_deprecation_warnings_as_errors: config[:deprecations_as_errors],
|
|
235
|
+
}
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Generates a rendered client.rb/solo.rb formatted file as a
|
|
239
|
+
# String.
|
|
240
|
+
#
|
|
241
|
+
# @param data [Hash] a key/value pair hash of configuration
|
|
242
|
+
# @return [String] a rendered config file as a String
|
|
243
|
+
# @api private
|
|
244
|
+
def format_config_file(data)
|
|
245
|
+
data.each.map do |attr, value|
|
|
246
|
+
[attr, format_value(value)].join(" ")
|
|
247
|
+
end.join("\n")
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Converts a Ruby object to a String interpretation suitable for writing
|
|
251
|
+
# out to a client.rb/solo.rb file.
|
|
252
|
+
#
|
|
253
|
+
# @param obj [Object] an object
|
|
254
|
+
# @return [String] a string representation
|
|
255
|
+
# @api private
|
|
256
|
+
def format_value(obj)
|
|
257
|
+
if obj.is_a?(String) && obj =~ /^:/
|
|
258
|
+
obj
|
|
259
|
+
elsif obj.is_a?(String)
|
|
260
|
+
%{"#{obj.gsub(/["\\]/) { |m| "\\" + m }}"}
|
|
261
|
+
elsif obj.is_a?(Array)
|
|
262
|
+
%{[#{obj.map { |i| format_value(i) }.join(", ")}]}
|
|
263
|
+
else
|
|
264
|
+
obj.inspect
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Generates the init command variables for Bourne shell-based platforms.
|
|
269
|
+
#
|
|
270
|
+
# @param dirs [Array<String>] directories
|
|
271
|
+
# @return [String] shell variable lines
|
|
272
|
+
# @api private
|
|
273
|
+
def init_command_vars_for_bourne(dirs)
|
|
274
|
+
[
|
|
275
|
+
shell_var("sudo_rm", sudo("rm")),
|
|
276
|
+
shell_var("dirs", dirs.join(" ")),
|
|
277
|
+
shell_var("root_path", config[:root_path]),
|
|
278
|
+
].join("\n")
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Generates the init command variables for PowerShell-based platforms.
|
|
282
|
+
#
|
|
283
|
+
# @param dirs [Array<String>] directories
|
|
284
|
+
# @return [String] shell variable lines
|
|
285
|
+
# @api private
|
|
286
|
+
def init_command_vars_for_powershell(dirs)
|
|
287
|
+
[
|
|
288
|
+
%{$dirs = @(#{dirs.map { |d| %{"#{d}"} }.join(", ")})},
|
|
289
|
+
shell_var("root_path", config[:root_path]),
|
|
290
|
+
].join("\n")
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Load cookbook dependency resolver code, if required.
|
|
294
|
+
#
|
|
295
|
+
# (see Base#load_needed_dependencies!)
|
|
296
|
+
def load_needed_dependencies!
|
|
297
|
+
super
|
|
298
|
+
if File.exist?(policyfile)
|
|
299
|
+
debug("Policyfile found at #{policyfile}, using Policyfile to resolve cookbook dependencies")
|
|
300
|
+
Cinc::Policyfile.load!(logger:)
|
|
301
|
+
elsif File.exist?(berksfile)
|
|
302
|
+
debug("Berksfile found at #{berksfile}, using Berkshelf to resolve cookbook dependencies")
|
|
303
|
+
Cinc::Berkshelf.load!(logger:)
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# @return [String] contents of the install script
|
|
308
|
+
# @api private
|
|
309
|
+
def install_script_contents
|
|
310
|
+
require "mixlib/install"
|
|
311
|
+
installer = Mixlib::Install.new({
|
|
312
|
+
product_name: config[:product_name],
|
|
313
|
+
product_version: config[:product_version],
|
|
314
|
+
channel: config[:channel].to_sym,
|
|
315
|
+
install_command_options: {
|
|
316
|
+
install_strategy: config[:install_strategy],
|
|
317
|
+
},
|
|
318
|
+
}.tap do |opts|
|
|
319
|
+
opts[:shell_type] = :ps1 if powershell_shell?
|
|
320
|
+
%i{platform platform_version architecture}.each do |key|
|
|
321
|
+
opts[key] = config[key] if config[key]
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
unless windows_os?
|
|
325
|
+
# omnitruck installer does not currently support a tmp dir option on windows
|
|
326
|
+
opts[:install_command_options][:tmp_dir] = config[:root_path]
|
|
327
|
+
opts[:install_command_options]["TMPDIR"] = config[:root_path]
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
if config[:download_url]
|
|
331
|
+
opts[:install_command_options][:download_url_override] = config[:download_url]
|
|
332
|
+
opts[:install_command_options][:checksum] = config[:checksum] if config[:checksum]
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
if instance.driver.cache_directory
|
|
336
|
+
download_dir_option = windows_os? ? :download_directory : :cmdline_dl_dir
|
|
337
|
+
opts[:install_command_options][download_dir_option] = instance.driver.cache_directory
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
proxies = {}.tap do |prox|
|
|
341
|
+
%i{http_proxy https_proxy ftp_proxy no_proxy}.each do |key|
|
|
342
|
+
prox[key] = config[key] if config[key]
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# install.ps1 only supports http_proxy
|
|
346
|
+
prox.delete_if { |p| %i{https_proxy ftp_proxy no_proxy}.include?(p) } if powershell_shell?
|
|
347
|
+
end
|
|
348
|
+
opts[:install_command_options].merge!(proxies)
|
|
349
|
+
end)
|
|
350
|
+
config[:cinc_omnibus_root] = installer.root
|
|
351
|
+
if powershell_shell?
|
|
352
|
+
installer.install_command
|
|
353
|
+
else
|
|
354
|
+
install_from_file(installer.install_command)
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def install_from_file(command)
|
|
359
|
+
install_file = "#{config[:root_path]}/cinc-installer.sh"
|
|
360
|
+
script = []
|
|
361
|
+
script << "mkdir -p #{config[:root_path]}"
|
|
362
|
+
script << "if [ $? -ne 0 ]; then"
|
|
363
|
+
script << " echo Kitchen config setting root_path: '#{config[:root_path]}' not creatable by regular user "
|
|
364
|
+
script << " exit 1"
|
|
365
|
+
script << "fi"
|
|
366
|
+
script << "cat > #{install_file} <<\"EOL\""
|
|
367
|
+
script << command
|
|
368
|
+
script << "EOL"
|
|
369
|
+
script << "chmod +x #{install_file}"
|
|
370
|
+
script << sudo(install_file)
|
|
371
|
+
script.join("\n")
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Hook used in subclasses to indicate support for policyfiles.
|
|
375
|
+
#
|
|
376
|
+
# @abstract
|
|
377
|
+
# @return [Boolean]
|
|
378
|
+
# @api private
|
|
379
|
+
def supports_policyfile?
|
|
380
|
+
false
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# @return [void]
|
|
384
|
+
# @raise [UserError]
|
|
385
|
+
# @api private
|
|
386
|
+
def sanity_check_sandbox_options!
|
|
387
|
+
if (config[:policyfile_path] || config[:policyfile]) && !File.exist?(policyfile)
|
|
388
|
+
raise UserError, "policyfile_path set in config " \
|
|
389
|
+
"(#{config[:policyfile_path]} could not be found. " \
|
|
390
|
+
"Expected to find it at full path #{policyfile}."
|
|
391
|
+
end
|
|
392
|
+
if config[:berksfile_path] && !File.exist?(berksfile)
|
|
393
|
+
raise UserError, "berksfile_path set in config " \
|
|
394
|
+
"(#{config[:berksfile_path]} could not be found. " \
|
|
395
|
+
"Expected to find it at full path #{berksfile}."
|
|
396
|
+
end
|
|
397
|
+
if File.exist?(policyfile) && !supports_policyfile?
|
|
398
|
+
raise UserError, "policyfile detected, but provisioner " \
|
|
399
|
+
"#{self.class.name} doesn't support Policyfiles. " \
|
|
400
|
+
"Either use a different provisioner, or delete/rename " \
|
|
401
|
+
"#{policyfile}."
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# Writes a configuration file to the sandbox directory.
|
|
406
|
+
# @api private
|
|
407
|
+
def prepare_config_rb
|
|
408
|
+
data = default_config_rb.merge(config[config_filename.tr(".", "_").to_sym])
|
|
409
|
+
data = data.merge(named_run_list: config[:named_run_list]) if config[:named_run_list]
|
|
410
|
+
|
|
411
|
+
info("Preparing #{config_filename}")
|
|
412
|
+
debug("Creating #{config_filename} from #{data.inspect}")
|
|
413
|
+
|
|
414
|
+
File.open(File.join(sandbox_path, config_filename), "wb") do |file|
|
|
415
|
+
file.write(format_config_file(data))
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
prepare_config_idempotency_check(data) if config[:enforce_idempotency]
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# Writes a configuration file to the sandbox directory
|
|
422
|
+
# to check for idempotency of the run.
|
|
423
|
+
# @api private
|
|
424
|
+
def prepare_config_idempotency_check(data)
|
|
425
|
+
handler_filename = "cinc-client-fail-if-update-handler.rb"
|
|
426
|
+
source = File.join(
|
|
427
|
+
File.dirname(__FILE__), %w{.. .. .. support }, handler_filename
|
|
428
|
+
)
|
|
429
|
+
FileUtils.cp(source, File.join(sandbox_path, handler_filename))
|
|
430
|
+
File.open(File.join(sandbox_path, "client_no_updated_resources.rb"), "wb") do |file|
|
|
431
|
+
file.write(format_config_file(data))
|
|
432
|
+
file.write("\n\n")
|
|
433
|
+
file.write("handler_file = File.join(File.dirname(__FILE__), '#{handler_filename}')\n")
|
|
434
|
+
file.write "Chef::Config.from_file(handler_file)\n"
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# Returns an Array of command line arguments for the cinc client.
|
|
439
|
+
#
|
|
440
|
+
# @return [Array<String>] an array of command line arguments
|
|
441
|
+
# @api private
|
|
442
|
+
def chef_args(_config_filename)
|
|
443
|
+
raise "You must override in sub classes!"
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# Returns a filename for the configuration file
|
|
447
|
+
# defaults to client.rb
|
|
448
|
+
#
|
|
449
|
+
# @return [String] a filename
|
|
450
|
+
# @api private
|
|
451
|
+
def config_filename
|
|
452
|
+
"client.rb"
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
# Gives the command used to run cinc
|
|
456
|
+
# @api private
|
|
457
|
+
def chef_cmd(base_cmd)
|
|
458
|
+
if windows_os?
|
|
459
|
+
separator = [
|
|
460
|
+
"; if ($LastExitCode -ne 0) { ",
|
|
461
|
+
"throw \"Command failed with exit code $LastExitCode.\" } ;",
|
|
462
|
+
].join
|
|
463
|
+
else
|
|
464
|
+
separator = " && "
|
|
465
|
+
end
|
|
466
|
+
chef_cmds(base_cmd).join(separator)
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
# Gives an array of commands
|
|
470
|
+
# @api private
|
|
471
|
+
def chef_cmds(base_cmd)
|
|
472
|
+
cmds = []
|
|
473
|
+
num_converges = config[:multiple_converge].to_i
|
|
474
|
+
idempotency = config[:enforce_idempotency]
|
|
475
|
+
|
|
476
|
+
# Execute Cinc Client n-1 times, without exiting
|
|
477
|
+
(num_converges - 1).times do
|
|
478
|
+
cmds << wrapped_chef_cmd(base_cmd, config_filename)
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
# Append another execution with Windows specific Exit code helper or (for
|
|
482
|
+
# idempotency check) a specific config file which assures no changed resources.
|
|
483
|
+
cmds << unless idempotency
|
|
484
|
+
wrapped_chef_cmd(base_cmd, config_filename, append: last_exit_code)
|
|
485
|
+
else
|
|
486
|
+
wrapped_chef_cmd(base_cmd, "client_no_updated_resources.rb", append: last_exit_code)
|
|
487
|
+
end
|
|
488
|
+
cmds
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# Concatenate all arguments and wrap it with shell-specifics
|
|
492
|
+
# @api private
|
|
493
|
+
def wrapped_chef_cmd(base_cmd, configfile, append: "")
|
|
494
|
+
args = []
|
|
495
|
+
|
|
496
|
+
args << base_cmd
|
|
497
|
+
args << chef_args(configfile)
|
|
498
|
+
args << append
|
|
499
|
+
|
|
500
|
+
shell_cmd = args.flatten.join(" ")
|
|
501
|
+
shell_cmd = shell_cmd.prepend(reload_ps1_path) if windows_os?
|
|
502
|
+
|
|
503
|
+
prefix_command(wrap_shell_code(shell_cmd))
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
# Builds a complete command given a variables String preamble and a file
|
|
507
|
+
# containing shell code.
|
|
508
|
+
#
|
|
509
|
+
# @param vars [String] shell variables, as a String
|
|
510
|
+
# @param file [String] file basename (without extension) containing
|
|
511
|
+
# shell code
|
|
512
|
+
# @return [String] command
|
|
513
|
+
# @api private
|
|
514
|
+
def shell_code_from_file(vars, file)
|
|
515
|
+
src_file = File.join(
|
|
516
|
+
File.dirname(__FILE__),
|
|
517
|
+
%w{.. .. .. support},
|
|
518
|
+
file + (powershell_shell? ? ".ps1" : ".sh")
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
wrap_shell_code([vars, "", File.read(src_file)].join("\n"))
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
end
|
|
@@ -0,0 +1,164 @@
|
|
|
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_relative "cinc_base"
|
|
18
|
+
|
|
19
|
+
module Kitchen
|
|
20
|
+
module Provisioner
|
|
21
|
+
# Cinc Infra provisioner using cinc-client in local mode.
|
|
22
|
+
#
|
|
23
|
+
# @author Cinc Project
|
|
24
|
+
class CincInfra < CincBase
|
|
25
|
+
kitchen_provisioner_api_version 2
|
|
26
|
+
|
|
27
|
+
plugin_version Kitchen::VERSION
|
|
28
|
+
|
|
29
|
+
default_config :client_rb, {}
|
|
30
|
+
default_config :named_run_list, {}
|
|
31
|
+
default_config :json_attributes, true
|
|
32
|
+
default_config :cinc_zero_host, nil
|
|
33
|
+
default_config :cinc_zero_port, 8889
|
|
34
|
+
|
|
35
|
+
default_config :cinc_client_path do |provisioner|
|
|
36
|
+
provisioner
|
|
37
|
+
.remote_path_join(%W{#{provisioner[:cinc_omnibus_root]} bin cinc-client})
|
|
38
|
+
.tap { |path| path.concat(".bat") if provisioner.windows_os? }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
default_config :ruby_bindir do |provisioner|
|
|
42
|
+
provisioner
|
|
43
|
+
.remote_path_join(%W{#{provisioner[:cinc_omnibus_root]} embedded bin})
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# (see Base#create_sandbox)
|
|
47
|
+
def create_sandbox
|
|
48
|
+
super
|
|
49
|
+
prepare_validation_pem
|
|
50
|
+
prepare_config_rb
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def run_command
|
|
54
|
+
cmd = "#{sudo(config[:cinc_client_path])} --local-mode".tap { |str| str.insert(0, "& ") if powershell_shell? }
|
|
55
|
+
|
|
56
|
+
chef_cmd(cmd)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
# Adds optional flags to a cinc-client command, depending on
|
|
62
|
+
# configuration data. Note that this method mutates the incoming Array.
|
|
63
|
+
#
|
|
64
|
+
# @param args [Array<String>] array of flags
|
|
65
|
+
# @api private
|
|
66
|
+
def add_optional_chef_client_args!(args)
|
|
67
|
+
if config[:json_attributes]
|
|
68
|
+
json = remote_path_join(config[:root_path], "dna.json")
|
|
69
|
+
args << "--json-attributes #{json}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
args << "--logfile #{config[:log_file]}" if config[:log_file]
|
|
73
|
+
|
|
74
|
+
# these flags are cinc-client local mode only and will not work
|
|
75
|
+
# on older versions of cinc-client
|
|
76
|
+
if config[:cinc_zero_host]
|
|
77
|
+
args << "--chef-zero-host #{config[:cinc_zero_host]}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if config[:cinc_zero_port]
|
|
81
|
+
args << "--chef-zero-port #{config[:cinc_zero_port]}"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
args << "--profile-ruby" if config[:profile_ruby]
|
|
85
|
+
|
|
86
|
+
if config[:slow_resource_report]
|
|
87
|
+
if config[:slow_resource_report].is_a?(Integer)
|
|
88
|
+
args << "--slow-report #{config[:slow_resource_report]}"
|
|
89
|
+
else
|
|
90
|
+
args << "--slow-report"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Returns an Array of command line arguments for the cinc client.
|
|
96
|
+
#
|
|
97
|
+
# @return [Array<String>] an array of command line arguments
|
|
98
|
+
# @api private
|
|
99
|
+
def chef_args(client_rb_filename)
|
|
100
|
+
level = config[:log_level]
|
|
101
|
+
args = [
|
|
102
|
+
"--config #{remote_path_join(config[:root_path], client_rb_filename)}",
|
|
103
|
+
"--log_level #{level}",
|
|
104
|
+
"--force-formatter",
|
|
105
|
+
"--no-color",
|
|
106
|
+
]
|
|
107
|
+
add_optional_chef_client_args!(args)
|
|
108
|
+
|
|
109
|
+
args
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Generates a string of shell environment variables needed for the
|
|
113
|
+
# cinc-client-zero.rb shim script to properly function.
|
|
114
|
+
#
|
|
115
|
+
# @return [String] a shell script string
|
|
116
|
+
# @api private
|
|
117
|
+
def chef_client_zero_env
|
|
118
|
+
root = config[:root_path]
|
|
119
|
+
gem_home = gem_path = remote_path_join(root, "chef-client-zero-gems")
|
|
120
|
+
gem_cache = remote_path_join(gem_home, "cache")
|
|
121
|
+
|
|
122
|
+
[
|
|
123
|
+
shell_env_var("CHEF_REPO_PATH", root),
|
|
124
|
+
shell_env_var("GEM_HOME", gem_home),
|
|
125
|
+
shell_env_var("GEM_PATH", gem_path),
|
|
126
|
+
shell_env_var("GEM_CACHE", gem_cache),
|
|
127
|
+
].join("\n").concat("\n")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Writes a fake (but valid) validation.pem into the sandbox directory.
|
|
131
|
+
#
|
|
132
|
+
# @api private
|
|
133
|
+
def prepare_validation_pem
|
|
134
|
+
info("Preparing validation.pem")
|
|
135
|
+
debug("Using a dummy validation.pem")
|
|
136
|
+
|
|
137
|
+
source = File.join(File.dirname(__FILE__),
|
|
138
|
+
%w{.. .. .. support dummy-validation.pem})
|
|
139
|
+
FileUtils.cp(source, File.join(sandbox_path, "validation.pem"))
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Returns the command that will run a backwards compatible shim script
|
|
143
|
+
# that approximates local mode in a modern cinc-client run.
|
|
144
|
+
#
|
|
145
|
+
# @return [String] the command string
|
|
146
|
+
# @api private
|
|
147
|
+
def shim_command
|
|
148
|
+
ruby = remote_path_join(config[:ruby_bindir], "ruby")
|
|
149
|
+
.tap { |path| path.concat(".exe") if windows_os? }
|
|
150
|
+
shim = remote_path_join(config[:root_path], "chef-client-zero.rb")
|
|
151
|
+
|
|
152
|
+
"#{chef_client_zero_env}\n#{sudo(ruby)} #{shim}"
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# This provisioner supports policyfiles, so override the default (which
|
|
156
|
+
# is false)
|
|
157
|
+
# @return [true] always returns true
|
|
158
|
+
# @api private
|
|
159
|
+
def supports_policyfile?
|
|
160
|
+
true
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|