On 2014-08-07 18:09, John Bollinger wrote:
On Sunday, July 6, 2014 8:26:28 PM UTC-5, henrik lindberg wrote:
Thank you everyone that commented on the first RFC for Resource
Defaults
(and Collection) - some very good ideas came up. I am creating a new
thread because I wanted to take the Collection part in a separate
discussion (honestly I would like to burn the entire implementation -
but that is separate topic), and I want to present some new ideas about
defaults triggered by David Schmitts idea about defaults applicable to
only a resource body.
Let me back up and first describe a problem we have in the grammar.
The egrammar (as well as the current grammar in 3x) tries to be helpful
by recognizing certain combinations as illegal (an override that is
virtual or exported), a resource default or override cannot specify a
title. This unfortunately means that the grammar has to recognize
sequences of tokens that makes this grammar ambiguous and it has to be
solved via operator precedence tricks (that makes the problem show
up as
other corner cases of the grammar).
I agree that that is a problem. Are you saying that the 3.x grammar
also has such ambiguities, or just that it also rejects some token
sequences (as any grammar must do)?
3x grammar does not have this specific problem; instead it has a dozen
other problems because people worked around ambiguities by creating
sub-trees of the expression tree for different kinds of expressions. It
is just horrible.
(This is a classic mistake of trying
to implement too much semantics in the grammar / parser).
So...
What if we simply made the three resource expressions (create resource,
set resource defaults, an resource override) have exactly the same
grammar, and push of validation to the static validation that takes
place and the runtime.
Basically the grammar would be (I am cheating just a little here to
avoid irrelevant details):
ResourceExpression
: At? left_expr = Expression '{' ResourceBodies ';'? '}'
;
ResourceBodies
: ResourceBody (';' ResourceBody)*
;
ResourceBody
: title = Expression ':' AttributeOperations ','?
;
AttributeOperations
: AttributeOperation (',' AttributeOperation)*
;
AttributeOperation
: AttributeName ('=>' | '+>') Expression
AttributeName
: NAME | KeywordsAcceptableAsAttributeName
;
# Details here irrelevant, meaning is: virtual or exported
resource
# AT is the '@' token
At
: AT
| AT AT
| ATAT
;
So, how are the three kinds expressed? Notice that a title is required
for each ResourceBody. So we are basically going to handle different
combinations of left_expr and titles. We simply evaluate the left_expr
at runtime and treat the combinations of the *resulting* type and type
of title:
If left (result) is a String, it must be the name of a Resource Type.
The title works as it does now in 3x. In addition the title (of one
resource body) may be Default, which sets the defaults for this
resource
expression only (all bodies in the same resource expression) - i.e.
"Schmitt style".
notify { hi: message => 'hello there' }
file {
default:
mode => '0331',
owner => 'weird';
'/tmp:foo':
content => 'content 1'
}
If the left is the keyword 'class', it works the same way as for when
the left is a String but defaults can now only set defaults for meta
parameters since there are no other attributes that work safely across
all classes. (Yes you can do this today with Class { } in 3x)
class { 'a::b': param => value }
class { default: audit => true }
If the left is Type[CatalogEntry] (i.e. a resource or class reference),
the meaning changes to either a default or an override.
I'm not fully up to speed on the type system, but surely if the left
side evaluates to a resource or class *reference* then the statement can
only be an override. Right? For it to express resource defaults, the
left side must evaluate to a type -- either Class or a resource type.
The problem is that this happens before anything is evaluated, the
grammar is making decisions on tokens, not what the result evaluates to.
What I want to do is to push the validation off to the runtime (where
it is known what the expressions evaluate to). Doing so requires that
the resource expression is unambiguous - now I have to treat the
following { } body as a kind of operator having a particular precedence.
To be able to do this, I would like all resource expressions to have a
title, as that makes the expr { expr : part unique
and cannot be confused with an expression followed by a hash.
A resource reference is essentially expr [ expr ], it now considers any
such expression as one that should result in a type reference, but it
can not know for sure statically - it must evaluate the expression to
see what it gets (yet, the grammar contains checks that cannot answer
this correctly). (it is the remaining messy part of the egrammar).
An example is
probably easier that lots of words to describe this:
Define the defaults for all instances of the resource type Notify:
Notify { default:
message => 'the default message'
}
Override the message for the notify resource hi.
Notify { hi:
message => 'ciao'
}
If the left type is instance specific it is now an error (since a title
followed by ':' is required to create a less ambiguous grammar) - if we
allowed this, a user could write:
Notify[hi] { bye: message => 'adieu' }
we allow a very odd statement (and the reason why resource overrides
currently does not allow a title in its "resource body"). So, an error
for this case.
I tried hard to not like this, but then I recognized how congruent it is
-- or could be -- with one of my pet ideas: resource constraints. Felix
implemented part of that idea, but as I understand it, he handled only
the diagnostic part (recognizing whether constraints were satisfied) and
not the prescriptive part (updating the catalog, if possible, to ensure
that declared constraints /are/ satisfied).
I don't much like the idea of "overriding" resource declarations as
such, but it's not such a big leap to reposition that as simply
declaring additional requirements (i.e. constraints) on resources that
(may) be declared elsewhere. We get the rest of the way to a limited
form of prescriptive constraints by collapsing this variety of resource
overrides with resource declarations. In other words, consider these
two expressions:
file { '/tmp/hello': ensure => 'file' }
File { '/tmp/hello': ensure => 'file' }
What if they meant exactly the same thing? Specifically, what if they
meant "there must be a resource in the catalog of type File and name
'/tmp/hello', having the value 'file' for its 'ensure' property"?
Either one would then insert such a resource into the catalog if
necessary, and afterward attempt to set the (then) existing resource's
'ensure' parameter.
I say "attempt to set" the existing resource's parameter, because I
think we still need to forbid modifying a value that was set (to a
different value) somewhere else. And that's more complicated than just
checking for undef, because it may be that undef is itself the
intentionally assigned parameter value, so that it would be an error to
change it. It also might need to be more permissive for subclasses and
collectors.
Note that collection currently can modify any already set value on all
kinds of resources (regular, virtual and exported) at any point
throughout the evaluation. How is it that these "rules" are given such
mighty powers when a rule such as "File['tmp/foo'] { owner => x }" is
not allowed to override a set mode of the same file? (I understand the
need to guard against typos and unintentional changes). Basically I see
File[id] { x => y } as the same expression as File <| title == id |> { x
=> y }.
We could go farther with that. If it seems wasteful to have two
different forms of the same statement, then we could apply additional
semantics to one or the other. For example, perhaps the lowercase form
could implicitly declare all unmentioned parameters as undef, with the
effect that they could not be overridden to anything different.. I
don't know whether that would be useful, or whether there is some other
behavior that would be more useful. Perhaps it would be best to just
let the two forms be equivalent, or maybe even to deprecate one.
Anyway, a few corner cases exist. A +> is only allowed if it is a
default or override expression.
+> is a bit of a dark horse in the regime of overrides, as modifying a
previously-declared parameter value is inherent in its design. On the
other hand, it is currently useful for overrides only in subclasses and
collectors, where modifying a declared value is considered acceptable.
I don't think it's a major issue that evaluating a statement containing
a plussignment may yield an error, as long as the meaning of the
statement is not itself in question.
I like the direction this is going! You are absolutely right that
basically all these expressions (resource attribute settings,
"overrides", defaults) basically define a set of rules that together
should define the resulting resources and the values of their attributes
(call them constraints or rules).
This would mean that +> would be perfectly valid even in definitions
that does not modify something inherited, instead it basically says "use
both what someone else said, and what I said" (if the parameter can hold
multiple values).
Do you like these ideas? Is it worth trying to make this work to try it
in practice? (it will take 1-2 days for the implementation, and a bit
more to fix all breaking tests).
As I said before, I tried to dislike them, but I like them despite
myself. Especially if you're willing to accept my extension.
Yes, I think your extension was that there is no difference between a
LHS that is a type (e.g. Notify), and a lower case name of a type (e.g.
notify). I agree completely.
My own main issue with the idea is that it makes code backwards
incompatible; you cannot write a manifest that uses defaults and
overrides in a way that works both in 3.x and 4.x. (Or, I have not
figured out a way yet at least).
There is one way that works across all versions; calling functions -
e.g. something like set_defaults(Type[CatalogEntry] type, Hash[String,
Any] values), and override(CatalogEntry resource_ref, Hash[String, Any]
values). This obviously works because it does not rely on a particular
syntax.
That's the main reason I tried to dislike the idea, and it's not
negligible. It seems like the best alternative for that would be to
select an alternative syntax for type expressions that cannot be
confused with resource references. Doing so would remove some of the
reason for the above idea, but would not render it moot.
Even with functions added, it still means a change, and since it from
the point of view of a user does not provide immediate value (just
translation (a.k.a pain)) the entire idea may be a hard sell. I do think
we can put this and other ideas regarding queries (collection) together
in such a way that it justifies the required changes. Not just sure yet
on the entire package...
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/lphjit%24fvn%241%40ger.gmane.org.
For more options, visit https://groups.google.com/d/optout.