Add --cache option to inspec exec

This allows users to run:

  inspec exec ./ --cache PATH

which will use `PATH` as the dir to retrieve and store remote
dependencies.  The hope is that this can eventually be used with
`inspec vendor PATH` to package up a profile for offline use.

Signed-off-by: Steven Danna <steve@chef.io>
This commit is contained in:
Steven Danna 2016-09-21 12:01:59 +01:00
parent a63149a5b6
commit 2d28c786c3
No known key found for this signature in database
GPG key ID: 94DFB46E861A7DAE
8 changed files with 37 additions and 38 deletions

View file

@ -58,6 +58,8 @@ module Inspec
desc: 'Use colors in output.'
option :attrs, type: :array,
desc: 'Load attributes file (experimental)'
option :cache, type: :string,
desc: 'Use the given path for caching dependencies. (default: ~/.inspec/cache)'
end
private

View file

@ -102,8 +102,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI # rubocop:disable Metrics/ClassLength
desc 'vendor', 'Download all dependencies and generate a lockfile'
def vendor(path = nil)
configure_logger(opts)
profile = Inspec::Profile.for_target('./', opts)
lockfile = profile.generate_lockfile(path)
profile = Inspec::Profile.for_target('./', opts.merge(cache: Inspec::Cache.new(path)))
lockfile = profile.generate_lockfile
File.write('inspec.lock', lockfile.to_yaml)
end

View file

@ -4,7 +4,7 @@ require 'fileutils'
module Inspec
#
# VendorIndex manages an on-disk cache of inspec profiles. The
# Inspec::Cache manages an on-disk cache of inspec profiles. The
# cache can contain:
#
# - .tar.gz profile archives
@ -16,7 +16,7 @@ module Inspec
# sources.
#
#
class VendorIndex
class Cache
attr_reader :path
def initialize(path = nil)
@path = path || File.join(Dir.home, '.inspec', 'cache')
@ -43,7 +43,7 @@ module Inspec
#
# For a given name and source_url, return true if the
# profile exists in the VendorIndex.
# profile exists in the Cache.
#
# @param [String] name
# @param [String] source_url
@ -56,7 +56,7 @@ module Inspec
end
#
# Return the path to given profile in the vendor index.
# Return the path to given profile in the cache.
#
# The `source_url` parameter should be a URI-like string that
# fully specifies the source of the exact version we want to pull

View file

@ -1,5 +1,4 @@
# encoding: utf-8
require 'inspec/dependencies/vendor_index'
require 'inspec/dependencies/requirement'
require 'inspec/dependencies/resolver'
@ -7,9 +6,6 @@ module Inspec
#
# A DependencySet manages a list of dependencies for a profile.
#
# Currently this class is a thin wrapper interface to coordinate the
# VendorIndex and the Resolver.
#
class DependencySet
#
# Return a dependency set given a lockfile.
@ -18,22 +14,21 @@ module Inspec
# @param cwd [String] Current working directory for relative path includes
# @param vendor_path [String] Path to the vendor directory
#
def self.from_lockfile(lockfile, cwd, vendor_path, backend)
vendor_index = VendorIndex.new(vendor_path)
def self.from_lockfile(lockfile, cwd, cache, backend)
dep_tree = lockfile.deps.map do |dep|
Inspec::Requirement.from_lock_entry(dep, cwd, vendor_index, backend)
Inspec::Requirement.from_lock_entry(dep, cwd, cache, backend)
end
dep_list = flatten_dep_tree(dep_tree)
new(cwd, vendor_path, dep_list, backend)
new(cwd, cache, dep_list, backend)
end
def self.from_array(dependencies, cwd, vendor_path, backend)
def self.from_array(dependencies, cwd, cache, backend)
dep_list = {}
dependencies.each do |d|
dep_list[d.name] = d
end
new(cwd, vendor_path, dep_list, backend)
new(cwd, cache, dep_list, backend)
end
# This is experimental code to test the working of the
@ -58,9 +53,9 @@ module Inspec
# @param cwd [String] current working directory for relative path includes
# @param vendor_path [String] path which contains vendored dependencies
# @return [dependencies] this
def initialize(cwd, vendor_path, dep_list, backend)
def initialize(cwd, cache, dep_list, backend)
@cwd = cwd
@vendor_path = vendor_path
@cache = cache
@dep_list = dep_list
@backend = backend
end
@ -91,8 +86,7 @@ module Inspec
#
def vendor(dependencies)
return nil if dependencies.nil? || dependencies.empty?
@vendor_index ||= VendorIndex.new(@vendor_path)
@dep_list = Resolver.resolve(dependencies, @vendor_index, @cwd, @backend)
@dep_list = Resolver.resolve(dependencies, @cache, @cwd, @backend)
end
end
end

View file

