Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GitFetcher and rework Fetchers+SourceReaders #1034

Merged
merged 4 commits into from
Sep 9, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions lib/bundles/inspec-compliance/target.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

require 'uri'
require 'inspec/fetcher'
require 'fetchers/url'

# InSpec Target Helper for Chef Compliance
# reuses UrlHelper, but it knows the target server and the access token already
Expand All @@ -14,11 +13,14 @@ class Fetcher < Fetchers::Url
name 'compliance'
priority 500

def self.resolve(target, _opts = {})
return nil unless target.is_a?(String)
# check for local scheme compliance://
uri = URI(target)
return nil unless URI(uri).scheme == 'compliance'
def self.resolve(target)
uri = if target.is_a?(String) && URI(target).scheme == 'compliance'
URI(target)
elsif target.respond_to?(:key?) && target.key?(:compliance)
URI("compliance://#{target[:compliance]}")
end

return nil if uri.nil?

# check if we have a compliance token
config = Compliance::Configuration.new
Expand All @@ -27,18 +29,33 @@ def self.resolve(target, _opts = {})
# verifies that the target e.g base/ssh exists
profile = uri.host + uri.path
Compliance::API.exist?(config, profile)
super(target_url(config, profile), config)
new(target_url(profile, config), config)
rescue URI::Error => _e
nil
end

def self.target_url(config, profile)
def self.target_url(profile, config)
owner, id = profile.split('/')
"#{config['server']}/owners/#{owner}/compliance/#{id}/tar"
end

#
# We want to save supermarket: in the lockfile rather than url: to
# make sure we go back through the ComplianceAPI handling.
#
def resolved_source
{ supermarket: supermarket_profile_name }
end

def to_s
'Chef Compliance Profile Loader'
end

private

