mirror of
https://github.com/inspec/inspec
synced 2024-11-15 01:17:08 +00:00
Merge pull request #1 from chef/linuxfile
shared linux file handling + specinfra config + cleanup
This commit is contained in:
commit
8da2a2267d
8 changed files with 254 additions and 258 deletions
|
@ -1,5 +1,4 @@
|
|||
# encoding: utf-8
|
||||
require 'shellwords'
|
||||
|
||||
if IO.respond_to?(:popen4)
|
||||
def open4(*args)
|
||||
|
@ -19,7 +18,7 @@ module Vulcano::Backends
|
|||
end
|
||||
|
||||
def file(path)
|
||||
@files[path] ||= File.new(self, path)
|
||||
@files[path] ||= LinuxFile.new(self, path)
|
||||
end
|
||||
|
||||
def run_command(cmd)
|
||||
|
@ -44,84 +43,5 @@ module Vulcano::Backends
|
|||
@exit_status = status.exitstatus
|
||||
end
|
||||
end
|
||||
|
||||
class File < FileCommon
|
||||
def initialize(backend, path)
|
||||
@backend = backend
|
||||
@path = path
|
||||
@spath = Shellwords.escape(@path)
|
||||
@stat = nil
|
||||
end
|
||||
|
||||
def content
|
||||
@content ||= @backend.run_command(
|
||||
"cat #{@spath} 2>/dev/null || echo -n").stdout
|
||||
end
|
||||
|
||||
def exists?
|
||||
@exists ||= (
|
||||
@backend.
|
||||
run_command("test -e #{@spath}").
|
||||
exit_status == 0
|
||||
)
|
||||
end
|
||||
|
||||
def link_target
|
||||
# @TODO: handle error
|
||||
@link_target ||= (
|
||||
@backend.
|
||||
run_command("readlink #{@spath}").stdout
|
||||
)
|
||||
end
|
||||
|
||||
%w{
|
||||
type mode owner group mtime size
|
||||
}.each do |field|
|
||||
define_method field.to_sym do
|
||||
stat[field.to_sym]
|
||||
end
|
||||
end
|
||||
|
||||
# @TODO: selinux_label product_version file_version
|
||||
|
||||
private
|
||||
|
||||
TYPES = {
|
||||
socket: 00140000,
|
||||
symlink: 00120000,
|
||||
file: 00100000,
|
||||
block_device: 00060000,
|
||||
directory: 00040000,
|
||||
character_device: 00020000,
|
||||
pipe: 00010000
|
||||
}
|
||||
|
||||
def stat
|
||||
return @stat unless @stat.nil?
|
||||
res = @backend.run_command("stat #{@spath} 2>/dev/null --printf '%s\n%f\n%u\n%g\n%X\n%Y'")
|
||||
|
||||
if res.exit_status != 0
|
||||
return @stat = {}
|
||||
end
|
||||
|
||||
fields = res.stdout.split("\n")
|
||||
if fields.length != 6
|
||||
return @stat = {}
|
||||
end
|
||||
|
||||
tmask = fields[1].to_i(16)
|
||||
type = TYPES.find { |_, mask| mask & tmask == mask }
|
||||
type ||= [:unknown]
|
||||
|
||||
@stat = {
|
||||
type: type[0],
|
||||
mode: tmask & 00777,
|
||||
owner: fields[2],
|
||||
group: fields[3],
|
||||
mtime: fields[5].to_i,
|
||||
size: fields[0]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,7 +19,7 @@ module Vulcano::Backends
|
|||
# if we don't support it, error out
|
||||
m = "configure_#{type}"
|
||||
if self.respond_to?(m.to_sym)
|
||||
self.send(m)
|
||||
send(m)
|
||||
else
|
||||
fail "Cannot configure Specinfra backend #{type}: it isn't supported yet."
|
||||
end
|
||||
|
@ -73,12 +73,37 @@ module Vulcano::Backends
|
|||
Specinfra.configuration.backend = :exec
|
||||
end
|
||||
|
||||
def validate_ssh_options( ssh_opts )
|
||||
unless ssh_opts[:port] > 0
|
||||
fail "Port must be > 0 (not #{ssh_opts[:port]})"
|
||||
end
|
||||
|
||||
if ssh_opts[:user].to_s.empty?
|
||||
fail 'User must not be empty.'
|
||||
end
|
||||
|
||||
unless ssh_opts[:keys].empty?
|
||||
ssh_opts[:auth_methods].push('publickey')
|
||||
ssh_opts[:keys_only] = true if ssh_opts[:password].nil?
|
||||
end
|
||||
|
||||
unless ssh_opts[:password].nil?
|
||||
ssh_opts[:auth_methods].push('password')
|
||||
end
|
||||
|
||||
if ssh_opts[:keys].empty? and ssh_opts[:password].nil?
|
||||
fail 'You must configure at least one authentication method' \
|
||||
': Password or key.'
|
||||
end
|
||||
end
|
||||
|
||||
def configure_ssh
|
||||
si = Specinfra.configuration
|
||||
si.backend = :ssh
|
||||
si.request_pty = true
|
||||
|
||||
host = @conf['host'].to_s
|
||||
fail 'You must configure a target host.' if host.empty?
|
||||
RSpec.configuration.host = host
|
||||
|
||||
ssh_opts = {
|
||||
|
@ -89,30 +114,10 @@ module Vulcano::Backends
|
|||
number_of_password_prompts: 0,
|
||||
user: @conf['user'],
|
||||
password: @conf['password'],
|
||||
keys: [@conf['key_file']].compact,
|
||||
keys: [@conf['key_file']].compact
|
||||
}
|
||||
|
||||
if host.empty?
|
||||
fail 'You must configure a target host.'
|
||||
end
|
||||
unless ssh_opts[:port] > 0
|
||||
fail "Port must be > 0 (not #{ssh_opts[:port]})"
|
||||
end
|
||||
if ssh_opts[:user].to_s.empty?
|
||||
fail 'User must not be empty.'
|
||||
end
|
||||
unless ssh_opts[:keys].empty?
|
||||
ssh_opts[:auth_methods].push('publickey')
|
||||
ssh_opts[:keys_only] = true if ssh_opts[:password].nil?
|
||||
end
|
||||
unless ssh_opts[:password].nil?
|
||||
ssh_opts[:auth_methods].push('password')
|
||||
end
|
||||
if ssh_opts[:keys].empty? and ssh_opts[:password].nil?
|
||||
fail 'You must configure at least one authentication method' \
|
||||
': Password or key.'
|
||||
end
|
||||
|
||||
validate_ssh_options(ssh_opts)
|
||||
si.ssh_options = ssh_opts
|
||||
end
|
||||
|
||||
|
@ -165,27 +170,9 @@ module Vulcano::Backends
|
|||
end
|
||||
|
||||
class SpecinfraHelper
|
||||
class File < FileCommon
|
||||
TYPES = {
|
||||
socket: 00140000,
|
||||
symlink: 00120000,
|
||||
file: 00100000,
|
||||
block_device: 00060000,
|
||||
directory: 00040000,
|
||||
character_device: 00020000,
|
||||
pipe: 00010000
|
||||
}
|
||||
class File < LinuxFile
|
||||
def initialize(path)
|
||||
@path = path
|
||||
end
|
||||
|
||||
def type
|
||||
path = Shellwords.escape(@path)
|
||||
raw_type = Specinfra::Runner.run_command("stat -c %f #{path}").stdout
|
||||
tmask = raw_type.to_i(16)
|
||||
res = TYPES.find{ |x, mask| mask & tmask == mask }
|
||||
return :unknown if res.nil?
|
||||
res[0]
|
||||
super(Specinfra::Runner, path)
|
||||
end
|
||||
|
||||
def exists?
|
||||
|
|
|
@ -3,130 +3,23 @@
|
|||
require 'digest'
|
||||
|
||||
module Vulcano::Plugins
|
||||
|
||||
class Backend
|
||||
autoload :FileCommon, 'vulcano/plugins/backend_file_common'
|
||||
autoload :LinuxFile, 'vulcano/plugins/backend_linux_file'
|
||||
|
||||
def self.name(name)
|
||||
Vulcano::Plugins::Backend.__register(name, self)
|
||||
end
|
||||
|
||||
# raise errors for all missing methods
|
||||
%w{ file run_command os }.each do |m|
|
||||
define_method(m.to_sym) do |*args|
|
||||
fail NotImplementedError.new("Backend must implement the #{m}() method.")
|
||||
define_method(m.to_sym) do |*_|
|
||||
fail NotImplementedError, "Backend must implement the #{m}() method."
|
||||
end
|
||||
end
|
||||
|
||||
def self.__register(id, obj)
|
||||
Vulcano::Backend.registry[id] = obj
|
||||
end
|
||||
|
||||
class FileCommon
|
||||
# interface methods: these fields should be implemented by every
|
||||
# backend File
|
||||
%w{
|
||||
exists? mode owner group link_target content mtime size
|
||||
selinux_label product_version file_version
|
||||
}.each do |m|
|
||||
define_method m.to_sym do
|
||||
fail NotImplementedError.new("File must implement the #{m}() method.")
|
||||
end
|
||||
end
|
||||
|
||||
def type
|
||||
:unknown
|
||||
end
|
||||
|
||||
# The following methods can be overwritten by a derived class
|
||||
# if desired, to e.g. achieve optimizations.
|
||||
|
||||
def md5sum
|
||||
res = Digest::MD5.new
|
||||
res.update(content)
|
||||
res.hexdigest
|
||||
end
|
||||
|
||||
def sha256sum
|
||||
res = Digest::SHA256.new
|
||||
res.update(content)
|
||||
res.hexdigest
|
||||
end
|
||||
|
||||
# Additional methods for convenience
|
||||
|
||||
def file?
|
||||
type == :file
|
||||
end
|
||||
|
||||
def block_device?
|
||||
type == :block_device
|
||||
end
|
||||
|
||||
def character_device?
|
||||
type == :character_device
|
||||
end
|
||||
|
||||
def socket?
|
||||
type == :socket
|
||||
end
|
||||
|
||||
def directory?
|
||||
type == :directory
|
||||
end
|
||||
|
||||
def symlink?
|
||||
type == :symlink
|
||||
end
|
||||
|
||||
def pipe?
|
||||
type == :pipe?
|
||||
end
|
||||
|
||||
def mode?(mode)
|
||||
mode == mode
|
||||
end
|
||||
|
||||
def owned_by?(owner)
|
||||
owner == owner
|
||||
end
|
||||
|
||||
def grouped_into?(group)
|
||||
group == group
|
||||
end
|
||||
|
||||
def linked_to?(dst)
|
||||
link_target == dst
|
||||
end
|
||||
|
||||
def version?(version)
|
||||
product_version == version or
|
||||
file_version == version
|
||||
end
|
||||
|
||||
# helper methods provided to any implementing class
|
||||
|
||||
private
|
||||
|
||||
UNIX_MODE_OWNERS = {
|
||||
owner: 00700,
|
||||
group: 00070,
|
||||
other: 00007
|
||||
}
|
||||
|
||||
UNIX_MODE_TYPES = {
|
||||
r: 00444,
|
||||
w: 00222,
|
||||
x: 00111
|
||||
}
|
||||
|
||||
def unix_mode_mask(owner, type)
|
||||
o = UNIX_MODE_OWNERS[owner.to_sym]
|
||||
return nil if o.nil?
|
||||
|
||||
t = UNIX_MODE_TYPES[type.to_sym]
|
||||
return nil if t.nil?
|
||||
|
||||
t & o
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
112
lib/vulcano/plugins/backend_file_common.rb
Normal file
112
lib/vulcano/plugins/backend_file_common.rb
Normal file
|
@ -0,0 +1,112 @@
|
|||
# encoding: utf-8
|
||||
|
||||
class Vulcano::Plugins::Backend
|
||||
class FileCommon
|
||||
# interface methods: these fields should be implemented by every
|
||||
# backend File
|
||||
%w{
|
||||
exists? mode owner group link_target content mtime size
|
||||
selinux_label product_version file_version
|
||||
}.each do |m|
|
||||
define_method m.to_sym do
|
||||
fail NotImplementedError, "File must implement the #{m}() method."
|
||||
end
|
||||
end
|
||||
|
||||
def type
|
||||
:unknown
|
||||
end
|
||||
|
||||
# The following methods can be overwritten by a derived class
|
||||
# if desired, to e.g. achieve optimizations.
|
||||
|
||||
def md5sum
|
||||
res = Digest::MD5.new
|
||||
res.update(content)
|
||||
res.hexdigest
|
||||
end
|
||||
|
||||
def sha256sum
|
||||
res = Digest::SHA256.new
|
||||
res.update(content)
|
||||
res.hexdigest
|
||||
end
|
||||
|
||||
# Additional methods for convenience
|
||||
|
||||
def file?
|
||||
type == :file
|
||||
end
|
||||
|
||||
def block_device?
|
||||
type == :block_device
|
||||
end
|
||||
|
||||
def character_device?
|
||||
type == :character_device
|
||||
end
|
||||
|
||||
def socket?
|
||||
type == :socket
|
||||
end
|
||||
|
||||
def directory?
|
||||
type == :directory
|
||||
end
|
||||
|
||||
def symlink?
|
||||
type == :symlink
|
||||
end
|
||||
|
||||
def pipe?
|
||||
type == :pipe?
|
||||
end
|
||||
|
||||
def mode?(sth)
|
||||
mode == sth
|
||||
end
|
||||
|
||||
def owned_by?(sth)
|
||||
owner == sth
|
||||
end
|
||||
|
||||
def grouped_into?(sth)
|
||||
group == sth
|
||||
end
|
||||
|
||||
def linked_to?(dst)
|
||||
link_target == dst
|
||||
end
|
||||
|
||||
def version?(version)
|
||||
product_version == version or
|
||||
file_version == version
|
||||
end
|
||||
|
||||
# helper methods provided to any implementing class
|
||||
|
||||
private
|
||||
|
||||
UNIX_MODE_OWNERS = {
|
||||
owner: 00700,
|
||||
group: 00070,
|
||||
other: 00007
|
||||
}
|
||||
|
||||
UNIX_MODE_TYPES = {
|
||||
r: 00444,
|
||||
w: 00222,
|
||||
x: 00111
|
||||
}
|
||||
|
||||
def unix_mode_mask(owner, type)
|
||||
o = UNIX_MODE_OWNERS[owner.to_sym]
|
||||
return nil if o.nil?
|
||||
|
||||
t = UNIX_MODE_TYPES[type.to_sym]
|
||||
return nil if t.nil?
|
||||
|
||||
t & o
|
||||
end
|
||||
end
|
||||
end
|
84
lib/vulcano/plugins/backend_linux_file.rb
Normal file
84
lib/vulcano/plugins/backend_linux_file.rb
Normal file
|
@ -0,0 +1,84 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'shellwords'
|
||||
|
||||
class Vulcano::Plugins::Backend
|
||||
class LinuxFile < FileCommon
|
||||
def initialize(backend, path)
|
||||
@backend = backend
|
||||
@path = path
|
||||
@spath = Shellwords.escape(@path)
|
||||
@stat = nil
|
||||
end
|
||||
|
||||
def content
|
||||
@content ||= @backend.run_command(
|
||||
"cat #{@spath} 2>/dev/null || echo -n").stdout
|
||||
end
|
||||
|
||||
def exists?
|
||||
@exists ||= (
|
||||
@backend.
|
||||
run_command("test -e #{@spath}").
|
||||
exit_status == 0
|
||||
)
|
||||
end
|
||||
|
||||
def link_target
|
||||
# @TODO: handle error
|
||||
@link_target ||= (
|
||||
@backend.
|
||||
run_command("readlink #{@spath}").stdout
|
||||
)
|
||||
end
|
||||
|
||||
%w{
|
||||
type mode owner group mtime size
|
||||
}.each do |field|
|
||||
define_method field.to_sym do
|
||||
stat[field.to_sym]
|
||||
end
|
||||
end
|
||||
|
||||
# @TODO: selinux_label product_version file_version
|
||||
|
||||
private
|
||||
|
||||
TYPES = {
|
||||
socket: 00140000,
|
||||
symlink: 00120000,
|
||||
file: 00100000,
|
||||
block_device: 00060000,
|
||||
directory: 00040000,
|
||||
character_device: 00020000,
|
||||
pipe: 00010000
|
||||
}
|
||||
|
||||
def stat
|
||||
return @stat unless @stat.nil?
|
||||
res = @backend.run_command("stat #{@spath} 2>/dev/null --printf '%s\n%f\n%u\n%g\n%X\n%Y'")
|
||||
|
||||
if res.exit_status != 0
|
||||
return @stat = {}
|
||||
end
|
||||
|
||||
fields = res.stdout.split("\n")
|
||||
if fields.length != 6
|
||||
return @stat = {}
|
||||
end
|
||||
|
||||
tmask = fields[1].to_i(16)
|
||||
type = TYPES.find { |_, mask| mask & tmask == mask }
|
||||
type ||= [:unknown]
|
||||
|
||||
@stat = {
|
||||
type: type[0],
|
||||
mode: tmask & 00777,
|
||||
owner: fields[2],
|
||||
group: fields[3],
|
||||
mtime: fields[5].to_i,
|
||||
size: fields[0]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -44,12 +44,11 @@ module Vulcano
|
|||
|
||||
def configure_backend
|
||||
backend_name = (@conf['backend'] ||= 'local')
|
||||
bname, next_backend = backend_name.split('+')
|
||||
|
||||
# @TODO all backends except for mock revert to specinfra for now
|
||||
unless %w{ mock local }.include? backend_name
|
||||
backend_class = Vulcano::Backend.registry['specinfra']
|
||||
else
|
||||
backend_class = Vulcano::Backend.registry[backend_name]
|
||||
end
|
||||
backend_class = Vulcano::Backend.registry[bname]
|
||||
@conf['backend'] = next_backend if bname == 'specinfra'
|
||||
|
||||
# Return on failure
|
||||
if backend_class.nil?
|
||||
|
@ -82,11 +81,11 @@ module Vulcano
|
|||
ctx.rules.each do |rule_id, rule|
|
||||
#::Vulcano::DSL.execute_rule(rule, profile_id)
|
||||
checks = rule.instance_variable_get(:@checks)
|
||||
checks.each do |m, a, b|
|
||||
checks.each do |_, a, b|
|
||||
# resource skipping
|
||||
if !a.empty? &&
|
||||
a[0].respond_to?(:resource_skipped) &&
|
||||
!a[0].resource_skipped.nil?
|
||||
a[0].respond_to?(:resource_skipped) &&
|
||||
!a[0].resource_skipped.nil?
|
||||
example = RSpec::Core::ExampleGroup.describe(*a) do
|
||||
it a[0].resource_skipped
|
||||
end
|
||||
|
|
|
@ -18,12 +18,12 @@ class DockerTester
|
|||
end
|
||||
|
||||
def run
|
||||
puts ["Running tests:", @tests].flatten.join("\n- ")
|
||||
puts ['Running tests:', @tests].flatten.join("\n- ")
|
||||
puts ''
|
||||
# test all images
|
||||
@conf['images'].each{|n|
|
||||
@conf['images'].each do |n|
|
||||
test_image(n)
|
||||
}.all? or raise "Test failures"
|
||||
end.all? or fail 'Test failures'
|
||||
end
|
||||
|
||||
def docker_images_by_tag
|
||||
|
@ -39,13 +39,13 @@ class DockerTester
|
|||
|
||||
def tests_conf
|
||||
# get the test configuration
|
||||
conf_path = File::join(File::dirname(__FILE__), '..', '.tests.yaml')
|
||||
raise "Can't find tests config in #{conf_path}" unless File::file?(conf_path)
|
||||
conf = YAML.load(File::read(conf_path))
|
||||
conf_path = File.join(File.dirname(__FILE__), '..', '.tests.yaml')
|
||||
fail "Can't find tests config in #{conf_path}" unless File.file?(conf_path)
|
||||
YAML.load(File.read(conf_path))
|
||||
end
|
||||
|
||||
def test_container(container_id)
|
||||
opts = { 'target' => "docker://#{container_id}" }
|
||||
opts = { 'target' => "specinfra+docker://#{container_id}" }
|
||||
runner = Vulcano::Runner.new(nil, opts)
|
||||
runner.add_tests(@tests)
|
||||
runner.run
|
||||
|
@ -54,13 +54,13 @@ class DockerTester
|
|||
def test_image(name)
|
||||
dname = "docker-#{name}:latest"
|
||||
image = @images[dname]
|
||||
raise "Can't find docker image #{dname}" if image.nil?
|
||||
fail "Can't find docker image #{dname}" if image.nil?
|
||||
|
||||
puts "--> start docker #{name}"
|
||||
container = Docker::Container.create(
|
||||
'Cmd' => [ '/bin/bash' ],
|
||||
'Cmd' => %w{ /bin/bash },
|
||||
'Image' => image.id,
|
||||
'OpenStdin' => true,
|
||||
'OpenStdin' => true
|
||||
)
|
||||
container.start
|
||||
|
||||
|
|
|
@ -31,8 +31,9 @@ Gem::Specification.new do |spec|
|
|||
spec.add_dependency 'method_source', '~> 0.8'
|
||||
spec.add_dependency 'rubyzip', '~> 1.1'
|
||||
spec.add_dependency 'rspec', '~> 3.3'
|
||||
spec.add_dependency 'rspec-its'
|
||||
spec.add_dependency 'winrm'
|
||||
spec.add_dependency 'open4'
|
||||
spec.add_dependency 'specinfra'
|
||||
spec.add_dependency 'rspec-its', '~> 1.2'
|
||||
spec.add_dependency 'winrm', '~> 1.3'
|
||||
spec.add_dependency 'open4', '~> 1.3'
|
||||
spec.add_dependency 'specinfra', '~> 2.40'
|
||||
spec.add_dependency 'docker-api', '~> 1.22'
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue