Classes vs. Definitions - how to get closer to common design patterns

0 views
Skip to first unread message

jerico

unread,
Oct 11, 2008, 12:12:29 AM10/11/08
to Puppet Developers
Hi Puppet Developers!

I have some real practical problems with the maintainability and
readability of puppet code. Here are some thoughts about (backwards
compatible) language syntax additions that IMO could greatly help to
clean up puppet code.

I have to say beforehand that I'm not able to implement my own
propositions (ouhh). But maybe some puppet architect finds my ideas
interesting anyway more as a kind of teaser...

http://projects.reductivelabs.com/issues/show/1645

Here some superficial discussion I had on #puppet to validate the
idea:

[00:11] fujin: jerico: considering that your changes would break
backwards compatibility, and require a large number of users' manifest
rewrites.. it'd probably only ever be changed with a toggle option
[00:11] fujin: jerico: what problems do your changes fix? ;}
...
[00:23] jerico: fujin: Why would it break backwards compatibility? I
consider backwards compatibility in my feature request.
[00:24] jerico: fujin: The benefits are that you could avoid global
variables (which you cannot currently if you use classes with
templates in them).
[00:25] jerico: fujin: I think it is out of question that global
variables are a very bad idea. They are against the most basic design
principles.
[00:25] jerico: fujin: You could use definitions instead. But they are
not meant for singleton use and they do not let you define instance
variables as classes do.
[00:26] jerico: fujin: I also think that a merged type would be more
intuitive for newcomers. (Specially the host of us who have an OO
background).
[00:27] jerico: fujin: These are quite some benefits, don't you think
so? And as I think that you can have all this with full backwards
compat... I thought it would be a good idea.
...
[00:34] fujin: global variables are one of the very basic design
prinipals relied upon by many external node tools
[00:34] fujin: you're kinda throwing some heavy OO ideas into a
declarative, imperative manifest syntax
...
[00:35] jamesturnbull: jerico: I'm thinking about it - not sure I am
sold
[00:35] jamesturnbull: jerico: but lak is probably the best one to
discuss it with -- and on the -dev list
[00:36] fujin: mm, global variables may be frowned upon in your OO
language of choice, but here they are very useful for abstraction
[00:36] fujin: It's top-scope, always inherited imperation
[00:36] fujin: (imperation a word?)

These were the basic arguments I think. The rest was a little
repetitive.

Luke Kanies

unread,
Oct 30, 2008, 4:32:36 PM10/30/08
to puppe...@googlegroups.com
On Oct 10, 2008, at 11:12 PM, jerico wrote:

>
> Hi Puppet Developers!
>
> I have some real practical problems with the maintainability and
> readability of puppet code. Here are some thoughts about (backwards
> compatible) language syntax additions that IMO could greatly help to
> clean up puppet code.

For the record, changes that require deprecating existing syntax
doesn't really qualify as backward compatible. :)

>
> I have to say beforehand that I'm not able to implement my own
> propositions (ouhh). But maybe some puppet architect finds my ideas
> interesting anyway more as a kind of teaser...
>
> http://projects.reductivelabs.com/issues/show/1645
>
> Here some superficial discussion I had on #puppet to validate the
> idea:
>
> [00:11] fujin: jerico: considering that your changes would break
> backwards compatibility, and require a large number of users' manifest
> rewrites.. it'd probably only ever be changed with a toggle option
> [00:11] fujin: jerico: what problems do your changes fix? ;}
> ...
> [00:23] jerico: fujin: Why would it break backwards compatibility? I
> consider backwards compatibility in my feature request.
> [00:24] jerico: fujin: The benefits are that you could avoid global
> variables (which you cannot currently if you use classes with
> templates in them).
> [00:25] jerico: fujin: I think it is out of question that global
> variables are a very bad idea. They are against the most basic design
> principles.

Puppet will always have something resembling global attributes -- at
the least, client facts are global.

The key here is that these global variables aren't writable, which
completely changes whether they're such a bad idea. They're much more
like global constants than global variables.

>
> [00:25] jerico: fujin: You could use definitions instead. But they are
> not meant for singleton use and they do not let you define instance
> variables as classes do.
> [00:26] jerico: fujin: I also think that a merged type would be more
> intuitive for newcomers. (Specially the host of us who have an OO
> background).

