class ChefDK::Policyfile::GitLockFetcher

A Policyfile lock fetcher that can read a lock file from a git repository.

@author Ryan Hass @author Daniel DeLeo

@since 3.0

Attributes

branch[R]
name[RW]
path[R]
ref[R]
revision[R]
source_options[RW]
storage_config[RW]
tag[R]
uri[R]

Public Class Methods

new(name, source_options, storage_config) click to toggle source

Initialize a GitLockFetcher

@param name [String] The name of the policyfile @param source_options [Hash] A hash with a :path key pointing at the location

of the lock
# File lib/chef-dk/policyfile/git_lock_fetcher.rb, line 51
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

Public Instance Methods

apply_locked_source_options(options_from_lock) click to toggle source

Applies source options from a lock file. This is used to make sure that the same policyfile lock is loaded that was locked

@param options_from_lock [Hash] The source options loaded from a policyfile lock

# File lib/chef-dk/policyfile/git_lock_fetcher.rb, line 95
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 ChefDK::InvalidLockfile, "Invalid source_options provided from lock data: #{options_from_lock_file.inspect}" unless valid?
end
errors() click to toggle source

Check the options provided when craeting this class for errors

@return [Array<String>] A list of errors found

# File lib/chef-dk/policyfile/git_lock_fetcher.rb, line 75
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
lock_data() click to toggle source

@return [Hash] of the policyfile lock data

# File lib/chef-dk/policyfile/git_lock_fetcher.rb, line 105
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
source_options_for_lock() click to toggle source

@return [Hash] The source_options that describe how to fetch this exact lock again

# File lib/chef-dk/policyfile/git_lock_fetcher.rb, line 85
def source_options_for_lock
  source_options.merge({
                         revision: revision,
                       })
end
valid?() click to toggle source

@return [True] if there were no errors with the provided source_options @return [False] if there were errors with the provided source_options

# File lib/chef-dk/policyfile/git_lock_fetcher.rb, line 68
def valid?
  errors.empty?
end

Private Instance Methods

cache_path() click to toggle source

COPYPASTA from CookbookOmnifetch::Git (then munged by me) The path where this git repository is cached.

@return [Pathname]

# File lib/chef-dk/policyfile/git_lock_fetcher.rb, line 247
def cache_path
  Pathname.new(File.expand_path(File.join(ChefDK::Helpers.chefdk_home, "cache")))
    .join(".cache", "git", Digest::SHA1.hexdigest(uri))
end
cached?() click to toggle source

COPYPASTA from CookbookOmnifetch::Git Determine if this git repo has already been downloaded.

@return [Boolean]

# File lib/chef-dk/policyfile/git_lock_fetcher.rb, line 256
def cached?
  cache_path.exist?
end
fetch_lock_data() click to toggle source
# File lib/chef-dk/policyfile/git_lock_fetcher.rb, line 153
def fetch_lock_data
  install unless installed?
  FFI_Yajl::Parser.parse(
    show_file(rev_parse, lockfile_path)
  )
end
git(command, options = {}) click to toggle source

COPYPASTA from CookbookOmnifetch::Git Perform a git command.

@param [String] command

the command to run

@param [Boolean] error

whether to raise error if the command fails

@raise [String]

the +$stdout+ from the command
# File lib/chef-dk/policyfile/git_lock_fetcher.rb, line 227
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
install() click to toggle source

COPYPASTA from CookbookOmnifetch::GitLocation and Berkshelf::GitLocation then munged since we do not have Policyfile validation in scope. Install into the chefdk cookbook store. This method leverages a cached git copy.

# File lib/chef-dk/policyfile/git_lock_fetcher.rb, line 169
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
installed?() click to toggle source

COPYPASTA from CookbookOmnifetch

# File lib/chef-dk/policyfile/git_lock_fetcher.rb, line 161
def installed?
  !!(revision && cache_path.exist?)
end
lockfile_path() click to toggle source
# File lib/chef-dk/policyfile/git_lock_fetcher.rb, line 260
def lockfile_path
  @path.nil? ? "Policyfile.lock.json" : @path
end
rev_parse() click to toggle source
# File lib/chef-dk/policyfile/git_lock_fetcher.rb, line 183
def rev_parse
  source_options[:revision] || @rev_parse
end
show_file(version, file) click to toggle source

Shows contents of a file from a shallow or full clone repository for a given git version.

This method was originally made before I slammed a bunch of copypasta code in which is generally more tied to a specific git ref.

@param version Git version as a tag, branch, or ref. @param file Full path to file including filename in repository

@return [String] Content of specified file for a given revision.

# File lib/chef-dk/policyfile/git_lock_fetcher.rb, line 197
def show_file(version, file)
  git("show #{version}:#{file}", cwd: cache_path)
end
symbolize_keys(hash) click to toggle source

Helper method to normalize data.

@param [Hash] hash Hash with symbols and/or strings as keys. @return [Hash] Hash with only symbols as keys.

# File lib/chef-dk/policyfile/git_lock_fetcher.rb, line 149
def symbolize_keys(hash)
  hash.inject({}) { |memo, (k, v)| memo[k.to_sym] = v; memo }
end
which(executable) click to toggle source

COPYPASTA from CookbookOmnifetch Location an executable in the current user's $PATH

@return [String, nil]

the path to the executable, or +nil+ if not present
# File lib/chef-dk/policyfile/git_lock_fetcher.rb, line 206
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