def supermarket_profile_name
m = %r{^#{@config['server']}/owners/(?<owner>[^/]+)/compliance/(?<id>[^/]+)/tar$}.match(@target)
"#{m[:owner]}/#{m[:id]}"
end
end
end
28 changes: 12 additions & 16 deletions lib/bundles/inspec-supermarket/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,14 @@ module Supermarket
class API
SUPERMARKET_URL = 'https://supermarket.chef.io'.freeze

def self.supermarket_url
SUPERMARKET_URL
end

# displays a list of profiles
def self.profiles
url = "#{SUPERMARKET_URL}/api/v1/tools-search"
def self.profiles(supermarket_url = SUPERMARKET_URL)
url = "#{supermarket_url}/api/v1/tools-search"
_success, data = get(url, { q: 'compliance_profile' })
if !data.nil?
profiles = JSON.parse(data)
profiles['items'].map { |x|
m = %r{^#{Supermarket::API.supermarket_url}/api/v1/tools/(?<slug>[\w-]+)(/)?$}.match(x['tool'])
m = %r{^#{supermarket_url}/api/v1/tools/(?<slug>[\w-]+)(/)?$}.match(x['tool'])
x['slug'] = m[:slug]
x
}
Expand All @@ -37,35 +33,35 @@ def self.profile_name(profile)
end

# displays profile infos
def self.info(profile)
def self.info(profile, supermarket_url = SUPERMARKET_URL)
_tool_owner, tool_name = profile_name("supermarket://#{profile}")
return if tool_name.nil? || tool_name.empty?
url = "#{SUPERMARKET_URL}/api/v1/tools/#{tool_name}"
url = "#{supermarket_url}/api/v1/tools/#{tool_name}"
_success, data = get(url, {})
JSON.parse(data) if !data.nil?
rescue JSON::ParserError
nil
end

# compares a profile with the supermarket tool info
def self.same?(profile, supermarket_tool)
def self.same?(profile, supermarket_tool, supermarket_url = SUPERMARKET_URL)
tool_owner, tool_name = profile_name(profile)
tool = "#{SUPERMARKET_URL}/api/v1/tools/#{tool_name}"
tool = "#{supermarket_url}/api/v1/tools/#{tool_name}"
supermarket_tool['tool_owner'] == tool_owner && supermarket_tool['tool'] == tool
end

def self.find(profile)
profiles = Supermarket::API.profiles
def self.find(profile, supermarket_url)
profiles = Supermarket::API.profiles(supermarket_url=SUPERMARKET_URL)
if !profiles.empty?
index = profiles.index { |t| same?(profile, t) }
index = profiles.index { |t| same?(profile, t, supermarket_url) }
# return profile or nil
profiles[index] if !index.nil? && index >= 0
end
end

# verifies that a profile exists
def self.exist?(profile)
!find(profile).nil?
def self.exist?(profile, supermarket_url = SUPERMARKET_URL)
!find(profile, supermarket_url).nil?
end

def self.get(url, params)
Expand Down
17 changes: 11 additions & 6 deletions lib/bundles/inspec-supermarket/target.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@

# InSpec Target Helper for Supermarket
module Supermarket
class Fetcher < Fetchers::Url
class Fetcher < Inspec.fetcher(1)
name 'supermarket'
priority 500

def self.resolve(target, opts = {})
return nil unless target.is_a?(String)
return nil unless URI(target).scheme == 'supermarket'
return nil unless Supermarket::API.exist?(target)
tool_info = Supermarket::API.find(target)
super(tool_info['tool_source_url'], opts)
supermarket_uri, supermarket_server = if target.is_a?(String) && URI(target).scheme == 'supermarket'
[target, Supermarket::API::SUPERMARKET_URL]
elsif target.respond_to?(:key?) && target.key?(:supermarket)
supermarket_server = target[:supermarket_url] || Supermarket::API::SUPERMARKET_URL
["supermarket://#{target[:supermarket]}", supermarket_server]
end
return nil unless supermarket_uri
return nil unless Supermarket::API.exist?(supermarket_uri, supermarket_server)
tool_info = Supermarket::API.find(supermarket_uri, supermarket_server)
resolve_next(tool_info['tool_source_url'], opts)
rescue URI::Error
nil
end
Expand Down
162 changes: 162 additions & 0 deletions lib/fetchers/git.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# encoding: utf-8
require 'tmpdir'
require 'fileutils'
require 'mixlib/shellout'
require 'inspec/log'

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.
#
class Git < Inspec.fetcher(1)
name 'git'
priority 200

def self.resolve(target, opts = {})
if target.respond_to?(:has_key?) &&target.key?(:git)
new(target[:git], opts.merge(target))
end
end

def initialize(remote_url, opts = {})
@branch = opts[:branch]
@tag = opts[:tag]
@ref = opts[:ref]
@remote_url = remote_url
@repo_directory = nil
end

def fetch(dir)
@repo_directory = dir
if cloned?
checkout
else
Dir.mktmpdir do |tmpdir|
checkout(tmpdir)
Inspec::Log.debug("Checkout of #{resolved_ref} successful. Moving checkout to #{dir}")
FileUtils.cp_r(tmpdir, @repo_directory)
end
end
@repo_directory
end

def archive_path
@repo_directory
end

def resolved_source
{ git: @remote_url, ref: resolved_ref }
end

private

def resolved_ref
@resolved_ref ||= if @ref
@ref
elsif @branch
resolve_ref(@branch)
elsif @tag
resolve_ref(@tag)
else
resolve_ref('master')
end
end

def resolve_ref(ref_name)
cmd = shellout("git ls-remote \"#{@remote_url}\" \"#{ref_name}*\"")
ref = parse_ls_remote(cmd.stdout, ref_name)
if !ref
fail "Unable to resolve #{ref_name} to a specific git commit for #{@remote_url}"
end
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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is basically copy/pasta from the omnibus git fetcher. There is a comment there explaining it a bit, we should copy/pasta the comment as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add a shout out to the omnibus project for inspiration in our top header comment.

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
found = pairs.find { |m| m[1].end_with?(ref_name.to_s) }
if found
found.first
end
end
end

def cloned?
File.directory?(File.join(@repo_directory, '.git'))
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
raise 'To use git sources, you must have git installed.'
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}")
Inspec::Log.debug('External command: STDOUT BEGIN')
Inspec::Log.debug(cmd.stdout)
Inspec::Log.debug('External command: STDOUT END')
Inspec::Log.debug('External command: STDERR BEGIN')
Inspec::Log.debug(cmd.stderr)
Inspec::Log.debug('External command: STDERR END')
cmd
end
end
end
49 changes: 33 additions & 16 deletions lib/fetchers/local.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,29 @@ class Local < Inspec.fetcher(1)
name 'local'
priority 0

attr_reader :files

def self.resolve(target)
return nil unless target.is_a?(String)
local_path = if target.is_a?(String)
resolve_from_string(target)
elsif target.is_a?(Hash)
resolve_from_hash(target)
end

if local_path
new(local_path)
end
end

def self.resolve_from_hash(target)
if target.key?(:path)
local_path = target[:path]
if target.key?(:cwd)
local_path = File.expand_path(local_path, target[:cwd])
end
local_path
end
end

def self.resolve_from_string(target)
# Support "urls" in the form of file://
if target.start_with?('file://')
target = target.gsub(%r{^file://}, '')
Expand All @@ -20,26 +38,25 @@ def self.resolve(target)
target = target.tr('\\', '/')
end

if !File.exist?(target)
nil
else
new(target)
if File.exist?(target)
target
end
end

def initialize(target)
@target = target
if File.file?(target)
@files = [target]
else
@files = Dir[File.join(target, '**', '*')]
end
end

def read(file)
return nil unless files.include?(file)
return nil unless File.file?(file)
File.read(file)
def fetch(_path)
archive_path
end

def archive_path
@target
end

def resolved_source
{ path: @target }
end
end
end
Loading