I.e., more useful to programmers, but not necessarily so much for
sysadmins.

It's true that you're not the first person to make this request or to
find the distinction either confusing or arbitrary, but don't discount
the fact that Puppet isn't written by and for developers, it's for
sysadmins, surprisingly few of whom have much OO experience.

>
> [00:27] jerico: fujin: These are quite some benefits, don't you think
> so? And as I think that you can have all this with full backwards
> compat... I thought it would be a good idea.
> ...
> [00:34] fujin: global variables are one of the very basic design
> prinipals relied upon by many external node tools
> [00:34] fujin: you're kinda throwing some heavy OO ideas into a
> declarative, imperative manifest syntax
> ...
> [00:35] jamesturnbull: jerico: I'm thinking about it - not sure I am
> sold
> [00:35] jamesturnbull: jerico: but lak is probably the best one to
> discuss it with -- and on the -dev list
> [00:36] fujin: mm, global variables may be frowned upon in your OO
> language of choice, but here they are very useful for abstraction
> [00:36] fujin: It's top-scope, always inherited imperation
> [00:36] fujin: (imperation a word?)
>
> These were the basic arguments I think. The rest was a little
> repetitive.


--
A government that robs Peter to pay Paul can always depend on the
support of Paul. -- George Bernard Shaw
---------------------------------------------------------------------
Luke Kanies | http://reductivelabs.com | http://madstop.com

Gunnar Wrobel

unread,
Oct 31, 2008, 6:28:30 AM10/31/08
to Puppet Developers
So taking that fact into account means one should refrain from using
puppet for more complex administration work?

Isn't there a possibility to somehow please both sides?

I'm having the same problem as I'm establishing a groupware setup with
puppet and I have no reasonable solution for keeping track of all the
variables so far.

Cheers,

Gunnar

jerico

unread,
Oct 31, 2008, 1:06:34 PM10/31/08
to Puppet Developers
Hi Gunnar,

> So taking that fact into account means one should refrain from using
> puppet for more complex administration work?

No not at all!! If this was the impression you gained from my post
then I really have to apologize. I think there is no configuration
management tool better placed than puppet today for managing complex
systems. And puppet is also maturing in other important areas (like
memory usage for instance). I wouldn't invest time into trying to
improve puppet if I wasn't fully committed to this tool. :-)

> Isn't there a possibility to somehow please both sides?

I for myself have established very rigid coding rules that work around
most of the issues I mentioned. As Luke already suspected I heavily
rely on definitions and use classes only in some very specific and
well defined situations.

Mainly I use *definitions* for any kind of configuration, also on node
level. By convention I use the node name as instance name for node-
level configuration. I thereby loose inheritance and instance
variables. The former is not important to me as I use only one single
Linux distribution on all of my nodes. IMO inheritance is only really
important when you work with different distributions at the same time.
And in most cases I can live without public class variables. I only
use them where I use classes and so far this revealed itself as being
very consistent anyway. I further disallow any usage of global
variables within definitions. This gives me full encapsulation.

NB: My system is a solution very specific to my needs and not a
general recommendation. Especially not if you use (or plan to use)
several Linux distributions.

I use *classes* only for one purpose: giving per-package namescope to
definitions and provide some way to document numbers as constants
(=scoped class variables)...

Distribution packages (not the puppet package type!) are my smallest
configuration unit. I have exactly one puppet module per Linux
distribution package, named after the package. This may again not be
appropriate if you use more than one distribution as package names
vary! In its init.pp this module contains exactly one class (named
after the package). The class contains exactly one definition always
named "state" that defines the per-node state of the package
(including dependencies, the package type instance and any service or
node-wide configuration coming with the package). The state-definition
takes those parameters that were global variables if I used classes as
recommended by the puppet documentation. To me encapsulation and
maintainability is more important than the (in my eyes) useless class/
definition distinction. That's why I deviate from puppet
recommendations here. Example apache2::state(...) .

Again scoped by the package class I have definitions of *package-
services* that define anything that is a "real" definition in the
sense of the puppet documentation and may be instantiated (=consumed)
by other package's state or service definitions. These are not the
services Luke speaks about but rather public interfaces to
configuration functionality offered by a specific Linux package.
Example: apache2::virtual-host(...)

