Puppet: System Administration Automated

Giving Git a run-out


Something apparently snapped while I was at OSCON, and I apparently collapsed my distributed source control management quandary down to Git. I think in the end it doesn't matter all that much, since they're so similar in basic functionality, and I think I mostly got tired of sitting on the fence looking over but not being willing to commit to a specific dSCM.

Once I decided I'd go ahead with Git, my main priority was to get to the point where I could do my development on Puppet in it, which is especially important since it's the only real way for me to figure out if it will work for me, not that I really know what "work for me" means.

There are two crucial steps to testing an SCM for me: Getting Puppet's code into it, with as much history as possible, and making it available for others to have access to.

Getting the code was moderately easy, but made harder by the fact that when I first made my Subversion repository, when SVN was just starting to get popular, so I started without the typical branches/tags/trunk directory set. Here's the command I used in the end:

git svnimport -A ~/puppet-users -i -v http://reductivelabs.com/svn/puppet/ > /tmp/git.out

I tried git-svn, but it never got past revision 567 or so (which is when I switch to the popular directory structure). In addition, I was never able to actually get a working copy of the repository up to that point.

The puppet-users file contains a mapping from svn-style user names to email addresses:

luke = Luke Kanies <luke@domain.com>
lutter = David Lutterkort <dlutter@domain.com>
mpalmer = Matthew Palmer <mpalmer@domain.org>

I redirect output to a file, because it produces a bunch of output (I've got about 2800 revisions) and I don't actually care about any of it, and in addition, because I use iTerm, it takes a whole freaking cpu to scroll a terminal.

This basically worked, except that it started at revision 600 (arbitrarily close enough to the time when I changed the directory structure in the repository).

To make the repository shareable, I first just exported it via http, which was pretty easy, but then I was told I need to use git-server for performance reasons. I built a Puppet module to set it all up, and although the server doesn't work as well as I like (I really like SVN's auth file, which allows me to control who has access to the 32 repositories I maintain).

I'm getting some gritching from the Australians, and it's not like it's perfect, but at least I know I want something like that.

At the least, this has been a great experiment, and I figure we'll spend a week or so messing around with it. I'm not sure I can afford the time to experiment with all of the competitors; Matt's really pushing on darcs, but... I dunno, it seems niche, and at this point, I'm niche enough for all of us.

add to del.icio.us Add to Blinkslist add to furl Digg it add to ma.gnolia Stumble It! add to simpy seed the vine TailRank post to facebook

Tue, 07 Aug 2007 | Tags: , , , , ,


How I Add Code to My Presentations


Many of my presentations include source code (including yesterday's), and I always struggle a bit to find a way to get attractive, syntax highlighted code into my presentations. This is further complicated by the fact that I often include Puppet code, and there are currently only syntax highlighters for Vim and Emacs, so the apparently-normal Enscript highlighting won't work for me.

So, I hacked up a Vim script that can create marked up HTML, then I threw that into a Rake file that converts my code samples into HTML. Here's the code:

outputs = []

FileList['examples/*'].each do |input|
    output = File.join("output", File.basename(input)).sub(/\.\w+/, ".html")
    outputs << output

    file output => ["Rakefile", input] do
        vimsyn(input, output)
    end
end

def vimsyn(file, outfile = nil)
    filetype = case file
        when /\.pp/: "puppet"
        when /\.rb/: "ruby"
        when /\.mk/: "make"
        when /\.sh/: "bash"
        when /\.txt/: "text"
        else
            raise "Unknown File type for %s" %  file
        end
    name = File.basename(file).sub(/\..+/, '')
    outfile ||= "/tmp/outfile"
    synfile = outfile + ".tmp"
    codefile = file

    filetype="puppet"

    puts "Creating %s" % outfile
    expr = %Q%/usr/bin/vim -f -n -X -e -s -c  'set filetype=#{filetype}'  -c 'syntax on'    -c 'let html_use_css=1'  -c 'run syntax/2html.vim'  -c 'wq! #{synfile}' -c 'q'  #{codefile}%

    `#{expr}`
    html = File.read(synfile)
    html.sub!(/<title>.+<\/title>/, "<title>#{name}</title>")
    html.sub!(/^(pre.+) \}/) { $1 + "font-size: 22pt; }" }
    html.gsub!(/text-decoration: underline;/, '')
    File.open(outfile, "w") { |f| f.print html }
    File.unlink(synfile)
