import resources

Signed-off-by: Dominik Richter <dominik.richter@gmail.com>
This commit is contained in:
Dominik Richter 2015-04-09 22:01:23 +02:00
parent 8d7ee71437
commit 985552731a
10 changed files with 650 additions and 0 deletions

View file

@ -0,0 +1,61 @@
# encoding: utf-8
# copyright: 2015, Dominik Richter
# license: All rights reserved
require 'utils/parseconfig'
class MysqlConf
def initialize( conf_path )
@runner = Specinfra::Runner
@conf_path = conf_path
@files_contents = {}
@content = nil
@params = nil
read_content
end
def content
@content ||= read_content
end
def params *opts
@params || read_content
res = @params
opts.each do |opt|
res = res[opt] unless res.nil?
end
res
end
def read_content
@content = ""
@params = {}
to_read = [@conf_path]
while !to_read.empty?
raw_conf = read_file(to_read[0])
@content += raw_conf
params = ParseConfig.new(raw_conf).params
@params.merge!(params)
to_read = to_read.drop(1)
# see if there is more stuff to include
include_files = raw_conf.scan(/^!include\s+(.*)\s*/).flatten.compact
include_dirs = raw_conf.scan(/^!includedir\s+(.*)\s*/).flatten.compact
include_dirs.map do |include_dir|
include_files += Dir.glob(File.join include_dir, '*')
end
to_read += include_files.find_all do |fp|
not @files_contents.key? fp
end
end
#
@content
end
def read_file(path)
@files_contents[path] ||= @runner.get_file_content(path).stdout
end
end

View file

