Language: Namespaces and Autoloading
Class and defined type names may be broken up into segments called namespaces. Namespaces tell the autoloader how to find the class or defined type in your modules.
Important note: Earlier versions of Puppet used namespaces to navigate nested class/type definitions, and the code that resolves names still behaves as though this were their primary use. This can sometimes result in the wrong class being loaded. This is a major outstanding design issue (issue #2053) which will not be resolved in Puppet 3. See below for a full description of the issue.
Syntax
Puppet class and defined type names may consist of any number of namespace segments separated by the ::
(double colon) namespace separator. (This separator is analogous to the /
[slash] in a file path.)
class apache { ... }
class apache::mod { ... }
class apache::mod::passenger { ... }
define apache::vhost { ... }
Optionally, class/define names can begin with the top namespace, which is the empty string. The following names are equivalent:
apache
and::apache
apache::mod
and::apache::mod
- etc.
This is ugly and should be unnecessary, but is occasionally required due to an outstanding design issue. See below for details.
Autoloader Behavior
When a class or defined resource is declared, Puppet will use its full name to find the class or defined type in your modules. Names are interpreted as follows:
- The first segment in a name (excluding the empty “top” namespace) identifies the module. Every class and defined type should be in its own file in the module’s
manifests
directory, and each file should have the.pp
file extension. - If there are no additional namespaces, Puppet will look for the class or defined type in the module’s
init.pp
file. - Otherwise, Puppet will treat the final segment as the file name and any interior segments as a series of subdirectories under the
manifests
directory.
Thus, every class or defined type name maps directly to a file path within Puppet’s modulepath
:
name | file path |
---|---|
apache |
<modulepath>/apache/manifests/init.pp |
apache::mod |
<modulepath>/apache/manifests/mod.pp |
apache::mod::passenger |
<modulepath>/apache/manifests/mod/passenger.pp |
Note again that init.pp
always contains a class or defined type named after the module, and any other .pp
file contains a class or type with at least two namespace segments. (That is, apache.pp
would contain a class named apache::apache
.)
Relative Name Lookup and Incorrect Name Resolution
In Puppet 3, class name resolution is partially broken — if the final namespace segment of a class in one module matches the name of another module, Puppet will sometimes load the wrong class.
class bar {
notice("From class bar")
}
class foo::bar {
notice("From class foo::bar")
}
class foo {
include bar
}
include foo
In the example above, the invocation of include bar
will actually declare class foo::bar
. This is because Puppet assumes class and defined type names are relative until proven otherwise. This is a major outstanding design issue (issue #2053) which will not be resolved in Puppet 3, as the fix will break a large amount of existing code and require a long deprecation period.
Behavior
When asked to load a class or defined type foo
, Puppet will:
- Attempt to load
<current namespace>::foo
- If that fails, attempt to load
<parent of current namespace>::foo
- If that fails, continue searching for
foo
through every ancestor namespace - Finally, attempt to load
foo
from the top namespace (AKA::foo
)
A concrete example:
class apache::nagios {
include nagios
...
}
When asked to include nagios
, Puppet will first attempt to load apache::nagios::nagios
. Since that class does not exist, it will then attempt to load apache::nagios
. This exists, and since the include function can safely declare a class multiple times, Puppet does not complain. It will not attempt to load class nagios
from the nagios
module.
Workaround
If a class within another module is blocking the declaration of a top-namespace class, you can force the correct class to load by specifying its name from the top namespace (as seen above). To specify a name from the top namespace, prepend ::
(double colon) to it:
class apache::nagios {
include ::nagios # Start searching from the top namespace instead of the local namespace
...
}
In the example above, Puppet will load class nagios
from the nagios
module instead of declaring apache::nagios
a second time.
Aside: Historical Context
Relative name lookup was introduced in pre-module versions of Puppet. It reflects an outdated assumption about how modules would be used.
Proto-Modules
Before modules were introduced, users would create module-like blobs by putting a group of related classes and defined types into one manifest file, then using an import statement in
site.pp
to make the group available to the parser.
# /etc/puppet/manifests/apache.pp
class apache { ... } # Manage Apache
class ssl { ... } # Optional SSL support for Apache
class python { ... } # Optional mod_python support for Apache
define vhost ($port) { ... } # Create an Apache vhost
# /etc/puppet/manifests/site.pp
import apache.pp
include apache
include ssl
Namespacing for Redistribution
As proto-modules got more sophisticated, their authors wanted to share them with other users. The problem with this is visible above: many modules were likely to have a
python
orssl
class, and thelighttpd
module probably had avhost
define that clashed with the Apache one.The solution was namespacing, which would allow different proto-modules to use common class and defined type names without competing for global identifiers.
Private vs. Public
The implementation of namespaces relied on an assumption that turned out to be incorrect: that classes and defined types other than the module’s main class would (and should) mostly be used inside the module, rather than applied directly to nodes. (That is, they would be private, much like local variables.) Thus, namespacing was done by hiding definitions within other definitions:
class apache {
...
class ssl { ... }
class python { ... }
define vhost ($port) { ... }
}
The short names of the internal classes and defined types could only be used inside the main class. However, much like qualified variables, you could access them from anywhere by using their full (that is, namespaced) name. Full names were constructed by prepending the full name of the “outer” class, along with the
::
namespace separator. (That is, the full name ofssl
would beapache::ssl
,python
would beapache::python
, etc.)This was the origin of the relative name lookup behavior, as Puppet assumed that a class that had its own private
python
class would want to use that instead of the top-namespacepython
class.This Turned Out to be Pointless
Users and developers eventually realized several things about this arrangement:
- Using a class’s full name everywhere was actually not that big a deal and was in fact a lot clearer and easier to read and maintain.
- Public classes and defined types were more common than private ones and optimizing for the less common case was an odd approach.
- Even for classes and defined types that were only used within their module, there was little real benefit to be gained by making them “private,” since they were effectively public via their full name anyway.
Those realizations led to the superior module autoloader design used today, where a class’s “full” name is effectively its only name. However, the previous name lookup behavior was never deprecated or removed, for fear of breaking large amounts of existing code. This leaves it present in Puppet 3, where it often annoys users who have adopted the modern code style.
We plan to fix this in a future release, after a suitable deprecation period.