end

task :clean do
    outputs.each do |file|
        if FileTest.exists?(file)
            File.unlink(file)
        end
    end
end

task :default => outputs

I usually (but not always) use Apple's Keynote. So, for this presentation, I used Keynote's "Web Views" to include the HTML directly in the presentations. There were some significant hitches to doing so: First and most stupidly, Keynote won't include local file:/// URLs, only http:///, which means that you can't embed local files. This is apparently by design (someone mumbled something about security, which is, um, inane).

The second hitch is that there does not appear to be any way to change the text size within the web view, so you have to generate the html with the correct size. This is pretty stupid, considering that we otherwise make sure we use scalar graphics instead of PNGs or whatever so they're easy to resize. This makes it pretty annoying to use lots of code samples, because they're invairably different lengths and thus should be different sizes for best readability.

The last problem is that the web views update automatically be default, which means that if you go off the 'net, they lose all of their content. Thus, you have to go through and mark them all to be updating manually.

I also use OmniGraffle to build all of my graphics, which works easily and makes pretty attractive pictures.

This isn't an awesome solution, but it seems to be working for now, anyway, and hopefully it will make it a bit easier for others to get code into their presentations.

add to del.icio.us Add to Blinkslist add to furl Digg it add to ma.gnolia Stumble It! add to simpy seed the vine TailRank post to facebook

Thu, 26 Jul 2007 | Tags: , , , , ,


Day 3 at OSCON


Now that UbuntuLive has officially ended, I'm at OSCON entirely. I got my talk on Racc done at FOSCON yesterday; I feel like it didn't go all that well, but I only had 10 minutes, so you can't expect all that much. My Puppet talk is in a bit over an hour; you can get the slides if you're interested, although I generally don't put much information in my slides -- I find it a lot easier to talk when the slides are just reminders, rather than the content itself. Otherwise I end up trying to read the slides instead of talking, which is no good.

I'm stoked to be finished with the talk, since I can completely relax now, it'll be just parties and the hallway track.

add to del.icio.us Add to Blinkslist add to furl Digg it add to ma.gnolia Stumble It! add to simpy seed the vine TailRank post to facebook

Wed, 25 Jul 2007 | Tags: , , , ,


Preparing for OSCON


Looks like my insane conference travel season is starting again. I'm heading to Portland, OR for OSCON again, and my talk is on Wednesday at 5:20pm. I'll probably also do a talk or two (hopefully) at FOSCON (as you might already know), and Ubuntu LIVE starts the day after I arrive. I was planning on having some downtime in Portland before OSCON, but now I'll likely be hanging around the Ubuntu conference instead. Considering that I just submitted a talk for LinuxConf Australia, I'm hoping to put out 0.23.1 tomorrow, and I still need to write my presentation for OSCON, I'm not getting much in the way of downtime.

If any of you are going to be at OSCON, send me a note or something; we should probably organize a Puppet BoF or Meetup or something. Any volunteers?

add to del.icio.us Add to Blinkslist add to furl Digg it add to ma.gnolia Stumble It! add to simpy seed the vine TailRank post to facebook

Thu, 19 Jul 2007 | Tags: , , , , ,


Ruby Talk Proposals for FOSCON


I got an email from Thomas Lockney about FOSCON, asking if I were interested in talking about the crazy stuff I'm doing with Ruby in Puppet. Here's what I sent him:


Generating classes rather than declaring them

...which I do throughout Puppet. For instance, here is a recent set of classes I created in Puppet:

module Puppet
    newtype(:maillist) do
        @doc = "Manage email lists.  This resource type currently can only create
            and remove lists, it cannot reconfigure them."

        newparam(:name, :namevar => true) do
            desc "The name of the email list."
        end

        newparam(:description) do
            desc "The description of the mailing list."
        end
        ...

Both newtype and newparam create new classes. I've got enough of this that I've actually created a module to do this for me.

I only do this when I'm storing references to the class or module by name, and this classgen module sticks the generated class into a hash or array as necessary, sets up constants (which I don't use but Ruby does), sets attributes, calls initializations hooks, and much more.

I could fake a lot of this using the 'inherited' class method is called on the parent class, but the class when passed to that method is uninitialized (no associated code has yet been evaluated), it does not work well for subclasses of subclasses, and it doesn't make it easy to reload classes in memory.

Using Racc

