class Kitchen::Provisioner::Chef::CommonSandbox

Internal object to manage common sandbox preparation for Chef-related provisioners.

@author Fletcher Nichol <fnichol@nichol.ca> @api private

Attributes

config[R]

@return [Hash] configuration hash @api private

instance[R]

@return [Instance] an instance @api private

policy_group[R]

@return [String] name of the policy_group, nil results in “local” @api private

sandbox_path[R]

@return [String] path to local sandbox directory @api private

Public Class Methods

new(config, sandbox_path, instance) click to toggle source

Constructs a new object, taking config, a sandbox path, and an instance.

@param config [Hash] configuration hash @param sandbox_path [String] path to local sandbox directory @param instance [Instance] an instance

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 37
def initialize(config, sandbox_path, instance)
  @config = config
  @sandbox_path = sandbox_path
  @instance = instance
end

Public Instance Methods

populate() click to toggle source

Populate the sandbox.

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 44
def populate
  prepare_json
  prepare_cache
  prepare_cookbooks
  prepare(:data)
  prepare(:data_bags)
  prepare(:environments)
  prepare(:nodes)
  prepare(:roles)
  prepare(:clients)
  prepare(
    :secret,
    type: :file,
    dest_name: "encrypted_data_bag_secret",
    key_name: :encrypted_data_bag_secret_key_path
  )
end

Private Instance Methods

all_files_in_cookbooks() click to toggle source

Generates a list of all files in the cookbooks directory in the sandbox path.

@return [Array<String>] an array of absolute paths to files @api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 85
def all_files_in_cookbooks
  Util.list_directory(tmpbooks_dir, include_dot: true, recurse: true)
    .select { |fn| File.file?(fn) }
end
berksfile() click to toggle source

@return [String] an absolute path to a Berksfile, relative to the

kitchen root

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 101
def berksfile
  basename = config[:berksfile_path] || "Berksfile"
  File.expand_path(basename, config[:kitchen_root])
end
cookbooks_dir() click to toggle source

@return [String] an absolute path to a cookbooks/ directory, relative

to the kitchen root

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 109
def cookbooks_dir
  File.join(config[:kitchen_root], "cookbooks")
end
cp_cookbooks() click to toggle source

Copies a cookbooks/ directory into the sandbox path.

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 116
def cp_cookbooks
  info("Preparing cookbooks from project directory")
  debug("Using cookbooks from #{cookbooks_dir}")

  FileUtils.mkdir_p(tmpbooks_dir)
  FileUtils.cp_r(File.join(cookbooks_dir, "."), tmpbooks_dir)

  cp_site_cookbooks if File.directory?(site_cookbooks_dir)
  cp_this_cookbook if File.exist?(metadata_rb)
end
cp_site_cookbooks() click to toggle source

Copies a site-cookbooks/ directory into the sandbox path.

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 130
def cp_site_cookbooks
  info("Preparing site-cookbooks from project directory")
  debug("Using cookbooks from #{site_cookbooks_dir}")

  FileUtils.mkdir_p(tmpsitebooks_dir)
  FileUtils.cp_r(File.join(site_cookbooks_dir, "."), tmpsitebooks_dir)
end
cp_this_cookbook() click to toggle source

Copies the current project, assumed to be a Chef cookbook into the sandbox path.

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 142
def cp_this_cookbook
  info("Preparing current project directory as a cookbook")
  debug("Using metadata.rb from #{metadata_rb}")

  cb_name = MetadataChopper.extract(metadata_rb).first || raise(UserError,
    "The metadata.rb does not define the 'name' key." \
      " Please add: `name '<cookbook_name>'` to metadata.rb and retry")

  cb_path = File.join(tmpbooks_dir, cb_name)

  glob = Util.list_directory(config[:kitchen_root])

  FileUtils.mkdir_p(cb_path)
  FileUtils.cp_r(glob, cb_path)
end
filter_only_cookbook_files() click to toggle source

Removes all non-cookbook files in the sandbox path.

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 161
def filter_only_cookbook_files
  info("Removing non-cookbook files before transfer")
  FileUtils.rm(all_files_in_cookbooks - only_cookbook_files)
  Util.list_directory(tmpbooks_dir, recurse: true)
    .reverse_each { |fn| FileUtils.rmdir(fn) if File.directory?(fn) && Dir.entries(fn).size == 2 }
end
logger() click to toggle source

@return [Logger] the instance's logger or Test Kitchen's common

logger otherwise

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 171
def logger
  instance ? instance.logger : Kitchen.logger
end
make_fake_cookbook() click to toggle source

Creates a minimal, no-op cookbook in the sandbox path.

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 178
def make_fake_cookbook
  info("Policyfile, Berksfile, cookbooks/, or metadata.rb not found " \
    "so Chef Infra Client will run, but do nothing. Is this intended?")
  name = File.basename(config[:kitchen_root])
  fake_cb = File.join(tmpbooks_dir, name)
  FileUtils.mkdir_p(fake_cb)
  File.open(File.join(fake_cb, "metadata.rb"), "wb") do |file|
    file.write(%{name "#{name}"\n})
  end
end
metadata_rb() click to toggle source

@return [String] an absolute path to a metadata.rb, relative to the

kitchen root

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 192
def metadata_rb
  File.join(config[:kitchen_root], "metadata.rb")
end
only_cookbook_files() click to toggle source

