No Op Deploys with Moonshine

wfarr edited this page Feb 1, 2011 · 6 revisions

With more recent versions, Moonshine supports doing a no-op deploy. Essentially, this process shows you any commands or changes Moonshine would make on the next deploy. This is really useful for testing out manifest changes, in particular when developing a new Moonshine plugin against an existing application (it also pairs well with capistrano-cowboy)!

In this example, we're going to assume that we are working on a new Moonshine plugin for a critical application that doesn't have a staging environment called very_important_app. Being the dare-devils we are, we'll be testing these changes with cap cowboy deploy — but we'll be doing some testing in the form of no-op deploys along the way.

Doing a No-Op Deploy


Let's say we've generated this new Moonshine plugin for the sl command line utility:

› rails g moonshine:plugin sl    
      create  vendor/plugins/moonshine_sl/README.rdoc
      create  vendor/plugins/moonshine_sl/moonshine/init.rb
      create  vendor/plugins/moonshine_sl/lib/sl.rb
      create  vendor/plugins/moonshine_sl/spec/sl_spec.rb
      create  vendor/plugins/moonshine_sl/spec/spec_helper.rb

We've updated vendor/plugins/moonshine_sl/lib/sl.rb to look like so:

module Sl
  def sl(options = {})
    package 'sl',
      :ensure => :present
  end
end

And our app/manifests/application_manifest.rb looks like this:

require "#{File.dirname(__FILE__)}/../../vendor/plugins/moonshine/lib/moonshine.rb"
class ApplicationManifest < Moonshine::Manifest::Rails

  recipe :default_stack

  recipe :sl

  def application_packages
  end

  recipe :application_packages
end

Now, we want to test these changes without applying them. Let's do a no-op deploy with capistrano-cowboy! You'll notice that Moonshine prints out diffs of files and any commands it wants to run and any packages it wants to install: in essence, anything that Moonshine would change during a real deploy.

IMPORTANT: Make sure it's okay to run any custom Capistrano tasks/callbacks you may use, because this WILL cause (at least some of) them to run! There is a section below on making your customer deploy tasks no-op-friendly.

