class ChefCLI::Policyfile::GitLockFetcher


@since 3.0
@author Daniel DeLeo
@author Ryan Hass
A Policyfile lock fetcher that can read a lock file from a git repository.

def apply_locked_source_options(options_from_lock)

Parameters:
  • options_from_lock (Hash) -- The source options loaded from a policyfile lock
def apply_locked_source_options(options_from_lock)
  options = options_from_lock.inject({}) do |acc, (key, value)|
    acc[key.to_sym] = value
    acc
  end
  source_options.merge!(options)
  raise ChefCLI::InvalidLockfile, "Invalid source_options provided from lock data: #{options_from_lock_file.inspect}" unless valid?
end

def cache_path

Returns:
  • (Pathname) -
def cache_path
  Pathname.new(File.expand_path(File.join(ChefCLI::Helpers.package_home, "cache")))
    .join(".cache", "git", Digest::SHA1.hexdigest(uri))
end

def cached?

Returns:
  • (Boolean) -
def cached?
  cache_path.exist?
end

def errors

Returns:
  • (Array) - A list of errors found
def errors
  error_messages = []
  [:git].each do |key|
    error_messages << "include_policy for #{name} is missing key #{key}" unless source_options[key]
  end
  error_messages
end

def fetch_lock_data

def fetch_lock_data
  install unless installed?
  FFI_Yajl::Parser.parse(
    show_file(rev_parse, lockfile_path)
  )
end

def git(command, options = {})

Raises:
  • (String) -

Parameters:
  • error (Boolean) --
  • command (String) --
def git(command, options = {})
  error = options[:error] || true
  unless which("git") || which("git.exe") || which("git.bat")
    raise GitNotInstalled
  end
  response = Mixlib::ShellOut.new(%{git #{command}}, options)
  response.run_command
  if error && response.error?
    raise GitError.new "#{command} #{cache_path}: #{response.stderr}"
  end
  response.stdout.strip
end

def initialize(name, source_options, storage_config)

Parameters:
  • source_options (Hash) -- A hash with a :path key pointing at the location
  • name (String) -- The name of the policyfile
def initialize(name, source_options, storage_config)
  @name           = name
  @storage_config = storage_config
  @source_options = symbolize_keys(source_options)
  @revision = @source_options[:revision]
  @path     = @source_options[:path] || @source_options[:rel]
  @uri      = @source_options[:git]
  @branch   = @source_options[:branch]
  @tag      = @source_options[:tag]
  @ref      = @source_options[:ref]
  # The revision to parse
  @rev_parse = @source_options[:ref] || @source_options[:branch] || @source_options[:tag] || "master"
end

def install

git copy.
Install into the chefcli cookbook store. This method leverages a cached
then munged since we do not have Policyfile validation in scope.
COPYPASTA from CookbookOmnifetch::GitLocation and Berkshelf::GitLocation
def install
  if cached?
    Dir.chdir(cache_path) do
      git %{fetch --force --tags #{uri} "refs/heads/*:refs/heads/*"}
    end
  else
    git %{clone #{uri} "#{cache_path}" --bare --no-hardlinks}
  end
  Dir.chdir(cache_path) do
    @revision ||= git %{rev-parse #{rev_parse}}
  end
end

def installed?

COPYPASTA from CookbookOmnifetch
def installed?
  !!(revision && cache_path.exist?)
end

def lock_data

Returns:
  • (Hash) - of the policyfile lock data
def lock_data
  @lock_data ||= fetch_lock_data.tap do |data|
    data["cookbook_locks"].each do |cookbook_name, cookbook_lock|
      if cookbook_lock["source_options"].key?("path")
        cookbook_lock["source_options"].tap do |opt|
          opt["git"]      = uri unless opt.key?("git")
          opt["revision"] = revision unless opt.key?("revision")
          opt["branch"]   = branch unless opt.key?("branch") || branch.nil?
          opt["tag"]      = tag unless opt.key?("tag") || branch.nil?
          opt["ref"]      = ref unless opt.key?("ref") || ref.nil?
          path_keys = %w{path rel}.map { |path_key| path_key if opt.key?(path_key) }.compact
          path_keys.each do |name|
            # We can safely grab the entire cookbook when the Policyfile defines a cookbook path of itself (".")
            if opt[name] == "."
              opt.delete(name)
              next
            end
            # Mutate the path key to a rel key so that we identify the source_type
            # as a git repo and not a local directory. Git also doesn't like paths
            # prefixed with `./` and cannot use relative paths outside the repo.
            # http://rubular.com/r/JYpdYHT19p
            pattern = %r{(^../)|(^./)}
            opt["rel"] = opt[name].gsub(pattern, "")
          end
          # Delete the path key if present to ensure we use the git source_type
          opt.delete("path")
        end
      end # cookbook_lock["source_options"]
    end # data["cookbook_locks"].each
  end # fetch_lock_data.tap
  @lock_data
end

def lockfile_path

def lockfile_path
  @path.nil? ? "Policyfile.lock.json" : @path
end

def rev_parse

def rev_parse
  source_options[:revision] || @rev_parse
end

def show_file(version, file)

Returns:
  • (String) - Content of specified file for a given revision.

Parameters:
  • file () -- Full path to file including filename in repository
  • version () -- Git version as a tag, branch, or ref.
def show_file(version, file)
  git("show #{version}:#{file}", cwd: cache_path.to_s)
end

def source_options_for_lock

Returns:
  • (Hash) - The source_options that describe how to fetch this exact lock again
def source_options_for_lock
  source_options.merge({
                         revision:,
                       })
end

def symbolize_keys(hash)

Returns:
  • (Hash) - Hash with only symbols as keys.

Parameters:
  • hash (Hash) -- Hash with symbols and/or strings as keys.
def symbolize_keys(hash)
  hash.inject({}) { |memo, (k, v)| memo[k.to_sym] = v; memo }
end

def valid?

Returns:
  • (False) - if there were errors with the provided source_options
  • (True) - if there were no errors with the provided source_options
def valid?
  errors.empty?
end

def which(executable)

Returns:
  • (String, nil) -
def which(executable)
  if File.file?(executable) && File.executable?(executable)
    executable
  elsif ENV["PATH"]
    path = ENV["PATH"].split(File::PATH_SEPARATOR).find do |p|
      File.executable?(File.join(p, executable))
    end
    path && File.expand_path(executable, path)
  end
end