Generates a list of all typical cookbook files needed in a Chef run, located in the cookbooks directory in the sandbox path.

@return [Array<String>] an array of absolute paths to files @api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 201
def only_cookbook_files
  glob = File.join("*", "{#{config[:cookbook_files_glob]}}")
  Util.safe_glob(tmpbooks_dir, glob, File::FNM_DOTMATCH)
    .select { |fn| File.file?(fn) && ! %w{. ..}.include?(fn) }
end
policyfile() click to toggle source

@return [String] an absolute path to a Policyfile, relative to the

kitchen root

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 93
def policyfile
  basename = config[:policyfile_path] || config[:policyfile] || "Policyfile.rb"
  File.expand_path(basename, config[:kitchen_root])
end
prepare(component, opts = {}) click to toggle source

Prepares a generic Chef component source directory or file for inclusion in the sandbox path. These components might includes nodes, roles, etc.

@param component [Symbol,String] a component name such as `:node` @param opts [Hash] optional configuration @option opts [Symbol] :type whether the component is a directory or

file (default: `:directory`)

@option opts [Symbol] :key_name the key name in the config hash from

which to pull the source path (default: `"#{component}_path"`)

@option opts [String] :dest_name the destination file or directory

basename in the sandbox path (default: `component.to_s`)

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 220
def prepare(component, opts = {})
  opts = { type: :directory }.merge(opts)
  key_name = opts.fetch(:key_name, "#{component}_path")
  src = config[key_name.to_sym]
  return if src.nil?

  info("Preparing #{component}")
  debug("Using #{component} from #{src}")

  dest = File.join(sandbox_path, opts.fetch(:dest_name, component.to_s))

  case opts[:type]
  when :directory
    FileUtils.mkdir_p(dest)
    Array(src).each { |dir| FileUtils.cp_r(Util.list_directory(dir), dest) }
  when :file
    FileUtils.mkdir_p(File.dirname(dest))
    Array(src).each { |file| FileUtils.cp_r(file, dest) }
  end
end
prepare_cache() click to toggle source

Prepares a cache directory for inclusion in the sandbox path.

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 244
def prepare_cache
  FileUtils.mkdir_p(File.join(sandbox_path, "cache"))
end
prepare_cookbooks() click to toggle source

Prepares Chef cookbooks for inclusion in the sandbox path.

@api private rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 252
def prepare_cookbooks
  if File.exist?(policyfile)
    resolve_with_policyfile
  elsif File.exist?(berksfile)
    resolve_with_berkshelf
  elsif File.directory?(cookbooks_dir)
    cp_cookbooks
  elsif File.exist?(metadata_rb)
    cp_this_cookbook
  else
    make_fake_cookbook
  end

  filter_only_cookbook_files
end
prepare_json() click to toggle source

Prepares a Chef JSON file, sometimes called a dna.json or first-boot.json, for inclusion in the sandbox path.

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 272
def prepare_json
  dna = if File.exist?(policyfile)
          update_dna_for_policyfile
        else
          config[:attributes].merge(run_list: config[:run_list])
        end

  info("Preparing dna.json")
  debug("Creating dna.json from #{dna.inspect}")

  File.open(File.join(sandbox_path, "dna.json"), "wb") do |file|
    file.write(dna.to_json)
  end
end
resolve_with_berkshelf() click to toggle source

Performs a Berkshelf cookbook resolution inside a common mutex.

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 321
def resolve_with_berkshelf
  Kitchen.mutex.synchronize do
    Chef::Berkshelf.new(berksfile, tmpbooks_dir,
      logger: logger,
      always_update: config[:always_update_cookbooks]).resolve
  end
end
resolve_with_policyfile() click to toggle source

Performs a Policyfile cookbook resolution inside a common mutex.

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 306
def resolve_with_policyfile
  Kitchen.mutex.synchronize do
    Chef::Policyfile.new(
      policyfile, sandbox_path,
      logger: logger,
      always_update: config[:always_update_cookbooks],
      policy_group: config[:policy_group],
      license: config[:chef_license]
    ).resolve
  end
end
site_cookbooks_dir() click to toggle source

@return [String] an absolute path to a site-cookbooks/ directory,

relative to the kitchen root

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 332
def site_cookbooks_dir
  File.join(config[:kitchen_root], "site-cookbooks")
end
tmpbooks_dir() click to toggle source

@return [String] an absolute path to a cookbooks/ directory in the

sandbox path

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 339
def tmpbooks_dir
  File.join(sandbox_path, "cookbooks")
end
tmpsitebooks_dir() click to toggle source

@return [String] an absolute path to a site cookbooks directory in the

sandbox path

@api private

# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 346
def tmpsitebooks_dir
  File.join(sandbox_path, "cookbooks")
end
update_dna_for_policyfile() click to toggle source
# File lib/kitchen/provisioner/chef/common_sandbox.rb, line 287
def update_dna_for_policyfile
  policy = Chef::Policyfile.new(
    policyfile, sandbox_path,
    logger: logger,
    always_update: config[:always_update_cookbooks],
    policy_group: policy_group,
    license: config[:chef_license]
  )
  Kitchen.mutex.synchronize do
    policy.compile
  end
  policy_name = JSON.parse(IO.read(policy.lockfile))["name"]
  policy_group = config[:policy_group] || "local"
  config[:attributes].merge(policy_name: policy_name, policy_group: policy_group)
end