After a long and hard month of trying different things and getting bitten every time I realized,
just because Heroku uses a git repository as a deployment mechanism, you should not treat it as a git repository
it could have been rsync just as well, they went for git, don't get distracted because of this
if you do so, you open up yourself to all kinds of hurt. All of the aforementioned solutions fail miserably somewhere:
- it requires something to be done every time, or periodically, or unexpected things happen (pushing submodules, syncing subtrees, ...)
- if you use an engine for example to modularize your code, Bundler will eat you alive, it's impossible to describe the amount of frustration I've had with that project during the quest to find a good solution for this
- you try to add the engine as git repo link +
bundle deploy
- fail, you need to bundle update every time
- you try to add the engine as a
:path
+ bundle deploy
- fail, the dev team considers :path
option as "you're not using Bundler with this gem option" so it'll not bundle for production
- also, every refresh of the engine wants to update your rails stack -_-
- only solution I've found is to use the engine as a
/vendor
symlink in development, and actually copy the files for production
The solution
The app in question has 4 projects in git root:
- api - depending on the profile will run on 2 different heroku hosts - upload and api
- web - the website
- web-old - the old website, still in migration
- common - the common components extracted in an engine
All of the projects have a vendor/common
symlink looking at the root of the common
engine. When compiling source code for deployment to heroku we need to remove the symlink and rsync it's code to physically be in the vendor folder of each separate host.
- accepts a list of hostnames as arguments
- runs a git push in your development repo and then runs a clean git pull in a separate folder, making sure no dirty (uncommited) changes are pushed to the hosts automatically
- deploys the hosts in parallel - every heroku git repo is pulled, new code is rsynced into the right places, commited with basic push information in the git commit comment,
- in the end, we send a ping with curl to tell the hobby hosts to wake up and tail the logs to see if all went wine
- plays nice with jenkins too :D (automatic code push to test servers after successful tests)
Works very very nice in the wild with minimal (no?) problems 6 months now
Here's the script https://gist.github.com/bbozo/fafa2bbbf8c7b12d923f
Update 1
@AdamBuczynski, it's never so straightforward.
1st you will always have a production and test environment at the least - and a bunch of function specific clusters at the worse - suddenly 1 folder needs to map to n heroku projects as a pretty basic requirement and it all needs to be organized somehow so that the script "knows" what source you want to deploy where,
2nd you will want to share code between projects - now comes the sync_common
part, the shennanigans with symlinks in development being replaced by actual rsynced code on Heroku because Heroku requires a certain folder structure and bundler and rubygems really really really make things ugly very badly if you want to extract the common threads into a gem
3rd you will want to plug in CI and it will change a bit how subfolders and git repo need to be organized, in the end in the simplest possible use case you end up with the aforementioned gist.
In other projects I need to plug in Java builds, when selling software to multiple clients you will need to filter modules that get installed depending on the installation requirements and whatnot,
I should really consider exploring bundling things into a Rakefile or something and do everything that way...