Merge pull request #447 from chef/dr/target-helper-dirs

rework target to resolver connection
This commit is contained in:
jcreedcmu 2016-02-11 08:43:53 -05:00
commit a42b19414d
6 changed files with 114 additions and 82 deletions

View file

@ -8,40 +8,26 @@ require 'inspec/targets/dir'
module Inspec::Targets
class ArchiveHelper < DirsResolver
def get_files(target)
files, rootdir = structure(target)
attr_reader :files
def initialize(target, _opts = {})
@target = target
files, @rootdir = structure(target)
# remove trailing slashes
files = files.collect { |f| f.chomp('/') }
# remove leading directory
files.collect { |f|
Pathname(f).relative_path_from(Pathname(rootdir)).to_s
root = Pathname(@rootdir)
@files = files.collect { |f|
Pathname(f).relative_path_from(root).to_s
}
end
def resolve(target, _opts = {})
files = get_files(target)
helper = DirsHelper.get_handler(files) ||
fail("Don't know how to handle folder #{target}")
_, rootdir = structure(target)
res = {
test: collect(helper, files, :get_filenames),
library: collect(helper, files, :get_libraries),
metadata: collect(helper, files, :get_metadata),
}.map { |as, list|
content(target, list, rootdir, base_folder: target, as: as)
}
res.flatten
end
# FIXME(sr) dedup inspec/targets/folder
def collect(helper, files, getter)
return [] unless helper.respond_to? getter
helper.method(getter).call(files)
def resolve(path, opts = {})
o = (opts || {})
o[:base_folder] = @target
content(@target, path, @rootdir, o)
end
end
end

View file

@ -8,17 +8,42 @@ module Inspec
module Targets
extend Modulator
# Retrieve the resolver for a given target. Resolvers are any type of
# instance, that can take a target in the #resolve method, and turn
# it into raw InSpec code. It will actually retrieve all file's
# contents, including libraries and metadata.
#
# @param [any] target any target you are pointing to; typically a string
# @return [Inspec::Targets::*] the resolver handling this target
def self.find_resolver(target)
modules.values.find { |m| m.handles?(target) }
end
# Retrieve the target handler, i.e. the component that is going to handle
# all aspects of a given target. Unlike the resolver, this provides
# access to an object, that understands the target and is able to read
# all contents, while providing additional methods which InSpec itself
# does not care about (think: plugins).
#
# There is a special case, where you might be pointing to a URL containing
# a ZIP file which has a full InSpec Profile embedded. This will be
# resolved to a directory helper (Inspec::Targets::DirsResolver). This
# method will retrieve the right handler for this directory and provide it
# with full access all contents (i.e. the DirsResolver will be embedded).
#
# @param [any] target any target you are pointing to; typically a string
# @return [Inspec::Targets::*] the handler for this target
def self.find_handler(target)
resolver = find_resolver(target)
return resolver unless resolver.is_a?DirsResolver
files = resolver.get_files(target)
DirsHelper.get_handler(files)
return resolver unless resolver.ancestors.include? DirsResolver
resolver.from_target(target).handler
end
# Turn targets into actionable InSpec code, library-data and metadata.
#
# @param [any] targets any targets you are pointing to; typically strings
# @param [Hash] additional options for loading
# @return [Array] an array of resolved data, with :content, :ref, :type
def self.resolve(targets, opts = {})
Array(targets).map do |target|
handler = find_resolver(target) ||

View file

@ -18,7 +18,7 @@ module Inspec::Targets
extend Modulator
def self.get_handler(paths)
modules.values.find { |x| x.handles?(paths) }
modules.values.reverse.find { |x| x.handles?(paths) }
end
end
@ -27,8 +27,46 @@ module Inspec::Targets
#
# These resolvers must implement the required methods of this class.
class DirsResolver
def get_files(_target)
fail NotImplementedError, "Directory resolver #{self.class} must implement #get_files"
def files
fail NotImplementedError, "Directory resolver #{self.class} must implement #files"
end
def resolve(_path)
fail NotImplementedError, "Directory resolver #{self.class} must implement #content"
end
def handler
DirsHelper.get_handler(files) ||
fail("Don't know how to handle #{self}")
end
def self.from_target(target)
new(target)
end
def self.resolve(target, opts)
r = new(target, opts)
handler = r.handler
files = r.files
res = []
{
test: __collect(handler, :get_filenames, files),
library: __collect(handler, :get_libraries, files),
metadata: __collect(handler, :get_metadata, files),
}.each do |as, list|
Array(list).each { |path|
res.push(r.resolve(path, as: as))
}
end
return handler.resolve_contents(res) if handler.respond_to?(:resolve_contents)
res
end
def self.__collect(handler, getter, files)
return [] unless handler.respond_to? getter
handler.method(getter).call(files)
end
end
@ -97,7 +135,9 @@ module Inspec::Targets
end
def get_filenames(paths)
paths.find_all { |x| x.end_with?('.rb') and !x.include?('/') }
# TODO: eventually remove the metadata.rb exception here
# when we have fully phased out metadata.rb in 1.0
paths.find_all { |x| x.end_with?('.rb') && !x.include?('/') && x != 'metadata.rb' }
end
end
DirsHelper.add_module('flat-ruby-folder', FlatDir.new)

View file

@ -7,49 +7,32 @@ require 'inspec/targets/file'
module Inspec::Targets
class FolderHelper < DirsResolver
def handles?(target)
def self.handles?(target)
File.directory?(target)
end
def get_files(target)
attr_reader :files
def initialize(target, _opts = {})
@base_folder = target
# find all files in the folder
files = Dir[File.join(target, '**', '*')]
# remove the prefix
prefix = File.join(target, '')
files.map { |x| x.sub(prefix, '') }
@files = files.map { |x| x.sub(prefix, '') }
@file_handler = Inspec::Targets.modules['file']
end
def resolve(target, _opts = {})
# get the dirs helper
files = get_files(target)
helper = DirsHelper.get_handler(files) ||
fail("Don't know how to handle folder #{target}")
# get all test file contents
file_handler = Inspec::Targets.modules['file']
res = {
test: collect(helper, files, :get_filenames),
library: collect(helper, files, :get_libraries),
metadata: collect(helper, files, :get_metadata),
}.map { |as, list|
file_handler.resolve_all(list, base_folder: target, as: as)
}
# flatten the outer list layer
res.inject(&:+)
def resolve(path, opts = {})
o = (opts || {})
o[:base_folder] = @base_folder
@file_handler.resolve(path, o)
end
def to_s
'Folder Loader'
end
private
def collect(helper, files, getter)
return [] unless helper.respond_to? getter
helper.method(getter).call(files)
end
end
Inspec::Targets.add_module('folder', FolderHelper.new)
Inspec::Targets.add_module('folder', FolderHelper)
end

View file

@ -8,7 +8,7 @@ require 'inspec/targets/archive'
module Inspec::Targets
class TarHelper < ArchiveHelper
def handles?(target)
def self.handles?(target)
File.file?(target) && target.end_with?('.tar.gz', '.tgz')
end
@ -30,22 +30,20 @@ module Inspec::Targets
[files, rootdir]
end
def content(input, files, rootdir = nil, opts = {})
content = []
def content(input, path, rootdir = nil, opts = {})
content = nil
Gem::Package::TarReader.new(Zlib::GzipReader.open(input)) do |tar|
tar.each do |entry|
if entry.directory?
# nothing to do
elsif entry.file?
if files.include?(entry.full_name.gsub(rootdir, ''))
h = {
next unless path == entry.full_name.gsub(rootdir, '')
content = {
# NB if some file is empty, return empty-string, not nil
content: entry.read || '',
type: opts[:as] || :test,
ref: entry.full_name,
}
content.push(h)
end
elsif entry.header.typeflag == '2'
# ignore symlinks for now
end
@ -59,5 +57,5 @@ module Inspec::Targets
end
end
Inspec::Targets.add_module('tar', TarHelper.new)
Inspec::Targets.add_module('tar', TarHelper)
end

View file

@ -8,22 +8,22 @@ require 'inspec/targets/archive'
module Inspec::Targets
class ZipHelper < ArchiveHelper
def handles?(target)
def self.handles?(target)
File.file?(target) and target.end_with?('.zip')
end
def content(input, files, rootdir = nil, opts = {})
content = []
def content(input, path, rootdir = nil, opts = {})
content = nil
::Zip::InputStream.open(input) do |io|
while (entry = io.get_next_entry)
next if !files.include?(entry.name.gsub(rootdir, ''))
h = {
next unless path == entry.name.gsub(rootdir, '')
content = {
# NB if some file is empty, return empty-string, not nil
content: io.read || '',
type: opts[:as] || :test,
ref: entry.name,
}
content.push(h)
abort
end
end
content
@ -51,5 +51,5 @@ module Inspec::Targets
end
end
Inspec::Targets.add_module('zip', ZipHelper.new)
Inspec::Targets.add_module('zip', ZipHelper)
end