Hi Luke,
Luke Kanies schrieb:
> These emails are getting really long.
I think that's normal for such a generic and complex topic. And I see
that we both are investing a lot of time.
So please don't feel obliged to respond if you are no longer interested
in the discussion for whatever reason. I'd completely understand that
and I'd not at all be offended!!
> My main conclusion here, though, is that you're talking about a
> language that bears not a lot of resemblance to Puppet, in the end,
> and if this is something you want, then I recommend starting by
> essentially forking Puppet's parser and working from there.
I didn't say that I want all this in practice. As a user I'd be
completely happy with a very small change to the language (=class
parameters).
I think we were discussing: "Where could we go if there weren't
compatibility limitations?" My rationale wasn't one of forking puppet
but of discussing some idea of an "ideal" configuration language. The
practical result I hope for is to avoid proliferation of (in my eyes)
"difficult to learn" language constructs or some "bad" puppet recipes
that I saw in the documentation.
> Node, class, and define are all merged into a single syntactical
> element. This element is effectively equivalent to the existing
> 'define' element, in that it accepts parameters and is unique only for
> a given name. That is, you can always create a new instance of this
> element, as long as you give it a different name. When used with no
> name, the name is automatically set to the name of the element itself.
Everything correct, except: The default instance name is not the name of
the element itself but the name of the parent instance.
> Would it be:
>
> webserver { : }
>
> Or maybe:
>
> webserver { webserver: }
>
> This is actually how Puppet started, and I got rid of it because of
> how obviously unobvious they are.
See the examples in my last post... The syntax would be:
bundle webserver_node inherits node {
webserver { par1=>..., par2=>... }
database { par1=>..., par2=>... }
}
Or if you have no parameters then:
bundle webserver_node inherits node {
webserver
database
}
And then in site.pp:
webserver_node { "www.example.com": }
In this example the implicit names of the webserver and database
instances are "web.example.com".
I don't see anything unobvious here.
> You haven't made it explicit, but I assume you mean that specifying a
> relationship to a class would result in automatic evaluation. That
> is, I could say 'require => Webserver[foo]' and it would automatically
> evaluate that class? Or would I need to evaluate it elsewhere in
> order to specify a relationship?
1) Remember that there is no such thing as today's "class" in the
proposed "bundle syntax".
2) You cannot reference anything that has not been instantiated.
3) For better readability and maintainability there shouldn't be
anything being implicitly instantiated.
The following would raise an error:
bundle mynode inherits node {
webserver { require => Firewall }
}
This should work:
bundle mynode inherits node {
firewall
webserver { require => Firewall }
# This is implicitly the same as:
# firewall { "$name": }
# webserver { "$name": require => Firewall["$name"] } !
}
When you think that there is something you cannot do without an include
then please give me a specific example.
> Introduction of the keyword 'self':
>
> I actually don't know what you're doing here, but you're using it in
> all of your examples, so I figured I'd point it out.
It's just a common way to provide separate namespaces for instance
variables and local variables. I used a syntax similar to php, C++ or
java. In Ruby "@..." does the same.
Currently puppet does not distinguish between local variables and
instance variables.
Doing something like
define(...) {
...
$require = 5
...
}
raises an error. This is bad as it may cause maintenance trouble when
introducing new parameters to a definition in puppet that may already
exist as local variables.
> Templates become resources:
>
> Move templates from a function to a resource to avoid implicit
> consumption of variables. I assume this would involve modifying
> properties to know how to retrieve a template's content when
> necessary. I.e., you'd need to do something like:
>
> template { "whatever": foo => bar }
> file { "/my/file": content => Template[whatever] }
>
> You'd need to do this for every property, otherwise you'd lose the
> current ability of using templates for any rvalue.
right.
> Does this mean that we could have multiple subinstances or whatever
> you call them for a given node? That is, we could have webserver
> { mynode: } and dbserver { mynode: }?
No, you cannot have two "subinstances" for the same physical node.
If webserver and dbserver both inherit from node then an instance for a
given physical host can exist for one or the other but not both. If
you'd try to do something like this then puppet should throw an error.
If you want a single node to take over the roles of webserver and
dbserver at the same time then you'd have to define two resource bundles
(webserver, dbserver) that do not inherit from node:
# Resource bundle 1 (aka today's puppet class/definition)
bundle webserver($fqdn) {
.. some resources ..
}
# Resource bundle 2 (aka today's puppet class/definition)
bundle dbserver {
.. some resources ..
}
# Node definition
bundle my_combined_node_type inherits node {
webserver{ fqdn => $self::fqdn } # $self::fqdn is a facter variable!!
dbserver
}
# Node instance (in site.pp)
combined_db_web_node_type { "mynode": }
> Also, where are facts here? You've mentioned making them no longer
> global constants, but I don't know where they went.
Facts are automatically defined as instance variables of any "resource
bundle" that inherits from "node". This is the only way in which facts
are available (see the example above).
Facter variables have to be explicitly passed down from the node type to
any bundle that needs it (see the above example again). This avoids
global variables and improves encapsulation.
> This seems to be the crux of your argument -- that removing
> syntactical variety is simpler and hides complexity -- but I'm not
> convinced. Puppet's simplicity largely comes from its lack of options
> or complexity -- would you say LISP is simpler or more complex than
> Puppet?
The way you state it, it is not correct. Replace "simplicity" by "easy
to learn" and "maintainable" and you are much closer to what I am after.
IMO a language is "easy to learn" if it uses concepts that are already
known to the majority of users when they start to learn it.
I don't want to reduce syntactical variety. I want to...
1) reduce the number of new concepts that one has to learn to learn puppet.
2) reduce duplicate code and global variables to improve maintainability
and extensibility.
* easy to learn:
The distinction definition/class is uncommon outside puppet and has to
be learned by new users. IMO the usage of the term "class" for something
different than an OO class is also "bad" as it puts new users with OO
background (the majority I guess) on the wrong track. A puppet class is
something very different from an OO class while a puppet definition is
something close to an OO class. All this may be obvious to you as you
have lived with these concepts for years. But IMO it is not at all
obvious to new users.
In the syntax I propose it is no longer a problem but a real learning
aid to think of a "bundle" as an OO "class": "Resource bundles" are
conceptually very close to an OO class.
* maintainable:
The current puppet language provides no way to avoid usage of global
variables within classes and templates and produces code duplication on
node level and above (no clusters, no way to define similar nodes by way
of a "node bundle", see the cluster example below).
The language I propose has no problems with that.
* LISP:
I think we agree that LISP is more difficult to learn for the average
sysadmin than puppet. This comes from the sheer number of features
available in LISP and usage of concepts that are probably unknown to the
average sysadmin. LISP may however be easy to learn for a mathematician
(don't know).
If you think that the proposed language is difficult to learn for an
average sysadmin then please make an example. This would make it easier
for me to understand what exactly you don't like.
> One could argue either way, but I tend toward thinking its
> simplicity makes it potentially more complicated to use. Certainly it
> raises the bar for the programmer who would be involved.
Not agreed (see my definition of "simplicity" or "intuitiveness" above).
What is easy to learn depends exclusively on the prior knowledge of the
target audience and not on any inherent property of the language.
> Having spent the last three months attempting to teach my infant
> daughters how to nurse, I'm not a big believer in intuitiveness.
> Declarative languages aren't intuitive, modeling resources instead of
> files isn't intuitive, asynchronous administration (i.e., commit and
> wait) isn't intuitive; yet people have learned all of these and
> wouldn't give them up, in most cases.
You seem to think that only what we can use directly after birth is
really "intuitive". That's a quite narrow definition of "intuitive". But
yes: If you define it like that then my proposed syntax is completely
unintuitive. ;-) You certainly understand that I didn't claim my
proposed language to be understood by a new-born.
What I wanted to say is that the proposed syntax (at least more than
puppet language today) is based on concepts already known to its target
audience (i.e. based on basic OO principles, no mistakable re-definition
of terms already used in another context).
> I think you are missing a lot of corner cases (or maybe not-so-corner)
> that make it less so. In particular, the common case of a "class"
> with no name or parameter is particularly jarring syntactically:
>
> webserver { : }
See my examples in earlier posts and at the beginning of this message. I
think that's a case I've thought about from the beginning.
> This gets silly on relationships, too; do I do:
>
> require => Webserver[]
>
> or:
>
> require => Webserver[webserver]
Neither one nor the other, see the example at the beginning of this message.
Btw: Giving an instance name for what is a class today (=node-level
singleton) is not at all silly when you define clusters of nodes (see
cluster example below).
Send me a reasonably complex pp-file (with lots of "corner cases") and
I'll try to restate them in "bundle syntax". Maybe this helps you to
better judge the power of the proposed language or helps me to
understand what I am missing.
> Again, I don't think this is an implementation detail -- it's a
> fundamental aspect of how information enters Puppet, and it's
> something users really need to undestand.
If you don't agree then please give a specific example again what
exactly you cannot do if you never heard about entry points or the
origin of host information.
Otherwise it is very difficult for me to understand /why/ I need to
understand such information.
>> bundle full-system-cluster($needs_firewall = true) {
>> if $self::needs_firewall {
>> firewall-and-load-balancer-node { "${self::name}-fw": }
>> } else {
>> load-balancer-node { "${self::name}-lb": }
>> }
>>
>> webserver-node {
>> "${self::name}-web1": ;
>> "${self::name}-web2": ;
>> }
>>
>> application-server-node {
>> "${self::name}-app1":
>> "${self::name}-app2":
>> }
>>
>> db-server-node { "${self::name}-db": }
>> }
>>
>> And now you can instantiate whole clusters with the same
>> infrastructure
>> of containing nodes:
>>
>> full-system-cluster {
>> "production-cluster": ;
>> "staging-cluster": ;
>> "development-cluster": needs_firewall => false;
>> }
>>
> I don't think I understand this at all. Given a node name, how does
> it know that it's a part of the system cluster?
The algorithm to construct the configuration catalog for a node is:
- You resolve all variables.
- You produce a full parse tree of the configuration. To do so you'll
have to identify the top level (=root) elements. These are elements that
- You enter with a given node name, say "production-cluster-web1"
- You look for a node instance with this name. At this point you must
make sure that there is only one node instance with this name otherwise
you raise an error. In the cluster example I gave in my post we have
exactly one node instance named "production-cluster-web1".
- Now you can recursively compile all configuration instantiated by the
node into your configuration catalog. This is what you do today.
- Then you look whether the node has a parent instance. If you have a
parse tree of the complete configuration this should be as simple as
looking for child instances. In our case we have a parent instance
called Full-system-cluster["production-cluster"].
- Then you take over all resources defined for the parents into your
configuration catalogue. In our example we do not have any additional
resources in the parent element but there might be some cluster-wide
files or whatever that cannot be attributed to bundles at lower levels.
- You continue to recurse until you reach the root-level element.
That's all.
> If it's part of the
> cluster, how does it know which services from the cluster it provides?
If you follow the example then you'll see that a cluster usually
clusters nodes and not services. If you have some resources defined in a
cluster then they'll be present on all nodes that are defined by this
cluster. Services as you understand them (aka today's classes) are
always defined at node level or below.
> AFAICT, I could do this exact thing with Puppet's classes right now.
Can you? Maybe I am really missing some important puppet concept!
The only equivalent in current puppet syntax I can come up with is:
node "production-cluster-fw" {
class firewall-and-load-balancer-node
}
node "production-cluster-web1" {
class web-server
}
node "production-cluster-web2" {
class web-server
}
node "production-cluster-app1" {
class application-server
}
node "production-cluster-app2" {
class application-server
}
node "production-cluster-db" {
class db-server
}
node "staging-cluster-fw" {
class firewall-and-load-balancer-node
}
node "staging-cluster-web1" {
class web-server
}
node "staging-cluster-web2" {
class web-server
}
node "staging-cluster-app1" {
class application-server
}
node "staging-cluster-app2" {
class application-server
}
node "staging-cluster-db" {
class db-server
}
node "development-cluster-fw" {
class load-balancer-node
}
node "development-cluster-web1" {
class web-server
}
node "development-cluster-web2" {
class web-server
}
node "development-cluster-app1" {
class application-server
}
node "development-cluster-app2" {
class application-server
}
node "development-cluster-db" {
class db-server
}
You can also put all this into external nodes. But AFAIK you still have
to define a "flat" list of nodes that cannot express any hierarchical
relationship or "clusters" of nodes.
How would /you/ do this?
> the current interface to external
> nodes is very simple, and it sounds like you're talking about a pretty
> dramatic enhancement to this external interface. I'd say leave that
> discussion as a separate problem entirely.
ok.
> I can't come up with much in terms of specific examples right now, but
> I'm kinda short on time.
ok.
>> Templates as type:
>
> As mentioned at the top, this would require teaching the Parameter
> class how to understand template instances.
Maybe. I don't know the internals of puppet code. It's just a
possibility and I don't deny that it may be an expensive one.
Florian
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"Puppet Developers" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at
http://groups.google.com/group/puppet-dev?hl=en
-~----------~----~----~----~------~----~------~--~---