Chef deploy_revision and Capistrano git_style
One thing that was annoying me for a long time, was that, using Capistrano deployment, you cannot spawn a new vanilla virtual machine, and bring it to a fully up-and-running state with just one Chef command.
make deploy_revision compatible with Capistrano, so deployments can happen with Capistrano, until we’ve decided to fully migrate to Chef, or to stick with the push deployment
Attributes
Let’s manage some things with attributes, so we can adjust them centrally later, in case needed.
default['rails']['app-root'] = '/var/www/example.com'
default['rails']['owner'] = 'deploy'
default['rails']['group'] = 'deploy'
# The shared directories that we're going to need later
default['rails']['shared_directories'] = %w{
shared
shared/config
shared/pids
shared/sockets
}
Workaround to maintain the git repository with a unprivileged user
Due to a bug in git, we cannot use the “user” and “group” attributes in the deploy resource. This would result in the following error
STDERR: fatal: unable to access '/root/.config/git/config': Permission denied
Therefore, we’re chowning the app directory manually after the deploy. See CHEF-3940.
execute 'chown app root' do
command "chown -R #{node['rails']['owner']}:#{node['rails']['group']} #{node['rails']['app-root']}"
action :nothing
end
bundle install
We cannot start up the application without an installed bundle. The following example mimicks the Capistrano way of “bundle install”, using chruby
execute 'bundle install' do
user node['rails']['owner']
cwd "#{node['rails']['app-root']}/current"
# bundler needs LC_ALL set, to prevent "invalid byte sequence in US-ASCII" error
# HOME also needs to be set to allow a user install
environment 'LC_ALL' => 'en_US.UTF-8',
'HOME' => ::File.dirname(node['rails']['app-root'])
command [ "/usr/local/bin/chruby-exec #{node['rails']['ruby-string']} --",
"bundle install --gemfile #{node['rails']['app-root']}/current/Gemfile",
"--path #{node['rails']['app-root']}/shared/bundle",
"--deployment --quiet --without development test" ].join(' ')
action :nothing
end
The deploy resource
Now to the deploy resource. This was a little tricky, as we’re using capistrano_fanfare’s git_style deployment strategy in our deploy.rb
set :deploy_via, :git_style
This has several advantages to timestamped deploys, and actually Chef’s deploy_revision is using a similar approach. Unfortunately, there are some differences:
-
The directory where the repository is checked out. Capistrano (with :git_style) uses
current
, whereas Chef usesshared/cached-copy
. -
Release management. Capistrano creates empty folders in
releases
using timestamp-SHA directory names, Chef creates SHA directories, with the complete repository content (but without .git) -
Chef symlinks
current
toreleases/current-SHA
, and actually works with thereleases/current-SHA
directory during deployment, so we need to setup a temporary link tocurrent
-
Chef places an empty file with SHA of the current commit as the filename in the repository_cache directory. This gives Capistrano hickups when checkout out a certain commit (ambigious statement)
I was addressing those issues in the following way:
deploy_revision node['rails']['app-root'] do
repository 'git@github.com:chr4/rails-app.git'
# checkout the repository to current, as capistrano :git_style would do
repository_cache "../current"
before_symlink do
# create shared directories
node['rails']['shared_directories'].each do |dir|
directory "#{node['rails']['app-root']}/#{dir}" do
owner node['rails']['owner']
group node['rails']['group']
mode 00755
end
end
# remove the release directory (not a real git repo)
directory release_path do
recursive true
action :delete
end
# create a workaround symlink
link release_path do
to "#{File.dirname(release_path)}/../current"
end
end
# create symlinks to shared directory
purge_before_symlink %w{log tmp/sockets tmp/pids}
create_dirs_before_symlink %w{tmp}
symlinks( { 'sockets' => 'tmp/sockets',
'pids' => 'tmp/pids',
'log' => 'log'} )
# remove the workaround symlink after restarting services
after_restart do
link release_path do
action :delete
end
# remove strange SHA file, which is hindering capistrano
link "#{File.dirname(release_path)}/../current/#{File.basename(release_path)}" do
action :delete
end
end
# owner workaround (see comment above, CHEF-3940)
notifies :run, 'execute[chown app root]', :immediately
# install dependencies and start up application
notifies :run, 'execute[bundle install]', :immediately
notifies :restart, 'service[unicorn]'
# only run deployment once
# succeeding deploys will be done using capistrano (for now)
not_if "test -e #{node['rails']['app-root']}/current"
end
The not_if
statement allows us to continue using Capistrano to deploy like before, until we might decide to fully migrate to a continous deployment using Chef.