Class: Inspec::Fetcher::Git

Inherits:
Object
  • Object
show all
Defined in:
lib/inspec/fetcher/git.rb

Overview

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.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(remote_url, opts = {}) ⇒ Git

Returns a new instance of Git.



38
39
40
41
42
43
44
45
46
# File 'lib/inspec/fetcher/git.rb', line 38

def initialize(remote_url, opts = {})
  @branch = opts[:branch]
  @tag = opts[:tag]
  @ref = opts[:ref]
  @remote_url = expand_local_path(remote_url)
  @repo_directory = nil
  @resolved_ref = nil
  @relative_path = opts[:relative_path] if opts[:relative_path] && !opts[:relative_path].empty?
end

Class Method Details

.resolve(target, opts = {}) ⇒ Object



30
31
32
33
34
35
36
# File 'lib/inspec/fetcher/git.rb', line 30

def self.resolve(target, opts = {})
  if target.is_a?(String)
    new(target, opts) if target.start_with?("git@") || target.end_with?(".git")
  elsif target.respond_to?(:has_key?) && target.key?(:git)
    new(target[:git], opts.merge(target))
  end
end

Instance Method Details

#archive_pathObject



132
133
134
# File 'lib/inspec/fetcher/git.rb', line 132

def archive_path
  @repo_directory
end

#cache_keyObject



119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/inspec/fetcher/git.rb', line 119

def cache_key
  cache_key = if @relative_path && !resolved_ref.nil?
                OpenSSL::Digest.hexdigest("SHA256", resolved_ref + @relative_path)
              elsif @relative_path && resolved_ref.nil?
                OpenSSL::Digest.hexdigest("SHA256", @remote_url + @relative_path)
              elsif resolved_ref.nil?
                OpenSSL::Digest.hexdigest("SHA256", @remote_url)
              else
                resolved_ref
              end
  cache_key
end

#expand_local_path(url_or_file_path) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/inspec/fetcher/git.rb', line 48

def expand_local_path(url_or_file_path)
  # This paths to local on-disk repos, not relative paths within repos.
  # This is especially needed with testing.

  # We could try to do something clever with URI
  # processing, but then again, if you passed a relative path
  # to an on-disk repo, you probably expect it to exist.
  return url_or_file_path unless File.exist?(url_or_file_path)

  # It's important to expand this path, because it may be specified
  # locally in the metadata files, and when we clone, we will be
  # in a temp dir.
  File.expand_path(url_or_file_path)
end

#fetch(destination_path) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/inspec/fetcher/git.rb', line 63

def fetch(destination_path)
  @repo_directory = destination_path # Might be the cache, or vendoring, or something else
  FileUtils.mkdir_p(destination_path) unless Dir.exist?(destination_path)
  if cloned?
    checkout
  else
    Dir.mktmpdir do |working_dir|
      checkout(working_dir)
      if git_only_or_empty?(working_dir)
        # If the temporary working directory is empty after checkout,
        # this means the git repository did not contain any files (or the checkout failed).
        # In this case, remove the destination directory to avoid
        # leaving an empty or invalid profile directory.
        if Dir.exist?(destination_path)
          FileUtils.rm_rf(destination_path)
        end
        raise Inspec::FetcherFailure, "Profile git dependency failed for #{@remote_url} - no files found in the repository."
      end
      if @relative_path
        perform_relative_path_fetch(destination_path, working_dir)
      else
        Inspec::Log.debug("Checkout of #{resolved_ref.nil? ? @remote_url : resolved_ref} successful. " \
                            "Moving checkout to #{destination_path}")
        FileUtils.cp_r(working_dir + "/.", destination_path)
      end
    end
  end
  @repo_directory
end

#git_only_or_empty?(dir) ⇒ Boolean

Returns:

  • (Boolean)


93
94
95
96
97
98
99
100
101
# File 'lib/inspec/fetcher/git.rb', line 93

def git_only_or_empty?(dir)
  return false unless Dir.exist?(dir)

  children = Dir.children(dir)
  # Return true if:
  # - directory is completely empty
  # - or it contains only one entry: '.git'
  children.empty? || (children - [".git"]).empty?
end

#perform_relative_path_fetch(destination_path, working_dir) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/inspec/fetcher/git.rb', line 103

def perform_relative_path_fetch(destination_path, working_dir)
  Inspec::Log.debug("Checkout of #{resolved_ref.nil? ? @remote_url : resolved_ref} successful. " \
                    "Moving #{@relative_path} to #{destination_path}")
  unless File.exist?("#{working_dir}/#{@relative_path}")
    # Cleanup the destination path - otherwise we'll have an empty dir
    # in the cache, which is enough to confuse the cache reader
    # This is a courtesy, assuming we're writing to the cache; if we're
    # vendoring to something more complex, don't bother.
    FileUtils.rm_r(destination_path) if Dir.exist?(destination_path)

    raise Inspec::FetcherFailure, "Cannot find relative path '#{@relative_path}' " \
                                  "within profile in git repo specified by '#{@remote_url}'"
  end
  FileUtils.cp_r("#{working_dir}/#{@relative_path}", destination_path)
end

#requires_locking?Boolean

Git fetcher is sensitive to cache contention so it needs cache locking mechanism.

Returns:

  • (Boolean)


151
152
153
# File 'lib/inspec/fetcher/git.rb', line 151

def requires_locking?
  true
end

#resolved_sourceObject



136
137
138
139
140
141
142
143
144
# File 'lib/inspec/fetcher/git.rb', line 136

def resolved_source
  if resolved_ref.nil?
    source = { git: @remote_url }
  else
    source = { git: @remote_url, ref: resolved_ref }
  end
  source[:relative_path] = @relative_path if @relative_path
  source
end

#update_from_opts(opts) ⇒ Object



146
147
148
# File 'lib/inspec/fetcher/git.rb', line 146

def update_from_opts(opts)
  %i{branch tag ref}.map { |opt_name| update_ivar_from_opt(opt_name, opts) }.any?
end