2019-06-11 22:24:35 +00:00
|
|
|
require "tmpdir"
|
|
|
|
require "fileutils"
|
|
|
|
require "mixlib/shellout"
|
|
|
|
require "inspec/log"
|
2016-09-08 09:11:44 +00:00
|
|
|
|
|
|
|
module Fetchers
|
|
|
|
#
|
|
|
|
# The git fetcher uses the git binary to fetch remote git sources.
|
|
|
|
# Git-based sources should be specified with the `git:` key in the
|
|
|
|
# source hash. Additionally, we accept `:branch`, `:ref`, and `:tag`
|
|
|
|
# keys to allow users to pin to a particular revision.
|
|
|
|
#
|
|
|
|
# Parts of this class are derived from:
|
|
|
|
#
|
|
|
|
# https://github.com/chef/omnibus/blob/master/lib/omnibus/fetchers/git_fetcher.rb
|
|
|
|
#
|
|
|
|
# which is Copyright 2012-2014 Chef Software, Inc. and offered under
|
|
|
|
# the same Apache 2 software license as inspec.
|
|
|
|
#
|
|
|
|
# Many thanks to the omnibus authors!
|
|
|
|
#
|
|
|
|
# Note that we haven't replicated all of omnibus' features here. If
|
|
|
|
# you got to this file during debugging, you may want to look at the
|
|
|
|
# omnibus source for hints.
|
|
|
|
#
|
2017-11-21 07:49:41 +00:00
|
|
|
class Git < Inspec.fetcher(1)
|
2019-06-11 22:24:35 +00:00
|
|
|
name "git"
|
2016-09-08 09:11:44 +00:00
|
|
|
priority 200
|
|
|
|
|
|
|
|
def self.resolve(target, opts = {})
|
2018-11-08 20:55:15 +00:00
|
|
|
if target.is_a?(String)
|
2019-06-11 22:24:35 +00:00
|
|
|
new(target, opts) if target.start_with?("git@") || target.end_with?(".git")
|
2018-11-08 20:55:15 +00:00
|
|
|
elsif target.respond_to?(:has_key?) && target.key?(:git)
|
|
|
|
new(target[:git], opts.merge(target))
|
|
|
|
end
|
2016-09-08 09:11:44 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def initialize(remote_url, opts = {})
|
|
|
|
@branch = opts[:branch]
|
2018-10-17 04:23:27 +00:00
|
|
|
@profile_directory = nil
|
2016-09-08 09:11:44 +00:00
|
|
|
@tag = opts[:tag]
|
|
|
|
@ref = opts[:ref]
|
|
|
|
@remote_url = remote_url
|
|
|
|
@repo_directory = nil
|
2019-03-31 11:31:06 +00:00
|
|
|
@path_within_repo = opts[:path_within_repo]
|
2016-09-08 09:11:44 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def fetch(dir)
|
|
|
|
@repo_directory = dir
|
2017-03-24 20:28:00 +00:00
|
|
|
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
|
|
|
2016-09-08 09:11:44 +00:00
|
|
|
if cloned?
|
|
|
|
checkout
|
|
|
|
else
|
|
|
|
Dir.mktmpdir do |tmpdir|
|
|
|
|
checkout(tmpdir)
|
2019-03-31 11:31:06 +00:00
|
|
|
if @path_within_repo
|
2018-10-17 04:23:27 +00:00
|
|
|
@profile_directory = dir
|
|
|
|
Inspec::Log.debug("Checkout of #{resolved_ref} successful. " \
|
2019-03-31 11:31:06 +00:00
|
|
|
"Moving #{@path_within_repo} to #{dir}")
|
|
|
|
target_profile = File.join(tmpdir, @path_within_repo)
|
2018-10-17 04:23:27 +00:00
|
|
|
FileUtils.cp_r(target_profile, dir)
|
|
|
|
else
|
|
|
|
Inspec::Log.debug("Checkout of #{resolved_ref} successful. " \
|
|
|
|
"Moving checkout to #{dir}")
|
|
|
|
FileUtils.cp_r(tmpdir + '/.', dir)
|
|
|
|
end
|
2016-09-08 09:11:44 +00:00
|
|
|
end
|
|
|
|
end
|
2018-10-17 04:23:27 +00:00
|
|
|
@profile_directory || @repo_directory
|
2016-09-08 09:11:44 +00:00
|
|
|
end
|
2016-09-20 10:36:23 +00:00
|
|
|
|
|
|
|
def cache_key
|
2019-03-31 11:31:06 +00:00
|
|
|
return resolved_ref unless @path_within_repo
|
|
|
|
OpenSSL::Digest::SHA256.hexdigest(resolved_ref + @path_within_repo)
|
2016-09-20 10:36:23 +00:00
|
|
|
end
|
2016-09-08 09:11:44 +00:00
|
|
|
|
|
|
|
def archive_path
|
2018-10-17 04:23:27 +00:00
|
|
|
@profile_directory || @repo_directory
|
2016-09-08 09:11:44 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def resolved_source
|
2018-10-17 04:23:27 +00:00
|
|
|
source = { git: @remote_url, ref: resolved_ref }
|
2019-03-31 11:31:06 +00:00
|
|
|
source[:path_within_repo] = @path_within_repo if @path_within_repo
|
2018-10-17 04:23:27 +00:00
|
|
|
source
|
2016-09-08 09:11:44 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def resolved_ref
|
|
|
|
@resolved_ref ||= if @ref
|
|
|
|
@ref
|
|
|
|
elsif @branch
|
|
|
|
resolve_ref(@branch)
|
|
|
|
elsif @tag
|
|
|
|
resolve_ref(@tag)
|
|
|
|
else
|
2019-06-11 22:24:35 +00:00
|
|
|
resolve_ref("master")
|
2016-09-08 09:11:44 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def resolve_ref(ref_name)
|
2018-07-25 19:57:03 +00:00
|
|
|
command_string = "git ls-remote \"#{@remote_url}\" \"#{ref_name}*\""
|
|
|
|
cmd = shellout(command_string)
|
2018-12-12 19:08:48 +00:00
|
|
|
raise "Error running '#{command_string}': #{cmd.stderr}" unless cmd.exitstatus == 0
|
2019-07-09 00:20:30 +00:00
|
|
|
|
2016-09-08 09:11:44 +00:00
|
|
|
ref = parse_ls_remote(cmd.stdout, ref_name)
|
2019-07-09 00:20:30 +00:00
|
|
|
unless ref
|
2017-02-08 22:49:16 +00:00
|
|
|
raise "Unable to resolve #{ref_name} to a specific git commit for #{@remote_url}"
|
2016-09-08 09:11:44 +00:00
|
|
|
end
|
2019-07-09 00:20:30 +00:00
|
|
|
|
2016-09-08 09:11:44 +00:00
|
|
|
ref
|
|
|
|
end
|
|
|
|
|
|
|
|
#
|
|
|
|
# The following comment is a minor modification of the comment in
|
|
|
|
# the omnibus source for a similar function:
|
|
|
|
#
|
|
|
|
# Dereference annotated tags.
|
|
|
|
#
|
|
|
|
# The +remote_list+ parameter is assumed to look like this:
|
|
|
|
#
|
|
|
|
# a2ed66c01f42514bcab77fd628149eccb4ecee28 refs/tags/rel-0.11.0
|
|
|
|
# f915286abdbc1907878376cce9222ac0b08b12b8 refs/tags/rel-0.11.0^{}
|
|
|
|
#
|
|
|
|
# The SHA with ^{} is the commit pointed to by an annotated
|
|
|
|
# tag. If ref isn't an annotated tag, there will not be a line
|
|
|
|
# with trailing ^{}.
|
|
|
|
#
|
|
|
|
# @param [String] output
|
|
|
|
# output from `git ls-remote origin` command
|
|
|
|
# @param [String] ref_name
|
|
|
|
# the target git ref_name
|
|
|
|
#
|
|
|
|
# @return [String]
|
|
|
|
#
|
|
|
|
def parse_ls_remote(output, ref_name)
|
|
|
|
pairs = output.lines.map { |l| l.chomp.split("\t") }
|
|
|
|
tagged_commit = pairs.find { |m| m[1].end_with?("#{ref_name}^{}") }
|
|
|
|
if tagged_commit
|
|
|
|
tagged_commit.first
|
|
|
|
else
|
2017-11-21 07:49:41 +00:00
|
|
|
pairs.find { |m| m[1].end_with?(ref_name.to_s) }&.first
|
2016-09-08 09:11:44 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def cloned?
|
2019-06-11 22:24:35 +00:00
|
|
|
File.directory?(File.join(@repo_directory, ".git"))
|
2016-09-08 09:11:44 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def clone(dir = @repo_directory)
|
|
|
|
git_cmd("clone #{@remote_url} ./", dir) unless cloned?
|
|
|
|
@repo_directory
|
|
|
|
end
|
|
|
|
|
|
|
|
def checkout(dir = @repo_directory)
|
|
|
|
clone(dir)
|
|
|
|
git_cmd("checkout #{resolved_ref}", dir)
|
|
|
|
@repo_directory
|
|
|
|
end
|
|
|
|
|
|
|
|
def git_cmd(cmd, dir = @repo_directory)
|
|
|
|
cmd = shellout("git #{cmd}", cwd: dir)
|
|
|
|
cmd.error!
|
|
|
|
cmd.status
|
|
|
|
rescue Errno::ENOENT
|
2019-06-11 22:24:35 +00:00
|
|
|
raise "To use git sources, you must have git installed."
|
2016-09-08 09:11:44 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def shellout(cmd, opts = {})
|
|
|
|
Inspec::Log.debug("Running external command: #{cmd} (#{opts})")
|
|
|
|
cmd = Mixlib::ShellOut.new(cmd, opts)
|
|
|
|
cmd.run_command
|
|
|
|
Inspec::Log.debug("External command: completed with exit status: #{cmd.exitstatus}")
|
2019-06-11 22:24:35 +00:00
|
|
|
Inspec::Log.debug("External command: STDOUT BEGIN")
|
2016-09-08 09:11:44 +00:00
|
|
|
Inspec::Log.debug(cmd.stdout)
|
2019-06-11 22:24:35 +00:00
|
|
|
Inspec::Log.debug("External command: STDOUT END")
|
|
|
|
Inspec::Log.debug("External command: STDERR BEGIN")
|
2016-09-08 09:11:44 +00:00
|
|
|
Inspec::Log.debug(cmd.stderr)
|
2019-06-11 22:24:35 +00:00
|
|
|
Inspec::Log.debug("External command: STDERR END")
|
2016-09-08 09:11:44 +00:00
|
|
|
cmd
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|