Authors: Jeffrey McCune James Turnbull
Our simple ENC here captures the incoming node name and rejects and returns an empty hash (defined in thedefault
variable) if it is not an appropriately formed fully-qualified domain name (FQDN).
We then set up some basic defaults, thepuppetserver
variable, our environment, and a base class. The ENC then takes the host name portion of the FQDN and checks it against a list of host names, for example matching it againstweb
,web1
,web123
and so on for database and mail hosts.
For example, if we passed the ENC a node name of web.example.com, it would return a YAML hash of:
---
parameters:
puppetserver: puppet.example.com
classes:
- base
- apache
environment: production
Which would result in a node definition of:
node web.example.com {
$puppetserver = puppet.example.com
include base
include apache
}
This would specify that this node belonged to theproduction
environment.
If the ENC doesn't match any host names, then it will return an empty YAML hash.
In
Listing 5-5
, you can see another node classifier written in Perl.
Listing 5-5.
Perl-based node classifier
#!/usr/bin/perl -w
use strict;
use YAML qw( Dump );
my $hostname = shift || die "No hostname passed";
$hostname =~ /^(\w+)\.(\w+)\.(\w{3})$/
or die "Invalid hostname: $hostname";
my ( $host, $domain, $net ) = ( $1, $2, $3 );
my @classes = ( 'base', $domain );
my %parameters = (
puppetserver => "puppet.$domain.$net"
);
print Dump( {
classes => \@classes,
parameters => \%parameters,
} );
In
Listing 5-5
, we've created a Perl node classifier that makes use of the Perl YAML module. The YAML module can be installed via CPAN or your distribution's package management system. For example, on Debian it is thelibyaml-perl
package, or on Fedora it is theperl-YAML
package.
The classifier slices our hostname into sections; it assumes the input will be a fully qualified domain name and will fail if no hostname or an inappropriately structured hostname is passed. The classifier then uses those sections to classify the nodes and set parameters. If we called this node classifier with the hostnameweb.example.com
, it would return a node classification of:
---
classes:
- base
- example
parameters:
puppetserver: puppet.example.com
This would result in a node definition in Puppet structured like:
node 'web.example.com' {
include base, example
$puppetserver = "puppet.example.com"
}
Note
From Puppet 2.6.5 and later, you can also specify parameterized classes and resources in external node classifiers (seehttp://docs.puppetlabs.com/guides/external_nodes.html
for more details).
Lastly, as mentioned, we could also back-end our node classification script with a database, as you can see in
Listing 5-6
.
Listing 5-6.
A database back-end node classifier
#!/usr/bin/perl -w
use strict;
use YAML qw( Dump );
use DBI;
my $hostname = shift || die "No hostname passed";
$hostname =~ /^(\w+)\.(\w+)\.(\w{3})$/
or die "Invalid hostname: $hostname";
my ( $host, $domain, $net ) = ( $1, $2, $3 );
# MySQL Configuration
my $data_source = "dbi:mysql:database=puppet;host=localhost";
my $username = "puppet";
my $password = "password";
# Connect to the server
my $dbh = DBI->connect($data_source, $username, $password)
or die $DBI::errstr;
# Build the query
my $sth = $dbh->prepare( qq{SELECT class FROM nodes WHERE node = '$hostname'})
or die "Can't prepare statement: $DBI::errstr";
# Execute the query
my $rc = $sth->execute
or die "Can't execute statement: $DBI::errstr";
# Set parameters
my %parameters = (
puppet_server => "puppet.$domain.$net"
);
# Set classes
my @class;
while (my @row=$sth->fetchrow_array)
{ push(@class,@row) }
# Check for problems
die $sth->errstr if $sth->err;
# Disconnect from database
$dbh->disconnect;
# Print the YAML
print Dump( {
classes => \@class,
parameters => \%parameters,
} );
This node classifier would connect to a MySQL database called puppet running on the local host. Using the hostname, the script receiving it would query the database and return a list of classes to assign to the node. The nodes and classes would be stored in a table. The next lines comprise a SQL statement to create a very simple table to do this:
CREATE TABLE `nodes` (
`node` varchar(80) NOT NULL,
`class` varchar(80) NOT NULL ) TYPE=MyISAM;
The classes, and whatever parameters we set (which you could also place in the database in another table), are then returned and outputted as the required YAML data.
Tip
You can also access fact values in your node classifier scripts. Before the classifier is called, the$vardir/yaml/facts/
directory is populated with a YAML file named for the node containing fact values, for example/var/lib/puppet/yaml/facts/web.example.com.yaml
. This file can be queried for fact values.
All of these external node classifiers are very simple and could easily be expanded upon to provide more sophisticated functionality. It is important to remember that external nodes override node configuration in your manifest files. If you enable an external node classifier, any duplicate node definitions in your manifest files will not be processed and will in fact be ignored by Puppet.
Note
In Puppet versions earlier than 0.23, external node scripts were structured differently. We're not going to cover these earlier scripts, but you can read about them athttp://docs.puppetlabs.com/guides/external_nodes.html
.
In addition to external node classifiers, Puppet also allows the storage of node information in LDAP directories. Many organizations already have a wide variety of information about their environments, such as DNS, user and group data, stored in LDAP directories. This allows organizations to leverage these already-existing assets stored in LDAP directories or to decouple their configuration from Puppet and centralize it. Additionally, it also allows LDAP-enabled applications to have access to your configuration data.
Note
The use of LDAP nodes overrides node definitions in your manifest files and your ENC. If you use LDAP node definitions, you cannot define nodes in your manifest files or in an ENC.
The first step in using LDAP for your node configuration is to ensure the Ruby LDAP libraries are installed. First, check for the presence of the LDAP libraries:
# ruby -rldap -e "puts :installed"
If this command does not returninstalled
, the libraries are not installed. You can either install them via your distribution's package management system or download them from the Ruby/LDAP site. For Red hat and derivatives, this is theruby-ldap
package. For Ubuntu/Debian, the package islibldap-ruby1.8
.
If there isn't a package for your distribution, you can download the required libraries either in the form of an RPM or a source package from the Ruby/LDAP site. The Ruby/LDAP site is located athttp://ruby-ldap.sourceforge.net/.
Check out the current Ruby LDAP source code:
$ svn checkout http://ruby-activeldap.googlecode.com/svn/ldap/trunk/ ruby-ldap-ro
Then, change into the resulting directory and then make and install the code:
$ cd ruby-ldap-ro
$ ruby extconf.rb
$ sudo make && make install
Next, you need to set up your LDAP server. We're going to assume you've either already got one running or can set one up yourself. For an LDAP server, you can use OpenLDAP, Red Hat Directory Server (or
Fedora Directory Server), Sun's Directory Server, or one of a variety of other servers. We're going to use OpenLDAP for the purposes of demonstrating how to use LDAP node definitions.
Tip
For some quick start instructions on setting up OpenLDAP, you can refer tohttp://www.openldap.org/doc/admin23/quickstart.html
.