class Confiner::Cli
Attributes
action[RW]
parser[R]
plugin_arguments[R]
plugins[RW]
Public Class Methods
new(*options)
click to toggle source
# File lib/confiner/cli.rb, line 13 def initialize(*options) @plugin_arguments = [] # arguments to be passed to the plugin(s) @rules_files = [] # which files were loaded @rules = [] @plugin_options = { debug: false, dry_run: false } # default logging to standard out Logger.log_to = $stdout if options.include?('--') @plugin_arguments = options[options.index('--')..] options = options[0..options.index('--')] end @parser = OptionParser.new do |opts| opts.banner = <<~USAGE Usage: #{$PROGRAM_NAME} [options] [-- plugin_options] Examples: Run all rules within .confiner directory outputting to a log file: #{$PROGRAM_NAME} -o confiner.log Run a specific rule file overriding specific arguments for plugins: #{$PROGRAM_NAME} -r rules.yml -- --arg1=foo --arg2=bar Run all all_rules within a specific directory: #{$PROGRAM_NAME} -r ./all_rules Options: USAGE opts.on('-h', '--help', 'Show the help') do puts opts exit 0 end opts.on('-r RULES', '--rules RULES', 'Path to rule yaml file or directory of rules') do |rule| rule.strip! # strip any trailing/leading spacess rules = File.expand_path(rule) raise "Rule file or directory `#{rules}` does not exist" unless File.exist?(rules) @rules = if File.directory?(rules) Dir[File.join(rules, '**', '*.yml')].each_with_object([]) do |definitions, all_rules| all_rules << load_yaml(definitions) end else [load_yaml(rules)] end end opts.on('-v', '--version', 'Show the version') do $stdout.puts "#{$PROGRAM_NAME} version #{VERSION}" exit(0) end opts.on('--dry-run', 'Dry run') do @plugin_options[:dry_run] = true end opts.on('--debug', 'Toggle debug mode') do @plugin_options[:debug] = true end opts.on('-o OUTPUT', '--output-to OUTPUT', 'File to output the log to') do |output_to| Logger.log_to = output_to end end @parser.parse!(options) log :confiner, 'Program Start' if @rules.empty? # load any and all rules within .confiner raise 'No rules to run. Are you missing a .confiner directory or -r argument?' unless Dir.exist?('.confiner') @rules = Dir[File.join('.confiner', '**', '*.yml')].each_with_object([]) do |definitions, rules| rules << load_yaml(definitions) end end log :rules, 'Using rule files:' @rules_files.each do |file| log :loaded, file, 2 end end
run(*argv)
click to toggle source
# File lib/confiner/cli.rb, line 110 def self.run(*argv) new(*argv).run end
Public Instance Methods
run()
click to toggle source
Run the confiner
# File lib/confiner/cli.rb, line 100 def run @rules.each do |rules| rules.each do |rule| process_rule(rule) end end log :confiner, 'Done' end
Private Instance Methods
load_yaml(file)
click to toggle source
Load yaml file and validate all rules
# File lib/confiner/cli.rb, line 175 def load_yaml(file) raise 'File is not a YAML file' unless File.extname(file).match(/yml|yaml/i) @rules_files << file validate_rules(YAML.load_file(file), file) end
process_rule(rule)
click to toggle source
Process a singular rule @param [Hash] rule
# File lib/confiner/cli.rb, line 118 def process_rule(rule) log :rule, rule.keys.map { |k| "\t#{k}=#{rule[k]}" }.join(',') rule['plugin']['args'] ||= {} rule['plugin']['args'].transform_keys!(&:to_sym) # ruby 2.5 compatability plugin = Plugins.const_get(translate_plugin_name(rule['plugin']['name'])).new(@plugin_options, **rule['plugin']['args']) # perform verification of actions before execution rule['actions'].each do |action| raise "YAML is invalid. Action `#{action}` does not exist." unless plugin.respond_to?(action) end # execute each action rule['actions'].each do |action| plugin.run(action) { |p| p.public_send(action) } end end
translate_plugin_name(plugin_name)
click to toggle source
Translate a plugin name from snake_case to PascalCase
# File lib/confiner/cli.rb, line 170 def translate_plugin_name(plugin_name) plugin_name.split('_').map(&:capitalize).join end
validate_rules(rules, file)
click to toggle source
Ensure that the rules are well-formed
# File lib/confiner/cli.rb, line 138 def validate_rules(rules, file) raise "YAML is invalid. Rules must be an array (from #{file})." unless rules.is_a? Array rules.each do |rule| # name is required raise "YAML is invalid. Rule must have a name. (from #{file})" unless rule['name'] # actions are required raise "YAML is invalid. Rule `#{rule['name']}` must have actions and it must be an Array (from #{file})" unless rule['actions']&.is_a? Array # plugin is required and must be well-formed raise "YAML is invalid. Rule `#{rule['name']}` must have a plugin and it must have a name (from #{file})" unless rule['plugin'] && rule['plugin']['name'] # Plugin must exist plugin = begin Plugins.const_get(translate_plugin_name(rule['plugin']['name'])) rescue NameError raise "YAML is invalid. Rule `#{rule['name']}` does not have plugin `#{rule['plugin']['name']}` (from #{file})" end # Validate the actions rule['actions'].each do |action| begin plugin.instance_method(action) rescue NameError raise "YAML is invalid. Rule `#{rule['name']}` plugin `#{rule['plugin']['name']}` has no action `#{action}` (from #{file})" end end end end