class Kitchen::Provisioner::ChefBase
@author Fletcher Nichol <fnichol@nichol.ca>
Common implementation details for Chef-related provisioners.
def self.enterprise_gem_available?
- Api: - private
Returns:
-
(String, nil)- the name of the enterprise gem if available
def self.enterprise_gem_available? @enterprise_gem_checked ||= false return @enterprise_gem if @enterprise_gem_checked @enterprise_gem_checked = true enterprise_gem = begin # Try kitchen-chef-enterprise first (Progress Chef Enterprise) if Gem::Specification.find_by_name("kitchen-chef-enterprise") "kitchen-chef-enterprise" end rescue Gem::LoadError nil end cinc_gem = nil if enterprise_gem.nil? cinc_gem = begin # Fall back to kitchen-cinc (Cinc Project) if Gem::Specification.find_by_name("kitchen-cinc") "kitchen-cinc" end rescue Gem::LoadError nil end end @enterprise_gem = enterprise_gem || cinc_gem end
def add_omnibus_directory_option
- Api: - private
def add_omnibus_directory_option cache_dir_option = "#{omnibus_dir_option} #{instance.driver.cache_directory}" if config[:chef_omnibus_install_options].nil? config[:chef_omnibus_install_options] = cache_dir_option elsif config[:chef_omnibus_install_options].match(/\s*#{omnibus_dir_option}\s*/).nil? config[:chef_omnibus_install_options] << " " << cache_dir_option end end
def berksfile
- Api: - private
Returns:
-
(String)- an absolute path to a Berksfile, relative to the
def berksfile berksfile_basename = config[:berksfile_path] || config[:berksfile] || "Berksfile" File.expand_path(berksfile_basename, config[:kitchen_root]) end
def check_license
def check_license unless config[:download_url] warn( <<~WARNING ============================================================================================================== \e[1m\e[93m!!!WARNING!!! kitchen-omnibus-chef is deprecated\e[0m Omnitruck downloads are being shutdown for specific Chef Infra Client versions and will stop working entirely in the future. This kitchen-omnibus-chef gem is also not compatible with infra-client 19+ new habitat based installation method. For Chef customers it is recommended to switch to using the new kitchen-chef-enterprise plugin found with chef-test-kitchen-enterprise and bundled in chef-workstation 26.x+. Please refer to this blog for schedule of which chef-client versions and when they will be affected: https://www.chef.io/blog/decoding-the-change-progress-chef-is-moving-to-licensed-downloads For non Chef customers or community users it is recommended to switch to the new kitchen-cinc plugin and cinc provisioners like cinc_infra. ============================================================================================================== WARNING ) end name = license_acceptance_id version = product_version debug("Checking if we need to prompt for license acceptance on product: #{name} version: #{version}.") acceptor = LicenseAcceptance::Acceptor.new(logger: Kitchen.logger, provided: config[:chef_license]) if acceptor.license_required?(name, version) debug("License acceptance required for #{name} version: #{version}. Prompting") license_id = acceptor.id_from_mixlib(name) begin acceptor.check_and_persist(license_id, version.to_s) rescue LicenseAcceptance::LicenseNotAcceptedError => e error("Cannot converge without accepting the #{e.product.pretty_name} License. Set it in your kitchen.yml or using the CHEF_LICENSE environment variable") raise end config[:chef_license] ||= acceptor.acceptance_value end end
def chef_args(_config_filename)
- Api: - private
Returns:
-
(Array- an array of command line arguments)
def chef_args(_config_filename) raise "You must override in sub classes!" end
def chef_cmd(base_cmd)
- Api: - private
def chef_cmd(base_cmd) if windows_os? separator = [ "; if ($LastExitCode -ne 0) { ", "throw \"Command failed with exit code $LastExitCode.\" } ;", ].join else separator = " && " end chef_cmds(base_cmd).join(separator) end
def chef_cmds(base_cmd)
- Api: - private
def chef_cmds(base_cmd) cmds = [] num_converges = config[:multiple_converge].to_i idempotency = config[:enforce_idempotency] # Execute Chef Client n-1 times, without exiting (num_converges - 1).times do cmds << wrapped_chef_cmd(base_cmd, config_filename) end # Append another execution with Windows specific Exit code helper or (for # idempotency check) a specific config file which assures no changed resources. cmds << unless idempotency wrapped_chef_cmd(base_cmd, config_filename, append: last_exit_code) else wrapped_chef_cmd(base_cmd, "client_no_updated_resources.rb", append: last_exit_code) end cmds end
def config_filename
- Api: - private
Returns:
-
(String)- a filename
def config_filename "client.rb" end
def create_sandbox
def create_sandbox super sanity_check_sandbox_options! Chef::CommonSandbox.new(config, sandbox_path, instance).populate end
def default_config_rb # rubocop:disable Metrics/MethodLength
- Api: - private
Returns:
-
(Hash)- a configuration hash
def default_config_rb # rubocop:disable Metrics/MethodLength root = config[:root_path].gsub("$env:TEMP", "\#{ENV['TEMP']}") config_rb = { node_name: instance.name, checksum_path: remote_path_join(root, "checksums"), file_cache_path: remote_path_join(root, "cache"), file_backup_path: remote_path_join(root, "backup"), cookbook_path: [ remote_path_join(root, "cookbooks"), remote_path_join(root, "site-cookbooks"), ], data_bag_path: remote_path_join(root, "data_bags"), environment_path: remote_path_join(root, "environments"), node_path: remote_path_join(root, "nodes"), role_path: remote_path_join(root, "roles"), client_path: remote_path_join(root, "clients"), user_path: remote_path_join(root, "users"), validation_key: remote_path_join(root, "validation.pem"), client_key: remote_path_join(root, "client.pem"), chef_server_url: "http://127.0.0.1:8889", encrypted_data_bag_secret: remote_path_join( root, "encrypted_data_bag_secret" ), treat_deprecation_warnings_as_errors: config[:deprecations_as_errors], } config_rb[:chef_license] = config[:chef_license] unless config[:chef_license].nil? config_rb end
def doctor(state)
def doctor(state) deprecated_config = instance.driver.instance_variable_get(:@deprecated_config) deprecated_config.each do |attr, msg| info("**** #{attr} deprecated\n#{msg}") end end
def format_config_file(data)
- Api: - private
Returns:
-
(String)- a rendered Chef config file as a String
Parameters:
-
data(Hash) -- a key/value pair hash of configuration
def format_config_file(data) data.each.map do |attr, value| [attr, format_value(value)].join(" ") end.join("\n") end
def format_value(obj)
- Api: - private
Returns:
-
(String)- a string representation
Parameters:
-
obj(Object) -- an object
def format_value(obj) if obj.is_a?(String) && obj =~ /^:/ obj elsif obj.is_a?(String) %{"#{obj.gsub("\\", "\\\\\\\\")}"} elsif obj.is_a?(Array) %{[#{obj.map { |i| format_value(i) }.join(", ")}]} else obj.inspect end end
def init_command
def init_command dirs = %w{ cookbooks data data_bags environments roles clients encrypted_data_bag_secret }.sort.map { |dir| remote_path_join(config[:root_path], dir) } vars = if powershell_shell? init_command_vars_for_powershell(dirs) else init_command_vars_for_bourne(dirs) end prefix_command(shell_code_from_file(vars, "chef_base_init_command")) end
def init_command_vars_for_bourne(dirs)
- Api: - private
Returns:
-
(String)- shell variable lines
Parameters:
-
dirs(Array) -- directories
def init_command_vars_for_bourne(dirs) [ shell_var("sudo_rm", sudo("rm")), shell_var("dirs", dirs.join(" ")), shell_var("root_path", config[:root_path]), ].join("\n") end
def init_command_vars_for_powershell(dirs)
- Api: - private
Returns:
-
(String)- shell variable lines
Parameters:
-
dirs(Array) -- directories
def init_command_vars_for_powershell(dirs) [ %{$dirs = @(#{dirs.map { |d| %{"#{d}"} }.join(", ")})}, shell_var("root_path", config[:root_path]), ].join("\n") end
def initialize(config = {})
-
config(Hash) -- initial provided configuration
def initialize(config = {}) super(config) if defined?(ChefConfig::WorkstationConfigLoader) ChefConfig::WorkstationConfigLoader.new(config[:config_path]).load end # This exports any proxy config present in the Chef config to # appropriate environment variables, which Test Kitchen respects ChefConfig::Config.export_proxies if defined?(ChefConfig::Config.export_proxies) end
def install_command
def install_command return unless config[:require_chef_omnibus] || config[:product_name] return if config[:product_name] && config[:install_strategy] == "skip" prefix_command(install_script_contents) end
def install_from_file(command)
def install_from_file(command) install_file = "#{config[:root_path]}/chef-installer.sh" script = [] script << "mkdir -p #{config[:root_path]}" script << "if [ $? -ne 0 ]; then" script << " echo Kitchen config setting root_path: '#{config[:root_path]}' not creatable by regular user " script << " exit 1" script << "fi" script << "cat > #{install_file} <<\"EOL\"" script << command script << "EOL" script << "chmod +x #{install_file}" script << sudo(install_file) script.join("\n") end
def install_options
- Api: - private
Returns:
-
(Hash)- an option hash for the install commands
def install_options add_omnibus_directory_option if instance.driver.cache_directory project = /\s*-P (\w+)\s*/.match(config[:chef_omnibus_install_options]) { omnibus_url: config[:chef_omnibus_url], project: project.nil? ? nil : project[1], install_flags: config[:chef_omnibus_install_options], sudo_command:, }.tap do |opts| opts[:root] = config[:chef_omnibus_root] if config.key? :chef_omnibus_root %i{install_msi_url http_proxy https_proxy}.each do |key| opts[key] = config[key] if config.key? key end end end
def install_script_contents
- Api: - private
Returns:
-
(String)- contents of the install script
def install_script_contents # by default require_chef_omnibus is set to true. Check config[:product_name] first # so that we can use it if configured. if config[:product_name] script_for_product elsif config[:require_chef_omnibus] script_for_omnibus_version end end
def last_exit_code
def last_exit_code "; exit $LastExitCode" if powershell_shell? end
def license_acceptance_id
-
(String)- license id to prompt for acceptance
def license_acceptance_id case when File.exist?(policyfile) && (config[:product_name].nil? || config[:product_name].start_with?("chef")) "chef-workstation" when config[:product_name] config[:product_name] else "chef" end end
def load_needed_dependencies!
Load cookbook dependency resolver code, if required.
def load_needed_dependencies! super if File.exist?(policyfile) debug("Policyfile found at #{policyfile}, using Policyfile to resolve cookbook dependencies") Chef::Policyfile.load!(logger:) elsif File.exist?(berksfile) debug("Berksfile found at #{berksfile}, using Berkshelf to resolve cookbook dependencies") Chef::Berkshelf.load!(logger:) end end
def omnibus_dir_option
- Api: - private
Returns:
-
(String)- Correct option per platform to specify the the
def omnibus_dir_option windows_os? ? "-download_directory" : "-d" end
def policyfile
- Api: - private
Returns:
-
(String)- an absolute path to a Policyfile, relative to the
def policyfile policyfile_basename = config[:policyfile_path] || config[:policyfile] || "Policyfile.rb" File.expand_path(policyfile_basename, config[:kitchen_root]) end
def prepare_config_idempotency_check(data)
- Api: - private
def prepare_config_idempotency_check(data) handler_filename = "chef-client-fail-if-update-handler.rb" source = File.join( File.dirname(__FILE__), %w{.. .. .. support }, handler_filename ) FileUtils.cp(source, File.join(sandbox_path, handler_filename)) File.open(File.join(sandbox_path, "client_no_updated_resources.rb"), "wb") do |file| file.write(format_config_file(data)) file.write("\n\n") file.write("handler_file = File.join(File.dirname(__FILE__), '#{handler_filename}')\n") file.write "Chef::Config.from_file(handler_file)\n" end end
def prepare_config_rb
- Api: - private
def prepare_config_rb data = default_config_rb.merge(config[config_filename.tr(".", "_").to_sym]) data = data.merge(named_run_list: config[:named_run_list]) if config[:named_run_list] info("Preparing #{config_filename}") debug("Creating #{config_filename} from #{data.inspect}") File.open(File.join(sandbox_path, config_filename), "wb") do |file| file.write(format_config_file(data)) end prepare_config_idempotency_check(data) if config[:enforce_idempotency] end
def product_version
-
(String, Symbol, NilClass)- version or nil if not applicable
def product_version case config[:require_chef_omnibus] when FalseClass nil when TrueClass config[:product_version] else config[:require_chef_omnibus] end end
def sanity_check_sandbox_options!
- Api: - private
Raises:
-
(UserError)-
Returns:
-
(void)-
def sanity_check_sandbox_options! if (config[:policyfile_path] || config[:policyfile]) && !File.exist?(policyfile) raise UserError, "policyfile_path set in config " \ "(#{config[:policyfile_path]} could not be found. " \ "Expected to find it at full path #{policyfile}." end if config[:berksfile_path] && !File.exist?(berksfile) raise UserError, "berksfile_path set in config " \ "(#{config[:berksfile_path]} could not be found. " \ "Expected to find it at full path #{berksfile}." end if File.exist?(policyfile) && !supports_policyfile? raise UserError, "policyfile detected, but provisioner " \ "#{self.class.name} doesn't support Policyfiles. " \ "Either use a different provisioner, or delete/rename " \ "#{policyfile}." end end
def script_for_omnibus_version
- Api: - private
Returns:
-
(String)- contents of version based install script
def script_for_omnibus_version require "mixlib/install/script_generator" installer = Mixlib::Install::ScriptGenerator.new( config[:require_chef_omnibus], powershell_shell?, install_options ) config[:chef_omnibus_root] = installer.root sudo(installer.install_command) end
def script_for_product
- Api: - private
Returns:
-
(String)- contents of product based install script
def script_for_product require "mixlib/install" installer = Mixlib::Install.new({ product_name: config[:product_name], product_version: config[:product_version], channel: config[:channel].to_sym, install_command_options: { install_strategy: config[:install_strategy], }, }.tap do |opts| opts[:shell_type] = :ps1 if powershell_shell? %i{platform platform_version architecture}.each do |key| opts[key] = config[key] if config[key] end unless windows_os? # omnitruck installer does not currently support a tmp dir option on windows opts[:install_command_options][:tmp_dir] = config[:root_path] opts[:install_command_options]["TMPDIR"] = config[:root_path] end if config[:download_url] opts[:install_command_options][:download_url_override] = config[:download_url] opts[:install_command_options][:checksum] = config[:checksum] if config[:checksum] end if config[:chef_license_key] opts[:install_command_options][:license_id] = config[:chef_license_key] end if instance.driver.cache_directory download_dir_option = windows_os? ? :download_directory : :cmdline_dl_dir opts[:install_command_options][download_dir_option] = instance.driver.cache_directory end proxies = {}.tap do |prox| %i{http_proxy https_proxy ftp_proxy no_proxy}.each do |key| prox[key] = config[key] if config[key] end # install.ps1 only supports http_proxy prox.delete_if { |p| %i{https_proxy ftp_proxy no_proxy}.include?(p) } if powershell_shell? end opts[:install_command_options].merge!(proxies) end) config[:chef_omnibus_root] = installer.root if powershell_shell? installer.install_command else install_from_file(installer.install_command) end end
def shell_code_from_file(vars, file)
- Api: - private
Returns:
-
(String)- command
Parameters:
-
file(String) -- file basename (without extension) containing -
vars(String) -- shell variables, as a String
def shell_code_from_file(vars, file) src_file = File.join( File.dirname(__FILE__), %w{.. .. .. support}, file + (powershell_shell? ? ".ps1" : ".sh") ) wrap_shell_code([vars, "", File.read(src_file)].join("\n")) end
def supports_policyfile?
- Api: - private
Returns:
-
(Boolean)-
Other tags:
- Abstract: -
def supports_policyfile? false end
def wrapped_chef_cmd(base_cmd, configfile, append: "")
- Api: - private
def wrapped_chef_cmd(base_cmd, configfile, append: "") args = [] args << base_cmd args << chef_args(configfile) args << append shell_cmd = args.flatten.join(" ") shell_cmd = shell_cmd.prepend(reload_ps1_path) if windows_os? prefix_command(wrap_shell_code(shell_cmd)) end