encapsulated filters

This commit is contained in:
Dominik Richter 2016-03-12 21:43:38 +01:00 committed by Christoph Hartmann
parent 652f10ad9a
commit 048a1584b9
4 changed files with 184 additions and 49 deletions

View file

@ -23,7 +23,6 @@ module Inspec::Resources
def initialize(conf_path = '/etc/xinetd.conf')
@conf_path = conf_path
@filters = ''
@contents = {}
end
@ -35,28 +34,15 @@ module Inspec::Resources
@params ||= read_params
end
extend Inspec::Filter
add_filter 'service'
add_filter 'id'
add_filter 'socket_type'
add_filter 'type'
add_filter 'wait'
def disabled?
where({ 'disable' => 'no' }).services.empty?
end
def enabled?
where({ 'disable' => 'yes' }).services.empty?
end
def where(conditions = {})
fields, filters = Inspec::Filter.where(service_lines, conditions)
res = clone
res.instance_variable_set(:@filters, @filters + filters)
res.instance_variable_set(:@services, fields)
res
end
filter = FilterTable.create(self, :service_lines)
filter.add_delegator(:where)
.add(:services, field: 'service')
.add(:ids, field: 'id')
.add(:socket_types, field: 'socket_type')
.add(:types, field: 'type')
.add(:wait, field: 'wait')
.add(:disabled?) { |x| x.where('disable' => 'no').services.empty? }
.add(:enabled?) { |x| x.where('disable' => 'yes').services.empty? }
private
@ -97,13 +83,5 @@ module Inspec::Resources
def service_lines
@services ||= params['services'].values.flatten.map(&:params)
end
def get_fields(*fields)
res = service_lines.map do |line|
fields.map { |f| line[f] }
end.flatten
return res unless fields == ['service']
res.uniq
end
end
end

View file

@ -1,34 +1,45 @@
# encoding: utf-8
# author: Dominik Richter
# author: Stephan Renatus
# author: Christoph Hartmann
module Inspec
module Filter
module Show; end
module FilterTable
module Show; end
def add_filter(field_name)
fail "Called add_filter in resource #{self} with field name nil!" if field_name.nil?
method_name = field_name.to_s
# methods will alwas target plurals; this is why the suffix 's' is mandatory
method_name += 's' unless method_name.end_with? 's'
define_method method_name.to_sym do |condition = Show|
return get_fields(field_name) if condition == Show
where({ field_name => condition })
end
class Table
attr_reader :params
def initialize(resource, params, filters)
@resource = resource
@params = params
@filters = filters
end
def self.where(table, conditions = {})
def where(conditions)
return self if !conditions.is_a?(Hash) || conditions.empty?
filters = ''
table = @params
conditions.each do |field, condition|
filters += " #{field} = #{condition.inspect}"
table = filter_lines(table, field, condition)
end
[table, filters]
self.class.new(@resource, table, @filters + filters)
end
def self.filter_lines(table, field, condition)
def get_fields(*fields)
@params.map do |line|
fields.map { |f| line[f] }
end.flatten
end
def to_s
@resource.to_s + @filters
end
alias inspect to_s
private
def filter_lines(table, field, condition)
table.find_all do |line|
next unless line.key?(field)
case line[field]
@ -40,4 +51,48 @@ module Inspec
end
end
end
class Factory
def initialize(resource, accessor)
@resource = resource
@accessor = accessor
@table = Class.new(Table)
end
def table(instance)
table.new(self, instance.method(table_accessor).call, '')
end
def add_delegator(method_name)
if method_name.nil?
throw RuntimeError, "Called filter.add_delegator for resource #{@resource} with method name nil!"
end
table_accessor = @accessor
table = @table
@resource.send(:define_method, method_name.to_sym) do |*args|
filter = table.new(self, method(table_accessor).call, '')
filter.method(method_name.to_sym).call(*args)
end
self
end
def add(method_name, opts = {}, &block)
if method_name.nil?
throw RuntimeError, "Called filter.add for resource #{@resource} with method name nil!"
end
field_name = opts[:field] || method_name
@table.send(:define_method, method_name.to_sym) do |condition = Show|
return block.call(self) unless block.nil? # rubocop:disable Performance/RedundantBlockCall
return where(nil).get_fields(field_name) if condition == Show
where({ field_name => condition })
end
add_delegator(method_name)
end
end
def self.create(resource, accessor)
Factory.new(resource, accessor)
end
end

View file

@ -17,7 +17,7 @@ describe 'Inspec::Resources::XinetdConf' do
describe 'with services from child configs' do
it 'has one service name' do
_(resource.services).must_equal %w{chargen}
_(resource.services.uniq).must_equal %w{chargen}
end
it 'has multiple service definitions' do
@ -30,7 +30,7 @@ describe 'Inspec::Resources::XinetdConf' do
it 'can chain filters' do
one = resource.services('chargen').socket_types('dgram')
_(one.params['services'].length).must_equal 1
_(one.services.length).must_equal 1
_(one.ids).must_equal %w{chargen-dgram}
end

View file

@ -0,0 +1,102 @@
# encoding: utf-8
# author: Dominik Richter
# author: Stephan Renatus
# author: Christoph Hartmann
require 'helper'
describe FilterTable do
let (:data) {[
{ foo: 3, bar: true, baz: 'yay', num: nil },
{ foo: 2, bar: false, baz: 'noo', num: 1 },
{ foo: 2, bar: false, baz: 'whatever', num: 2 },
]}
let (:resource) {
Class.new do
attr_reader :data
def initialize(data)
@data = data
end
end
}
let (:factory) { FilterTable.create(resource, :data) }
let (:instance) { resource.new(data) }
it 'has a create utility which creates a filter factory' do
factory.must_be_kind_of FilterTable::Factory
end
describe 'when calling add_delegator' do
it 'is chainable' do
factory.add_delegator(:sth).must_equal factory
end
it 'wont add nil' do
proc { factory.add_delegator(nil) }.must_throw RuntimeError
end
it 'can expose the where method' do
factory.add_delegator(:where)
_(instance.respond_to?(:where)).must_equal true
instance.where({ baz: 'yay' }).params.must_equal [data[0]]
end
it 'will delegate even non-existing methods' do
factory.add_delegator(:not_here)
_(instance.respond_to?(:not_here)).must_equal true
end
end
describe 'when calling add' do
it 'is chainable' do
factory.add(:sth).must_equal factory
end
it 'wont add nil' do
proc { factory.add(nil) }.must_throw RuntimeError
end
it 'can expose a data column' do
factory.add(:baz)
instance.baz(123).must_be_kind_of(FilterTable::Table)
end
end
describe 'with the number field' do
before { factory.add(:num) }
it 'filter by nil' do
instance.num(nil).params.must_equal [data[0]]
end
it 'filter by existing numbers' do
instance.num(1).params.must_equal [data[1]]
end
it 'filter by missing number' do
instance.num(-1).params.must_equal []
end
end
describe 'with the string field' do
before { factory.add(:baz) }
it 'filter by existing strings' do
instance.baz('yay').params.must_equal [data[0]]
end
it 'filter by missing string' do
instance.baz('num').params.must_equal []
end
it 'filter by existing regex' do
instance.baz(/A/i).params.must_equal [data[0], data[2]]
end
it 'filter by missing regex' do
instance.baz(/zzz/).params.must_equal []
end
end
end