Authors: Jeffrey McCune James Turnbull
Thehostclass
method is equivalent to the Puppetclass motd_location { … }
syntax and defines the new class. Thismotd_location
class carries out three actions:
motd_content
Using thescope.lookupvar
method, the developer obtains the value of theenc_location
string set by the ENC. When accessing parameters set by the ENC, by Facter, or in the node definitions ofsite.pp
,scope.lookupvar
should be used to obtain the value. Assigning the value to a local variable also has the benefit of bringing the value into the local scope.
To define the contents of/etc/motd
, the developer assigns another string variable, substituting the value of thelocation
variable. In Ruby the#{}
statement performs substring substitution and replaces the value of the variable contained inside the curly braces.
Finally, the Puppet file resource is declared using a similar syntax to the Puppet syntax. Thefile
method is called, specifying the title of the resource as the first argument. In addition, a list of the properties of the file is also specified. This file method declares a file resource and is equivalent tofile { motd: … }
in Puppet syntax.
With this module and node classification configured, the developer is ready to test the Ruby DSL as shown in
Listing 8-25
.
Listing 8-25.
Testing the Ruby DSL with the motd_location class
$ puppet apply --noop /etc/puppet/manifests/site.pp
--- /etc/motd 2011-02-24 01:20:12.000000000 -0500
+++ /tmp/puppet-file20110224-19081-ekisu3-0 2011-02-24 01:41:41.000000000 -0500
@@ -0,0 +1 @@
+This system is in: Florida
notice: /Stage[main]/Motd_location/File[motd]/content: is
{md5}d41d8cd98f00b204e9800998ecf8427e, should be {md5}3f9e49a378a930da4e06760635fcb810 (noop)
As we can see from the output of Puppet, the/etc/motd
file would have a single line added containing the value of theenc_location
parameter. This parameter was set by the ENC and declared in themotd_location
module using the Ruby DSL.
With a basic module in place, the developer decides to extend the ENC script. The extended ENC script provides all of the information about the accounts to manage. This information will be provided and stored in a Hash data type, which is also new in Puppet 2.6. Once the data is defined, a new module namedaccounts_ruby
will declare resources from the data. To accomplish this, the developer will iterate
over all of the information set by the ENC and declare user resources similar to the file resource declared in themotd_location
module.
If the developer used the Puppet DSL instead of the Ruby DSL this task would be particularly difficult. The Puppet language does not have loops and cannot easily iterate over a set of data. Let's see how the developer solves this problem in
Listing 8-26
. First, the extended ENC script produces output containing the account information:
Listing 8-26.
ENC script with account information
$ /etc/puppet/resources_enc.rb
--
parameters:
enc_location: Florida
account_resources:
alice:
groups:
- sudo
- sudo_nopw
- devel
comment: Alice
gid: 601
uid: 601
shell: /bin/bash
password: "!!"
home: /home/alice
bob:
groups:
- sudo
- sudo_nopw
- ops
comment: Bob
gid: 602
uid: 602
shell: /bin/zsh
password: "!!"
home: /home/bob
classes:
- motd_location
- accounts_ruby
The output of the ENC script in
Listing 8-26
now contains considerably more information. Notice a second class namedaccounts_ruby
has been added. In addition, a new parameter namedaccount_resources
contains a Hash key for each user account to be created. The value of the key is itself a Hash containing each parameter of the account resource. The ENC script producing this node classification is shown in
Listing 8-27
.
Listing 8-27.
ENC script for Ruby DSL accounts module
$ cat /etc/puppet/resources_enc.rb
#!/usr/bin/env ruby
#
# Load the YAML library in ruby. Provide the to_yaml method for all
# Ruby objects.
require 'yaml'
# The output hash. Must contain the "parameters" and "classes" key.
# See: http://docs.puppetlabs.com/guides/external_nodes.html
@out = Hash.new
# Output Array of classes, Hash of Parameters
@out["classes"] = Array.new
@out["parameters"] = Hash.new
# Add the motd_location class to the catalogs
@out["classes"] << "motd_location"
# And, add the accounts_ruby class to the catalog
@out["classes"] << "accounts_ruby"
# Add a location parameter
@out["parameters"]["enc_location"] = "Florida"
# Store account information dynamically in the account_resources
# parameter. These values could come from LDAP, SQL, etc...
@out["parameters"]['account_resources'] = Hash.new
@out["parameters"]['account_resources']["alice"] = {
"comment" => "Alice",
"home" => "/home/alice",
"uid" => 601,
"gid" => 601,
"groups" => [ "sudo", "sudo_nopw", "devel" ],
"shell" => "/bin/bash",
"password" => "!!",
}
@out["parameters"]['account_resources']["bob"] = {
"comment" => "Bob",
"home" => "/home/bob",
"uid" => 602,
"gid" => 602,
"groups" => [ "sudo", "sudo_nopw", "ops" ],
"shell" => "/bin/zsh",
"password" => "!!",
}
puts @out.to_yaml
exit(0)
This ENC script performs the following actions:
to_yaml
method@out
with two keys:classes
andparameters
account_resources
parameter to the parameter hashaccount_resources
hash@out
output hash as a YAML string to standard outputWith the account information defined by the ENC in a parameter namedaccount_resources
, the developer then writes theaccounts_ruby
class. This class declares user resources for all of the accounts. Once the developer writes the class new accounts only need to be added to node classification for Puppet to manage them. The Puppet module and manifests themselves need not be modified as people join the organization. This implementation cleanly separates code and data. The implementation also allows the developer the freedom to improve the ENC script without modifying Puppet. Information may be retrieved from data sources like the Human Resources directory, LDAP, or an SQL database. The complete Ruby DSLaccounts_ruby
class the developer has written is shown in
Listing 8-28
.
Listing 8-28.
The accounts_ruby class
$ cat /accounts_ruby/manifests/init.rb
# Define a new accounts_ruby class. This is equivalent to:
# class accounts_ruby { ... }
hostclass :accounts_ruby do
# Bring the accounts resources defined in the ENC into a local
# Ruby variable.
accounts = scope.lookupvar("account_resources")
# Perform a sanity check on the data provided by the ENC.
raise Puppet::Error,
"account_resources must be a Hash" unless accounts.kind_of?(Hash)
# First declare groups required by the accounts. These groups may be
# referenced in /etc/sudoers to grant sudo access and access without
# a password entry.
group([:sudo, :sudo_nopw], :ensure => "present")
# Iterate over each account
# The Hash key will be stored in the local title variable
# The value of the hash entry will be stored in parameters
# The parameters are the resource parameters for each user account.
accounts.each do |title, parameters|
# Some more sanity checking on the data passed in from the ENC.
raise Puppet::Error,
"account_resources[#{title}] must be a Hash" unless parameters.kind_of?(Hash)
# Manage the home directory of this account with a file resource.
file(parameters["home"],
:ensure => "directory",
:owner => title,
:group => title,
:mode => 0700)
# Each account should have a group of the same name.
group(title,
:ensure => "present",
:gid => parameters["gid"])
# Declare the user resource with the parameters for this account.
user(title,
:ensure => "present",
:uid => parameters["uid"],
:gid => parameters["gid"],
:comment => parameters["comment"],
:groups => parameters["groups"],
:shell => parameters["shell"],
:password => parameters["password"],
:home => parameters["home"],
:managehome => false)
end
end
Theaccounts_ruby
module class in
Listing 8-28
carries out a number of actions when declared in the Puppet catalog. These actions are:
accounts_ruby
using thehostclass
method.accounts
Ruby variable containing the information set by the ENC in theaccount_resources
parameter.sudo
andsudo_nopw
.The Ruby code composing theaccounts_ruby
module may be a little much to absorb at first. Like Puppet, Ruby code is often quite readable; so let's see how the developer solves the accounts problem. First, he defines a new class namedaccounts_ruby
using thehostclass
method. He passes a Ruby Block to thehostclass
method. This block will be evaluated when the class is declared in the catalog. Recall from
Listing 8-26
that the ENC script is declaring this class in theclasses
list.
With the new class defined in theinit.rb
file of the module manifests directory, the developer proceeds to bring the data defined in the ENC into the local scope. This is again accomplished with thescope.lookupvar
method. In addition, the data is validated using thekind_of?
method. This method returnstrue
orfalse
if the receiving object is a kind of the specified class. In this case the developer is checking to see if a Hash was actually passed into Puppet by the ENC or not. In the Puppet DSL, thefail()
function may be used to abort catalog compilation if this check does not pass. In the Ruby DSL, an exception class namedPuppet::Error
is one way to abort catalog compilation if invalid data has been passed in.
With the data validated, thesudo
andsudo_pw
groups are declared, just like they would be in a manifest written in Puppet syntax. With the basic requirements established, the developer then uses the Ruby idiom of calling theeach
method on the accounts Hash to iterate over each entry supplied by the ENC. This method also takes a block and executes this block of code once for each entry in the Hash. Inside the block, the hash key and value are stored in the local variables title and parameters, indicating these variables represent the resource title and contain parameters about the resource.
Finally, inside the block the developer declares three resources. First, a file resource manages the home directory of the user account. Next, a new group with the same name as the account is declared. Finally, the user account itself is declared. The parameters for all of these resources are retrieved from the information passed in the ENC script.
Let's see, in
Listing 8-29
, how theaccounts_ruby
module looks when Puppet runs.
Listing 8-29.
Running Puppet with the accounts_ruby module
# puppet apply --verbose --noop /etc/puppet/manifests/site.pp
info: Applying configuration version '1298536173'
notice: /Stage[main]/Accounts_ruby/Group[alice]/ensure: is absent, should be present (noop)
notice: /Stage[main]/Accounts_ruby/User[alice]/ensure: is absent, should be present (noop)
notice: /Stage[main]/Accounts_ruby/File[/home/alice]/ensure: is absent, should be directory
(noop)
notice: /Stage[main]/Accounts_ruby/Group[bob]/ensure: is absent, should be present (noop)
notice: /Stage[main]/Accounts_ruby/User[bob]/ensure: is absent, should be present (noop)
notice: /Stage[main]/Accounts_ruby/File[/home/bob]/ensure: is absent, should be directory (noop)