Moonshine Plugins: Yo dawg, I put a plugin in your plugin
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.


Comments
Attila
2009-05-07 at 10:13 AM
This is impressive. Thanks for the detailed example! I love moonshine plugins!
Rob Lingle
2009-05-07 at 09:54 PM
There's now a wiki page where we will collect the available plugins. Feel free to add your own.
http://wiki.github.com/railsmachine/moonshine/moonshine-plugins
Melvin Ram
2009-05-08 at 07:03 AM
Sounds interesting. I'll go through it this weekend and see if I can incorporate instructions for this into the step-by-step instructions guide. You guys continually impress me.
Rob Lingle
2009-05-08 at 03:12 PM
@melvin: I already updated it. Thanks for writing that, by the way. Awesome resource.
Edit: the link for those who don’t know.
Melvin Ram
2009-05-08 at 10:27 PM
Sweet. I'll give it a go this weekend.
Jason King
2009-05-14 at 06:17 AM
Regarding the updated guide, you have put the config stuff into the application_manifest.rb in a configure() call. Isn't the better/neater/right place for this config in moonshine.yml? Or is there a reason not to put plugin-specific config in the moonshine.yml?
Jason King
2009-05-14 at 06:56 AM
Hey, also, I think when you have:
plugin :ssh
You actually meant to say:
plugin :moonshine_ssh
Jason King
2009-05-14 at 07:19 AM
Forget my last (delete it if you want, and this one). I realized that I was using an old moonshine plugin.
Sure wish moonshine was a gem ;)
Post a comment