...to write a real domain specific language. Everyone asks why I do this, instead of just using Ruby, but this talk would focus more on the how and what than the why. As far as I know, I've got the only two complete and open-source Racc parsers available in the wild. Puppet's parser uses abstract syntax trees and thus is a far more complicated and thus more enlightening parser, while Naginator's parser is pleasantly simple to understand and is a good example. In both cases, I've created my own lexer, since Ruby seems to be without a good lexer generator.

Puppet's configuration system

I've created a stand-alone configuration class that handles all of my Puppet run-time configuration. You define configuration settings with a section and a default:

self.setdefaults(:main,
        :trace => [false, "Whether to print stack traces on some errors"],
        :autoflush => [false, "Whether log files should always flush to disk."],
        :syslogfacility => ["daemon", "What syslog facility to use when logging to
            syslog.  Syslog has a fixed list of valid facilities, and you must
            choose one of those; you cannot just make one up."],
        :statedir => { :default => "$vardir/state",
            :mode => 01777,
            :desc => "The directory where Puppet state is stored.  Generally,
                this directory can be removed without causing harm (although it
                might result in spurious service restarts)."
        },
            ....

See Puppet's configuration for many more examples.

I keep a global configuration object at Puppet.config, set all of my items there, and then I can access them or set them throughout my system with ease.

There are some really great things about this system:

The best thing about the class, though, is that it relies on Puppet. Any configuration item that manages files or directories can be converted into a Puppet resource and then evaluated, thus creating the directories if necessary and setting owner, group, and/or mode as necessary. Alternatively, you can generate Puppet code that you can then run manually if you prefer. You can even have Puppet create any necessary users and groups. You can use specific configuration sections (as defined during the configuration specification process), and only the files and directories from those sections will be used, so you can have hundreds of settings but only actually manage the settings you're using.

Provider localization

Puppet's portable resource types, like packages and users, are generally backed by less portable providers, like useradd, dpkg, apt, and about 22 other package providers. These providers vary in suitability for a given platform based on lots of different criteria, and Puppet still has to choose a default provider out of all of those that are suitable.

In addition, most providers are largely just wrappers around external binaries, which they call then parse their output. Because this is done so often throughout the providers, it's important that these calls to external binaries be easy to perform, along with it being easy to manage where the binaries are.

I've created a few simple methods that handle all of this functionality for me. I've got a confine class method that can confine a provider to only systems matching the specified critiria, which can include the presence of a file (usually a binary), comparing the output from Facter so you can confine providers to only Darwin systems, for instance, or arbitrary booleans, even returned from a block.

I went a bit further in handling required binaries by providing a commands method that allows you to specify required commands, either qualified or not, and the provider will then base its suitability on the presence of those commands, store the path for the command so the provider does not need to keep using a full path, and then define a method named after each command that will call out to the command, protecting the call and failing appropriately if the command is missing.

So, I can have simple provider definitions like this:

Puppet::Type.type(:package).provide :apt, :parent => :dpkg, :source => :dpkg do
    # Provide sorting functionality
    include Puppet::Util::Package

    desc "Package management via ``apt-get``."

    has_feature :versionable

    commands :aptget => "/usr/bin/apt-get"
    commands :aptcache => "/usr/bin/apt-cache"
    commands :preseed => "/usr/bin/debconf-set-selections"

    defaultfor :operatingsystem => :debian
    ...

Here I've specified three required commands, and now, for instance, I can just call aptget :install, :ruby (since the command accepts symbols as well as strings) and Puppet will call the right binary with those arguments and return the output, or throw an exception if the command fails.

You can see that I've also got a defaultfor method, which I can use to define whether a given provider is the default for some set of systems. We usually use operating system names and releases, but it can be anything that Facter knows about.

Note that this is another example of class generation -- each provider is a generated class, and you can specify as a parent class either a normal Ruby constant or the name of another provider to use as the parent class. Note that I'm also passing other options to the provider, which will get set before the associated code is evaluated.


There are plenty of other things that I think are pretty cool about Puppet, like the new provider features (search for features), which provide automatic documentation and validation, but these four are probably the coolest and most surprising things I do with Ruby in Puppet. If you need more talks, just let me know. :)

add to del.icio.us Add to Blinkslist add to furl Digg it add to ma.gnolia Stumble It! add to simpy seed the vine TailRank post to facebook

Tue, 17 Jul 2007 | Tags: , , , , , , ,