Ruby on Rails, Git submodules and Vlad the Deployer
Do you want to deploy your Ruby on Rails project with Vlad the Deployer? And does your project include Git submodules? If the answer to both questions is yes, it is likely that you’ll run into trouble as Vlad does not offer support for submodules. In this post I will walk you through my solution for this problem. For that I’m gonna assume you know the general technicalities about Vlad and that you have it running, being able to remotely perform tasks and everything. Just not able to deploy your project including its submodules. Soon you will be!
Of course there have been others with this problem. Here’s a repo with an adjusted version of Vlad that handles submodules. (And it comes with some other improvements too.) However, it is based on an older version of Vlad and I would like to use the latest version available (2.0.0 at the time of writing). Another downside is the fact that it copies over the whole Git project, including .git folders, to the release directory, which means you’ll end up with all of Git’s objects in each of the releases that remain on the host; not very efficient in terms of disk space usage.
In my approach we’ll override two methods in the Vlad::Git class (From the the vlad-git gem) and we’ll use a custom Shell script too. The process has been separated into 3 simple steps.
1. Creating a module for the overrides
We’re gonna put our versions of the methods that we want to override in a separate module. I have named it Vlad::Git::Submodules and saved it to lib/vlad/git/submodules.rb:
module Vlad::Git::Submodules
# Our methods go here
end
To make sure the methods in this module are available to Vlad you’ll need to edit your project’s Rakefile:
#...
begin
require 'vlad'
Vlad.load :app => :passenger, :scm => :git
# Add these lines to inject our methods:
require 'lib/vlad/git/submodules'
source.instance_eval do
extend Vlad::Git::Submodules
end
rescue LoadError
puts 'Could not load Vlad'
end
2. Overriding Vlad::Git#checkout
Vlad::Git’s checkout method is responsible for cloning the latest revision of your project from the repository. It doesn’t initialize submodules, so we’ll need to make it do that by adding one line of code:
module Vlad::Git::Submodules
# A copy of Vlad::Git#checkout, but also inits submodules.
#
def checkout(revision, destination)
destination = File.join(destination, 'repo')
revision = 'HEAD' if revision =~ /head/i
[ "rm -rf #{destination}",
"#{git_cmd} clone #{repository} #{destination}",
"cd #{destination}",
"#{git_cmd} checkout -f -b deployed-#{revision} #{revision}",
"git submodule -q update --init", # This line is added to make sure submodules are fetched too.
"cd -"
].join(" && ")
end
end
3. Overriding Vlad::Git#export
The original export method copies over the clone to the location for the new release (e.g. releases/20090101154101/). It uses git archive to create a tarball of the project and then untars it in the release directory in order to make sure no Git project files are included. (You don’t want to end up with all of Git’s objects in the actual release directories for reasons mentioned above.) The problem here is that git archive does not archive the files in directories which are submodules. This is where we need our own Shell script. The script is called git-archive-all.sh (Download from github) and it lets you create a tarball that does include submodules! Put the script in a location that’s in your PATH first, e.g. /usr/bin, and make it executable. Now we can override the export method to make use of it:
module Vlad::Git::Submodules
#...
def checkout(revision, destination)
#...
end
# A copy of Vlad::Git#export, but also exports submodules.
#
def export(revision, destination)
[ "mkdir -p #{destination}",
"cd repo",
"git-archive-all.sh #{application}.tar", # write complete project to a tarball with git-archive-all
"cat #{application}.tar | (cd #{destination} && tar xf -)", # untar it in the release directory
"rm -f #{application}.tar", # remove the temporary tarball
"cd -",
"cd .."
].join(" && ")
end
end
Now rake vlad:update will use these methods and that’s really all there is to making Vlad deploy your submodules with your project in a clean, disk space efficient way.
Note that nothing is perfect and there are some known downsides of this approach:
- git-archive-all.sh does not support archiving a specific revision, therefor the revision argument in #export is ignored. Making the Shell script accept a revision argument would be trivial though.
- git-archive-all.sh seems to break when there’re no submodules in the project, so make sure you only apply this approach when you really need it.
Good luck!
