-
They run in the background, thus there is no visibility as to whether they succeed or fail.
-
Deploying an app from start to finish involves multiple users, and if done right requires sudo privileges. The system user under which the app runs must not have sudo privileges, but the app itself would use Foreman ideally. If you export to Sys V or Upstart, you will need sudo privileges.
-
-
Save gerhard/1852864 to your computer and use it in GitHub Desktop.
The intrepid developer could also embrace the ideas in the upstart cookbook to Run a Job When a File or Directory is Created/Deleted and merge this with some sort of evented file monitoring (inotify on linux these days?).
That screen shot was meant to include the command that triggered it, sorry:
Production is git remote, twatter_production@<somewhere>.com:current
. The server is an ubuntu machine with a user, twatter_production
, whose shell is set to /usr/bin/git-shell
and whose home directory houses the git repository (of the application). There are a couple of interesting things about the git repository:
git config --add receive.denycurrentbranch ignore
.git/hooks/pre-receive
:
#!/usr/bin/env ruby
refspecs = STDIN.read.chomp.split("\n")
if refspecs.length != 1
puts %Q{
remote says:
Hi there, you just pushed #{refspecs.length} branches, but I only accept one branch
at a time, because I use it to update the working copy.
The best way to push just a single branch is like this:
$ git push <remote-name> <branch-name>
}
exit 1
end
.git/hooks/post-receive
:
#!/usr/local/bin/rvm-auto-ruby
def fail message=nil
puts message unless message.nil?
exit 1
end
def shell cmd
output = `#{cmd}`
output.chomp if $? == 0
end
def log_shell message, cmd
print "#{message}... "
output = `#{cmd}`
if $? == 0
puts "done."
else
fail "failed!\n\n#{output.chomp}"
end
end
STDOUT.sync = true
refspecs = STDIN.read.chomp.split("\n")
old_id, new_id, ref_name = refspecs.first.split(/\s+/)
new_branch = ref_name.scan(/^refs\/heads\/(.*)$/).flatten.first
# Otherwise operations in sub-gits fail
ENV.delete "GIT_DIR"
if new_branch.nil?
fail "Couldn't figure out what branch '#{ref_name}' refers to, not updating."
else
env_git = "env -i #{`which git`.chomp}"
Dir.chdir('..') do # change dir to .git/..
branches = shell("#{env_git} branch").split("\n")
star_branches = branches.grep(/^\*/)
old_branch = star_branches.empty? ? nil : star_branches.first.split(/\s+/, 2)[-1]
branches.map! { |branch| branch.split(/\s+/, 2).last }
if !branches.include?(new_branch)
log_shell "Creating the '#{new_branch}' branch", "#{env_git} checkout -b '#{new_branch}'"
end
if old_branch != new_branch
log_shell "Switching to the '#{new_branch}' branch", "#{env_git} checkout '#{new_branch}'"
end
print "Writing database.yml... "
File.open 'config/database.yml', 'w' do |file|
file.write "production:\n adapter: postgresql\n database: twatter_production\n username: twatter_production\n password: d0178bd6923df8eb251cca416c56eb7ac9325820\n"
end
puts "done."
log_shell "Enabling maintenance mode", "mkdir -p tmp && touch tmp/maintenance.txt"
log_shell "Updating to #{new_id[0...7]}", "#{env_git} reset --hard '#{new_id}'"
log_shell "Updating submodules", "#{env_git} submodule update --init"
log_shell "Bundling", 'bundle --deployment'
log_shell "Pre-compiling assets", 'bundle exec rake RAILS_ENV=production assets:clean assets:precompile'
log_shell "Migrating", 'bundle exec rake RAILS_ENV=production db:migrate'
log_shell "Flagging app for restart", 'mkdir -p tmp && touch tmp/restart.txt'
log_shell "Disabling maintenance mode", "rm tmp/maintenance.txt"
end
end
Being that this is small scale, this application is currently deployed with passenger, but the principles are the same. The caveat in this particular git script is that it doesn't rollback on error, although adding that behaviour would be fairly easy. I have other apps deployed via nginx/foreman/thin/resque setups this way with upstart signals.
(Also, credit to @benhoskings' early git deployment scripts from babushka for the above.)
1. Nice, I didn't know git hooks would run synchronously. Thanks!
With git hooks, don't you have to keep modifying the remote git hooks whenever there is an extra deployment step?
With deliver, I wanted to keep the strategy under version control, together with the project, similar to Capfile. With this approach, I can test deployment changes on a local VM before committing them.
Also, git-hooks alone can't work with multi-server environments, you need something extra to orchestrate the deploy. What about cloud environments which are in constant flux? A deliver strategy which uses eg. an API to fetch the instances where it should push code to feels a lot more flexible. That feature will be coming soon, we're using it at GoSquared where instances auto-scale based on load. One minute I might be pushing the code to 2 instances, half an hour later it might be 10.
2. Can you run restart app-name
on Ubuntu without sudo privileges? Also, an app is no longer just a rack server. It might have schedulers, bg workers, RabbitMQ consumers & publishers etc. restart app-name
is the most efficient way I know of restarting all those services which are part of it. How would you approach this?
Run a Job When a File or Directory is Created/Deleted
That's a brilliant tip! I shall explore the idea further ; ).
Care to paste your /usr/local/bin/rvm-auto-ruby
? Would really like to see how it works!
You could have your git hook call something inside your application as a deployment step. rake deploy
might be interesting, then you can do whatever is necessary with all the power of rake dependencies.
Yeah, the multi-server stuff would be a kicker, but I don't see Capistrano/Chef falling short here. What will deliver offer which is different?
Keep in mind that restart
on ubuntu simply emits a stop
and start
event for an upstart job. You can make your application job emit a deploy
job from the git deploy hook, then fire any additional events you need—reload configuration, bounce your message queue, scale up/down background workers, etc., even make it re-export the forman upstart scripts.
rvm-auto-ruby
comes with rvm. :-)
touch tmp/restart.txt
which most rack servers respect.