(There's more stuff after the big terminal output below, so just scroll on down!)

> cap cowboy noop deploy
    triggering start callbacks for `cowboy'
  * == Currently executing `moonshine:configure'
  * == Currently executing `cowboy'
    triggering start callbacks for `noop'
  * == Currently executing `moonshine:configure'
  * == Currently executing `noop'
    triggering start callbacks for `deploy'
  * == Currently executing `moonshine:configure'
  * == Currently executing `deploy'
  * == Currently executing `deploy:update'
 ** transaction: start
  * == Currently executing `deploy:update_code'
    triggering before callbacks for `deploy:update_code'
  * == Currently executing `cowboy:configure'
  * getting (via checkout) revision  to /var/folders/Uf/UfoIWzizEt8A4CHWCu+B6E+++TI/-Tmp-/20110201031426
    executing locally: cp -R . /var/folders/Uf/UfoIWzizEt8A4CHWCu+B6E+++TI/-Tmp-/20110201031426
  * processing exclusions...
    compressing /var/folders/Uf/UfoIWzizEt8A4CHWCu+B6E+++TI/-Tmp-/20110201031426 to /var/folders/Uf/UfoIWzizEt8A4CHWCu+B6E+++TI/-Tmp-/20110201031426.tar.gz
    executing locally: tar czf 20110201031426.tar.gz 20110201031426
    servers: ["very.important.app.managedmachine.com"]
 ** sftp upload /var/folders/Uf/UfoIWzizEt8A4CHWCu+B6E+++TI/-Tmp-/20110201031426.tar.gz -> /tmp/20110201031426.tar.gz
    [very.important.app.managedmachine.com] /tmp/20110201031426.tar.gz
    [very.important.app.managedmachine.com] done
  * sftp upload complete
  * executing "cd /srv/very_important_app/releases && tar xzf /tmp/20110201031426.tar.gz && rm /tmp/20110201031426.tar.gz"
    servers: ["very.important.app.managedmachine.com"]
    [very.important.app.managedmachine.com] executing command
    command finished
  * == Currently executing `deploy:finalize_update'
  * executing "chmod -R g+w /srv/very_important_app/releases/20110201031426"
    servers: ["very.important.app.managedmachine.com"]
    [very.important.app.managedmachine.com] executing command
    command finished
  * executing "rm -rf /srv/very_important_app/releases/20110201031426/log /srv/very_important_app/releases/20110201031426/public/system /srv/very_important_app/releases/20110201031426/tmp/pids &&\\\n      mkdir -p /srv/very_important_app/releases/20110201031426/public &&\\\n      mkdir -p /srv/very_important_app/releases/20110201031426/tmp &&\\\n      ln -s /srv/very_important_app/shared/log /srv/very_important_app/releases/20110201031426/log &&\\\n      ln -s /srv/very_important_app/shared/system /srv/very_important_app/releases/20110201031426/public/system &&\\\n      ln -s /srv/very_important_app/shared/pids /srv/very_important_app/releases/20110201031426/tmp/pids"
    servers: ["very.important.app.managedmachine.com"]
    [very.important.app.managedmachine.com] executing command
    command finished
  * executing "find /srv/very_important_app/releases/20110201031426/public/images /srv/very_important_app/releases/20110201031426/public/stylesheets /srv/very_important_app/releases/20110201031426/public/javascripts -exec touch -t 201102010314.28 {} ';'; true"
    servers: ["very.important.app.managedmachine.com"]
    [very.important.app.managedmachine.com] executing command
    command finished
    triggering after callbacks for `deploy:finalize_update'
  * == Currently executing `local_config:upload'
  * == Currently executing `local_config:symlink'
  * == Currently executing `shared_config:symlink'
  * == Currently executing `app:symlinks:update'
  * == Currently executing `deploy:symlink'
    triggering before callbacks for `deploy:symlink'
  * == Currently executing `moonshine:noop_apply'
  * executing "sudo -p 'sudo password: ' RAILS_ROOT=/srv/very_important_app/releases/20110201031426 DEPLOY_STAGE= RAILS_ENV=production shadow_puppet --noop /srv/very_important_app/releases/20110201031426/app/manifests/application_manifest.rb"
    servers: ["very.important.app.managedmachine.com"]
    [very.important.app.managedmachine.com] executing command
 ** [out :: very.important.app.managedmachine.com] 1,2c1
 ** [out :: very.important.app.managedmachine.com] <  (in /srv/very_important_app/releases/20110201030801)
 ** [out :: very.important.app.managedmachine.com] < (in /srv/very_important_app/releases/20110201030801)
 ** [out :: very.important.app.managedmachine.com] ---
 ** [out :: very.important.app.managedmachine.com] >
 ** [out :: very.important.app.managedmachine.com] \ No newline at end of file
 ** [out :: very.important.app.managedmachine.com] 1,5c1
 ** [out :: very.important.app.managedmachine.com] < Linux very.important.app.managedmachine.com 2.6.35-23-virtual #41-Ubuntu SMP Wed Nov 24 12:29:11 UTC 2010 x86_64 GNU/Linux
 ** [out :: very.important.app.managedmachine.com] < Ubuntu 10.04 LTS
 ** [out :: very.important.app.managedmachine.com] <
 ** [out :: very.important.app.managedmachine.com] < Welcome to Ubuntu!
 ** [out :: very.important.app.managedmachine.com] <  * Documentation:  https://help.ubuntu.com/
 ** [out :: very.important.app.managedmachine.com] ---
 ** [out :: very.important.app.managedmachine.com] > Linux very.important.app.managedmachine.com 2.6.35-23-virtual #41-Ubuntu SMP Wed Nov 24 12:29:11 UTC 2010 x86_64
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#34128040/File[/var/run/motd]/content: is {md5}ce85278802394ab0e35e7c2122a2d7a7, should be {md5}f9bd5e619c3197cdbb7cd7f52c7c0c06 (noop)
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#34128040/Exec[apt-get update]/returns: is notrun, should be 0 (noop)
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#34128040/Package[sl]/ensure: is purged, should be present (noop)
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#34128040/Exec[bundle install]/returns: is notrun, should be 0 (noop)
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#34128040/Exec[rails_gems]/returns: is notrun, should be 0 (noop)
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#34128040/Exec[rake tasks]/returns: is notrun, should be 0 (noop)
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#34128040/Exec[rake db:migrate]/returns: is notrun, should be 0 (noop)
    command finished
  * executing "rm -f /srv/very_important_app/current && ln -s /srv/very_important_app/releases/20110201031426 /srv/very_important_app/current"
    servers: ["very.important.app.managedmachine.com"]
    [very.important.app.managedmachine.com] executing command
    command finished
 ** transaction: commit
  * == Currently executing `deploy:restart'
    triggering after callbacks for `deploy:restart'
  * == Currently executing `deploy:cleanup'
  * executing "ls -x /srv/very_important_app/releases"
    servers: ["very.important.app.managedmachine.com"]
    [very.important.app.managedmachine.com] executing command
    command finished
*** no old releases to clean up
    triggering after callbacks for `deploy'
  * == Currently executing `deploy:rollback'
  * == Currently executing `deploy:rollback:revision'
  * executing "rm /srv/very_important_app/current; ln -s /srv/very_important_app/releases/20110201030801 /srv/very_important_app/current"
    servers: ["very.important.app.managedmachine.com"]
    [very.important.app.managedmachine.com] executing command
    command finished
  * == Currently executing `deploy:restart'
    triggering after callbacks for `deploy:restart'
  * == Currently executing `deploy:cleanup'
*** no old releases to clean up
  * == Currently executing `deploy:rollback:cleanup'
  * executing "if [ `readlink /srv/very_important_app/current` != /srv/very_important_app/releases/20110201031426 ]; then rm -rf /srv/very_important_app/releases/20110201031426; fi"
    servers: ["very.important.app.managedmachine.com"]
    [very.important.app.managedmachine.com] executing command
    command finished

Since that all looks good, we can run this deploy for real to confirm that Moonshine does what it said it would:

› cap cowboy deploy     
    triggering start callbacks for `cowboy'
  * == Currently executing `moonshine:configure'
  * == Currently executing `cowboy'
    triggering start callbacks for `deploy'
  * == Currently executing `moonshine:configure'
  * == Currently executing `deploy'
  * == Currently executing `deploy:update'
 ** transaction: start
  * == Currently executing `deploy:update_code'
    triggering before callbacks for `deploy:update_code'
  * == Currently executing `cowboy:configure'
  * getting (via checkout) revision  to /var/folders/Uf/UfoIWzizEt8A4CHWCu+B6E+++TI/-Tmp-/20110201032448
    executing locally: cp -R . /var/folders/Uf/UfoIWzizEt8A4CHWCu+B6E+++TI/-Tmp-/20110201032448
  * processing exclusions...
    compressing /var/folders/Uf/UfoIWzizEt8A4CHWCu+B6E+++TI/-Tmp-/20110201032448 to /var/folders/Uf/UfoIWzizEt8A4CHWCu+B6E+++TI/-Tmp-/20110201032448.tar.gz
    executing locally: tar czf 20110201032448.tar.gz 20110201032448
    servers: ["very.important.app.managedmachine.com"]
 ** sftp upload /var/folders/Uf/UfoIWzizEt8A4CHWCu+B6E+++TI/-Tmp-/20110201032448.tar.gz -> /tmp/20110201032448.tar.gz
    [very.important.app.managedmachine.com] /tmp/20110201032448.tar.gz
    [very.important.app.managedmachine.com] done
  * sftp upload complete
  * executing "cd /srv/very_important_app/releases && tar xzf /tmp/20110201032448.tar.gz && rm /tmp/20110201032448.tar.gz"
    servers: ["very.important.app.managedmachine.com"]
    [very.important.app.managedmachine.com] executing command
    command finished
  * == Currently executing `deploy:finalize_update'
  * executing "chmod -R g+w /srv/very_important_app/releases/20110201032448"
    servers: ["very.important.app.managedmachine.com"]
    [very.important.app.managedmachine.com] executing command
    command finished
  * executing "rm -rf /srv/very_important_app/releases/20110201032448/log /srv/very_important_app/releases/20110201032448/public/system /srv/very_important_app/releases/20110201032448/tmp/pids &&\\\n      mkdir -p /srv/very_important_app/releases/20110201032448/public &&\\\n      mkdir -p /srv/very_important_app/releases/20110201032448/tmp &&\\\n      ln -s /srv/very_important_app/shared/log /srv/very_important_app/releases/20110201032448/log &&\\\n      ln -s /srv/very_important_app/shared/system /srv/very_important_app/releases/20110201032448/public/system &&\\\n      ln -s /srv/very_important_app/shared/pids /srv/very_important_app/releases/20110201032448/tmp/pids"
    servers: ["very.important.app.managedmachine.com"]
    [very.important.app.managedmachine.com] executing command
    command finished
  * executing "find /srv/very_important_app/releases/20110201032448/public/images /srv/very_important_app/releases/20110201032448/public/stylesheets /srv/very_important_app/releases/20110201032448/public/javascripts -exec touch -t 201102010324.51 {} ';'; true"
    servers: ["very.important.app.managedmachine.com"]
    [very.important.app.managedmachine.com] executing command
    command finished
    triggering after callbacks for `deploy:finalize_update'
  * == Currently executing `local_config:upload'
  * == Currently executing `local_config:symlink'
  * == Currently executing `shared_config:symlink'
  * == Currently executing `app:symlinks:update'
  * == Currently executing `deploy:symlink'
    triggering before callbacks for `deploy:symlink'
  * == Currently executing `moonshine:apply'
  * executing "sudo -p 'sudo password: ' RAILS_ROOT=/srv/very_important_app/releases/20110201032448 DEPLOY_STAGE= RAILS_ENV=production shadow_puppet /srv/very_important_app/releases/20110201032448/app/manifests/application_manifest.rb"
    servers: ["very.important.app.managedmachine.com"]
    [very.important.app.managedmachine.com] executing command
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/File[/var/run/motd]: Filebucketed to  with sum ce85278802394ab0e35e7c2122a2d7a7
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/File[/var/run/motd]/content: content changed '{md5}ce85278802394ab0e35e7c2122a2d7a7' to '{md5}f9bd5e619c3197cdbb7cd7f52c7c0c06'
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[apt-get update]/returns: executed successfully
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Package[sl]/ensure: ensure changed 'purged' to 'present'
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using rake (0.8.7) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using abstract (1.0.0) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using activesupport (3.0.3) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using builder (2.1.2) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using i18n (0.5.0) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using activemodel (3.0.3) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using erubis (2.6.6) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using rack (1.2.1) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using rack-mount (0.6.13) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using rack-test (0.5.7) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using tzinfo (0.3.24) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using actionpack (3.0.3) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using mime-types (1.16) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using polyglot (0.3.1) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using treetop (1.4.9) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using mail (2.2.15) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using actionmailer (3.0.3) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using arel (2.0.7) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using activerecord (3.0.3) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using activeresource (3.0.3) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using bundler (1.0.9) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using thor (0.14.6) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using railties (3.0.3) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using rails (3.0.3) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using sqlite3 (1.3.3) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Using sqlite3-ruby (1.3.3) 
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: Your bundle is complete! It was installed into /srv/very_important_app/shared/bundle
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[bundle install]/returns: executed successfully
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[rails_gems]/returns: executed successfully
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[rake tasks]/returns: (in /srv/very_important_app/releases/20110201032448)
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[rake tasks]/returns: executed successfully
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[rake db:migrate]/returns: (in /srv/very_important_app/releases/20110201032448)
 ** [out :: very.important.app.managedmachine.com] notice: /ApplicationManifest#35297420/Exec[rake db:migrate]/returns: executed successfully
    command finished
  * executing "rm -f /srv/very_important_app/current && ln -s /srv/very_important_app/releases/20110201032448 /srv/very_important_app/current"
    servers: ["very.important.app.managedmachine.com"]
    [very.important.app.managedmachine.com] executing command
    command finished
 ** transaction: commit
  * == Currently executing `deploy:restart'
  * executing "touch /srv/very_important_app/current/tmp/restart.txt"
    servers: ["very.important.app.managedmachine.com"]
    [very.important.app.managedmachine.com] executing command
    command finished
    triggering after callbacks for `deploy:restart'
  * == Currently executing `deploy:cleanup'
  * executing "ls -x /srv/very_important_app/releases"
    servers: ["very.important.app.managedmachine.com"]
    [very.important.app.managedmachine.com] executing command
    command finished
*** no old releases to clean up
    triggering after callbacks for `deploy'

Looks like Moonshine did just what it said it would! Now if we run sl on the server, it should work:

rails@very:~$ which sl
/usr/games/sl
rails@very:~$ sl

Works like a charm!

Making Your Custom Capistrano Tasks No-Op Friendly


Say we have a task set to restart god after deploy:restart:

namespace :god do
  task :restart do
    sudo 'restart god'
  end
end

On a no-op, we don't want to do that though! So, we simply wrap the actions we don't want to run with a check for the no-op:

namespace :god do
  task :restart do
    if fetch(:noop)
      sudo 'restart god'
    else
      puts "Not restarting god! (noop)"
    end
  end
end