DSL and indirection question

I'm trying to figure out the best way of writing a script that describes various requirements in the form of "12.34.56" (from a requirement document) that I can then create methods for reporting, verifying and remediating the requirment on a system.

This is script for checking and fixing systems based on a bunch of security requirements. There are a couple of hundred rules and a few hundred systems. I am thinking about building a hash of Procs, but I think it is going to be too ugly. Any suggestions on a clean and easy to read way of doing this?

Thanks for the help, I am way too rusty on my Ruby programming.

-- Matt
It's not what I know that counts.
It's what I can remember in time to use.

This almost sounds like a job for Chef or Puppet, if I understood correctly.

I'm not really getting what you are aiming for here. Can you show a simple example or two?

James Edward Gray II

···

On Oct 25, 2010, at 2:20 PM, Matt Lawrence wrote:

I'm trying to figure out the best way of writing a script that describes various requirements in the form of "12.34.56" (from a requirement document) that I can then create methods for reporting, verifying and remediating the requirment on a system.

This is script for checking and fixing systems based on a bunch of security requirements. There are a couple of hundred rules and a few hundred systems. I am thinking about building a hash of Procs, but I think it is going to be too ugly. Any suggestions on a clean and easy to read way of doing this?

Maybe this helps, at least on the syntactic side (it's still a hash of procs underneath):

hash_builder = proc {|h,k| h[k] = Hash.new(&hash_builder)}
@rules = Hash.new(&hash_builder)
@stack =

def section name, &block
   begin
     @stack.push name
     yield
   ensure
     @stack.pop
   end
end

def rule name, &block
   section = @stack.inject(@rules) {|sect,n| sect[n]}
   context = (@stack + [name]).join(".")
   section[name] = proc do
     block.call(context)
   end
end

section 27 do
   section "B" do
     rule 6 do
       puts "this is 27B-6"
     end

     rule "foo" do |name|
       puts "this is #{name}"
     end
   end
end

section 70 do
   section 1 do
     rule 9 do |name|
       puts "this is rule #{name}"
     end
   end
end

require 'pp'
pp @rules
puts
@rules[27]["B"][6].call
@rules[70][1][9].call

__END__

Output:

{27=>
   {"B"=>
     {6=>#<Proc:0x00007f179675e590@-:17>,
      "foo"=>#<Proc:0x00007f179675e590@-:17>}},
  70=>{1=>{9=>#<Proc:0x00007f179675e590@-:17>}}}

this is 27B-6
this is rule 70.1.9

···

On 10/25/2010 12:20 PM, Matt Lawrence wrote:

I'm trying to figure out the best way of writing a script that describes
various requirements in the form of "12.34.56" (from a requirement
document) that I can then create methods for reporting, verifying and
remediating the requirment on a system.

This is script for checking and fixing systems based on a bunch of
security requirements. There are a couple of hundred rules and a few
hundred systems. I am thinking about building a hash of Procs, but I
think it is going to be too ugly. Any suggestions on a clean and easy to
read way of doing this?

I'm trying to figure out the best way of writing a script that describes various requirements in the form of "12.34.56" (from a requirement document) that I can then create methods for reporting, verifying and remediating the requirment on a system.

This is script for checking and fixing systems based on a bunch of security requirements. There are a couple of hundred rules and a few hundred systems. I am thinking about building a hash of Procs, but I think it is going to be too ugly. Any suggestions on a clean and easy to read way of doing this?

This almost sounds like a job for Chef or Puppet, if I understood correctly.

Chef or Puppet would not be practical in this environment for various reasons. This place is not in the Stone Age like my last job, they're more like the Bronze Age. I am campaigning to at least get Cfengine set up.

I'm not really getting what you are aiming for here. Can you show a simple example or two?

I have a directory full of results from a security scan, on file per system. In that file each line starts with a rule number, like "12.34" and has a pass/fail/skipped result.

What I probably want to do is to create a class for rules with each rule having methods for testing and fixing each issue found. I know I can brute force the script and it will look ugly or I can ask for help on doing a nifty DSL in the new favoured Ruby style.

-- Matt
It's not what I know that counts.
It's what I can remember in time to use.

···

On Tue, 26 Oct 2010, James Edward Gray II wrote:

On Oct 25, 2010, at 2:20 PM, Matt Lawrence wrote:

I'm probably still not understanding well, but this seems pretty easy to model:

class SecurityTest
  def self.parse(scan_result_line)
    new(*scan_result_line.to_s.split)
  end
  
  def initialize(rule, result)
    @rule = rule
    @result = result
  end
  
  attr_reader :rule
  
  def passed?
    @result == "pass"
  end
  
  def failed?
    @result == "fail"
  end
  
  def skipped?
    @result == "skipped"
  end
end

st = SecurityTest.parse("12.34 fail")
puts st.rule
p st.failed?

Hope that helps.

James Edward Gray II

···

On Oct 25, 2010, at 3:30 PM, Matt Lawrence wrote:

On Tue, 26 Oct 2010, James Edward Gray II wrote:

I'm not really getting what you are aiming for here. Can you show a simple example or two?

I have a directory full of results from a security scan, on file per system. In that file each line starts with a rule number, like "12.34" and has a pass/fail/skipped result.

Rule = Struct.new :name do
  def test(&b)
    if b
      @test = b
    else
      @test
    end
  end

  # same for fix
end

AllRules = Hash.new {|h,k| h[k.freeze] = Rule[k]}

AllRules["12.34"].test |system| puts "Testing rule on system #{system}"}
AllRules["12.34"].fix {|system| puts "Fixing rule on system #{system}"}

or

AllRules["12.34"].tap do |rule|
  rule.test do |system|
    puts "Testing rule on system #{system}"
  end

  rule.fix do |system|
      puts "Fixing rule on system #{system}"
  end
end

RuleResult = Struct.new :rule, :result do
  def self.parse(str)
    raise ArgumentError, "Cannot parse %p" % str unless
/^(\d+(?:\.\d+)*).*(pass|fail|skip)/
    self[AllRules[$1], $2.to_sym]
  end

  alias __result= result=

  def result=(x)
    __result= x.to_sym
  end

  def ok?
    result == :pass
  end
end

Something like this?

Cheers

robert

···

On Mon, Oct 25, 2010 at 10:30 PM, Matt Lawrence <matt@technoronin.com> wrote:

On Tue, 26 Oct 2010, James Edward Gray II wrote:

On Oct 25, 2010, at 2:20 PM, Matt Lawrence wrote:

I'm trying to figure out the best way of writing a script that describes
various requirements in the form of "12.34.56" (from a requirement document)
that I can then create methods for reporting, verifying and remediating the
requirment on a system.

This is script for checking and fixing systems based on a bunch of
security requirements. There are a couple of hundred rules and a few
hundred systems. I am thinking about building a hash of Procs, but I think
it is going to be too ugly. Any suggestions on a clean and easy to read way
of doing this?

This almost sounds like a job for Chef or Puppet, if I understood
correctly.

Chef or Puppet would not be practical in this environment for various
reasons. This place is not in the Stone Age like my last job, they're more
like the Bronze Age. I am campaigning to at least get Cfengine set up.

I'm not really getting what you are aiming for here. Can you show a
simple example or two?

I have a directory full of results from a security scan, on file per system.
In that file each line starts with a rule number, like "12.34" and has a
pass/fail/skipped result.

What I probably want to do is to create a class for rules with each rule
having methods for testing and fixing each issue found. I know I can brute
force the script and it will look ugly or I can ask for help on doing a
nifty DSL in the new favoured Ruby style.

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/