On 27/01/12 18:27, jcbollinger wrote:
>> Anyway, I need to get back to work, I'll try to say more in a later email.
> 
> 
> That would help me to determine what I think about the idea.  As it
> is, I suspect I don't quite understand what you are hoping to
> accomplish with it.

Ok, here's a couple of examples.  Apologies for the length.


1. Packages

Let's say I'm writing a module called foo.

It uses a package called 'libfoo'.

Therefore I want to make sure this package is installed before the module's
configuration is applied.  I use the pattern where I define separate classes to
handle install and config, and chain them together:

 class foo::install {
    # Make sure libfoo is installed
    package { 'libfoo': ensure => present }
 }

 class foo::config {
    # adjusts foo.conf
    # ....
 }

 class foo {
   include foo::install
   include foo::config

   Class[foo::install] -> Class[foo::config]
 }


Bingo! Works fine.  Until I want to add another 3rd party module which needs
libfoo.  Now I have to move the package declaration out to a shared class and
update both modules to fit: maybe the other module wants a specific version,
other install_options, or whatever.

Sounds simple in a short example like this, except, that:

 - I've been forced to customise what should be an 'off the shelf' module,
 - I have to figure out what the shared class/module should say
 - and fix an arbitrary name for it
 - I've therefore hard-wired strong coupling in both to my shared class
 - I've added potential for refactoring breakage
 - and more of the same sort of problems when scaling up

Genuinely reusable modules seem nearly impossible to write as it stands. If I
want to publish my module on Puppet Forge, then the shared::libfoo module must
be published too.  Except it might not agree with other published modules' ideas
about what a shared::libfoo should declare, or even be called, and so it is not
typically re-usable without refactoring.

Or, I don't publish it, leave a dangling dependency on a class called
shared::libfoo.  I am still hard-wiring a name, but not what it does. I have to
tell users the name the module they must define, and a list of requirements to
put in it on our behalf.

Or, I just don't define anything about libfoo except in the documentation.
Which seems the most practical thing to do, assuming that things break
intelligibly if libfoo is absent, but this really amounts to giving up and
moving on.

Or, maybe there currently are better ways than all the above - but if so I'm
unclear what.




Now imagine we could simply assert a requirement on a package, without actually
managing it.  For the sake of this example, I'll invent a syntax for
"Assertions" similar to that used for Virtual  Resources which use an '@' sigil
prefix. I'll arbitrarily use a '+' instead:

  +package { 'libfoo': ensure => present }


This just means "ensure libfoo is installed".  It changes nothing about the
target system. It does not mean "go and install the 'libfoo' package".  It does
not mean "I own the 'libfoo' package resource, and I'll fight anyone who tries
to say otherwise".

Therefore, this type of assertion can be repeated multiple times in different
modules.  Possibly in slightly different ways - with extra attributes, etc.
Puppet should just check they are all consistent, and fail if they aren't, or if
the net requirements are not met.  I don't know enough about Puppet internals to
say for sure, but as described in my previous email:  because the Assertion
changes nothing, I hope this would be relatively easy to implement.

Now I can write my module using an Assertion instead:

 class foo::install {
    # Make sure libfoo is installed
    +package { 'libfoo': ensure => present }

    # ...
 }

...and I no longer have to find the common ground between modules which use
libfoo, and/or modify the modules to use the shared declaration.

Also, we have lost an explicit dependency on a shared module arbitrarily called
'shared::libfoo' which merely declared:

   package { 'libfoo': ensure => present }

So I no longer need to publish this shared module and either dictate to, or
negotiate with with potential users of my module about the intersection of our
requirements.  Nor do I need to omit this requirement entirely (which might be
the only practical alternative).

Yet I am still checking the prerequisites are there.

Of course, I may still have to create a package which actually does the
appropriate package install. Or maybe not? Perhaps my provisioning system does
that for me, and I can skip that step?  Either way, the knowledge that my system
is still checking the prerequisites are there.

If my prerequisites are missing, I would hope Puppet would give helpful errors
showing what needed what, and I can add a declaration to install the right
packages in a top-level "glue" class. But means we can avoid hard-wiring
arbitrary module names into the component modules.

In summary, this would be simpler and more effective than any existing Puppet
pattern I know about.




2. Creating user accounts

Another example, which was the topic of my earlier post "constraint checking".
Say I want to create a custom resource which sets up user accounts for me in a
manner of my choice.


  define user_account(
    $name,
    $home,
    $shell,
    $passwd,
    $uid,
    $gid,
    $groups,
  ) {
    # I want to validate $home, $shell, $groups exist and are usable...

    # This is a classic case where one is tempted to use this anti-pattern
    # to define something usable if it doesn't exist:
    if !define(Group[$gid]) {
       group { $gid: ensure => present }
    }

    # If I can't do that, perhaps I can just depend and hope it's picked up
    # elsewhere?
    require File[$shell]

    # ... except this can't say anything about $shell, like
    # "it must be executable".


    # .... do other stuff here ....

    # Now define the user resource which creates the user
    # (but in my tests, does not seem to check the requirements
    # exist.)
    user {$name:
      ensure => present,
      home => $home,
      shell => $shell,
      passwd =>
      uid => $uid,
      gid => $gid,
      groups => $groups,
    }
  }



Imagine we could use Assertions as described above.  Validating the parameters
is now straightforward:

  define user_account(
    $name,
    $home,
    $shell,
    $passwd,
    $uid,
    $gid,
    $groups,
  ) {
    # Ensure $home, $shell, gid, $groups exist

    # We want to own this one
    group { $gid: ensure => present }

    # These are shared
    +group { $groups:  ensure => present }

    # This is shared
    +file { $shell:
      ensure => present,
      mode => '0744', # ...if only I could merely say "a+x" here
    }

    # ... do other stuff ...

    # Now define the resource:
    user {$name:
      ensure => present,
      home => $home,
      shell => $shell,
      passwd =>
      uid => $uid,
      gid => $gid,
      groups => $groups,
    }
  }

And as above:

 - only User[$name] or Group[$gid] will ever conflict (which is what we want)
 - there are no shared module dependencies forced into existence
 - we are not risking silly mistakes like shell => '/bin/bqsh' or
   groups => ['weel']


There's more I could say, but I hope this gives the basic idea.


Cheers,

N

-- 
You received this message because you are subscribed to the Google Groups 
"Puppet Users" group.
To post to this group, send email to puppet-users@googlegroups.com.
To unsubscribe from this group, send email to 
puppet-users+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/puppet-users?hl=en.

Reply via email to