@ -0,0 +1,35 @@
# encoding: utf-8
# copyright: 2015, Dominik Richter
# license: All rights reserved
class MysqlSession
def initialize user, pass
@user = user
@pass = pass
end
def describe(query, db = "", &block)
# TODO: simple escape, must be handled by a library
# that does this securely
escaped_query = query.gsub(/\\/, '\\\\').gsub(/"/,'\\"').gsub(/\$/,'\\$')
# run the query
cmd = Serverspec::Type::Command.new("mysql -u#{@user} -p#{@pass} #{db} -s -e \"#{escaped_query}\"")
out = cmd.stdout + "\n" + cmd.stderr
if out =~ /Can't connect to .* MySQL server/ or
out.downcase =~ /^error/
# skip this test if the server can't run the query
RSpec.describe( cmd ) do
it "is skipped", skip: out do
end
end
else
RSpec.__send__( 'describe', cmd, &block )
end
end
end
def start_mysql_session( user, password )
MysqlSession.new(user, password)
end

View file

@ -0,0 +1,63 @@
# encoding: utf-8
# copyright: 2015, Dominik Richter
# license: All rights reserved
require 'utils/simpleconfig'
class PostgresConf
def initialize( conf_path )
@runner = Specinfra::Runner
@conf_path = conf_path
@conf_dir = File.expand_path(File.dirname @conf_path)
@files_contents = {}
@content = nil
@params = nil
read_content
end
def content
@content ||= read_content
end
def params *opts
@params || read_content
res = @params
opts.each do |opt|
res = res[opt] unless res.nil?
end
res
end
def read_content
@content = ""
@params = {}
to_read = [@conf_path]
while !to_read.empty?
raw_conf = read_file(to_read[0])
@content += raw_conf
params = SimpleConfig.new(raw_conf).params
@params.merge!(params)
to_read = to_read.drop(1)
# see if there is more config files to include
include_files = params['include'] || []
include_files += params['include_if_exists'] || []
(params['include_dir'] || []).each do |id|
id = File.join(@conf_dir, id) if id[0] != '/'
include_files += Dir.glob(File.join id, '*')
end
to_read += include_files.find_all do |fp|
not @files_contents.key? fp
end
end
#
@content
end
def read_file(path)
@files_contents[path] ||= @runner.get_file_content(path).stdout
end
end

View file

@ -0,0 +1,64 @@
# encoding: utf-8
# copyright: 2015, Dominik Richter
# license: All rights reserved
module Serverspec end
module Serverspec::Type
class Lines
def initialize raw, desc
@raw = raw
@desc = desc
end
def output
@raw
end
def lines
@raw.split("\n")
end
def to_s
@desc
end
end
end
class PostgresSession
def initialize user, pass
@user = user || 'postgres'
@pass = pass
end
def describe(query, db = [], &block)
dbs = db.map{|x| "-d #{x}" }.join(' ')
# TODO: simple escape, must be handled by a library
# that does this securely
escaped_query = query.gsub(/\\/, '\\\\').gsub(/"/,'\\"').gsub(/\$/,'\\$')
# run the query
cmd = Serverspec::Type::Command.new("PGPASSWORD='#{@pass}' psql -U #{@user} #{dbs} -c \"#{escaped_query}\"")
out = cmd.stdout + "\n" + cmd.stderr
if out =~ /Can't connect to .*/ or
out.downcase =~ /^error/
# skip this test if the server can't run the query
RSpec.describe( cmd ) do
it "is skipped", skip: out do
end
end
else
lines = cmd.stdout.
# remove the whole header (i.e. up to the first ^-----+------+------$)
sub(/(.*\n)+([-]+[+])*[-]+\n/,'').
# remove the tail
sub(/\n[^\n]*\n\n$/,'')
l = Serverspec::Type::Lines.new(lines.strip, "PostgreSQL query: #{query}")
RSpec.__send__( 'describe', l, &block )
end
end
end
def start_postgres_session( user, password )
PostgresSession.new(user, password)
end

View file

@ -0,0 +1,52 @@
# encoding: utf-8
# copyright: 2015, Dominik Richter
# license: All rights reserved
module Serverspec
module Type
class Processes < Base
def initialize grep
# turn into a regexp if it isn't one yet
if grep.class == String
grep = '(/[^/]*)*'+grep if grep[0] != '/'
grep = Regexp.new('^'+grep+'(\s|$)')
end
# get all running processes
cmd = Serverspec::Type::Command.new('ps aux')
all = cmd.stdout.split("\n")[1..-1]
all_cmds = all.map do |line|
# user 32296 0.0 0.0 42592 7972 pts/15 Ss+ Apr06 0:00 zsh
line.match(/^([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+(.*)$/)
end.compact.map do |m|
{
user: m[1],
pid: m[2],
cpu: m[3],
mem: m[4],
vsz: m[5],
rss: m[6],
tty: m[7],
stat: m[8],
start: m[9],
time: m[10],
command: m[11]
}
end
@list = all_cmds.find_all do |hm|
hm[:command] =~ grep
end
end
end
def list
@list
end
def processes( grep )
Processes.new(grep)
end
end
end
include Serverspec::Type

53
lib/resources/ssh_conf.rb Normal file
View file

@ -0,0 +1,53 @@
# encoding: utf-8
# copyright: 2015, Dominik Richter
# license: All rights reserved
require 'utils/simpleconfig'
class SshConf
def initialize( conf_path, type = nil )
@runner = Specinfra::Runner
@conf_path = conf_path
@conf_dir = File.expand_path(File.dirname @conf_path)
@files_contents = {}
@content = nil
@params = nil
typename = ( conf_path.include?('sshd') ? 'server' : 'client' )
@type = type || "SSH #{typename} configuration"
read_content
end
def to_s
@type
end
def content
@content ||= read_content
end
def params *opts
@params || read_content
res = @params
opts.each do |opt|
res = res[opt] unless res.nil?
end
res
end
def method_missing name
@params || read_content
@params[name.to_s]
end
def read_content
@content = read_file(@conf_path)
@params = SimpleConfig.new(@content, assignment_re: /^\s*(\S+?)\s+(.*?)\s*$/).params
@content
end
def read_file(path)
@files_contents[path] ||= @runner.get_file_content(path).stdout
end
end

182
lib/utils/parseconfig.rb Normal file
View file

@ -0,0 +1,182 @@
#
# Author:: BJ Dierkes <derks@datafolklabs.com>
# Copyright:: Copyright (c) 2006,2013 BJ Dierkes
# License:: MIT
# URL:: https://github.com/datafolklabs/ruby-parseconfig
#
# This class was written to simplify the parsing of configuration
# files in the format of "param = value". Please review the
# demo files included with this package.
#
# For further information please refer to the './doc' directory
# as well as the ChangeLog and README files included.
#
# Note: A group is a set of parameters defined for a subpart of a
# config file
class ParseConfig
Version = '1.0.6'
attr_accessor :conf, :params, :groups
# Initialize the class with raw config data 'conf'
# The class objects are dynamically generated by the
# name of the 'param' in the config file. Therefore, if
# the config file is 'param = value' then the itializer
# will eval "@param = value"
#
def initialize(conf=nil, separator = '=')
@params = {}
@groups = []
@splitRegex = '\s*' + separator + '\s*'
self.import_config(conf)
end
# Import data from the config to our config object.
def import_config(raw)
# The config is top down.. anything after a [group] gets added as part
# of that group until a new [group] is found.
group = nil
raw.split("\n").each_with_index do |line, i|
line.strip!
# force_encoding not available in all versions of ruby
begin
if i.eql? 0 and line.include?("\xef\xbb\xbf".force_encoding("UTF-8"))
line.delete!("\xef\xbb\xbf".force_encoding("UTF-8"))
end
rescue NoMethodError
end
unless (/^\#/.match(line))
if(/#{@splitRegex}/.match(line))
param, value = line.split(/#{@splitRegex}/, 2)
var_name = "#{param}".chomp.strip
value = value.chomp.strip
new_value = ''
if (value)
if value =~ /^['"](.*)['"]$/
new_value = $1
else
new_value = value
end
else
new_value = ''
end
if group
self.add_to_group(group, var_name, new_value)
else
self.add(var_name, new_value)
end
elsif(/^\[(.+)\]$/.match(line).to_a != [])
group = /^\[(.+)\]$/.match(line).to_a[1]
self.add(group, {})
end
end
end
end
# This method will provide the value held by the object "@param"
# where "@param" is actually the name of the param in the config
# file.
#
# DEPRECATED - will be removed in future versions
#
def get_value(param)
puts "ParseConfig Deprecation Warning: get_value() is deprecated. Use " + \
"config['param'] or config['group']['param'] instead."
return self.params[param]
end
# This method is a shortcut to accessing the @params variable
def [](param)
return self.params[param]
end
# This method returns all parameters/groups defined in a config file.
def get_params()
return self.params.keys
end
# List available sub-groups of the config.
def get_groups()
return self.groups
end
# This method adds an element to the config object (not the config file)
# By adding a Hash, you create a new group
def add(param_name, value)
if value.class == Hash
if self.params.has_key?(param_name)
if self.params[param_name].class == Hash
self.params[param_name].merge!(value)
elsif self.params.has_key?(param_name)
if self.params[param_name].class != value.class
raise ArgumentError, "#{param_name} already exists, and is of different type!"
end
end
else
self.params[param_name] = value
end
if ! self.groups.include?(param_name)
self.groups.push(param_name)
end
else
self.params[param_name] = value
end
end
# Add parameters to a group. Note that parameters with the same name
# could be placed in different groups
def add_to_group(group, param_name, value)
if ! self.groups.include?(group)
self.add(group, {})
end
self.params[group][param_name] = value
end
# Writes out the config file to output_stream
def write(output_stream=STDOUT, quoted=true)
self.params.each do |name,value|
if value.class.to_s != 'Hash'
if quoted == true
output_stream.puts "#{name} = \"#{value}\""
else
output_stream.puts "#{name} = #{value}"
end
end
end
output_stream.puts "\n"
self.groups.each do |group|
output_stream.puts "[#{group}]"
self.params[group].each do |param, value|
if quoted == true
output_stream.puts "#{param} = \"#{value}\""
else
output_stream.puts "#{param} = #{value}"
end
end
output_stream.puts "\n"
end
end
# Public: Compare this ParseConfig to some other ParseConfig. For two config to
# be equivalent, they must have the same sections with the same parameters
#
# other - The other ParseConfig.
#
# Returns true if ParseConfig are equivalent and false if they differ.
def eql?(other)
self.params == other.params && self.groups == other.groups
end
alias == eql?
end

81
lib/utils/simpleconfig.rb Normal file
View file

@ -0,0 +1,81 @@
# encoding: utf-8
# copyright: 2015, Dominik Richter
# license: All rights reserved
class SimpleConfig
attr_reader :params
def initialize( raw_data, opts = {} )
parse(raw_data, opts)
end
# Parse some data
# quotes: quoting characters, which are parsed, so everything inside
# it will be part of a string
# multiline: allow quoted text to span multiple lines
# comment_char: char which identifies comments
# standalone_comments: comments must appear alone in a line; if set to true,
# no comments can be added to the end of an assignment/statement line
def parse( raw_data, opts = {} )
@params = {}
options = default_options.merge(opts)
rest = raw_data
rest = parse_rest(rest, options) while rest.length > 0
end
private
def parse_rest( rest, opts )
idx_nl = rest.index("\n")
idx_comment = rest.index('#')
idx_nl = rest.length if idx_nl.nil?
idx_comment = idx_nl+1 if idx_comment.nil?
# is a comment inside this line
if idx_comment < idx_nl
if idx_comment == 0
line = ''
else
line = rest[0..(idx_comment-1)]
# in case we don't allow comments at the end
# of an assignment/statement, ignore it and fall
# back to treating this as a regular line
if opts[:standalone_comments] && !is_empty_line(line)
line = rest[0..(idx_nl-1)]
end
end
# if there is no comment in this line
else
if idx_nl == 0
line = ''
else
line = rest[0..(idx_nl-1)]
end
end
# now line contains what we are interested in parsing
# check if it is an assignment
m = opts[:assignment_re].match(line)
if !m.nil?
@params[m[1]] ||= []
@params[m[1]].push(m[2])
elsif !is_empty_line(line)
@params[line.strip] ||= []
end
# return whatever is left
return rest[(idx_nl+1)..-1] || ''
end
def is_empty_line l
l =~ /^\s*$/
end
def default_options
{
quotes: '',
multiline: false,
comment_char: '#',
assignment_re: /^\s*([^=]*?)\s*=\s*(.*?)\s*$/,
standalone_comments: false
}
end
end

40
lib/utils/spec_helper.rb Normal file
View file

@ -0,0 +1,40 @@
# encoding: utf-8
# copyright: 2015, Dominik Richter
# license: All rights reserved
require 'serverspec'
# Run spec on an ssh target
if ENV['SSH_SPEC']
require 'pathname'
require 'net/ssh'
set :backend, :ssh
RSpec.configure do |c|
options = {}
c.sudo_password = ENV['SUDO_PASSWORD'] || ENV['sudo_password']
c.host = ENV['TARGET_HOST']
options[:password] = ENV['LOGIN_PASSWORD'] || ENV['password']
options[:user] = ENV['LOGIN_USERNAME'] || ENV['user'] || Etc.getlogin
if !ENV['LOGIN_KEY'].nil?
options[:keys] = [ENV['LOGIN_KEY']]
options[:keys_only] = true
end
# TODO: optional, will be removed
if options[:user].nil?
raise 'specify a user for login via env LOGIN_USERNAME= or by adding user='
end
ssh_conf = Net::SSH::Config.for(c.host)
c.ssh_options = options.merge(ssh_conf)
end
# Run spec on local machine
else
require 'serverspec'
set :backend, :exec
end

19
lib/vulcano.rb Normal file
View file

@ -0,0 +1,19 @@
# encoding: utf-8
# copyright: 2015, Dominik Richter
# license: All rights reserved
require 'utils/spec_helper'
require 'resources/mysql_conf'
require 'resources/mysql_session'
require 'resources/postgres_conf'
require 'resources/postgres_session'
require 'resources/processes'
require 'resources/ssh_conf'
# Dummy module for handling additional attributes
# which may be injected by the user. This covers data
# like passwords, usernames, or configuration flags.
def attributes what, required: false
return nil
end