I had (and still have) some troubles with encapsulating dependency
(require, before) and subscription (notify, subscribe) relationships.
But I found a workaround with global defaults that (by convention)
only appear within the types concerned and work in most cases. Some
few cross-definition dependencies still remain, but very few. I would
rather not like to post it here as it's too hacky for now and I'd
prefer it to mature a little bit first.

To group packages into "node aspects" (like "webserver", "desktop",
etc.) I use definitions again that use the node name as instance name.
This gives me the possibility to route variables from the global
context into my state- and package-service definitions without using
any global variable whatsoever.

Node definitions may not contain anything else than classes, "node
aspects" or global usage of package-services that cannot be attributed
to any specific "consuming" package (like the node's "zone" and
"interface" definition in my firewall configuration).

My node definitions typically look like this:

node "xyz" {
$node-name="xyz"
$node-ip="a.b.c.d"
$admin-user="xyz"

include sudo
sudo::state( "$node-name": admin-users => $admin-user, lecture =>
true, ... ) # this is a node-level package state definition

include ssh
ssh::state( "$node-name": allowed-users => $admin-user )

include shorewall
shorewall::state(...)
shorewall::interface( "$node-name": zone=>"net", interface=>"eth1" )
# this is a node-level package service
# similar use of package services will mainly occur within state
definitions or within package-service definitions that require other
packages' services
# package classes may provide public parameters like this $ssh::port
to be consumed by other packages (state or services) as well.

webserver { "$node-name": ip => $node-ip } # this is a node aspect

...
}

Now comes the definition of the node aspect:

define webserver($ip, $...) {
include apache2
apache2::state(ip => $ip, ...)

...
}

A packages' init.pp looks like this:

class apache2 {
# this is a scoped variable (or better scoped constant as I use
it!!) that
# will never be changed and may be used to self document numbers
# in other contexts like apache2::ssl-port rather than using them
hard-coded
ssl-port = 443

define state($ip, $...) {
package{...}
service{...}
file{... template() ...) etc...
...
}

define virtual-host($name, ...) {
}
}

Global defaults only appear in a global context (site.pp) where they
really describe something that is valid for the whole system (like
default file permissions or something like that).

I am quite happy with this solution now and it already revealed itself
as being quite flexible and a lot easier to maintain when I had to
refactor something.

I just didn't post my solution here as it is probably better placed in
a "Puppet Recipe" and above all it is not in line with Luke's
definition of class and definition usage and its respective
documentation. So I am afraid that I'd cause more confusion than help
others if posting this in another place.

If you follow the ongoing discussion you'll probably end up with a
more "official" recommendation that will be supported by the puppet
developers as well. (My current solution will definitely not be
"officially" supported.)

Florian

Luke Kanies

unread,
Nov 4, 2008, 12:52:40 PM11/4/08
to puppe...@googlegroups.com
On Oct 31, 2008, at 5:28 AM, Gunnar Wrobel wrote:

> So taking that fact into account means one should refrain from using
> puppet for more complex administration work?

I certainly hope not. There will always be problems that any given
tool can't handle well, and there are certainly classes of problems
that Puppet can't do well with right now, but it's my hope to always
strive to reduce that list of unaddressable problems.

>
> Isn't there a possibility to somehow please both sides?
>
> I'm having the same problem as I'm establishing a groupware setup with
> puppet and I have no reasonable solution for keeping track of all the
> variables so far.


Can you please explain in more detail what your issue is?

One of the frustrations I have as Puppet's maintainer is that too many
people are willing to accept it as it is, rather than laying out their
frustrations and ideas of improvements. I'm thick-skinned, and I'd
bet I'm a harsher critic on Puppet than *nearly* all of you. If there
are problems you can't solve well with Puppet, please start a thread
about the problem on puppet-dev.

The only way Puppet can continue to grow to solve harder problems is
if we know what problems it can't do well right now.

--
The people who are regarded as moral luminaries are those who forego
ordinary pleasures themselves and find compensation in interfering
with the pleasures of others. -- Bertrand Russell

Reply all
Reply to author
Forward
0 new messages