On 2014-18-06 22:44, John Bollinger wrote:
On Wednesday, June 18, 2014 4:22:16 AM UTC-5, Trevor Vaughan wrote:
Hi Felix,
Yes, I'm very aware that the current recommendations are to very
cleanly model your system to be able to non-ambiguously define your
catalog.
However, there have always been arguments that the Puppet language
should be declarative throughout which makes for more maintainable
and intuitive manifests overall.
If you go look for posts surrounding this subject, I'd say that the
consensus is because reflection just doesn't work, not because it
shouldn't be there.
To the extent that I get to say what the consensus is -- and I have been
outspoken enough on the topic to suppose I have helped shape that
consensus -- I'd say it's that most anything dependent on evaluation
order does not work reliably, and reflection shouldn't be there because
it cannot be relieved of evaluation-order dependencies. (Sorry, Henrik,
if I'm getting my different orders mixed up.) To put it another way,
I'd say reflection *can't* work.
Agree, can never be made reliable. (And evaluation order it is).
If, however, some constrained context could be established in which to
perform reflection, then perhaps some form of reflection could be
introduced. For example, it might be possible to inspect which classes
are in the catalog from a construct wherein, or at a time when, no
further classes can be added. Adding bona fide resources would be
allowed (else what would be the point?), but they would need to actively
be prevented from adding classes.
Frankly, it can solve a LOT of problems for you and make the system
designer's life a lot easier.
It does not solve /any/ problems if it doesn't work reliably, and
general reflection cannot work reliably.
Agree again.
Scenario 1: A requires B if C but not otherwise.
If I don't have reflection in this case, then I have to have
documentation that says, hey, if you put A and C together on a
system, remember to add B! This is prone to error and is something
that the code should just "take care of" for me. I'm aware that you
could use a role/profile model to take care of this but you're just
making people write extra code for no really good reason except that
the language doesn't actually address this issue. (I'll rant about
the irritation of figuring out what code is doing in a role/profile
system later).
Scenario 2: A requires B if module B is present but do something
else otherwise.
This one is more about system introspection I suppose but we have
all of these lovely Module Forge description files and we can't use
them in the language! I would love to be able to do the following:
if $::module_mysql <= '1.2' { include 'mysql' } else { service {
'mysql': ensure => running, enable => true } }
You can then also use $::module_mysql to set up automatic Hiera
hierarchies that run from version down to a default
(hieradata/mysql/1.0, hieradata/mysql/0.9, hieradata/mysql/default)
and life is magic and wonderful when modules on the forge change.
Scenario 1 is obviously what we're referring to in this thread and I
think, with the way most cloud component architectures seem to be
designed, the burden is being placed on the system user as opposed
to the system designer in too many cases. And, frankly, some times
you just need to get something working and you hit a point where you
realize that you'd have to redesign the system to avoid using
reflection and you just don't have time for that or it is going to
add a LOT of unnecessary code.
Puppet already has a tool for separating the details of a resource from
the decision about whether to include it in the catalog: virtual
resources. Those decisions can be tied to whether a given class is
declared by having that class realize or (especially) collect the
resources. In many cases that can be done via a wrapper class or
definition if the target class itself must not be modified.
Here's an idea: Collectors are already processed very late, so maybe
they are late enough -- or could be /made/ late enough -- to provide the
kind of context needed for safe reflection. One might then write
something along these lines:
Firewall::Rule<| title == 'whatever' and defined == 'firewall' |>
@firewall::rule { "whatever":
# ...
}
Of course, the collector wouldn't need actually to be close to the
resource declaration, and it might be more general than just one
resource, but it could be right there with the resource declaration to
provide something much like an if defined() effect.
The only thing this does is to move the problem to a new "phase".
Since the purpose is to add resources, naturally you can add defined
types, which in turn creates many resources. The set of rules then
operate on a moving target (constraint solving).
For that to be safe though, it would be necessary to prevent the
collected resources from adding classes to the catalog, probably by
failing the catalog if any of them try to do so. That restriction might
be reserved for when the 'defined' key is used in the collector's select
expression, provided that such collectors were processed after all others.
I don't think we absolutely have to protect classes from being included.
In a way they are just resources only singletons.
I am thinking that a module can specify constraints as a predicate
(either on other modules being present, other classes, or resources, or
indeed on some abstract "capability"), and if the predicate becomes true
at the end of the catalog production, then it includes one class from
the module.
This adds a much asked for feature; autoinclude. Inclusion in the
catalog by just adding a module to the modulepath. (This is the reverse
dependency I was talking about earlier).
In all of these cases, some kind of constraint solver ability is needed
across the new feature, queries and the forming of dependencies (since
you most certainly need to also have control over the order in which the
"injected" classes/resources are later applied.
The other approach I can think of (I wrote about it earlier) is that you
make explicit statements about things that must have been evaluated
before "you" can be evaluated (i.e. "I need these Promises fulfilled /
defined before I can say what I want", and modules are given the ability
to define such promises. This (as John also points out) requires a
context in which a resolution of available Promises can take place
reliably). Maybe a Promise is nothing else but a class, but that you can
tie your evaluation to the outcome to the evaluation of one or many
other classes. (This is an area I hope to be able to work on ideas after
we have released puppet 4).
Regards
- henrik
--
Visit my Blog "Puppet on the Edge"
http://puppet-on-the-edge.blogspot.se/
--
You received this message because you are subscribed to the Google Groups "Puppet
Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to puppet-dev+unsubscr...@googlegroups.com.
To view this discussion on the web visit
https://groups.google.com/d/msgid/puppet-dev/lnt621%24n91%241%40ger.gmane.org.
For more options, visit https://groups.google.com/d/optout.