Virtual Parameter Pattern

This is part of a series of posts on puppet patterns. If you can spot any flaws or think of better solutions let me know!

The aim of this pattern is to reduce coupling between modules, or rather flip the coupling in the right direction. Imagine you have a module that can collate a number of parameters from other modules, for example mcollective has facts like facter, any module can add a fact to mcollective’s list so that it can be queried by a client (the version of the code you’ve deployed for example).

The first way to solve this is to have an “mcollective::facts” class which has all the facts preset as variables. This isn’t good for two obvious reasons, the list of facts is preset, and the mcollective module now contains references to other modules whether through references to variables in specific classes, calling functions in another module, or knowledge of how that module works (hiera keys etc)

class mcollective::facts {
  fact_one = $other_module::foo::var
  fact_two = function_from_another_module('var1')
  fact_tree = hiera('module_specific_key')

  file { 'mcollective facts':
    # write facts
  }
}

The second way is to have an “mcollective::fact” defined type that updates a file line (or whatever needs doing to set the parameter). The classes in other modules that want to set facts can now just define an instance of the fact class and pass the key and value in as parameters. This is good because the coupling is now reversed so that the mcollective module isn’t dependent on all the modules that want to use it. But, it’s now almost too tightly coupled, you can’t just have random file_line (or concat, or whatever) resources floating around the catalogue, there are dependencies that need to be defined, for example the file has to exist before you can set a line in it, and the service might need to be restarted once you’re set a new fact. So you now have to define before/require and notify/subscribe relationships between mcollective classes inside the classes that are setting facts. Your classes now have to have knowledge of how the mcollective module works which means you have to set it up properly, but it also means that if anything changes in the mcollectice module it could potentially break all the modules using it.

define mcollective::fact (
  $fact_value,
) {
  file_line { "mcollective fact $name":
    # write fact
  }
}

class foo {
  # class stuff

  mcollective::fact { 'foo-fact':
    fact_value => 'baz',
    require    => File['mcollective facts'],
    notify     => Service['mcollective'],
  }
}

The solution is to use virtual resources. We keep the “mcollective::fact” class but we make the file_line resource virtual. This way we can define facts in whichever classes we want, but all the logic is encapsulated inside the mcollective module. Resource collectors can grab all the virtual parameters and handle the dependencies. Use tags to make sure we only grab the file_line resources we actually want.

define mcollective::fact (
  $fact_value,
) {
  @file_line { "mcollective fact $name":
    # write fact
    tag => 'mcollective_fact'
  }
}

class mcollective::facts {
  # setup facts file and directories etc
  
  File['mcollective facts'] -> File_line <| tag == 'mcollective_fact' |> ~> Service['mcollective']
}

class foo {
  # class stuff

  mcollective::fact { 'foo-fact':
    fact_value => 'baz',
  }
}

Now the foo class only needs to know about one resource, and the mcollective module doesn’t need to know how it’s being used (if at all). If we want to change how mcollective manages it’s facts it doesn’t matter, we can change everything behind the scenes without having to change any other modules.

This pattern can be used in many other situations, it doesn’t just have to be facts, files and file_lines

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s