Moonshine Plugins: Yo dawg, I put a plugin in your plugin

Written By : Rails Machine Operations Team

We hope by now that you’ve had a chance to check out Moonshine and maybe even give it a go on your own server. We’ve been using Moonshine to deploy our customer’s servers for a while now, and we’ve started extracting little pieces of manifests into Moonshine plugins. I’m going to show you how to add Moonshine plugins to your manifest, and give you the lowdown on writing your own.

Installing and using plugins

Moonshine plugins are installed just like any other Rails plugin:

script/plugin install git://github.com/railsmachine/moonshine_ssh.git

Then add a few lines to your application’s manifest- app/manifest/application_manifest.rb, by default.

configure( :ssh => { :allow_users => ['rails'] } )
plugin :ssh
recipe :ssh

Not all plugins will need to be configured before they’re used and the recipe name doesn’t need to be the same as the plugin name- in fact, a plugin could provide as many recipes as you like. The next time you deploy your application, Moonshine will run the recipes you specified from the installed plugins.

Writing your own

Now here’s how you can really shine while you ‘shine. If you’ve added functionality in your manifest that you think others could use, create a new Moonshine plugin for it and put it up on github. Here’s how we do it.

Generate the plugin stub

Start by running the plugin generator from your application directory. Just switch out “ssh” for

whatever it is you’re plugin-ifying.

script/generate moonshine_plugin ssh

This creates the following structure in the vendor/plugins directory of your app:

|--moonshine_ssh
   |-- README.rdoc
   |-- moonshine 
   |  `-- init.rb
   |-- lib
   |  `-- ssh.rb
   |-- spec
       |-- spec_helper.rb
       |-- ssh_spec.rb

Add recipes

The lib/ directory is where we’ll put puppet recipes. For our SSH plugin we add this:

def ssh(options = {})
  package 'ssh', :ensure => :installed
  service 'ssh', :enable => true, :ensure => :running
 
  file '/etc/ssh/sshd_config',
    :mode => '644',
    :content => template(File.join(File.dirname(__FILE__), '..', 'templates', 'sshd_config'), binding),
    :require => package('ssh'),
    :notify => service('ssh')
end

This is straightforward- we’re making sure that SSH is installed, that the service is running and will start on system boot. We’ll create a templates/ directory with an ERB template called sshd_config. It will be rendered and saved as /etc/ssh/sshd_config. The SSH service will be notified to restart when it changes. The template will have lines like this sprinkled throughout:

LoginGraceTime <%= options[:login_grace_time] || 30 %>
PermitRootLogin <%= options[:permit_root_login] || 'no' %>

The options come from our configure line in the first example. If you recall, we defined a property called :ssh, which we set to a hash. This hash will be passed to the ssh recipe by Moonshine. This little convention helps keep the custom configuration of your server separate from its list of parts.

Do the safety dance

Now we’ve got a working plugin that can manage your SSH settings. Just one thing- what if

someone configured ssh like this?

configure(:ssh => {:port => 'two'})  

This clearly isn’t valid and we don’t want them to be locked out, so let’s test the file before we update it.

def ssh(options = {})
  package 'ssh', :ensure => :installed
  service 'ssh', :enable => true, :ensure => :running
 
  file '/etc/ssh/sshd_config.new',
    :mode => '644',
    :content => template(File.join(File.dirname(__FILE__), '..', 'templates', 'sshd_config'), binding),
    :require => package('ssh'),
    :notify => exec('update_sshd_config')
 
  exec 'cp /etc/ssh/sshd_config.new /etc/ssh/sshd_config',
    :alias => 'update_sshd_config'
    :onlyif => '/usr/sbin/sshd -t -f /etc/ssh/sshd_config.new',
    :refreshonly => true, # do nothing until notified
    :require => file('/etc/ssh/sshd_config.new'),
    :notify => service('ssh')
end

There you have it. We upload the new configuration to a temporary location and replace the previous one only if the syntax check returns 0. This plugin is ready to be put online for all to enjoy.

The first plugins

So far, we’ve released plugins for managing iptables, SSH,

and god. Look for more in the coming days. If you’ve written one, let us know about it in the comments!

Update: Check out the list of plugins and add your own on the plugin wiki page.