Saturday, November 9, 2013

My Puppet Module Writing Environment

I previously documented my Foreman/Puppet environment. In this entry I am documenting how I set things up so I could comfortably develop puppet modules inside of this environment. This includes the writing of a very simple module to make /etc/hosts the same on all of the hosts in my environment. This hosts module itself reinvents the wheel as there are many more advanced puppet forge modules for doing this. I am only trying to solve a simple problem for an environment exclusive to my own. What I hope is more relevant for this entry is how the development tools were set up and the conventions being used.

Puppet Mode for Emacs

I am planning to write my manifests locally in emacs so I will get puppet mode for emacs.


$ cd ~/elisp/
$ git clone http://github.com/puppetlabs/puppet-syntax-emacs.git
Cloning into 'puppet-syntax-emacs'...
remote: Counting objects: 63, done.
remote: Compressing objects: 100% (46/46), done.
remote: Total 63 (delta 20), reused 55 (delta 17)
Unpacking objects: 100% (63/63), done.
$ 
Add the following to ~/.emacs and evaluate the following S-expressions:
(add-to-list 'load-path "~/elisp/puppet-syntax-emacs")
(setq py-install-directory "~/elisp/puppet-syntax-emacs")
(require 'puppet-mode)


Set up versioning for a module

At work we use an authoritative (by convention) git depot. I also keep a personal authoritative git depot on a vps on the Internet so I will create a bare repository there but which depot I use is arbitrary.

ssh me.xen.prgmr.com
cd /opt/git/
mkdir hosts.git
cd hosts.git
git init --bare
logout
I will then clone this empty repository down into a place on my laptop to work:
$ cd ~/src/puppet/hosts
$ git clone me@me.xen.prgmr.com:/opt/git/hosts.git
Cloning into 'hosts'...
warning: You appear to have cloned an empty repository.
$ 

Write a simple module to manage /etc/hosts

Inside the empty repository that I cloned I will create the module:

$ mkdir -p hosts/{files,templates,manifests}
$ touch hosts/manifests/init.pp
$ mkdir -p hosts/templates
$ touch templates/hosts.erb 
Put the following in hosts/manifests/init.pp:
class hosts {
  file { "/etc/hosts":
    ensure => file,
    owner => "root",
    group => "root",
    mode => 0644,
  }
  File['/etc/hosts'] {
    content => template('hosts/hosts.erb'),
  }
}
Put the following in templates/hosts.erb:
127.0.0.1 localhost.localdomian localhost 
127.0.0.1 <%= @fqdn %> <%= @hostname %>
# continue with other hosts as you see fit

Commit the changes and push them back to the depot:

$ cd ~/src/puppet/hosts
$ git add *
$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached ..." to unstage)
#
# new file:   templates/hosts.erb
# new file:   manifests/init.pp
#
$ git commit -m "first version of hosts"
[master (root-commit) 524416d] first version of hosts
 2 files changed, 23 insertions(+)
 create mode 100644 hosts/templates/hosts.erb
 create mode 100644 hosts/manifests/init.pp
$ 
$ git push origin master
Counting objects: 8, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (8/8), 754 bytes, done.
Total 8 (delta 0), reused 0 (delta 0)
To me@me.xen.prgmr.com:/opt/git/hosts.git
 * [new branch]      master -> master
$ 


On the Puppet Server

A note about some conventions on my set up. My Puppet server has /etc/puppet/modules but it's a symlink to environments/common and I have used an ACL to give myself access to it so I can radiply make changes without being root (this will come in handy later when we talk about how I speed up the cycle):

[root@puppet puppet]# pwd
/etc/puppet
[root@puppet puppet]# ls -l modules
lrwxrwxrwx. 1 root root 19 Sep 24 02:10 modules -> environments/common
[root@puppet puppet]# cd environments
[root@puppet environments]# setfacl -m u:me:rwx common/
[root@puppet environments]# 

Import the new module into the puppet server from git:
[me@puppet ~]$ cd /etc/puppet/modules/
[me@puppet modules]$ git clone me@me.xen.prgmr.com:/opt/git/hosts.git 
Initialized empty Git repository in /etc/puppet/environments/common/hosts/.git/
remote: Counting objects: 8, done.
remote: Compressing objects: 100% (4/4), done.
Receiving objects: 100% (8/8), done.
remote: Total 8 (delta 0), reused 0 (delta 0)
[me@puppet modules]$ 

On Foreman

Login to the Foreman web interface and navigate to the puppet classes:
More > Configuration > Puppet Classes

Click "Import from puppet". You should then see the class name "hosts". Select it and then you should see something like the following:
















Select the environments you want to import the new module into and then select "Update". Once the module is imported, select the servers you want to have the new module. For example, you can apply the new module to the puppet-agent client.


On a Puppet client
From the puppet-agent server you can test the new configuration and observe the change.


[root@puppet-agent ~]# md5sum /etc/hosts
b259bdfd326846029c25beff10fc5ac6  /etc/hosts
[root@puppet-agent ~]# puppet agent
[root@puppet-agent ~]# puppet agent --test
Info: Retrieving plugin
Info: Caching catalog for puppet-agent.example.com
Info: Applying configuration version '1384016280'
Notice: Finished catalog run in 0.29 seconds
[root@puppet-agent ~]# md5sum /etc/hosts
fd34f5442b4209a472d6ea8ac8e24d77  /etc/hosts
[root@puppet-agent ~]# 

Speeding up the Cycle
I am used to having a git depot at work so I've shown how to stick one in the middle of the cycle but to do a commit and update for each revision of code is not practical. To get around this I can write code on the puppet VM itself (I am the only user for this environment), but use a local editor on my laptop. I prefer to do this with emacs tramp and have the following to my .emacs:


(setenv "puppet" "/ssh:me@puppet.example.com:/etc/puppet/modules/")

These conventions could be applied when developing puppet modules in a development environment like the one described here and then applied to a production environment. For example, I could write a module in this development puppet environment and check it into the git depot at work as I reached the appropriate points in my development. When ready it could then get checked into the puppet modules directory on a production puppet server but in the staging or QA environment of that puppet server (see Chapter 3 of Pro Puppet) so it could be further tested before getting pushed into production. The assumed goal here is that you would want to develop a puppet module without needing to be tethered to the servers at work.

Update: a previous version of this post had a bug as /etc/host was in files and not a template. This would not work correctly has there would be no reference to the the loopback device relative to the hostname. The need for a variable here means you need a template, not a file.


No comments: