class Inspec::ProfileVendor

Attributes

profile_path[R]

Public Class Methods

new(path) click to toggle source
# File lib/inspec/profile_vendor.rb, line 8
def initialize(path)
  @profile_path = Pathname.new(File.expand_path(path))
end

Public Instance Methods

cache_path() click to toggle source
# File lib/inspec/profile_vendor.rb, line 33
def cache_path
  profile_path.join("vendor")
end
lockfile() click to toggle source
# File lib/inspec/profile_vendor.rb, line 37
def lockfile
  profile_path.join("inspec.lock")
end
make_readable() click to toggle source

The URL fetcher uses a Tempfile to retrieve the vendored profile, which creates a file that is only readable by the current user. In most circumstances, this is likely OK. However, in environments like a Habitat package, these files need to be readable by all users or the Habitat Supervisor may not be able to start InSpec correctly.

This method makes sure all vendored files are mode 644 for this use case. This method is not called by default - the caller vendoring the profile must make the decision as to whether this is necessary.

# File lib/inspec/profile_vendor.rb, line 27
def make_readable
  Dir.glob("#{cache_path}/**/*") do |e|
    FileUtils.chmod(0644, e) if File.file?(e)
  end
end
vendor!(opts) click to toggle source
# File lib/inspec/profile_vendor.rb, line 12
def vendor!(opts)
  vendor_dependencies!(opts)
end

Private Instance Methods

delete_vendored_data() click to toggle source
# File lib/inspec/profile_vendor.rb, line 62
def delete_vendored_data
  FileUtils.rm_rf(cache_path) if cache_path.exist?
  File.delete(lockfile) if lockfile.exist?
end
extract_archives() click to toggle source
# File lib/inspec/profile_vendor.rb, line 67
def extract_archives
  Dir.glob(File.join(cache_path, "*")).each do |filepath|
    # Get SHA without extension
    # We use split since '.' is not valid in a SHA checksum
    destination_dir_name = File.basename(filepath).split(".")[0]
    destination_path = File.join(cache_path, destination_dir_name)

    provider = FileProvider.for_path(filepath)

    next unless provider.is_a?(ZipProvider) || provider.is_a?(TarProvider)

    Inspec::Log.debug("Extracting '#{filepath}' to '#{destination_path}'")
    provider.extract(destination_path)

    Inspec::Log.debug("Deleting archive '#{filepath}'")
    File.delete(filepath)
  end
end
profile() click to toggle source
# File lib/inspec/profile_vendor.rb, line 43
def profile
  @profile ||= Inspec::Profile.for_target(profile_path.to_s, profile_opts)
end
profile_opts() click to toggle source
# File lib/inspec/profile_vendor.rb, line 47
def profile_opts
  {
    vendor_cache: Inspec::Cache.new(cache_path.to_s),
    backend: Inspec::Backend.create(Inspec::Config.mock),
  }
end
vendor_dependencies!(opts) click to toggle source
# File lib/inspec/profile_vendor.rb, line 54
def vendor_dependencies!(opts)
  # This deletes any existing vendor/ directory
  delete_vendored_data
  warm_vendor_cache_from_archives if opts[:airgap]
  File.write(lockfile, profile.generate_lockfile.to_yaml)
  extract_archives
end
warm_vendor_cache_from_archives() click to toggle source

Check top-level profile metadata for any local archives if present, go ahead and inflate it into the vendor cache. This will cause subsequent fetchers to hit on the vendor cache, avoiding a possibly failing fetch under airgapped conditions.

# File lib/inspec/profile_vendor.rb, line 90
def warm_vendor_cache_from_archives
  # We need to determine the cwd of the profile, to expand the (likely)
  # relative path of the dependencies. There is no non-invasive way of doing that.
  profile_cwd = profile.source_reader.target.parent.path

  # list dependencies in profile
  profile.metadata.dependencies.each do |dep_info|
    next unless dep_info.key?(:path) # "Local"-type fetchers are identified by the :path key

    # Locate local dep
    dep_profile_path = File.expand_path(dep_info[:path], profile_cwd)
    raise(Inspec::FetcherFailure, "Profile dependency local path '#{dep_profile_path}' does not exist") unless File.exist?(dep_profile_path)

    # Determine if it is an archive
    file_provider = FileProvider.for_path(dep_profile_path)
    next unless file_provider.is_a?(ZipProvider) || file_provider.is_a?(TarProvider)

    # Place the local dependency in the vendor cache.
    # Fetchers are in charge of calculating cache keys.
    fetcher = Inspec::Fetcher::Local.resolve(dep_profile_path)
    # Use a shorter name here in hopes of dodging windows filesystems path length restrictions
    actual_dep_profile_cache_dir = "#{cache_path}/#{fetcher.cache_key}"
    short_dep_profile_cache_dir = "#{cache_path}/short"
    file_provider.extract(short_dep_profile_cache_dir)

    # The archive (probably) contained a vendor cache of its own.
    # Since we are warming the fetcher cache, and fetcher caches are one-level
    # while archive caches are potentially deep, we must flatten the archive cache,
    # so that all vendor cache entries are top-level.
    Dir["#{short_dep_profile_cache_dir}/vendor/*"].each do |sub_dep_cache_dir|
      FileUtils.mv(sub_dep_cache_dir, cache_path)
    end

    # And finally correctly name the dependency itself.
    FileUtils.mv(short_dep_profile_cache_dir, actual_dep_profile_cache_dir)

  end
end