mirror of
https://github.com/inspec/inspec
synced 2024-11-10 07:04:15 +00:00
import resources
Signed-off-by: Dominik Richter <dominik.richter@gmail.com>
This commit is contained in:
parent
8d7ee71437
commit
985552731a
10 changed files with 650 additions and 0 deletions
61
lib/resources/mysql_conf.rb
Normal file
61
lib/resources/mysql_conf.rb
Normal 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
|
||||
|
35
lib/resources/mysql_session.rb
Normal file
35
lib/resources/mysql_session.rb
Normal 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
|
||||
|
63
lib/resources/postgres_conf.rb
Normal file
63
lib/resources/postgres_conf.rb
Normal 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
|
||||
|
64
lib/resources/postgres_session.rb
Normal file
64
lib/resources/postgres_session.rb
Normal 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
|
||||
|
52
lib/resources/processes.rb
Normal file
52
lib/resources/processes.rb
Normal 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
53
lib/resources/ssh_conf.rb
Normal 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
182
lib/utils/parseconfig.rb
Normal 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
81
lib/utils/simpleconfig.rb
Normal 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
40
lib/utils/spec_helper.rb
Normal 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
19
lib/vulcano.rb
Normal 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
|
Loading…
Reference in a new issue