@ -9,31 +9,31 @@ module Inspec
# appropriate we delegate to Inspec::Profile directly.
#
class Requirement
def self.from_metadata(dep, vendor_index, opts)
def self.from_metadata(dep, cache, opts)
fail 'Cannot load empty dependency.' if dep.nil? || dep.empty?
new(dep[:name], dep[:version], vendor_index, opts[:cwd], opts.merge(dep))
new(dep[:name], dep[:version], cache, opts[:cwd], opts.merge(dep))
end
def self.from_lock_entry(entry, cwd, vendor_index, backend)
def self.from_lock_entry(entry, cwd, cache, backend)
req = new(entry[:name],
entry[:version_constraints],
vendor_index,
cache,
cwd,
entry[:resolved_source].merge(backend: backend))
locked_deps = []
Array(entry[:dependencies]).each do |dep_entry|
locked_deps << Inspec::Requirement.from_lock_entry(dep_entry, cwd, vendor_index, backend)
locked_deps << Inspec::Requirement.from_lock_entry(dep_entry, cwd, cache, backend)
end
req.lock_deps(locked_deps)
req
end
attr_reader :cwd, :opts, :required_version
def initialize(name, version_constraints, vendor_index, cwd, opts)
def initialize(name, version_constraints, cache, cwd, opts)
@name = name
@required_version = Gem::Requirement.new(Array(version_constraints))
@vendor_index = vendor_index
@cache = cache
@backend = opts[:backend]
@opts = opts
@cwd = cwd
@ -89,7 +89,7 @@ module Inspec
def dependencies
@dependencies ||= profile.metadata.dependencies.map do |r|
Inspec::Requirement.from_metadata(r, @vendor_index, cwd: @cwd, backend: @backend)
Inspec::Requirement.from_metadata(r, @cache, cwd: @cwd, backend: @backend)
end
end
@ -99,10 +99,10 @@ module Inspec
def profile
opts = @opts.dup
opts[:cache] = @vendor_index
opts[:cache] = @cache
opts[:backend] = @backend
if !@dependencies.nil?
opts[:dependencies] = Inspec::DependencySet.from_array(@dependencies, @cwd, @vendor_index, @backend)
opts[:dependencies] = Inspec::DependencySet.from_array(@dependencies, @cwd, @cache, @backend)
end
@profile ||= Inspec::Profile.for_target(opts, opts)
end

View file

@ -23,9 +23,9 @@ module Inspec
# implementation of the fetcher being used.
#
class Resolver
def self.resolve(dependencies, vendor_index, working_dir, backend)
def self.resolve(dependencies, cache, working_dir, backend)
reqs = dependencies.map do |dep|
req = Inspec::Requirement.from_metadata(dep, vendor_index, cwd: working_dir, backend: backend)
req = Inspec::Requirement.from_metadata(dep, cache, cwd: working_dir, backend: backend)
req || fail("Cannot initialize dependency: #{req}")
end
new.resolve(reqs)

View file

@ -13,7 +13,7 @@ require 'inspec/backend'
require 'inspec/rule'
require 'inspec/log'
require 'inspec/profile_context'
require 'inspec/dependencies/vendor_index'
require 'inspec/dependencies/cache'
require 'inspec/dependencies/lockfile'
require 'inspec/dependencies/dependency_set'
@ -25,7 +25,7 @@ module Inspec
# TODO: This function is getting pretty gross.
#
def self.resolve_target(target, cache = nil)
cache ||= VendorIndex.new
cache ||= Cache.new
fetcher = Inspec::Fetcher.resolve(target)
if fetcher.nil?
@ -88,6 +88,7 @@ EOF
@locked_dependencies = options[:dependencies]
@controls = options[:controls] || []
@profile_id = options[:id]
@cache = options[:cache] || Cache.new
@backend = options[:backend] || Inspec::Backend.create(options)
@source_reader = source_reader
@tests_collected = false
@ -368,14 +369,14 @@ EOF
# @param vendor_path [String] Path to the on-disk vendor dir
# @return [Inspec::Lockfile]
#
def generate_lockfile(vendor_path = nil)
res = Inspec::DependencySet.new(cwd, vendor_path, nil, @backend)
def generate_lockfile
res = Inspec::DependencySet.new(cwd, @cache, nil, @backend)
res.vendor(metadata.dependencies)
Inspec::Lockfile.from_dependency_set(res)
end
def load_dependencies
Inspec::DependencySet.from_lockfile(lockfile, cwd, nil, @backend)
Inspec::DependencySet.from_lockfile(lockfile, cwd, @cache, @backend)
end
private

View file

@ -11,6 +11,7 @@ require 'inspec/profile_context'
require 'inspec/profile'
require 'inspec/metadata'
require 'inspec/secrets'
require 'inspec/dependencies/cache'
# spec requirements
module Inspec
@ -41,7 +42,7 @@ module Inspec
@target_profiles = []
@controls = @conf[:controls] || []
@ignore_supports = @conf[:ignore_supports]
@cache = Inspec::Cache.new(@conf[:cache])
@test_collector = @conf.delete(:test_collector) || begin
require 'inspec/runner_rspec'
RunnerRspec.new(@conf)
@ -140,6 +141,7 @@ module Inspec
#
def add_target(target, _opts = [])
profile = Inspec::Profile.for_target(target,
cache: @cache,
backend: @backend,
controls: @controls,
attributes: @conf[:attributes])