Merge pull request #1 from chef/linuxfile

shared linux file handling + specinfra config + cleanup
This commit is contained in:
Christoph Hartmann 2015-09-08 14:43:29 +02:00 committed by Dominik Richter
commit 8da2a2267d
8 changed files with 254 additions and 258 deletions

View file

@ -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

View file

@ -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?

View file

@ -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

View 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

View 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

View file

@ -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,7 +81,7 @@ 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) &&

View file

@ -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

View file

@ -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