Skip to content
vpetrenko edited this page Aug 8, 2011 · 11 revisions

Robert is a configuration tool. It's not about what tasks you solve, it's about the approach you use.

##Configurations##

When you use Robert, you build a space of objects. These objects are called configurations. Each configuration has set of actions that it can perform. For example:

conf :my_project do
  act[:check] = seq(check.check_http, check.check_ssh)
end

Configurations definitions are opened so it's totally legal to do the following:

conf :my_project do
  act[:check] = seq(check.check_http, check.check_ssh)
end

conf :my_project do
  act[:backup] = remote.copy
end

You can combine configurations by using include statement. For example:

conf :another_project do
  include :my_project
end

include simply inserts the body of the named configuration in place of itself. So example above is equivalent to:

conf :another_project do
  act[:check] = seq(check.check_http, check.check_ssh)
  act[:backup] = remote.copy
end

##Actions##

Every configuration is filled with actions. Though actions definitions look like functions' calls, they're very much different. Actions definitions are declarative. When you define an action, you define a pipeline. For example:

act[:check] = seq(check.check_http, check.check_ssh)

Here we define action called "check". We use builtin action seq which can be configured with any number of other actions. When "check" action is called, seq's arguments will be executed sequentially. In any of seq's argument fails, the whole action will fail. Inside seq we specify check.check_http and check.check_ssh. Except for builtin actions (please see their list later), every action's name consists from a namespace and the name itself. Both check_http and check_ssh belong to namespace check.

Here's more elaborate example:

act[:check] = seq(onfail.continue(check.check_http), check.check_ssh)

When you use any non-builtin action in the definition, you can (optionally) specify another action as it's argument. A lot of actions expect to call something when they're done or when some special condition is met (or not met). In configuration above, for example, onfail.continue(check.check_http) action is used. When check action is executed, seq will execute it's first argument. onfail.continue will get controll and immediately call it's first argument. As soon as check.check_http finishes, the control will be returned back to onfail.continue. According to its' name, it will swallow all thrown exceptions and will just return success to its' caller. Its' caller is seq, which will pass the control to check.check_ssh.

So, to reiterate, when you define an action inside the configuration, you define an execution plan.

##Rules##

You use configurations to describe entities and you use actions to describe actions that can be performed by these entities. You need to use rules to tune the behavior of actions.

###Execution context### To understand rules you need to understand what execution context is. Let's look at the following example:

conf :some_conf do
  act[:check] = onfail.continue(notify.email(check.check_http))
end

Imagine that action check of configuration some_conf is executed.

  1. onfail.continue gets control. The execution context is :some_conf,:check,:onfail,:continue - note that execution context always starts with configuration name and action name
  2. onfail.continue passes control to notify.email. Execution context becomes: :some_conf,:check,:onfail,:continue,:notify,:email
  3. notify.email passes control to check.check_http. Execution context becomes: :some_conf,:check,:onfail,:continue,:notify,:email,:check,:check_http
  4. check.check_http returns control back to notify.email. Execution context becomes: :some_conf,:check,:onfail,:continue,:notify,:email
  5. notify.email returns control back to onfail.continue. Execution context becomes: :some_conf,:check,:onfail,:continue

###Rules definition###

You define rules using the following statement:

var[:rule,:context] = rule_value

rule_value can be a constant or lambda. If rule_value is a lambda, it will be executed every time the rule is executed. You can use shorter form to define lambda rules:

var(:rule,:context) { lambda_body }

During the execution Robert maintains of all the rules that were defined. Every rule in this list has a context. This actual context of the rule depends on the place where it was defined. There are several possible options.


Global rule - i.e. rule defined outside of any configuration. For example:

var[:some,:context] = 42

This kind of rule will be prefixed with :* in the global list:

:some,:context -> 42

:* means "matches any number of any tokens". Please see details of the rules matching process below.


Configuration rule - i.e. rule defined inside configuration definition. For example:

conf :some_conf do
  var[:some,:context] = 42
end

This kind of rule will be prefixed with configuration name and :* in the global list:

:some_conf,:*,:some,:context -> 42

Action rule - i.e. rule tied to particular action. For example:

conf :some_conf do
  act[:check] = onfail.continue(notify.email(check.check_http) { var[:sendmail,:command] = "/bin/sendmail" })
end

Here var[:sendmail,:command] rule is defined in the block that corresponds to notify.email action. This kind of rule will be prefixed with the execution context of this action:

 :some_conf,:check,:onfail,:continue,:notify,:email,:sendmail,:command -> "/bin/sendmail"

You can think about action rules as about the most concrete ones - as they don't contain :* unless you explicitly include it into the rule context.

###Rules matching###

(Almost) Every action uses rules to alter its' behavior. For example notify.email action uses rule to in order to determine where should email be sent to. Just like with execution context, the requested rule can be described by more than one token. For example, :sendmail,:command. When rule is requested, following things happen (let's imagine that notify.email from the previous example requested :sendmail,:command rule's contents):

  1. Rule's context is appended to current execution context. Rule's context is :sendmail,:command. Execution context is: :some_conf,:check,:onfail,:continue,:notify,:email. So total (or absolute) context is: :some_conf,:check,:onfail,:continue,:notify,:email,:sendmail,:command.
  2. Absolute context is matched against every rule that was previously defined. The order of matching is: from newest rules to oldest ones (in respect to the time when they were declared). If the match is found, the rule is executed and result is returned. If the match is not found, result is returned.

Here's the algorithm of matching execution context against a rule:

  • If the rule doesn't contain :* then it matches only when it is equal to execution context.
  • If the rule does contain :* then it is matched using :* as a non-greedy quantifier

For example:

a,b,c matches a,b,c
a,*,c  matches a,b,c
a,*,c matches a,b,b1,b2,c
a,*,c matches a,c
a,*,m,*,k,b,*,c matches a,k,b,m,k,b,c

Clone this wiki locally