This post originated from an RSS feed registered with Ruby Buzz
by Guy Naor.
Original Post: Improving Capistrano's put Command
Feed Title: Famundo - The Dev Blog
Feed URL: http://devblog.famundo.com/xml/rss/feed.xml
Feed Description: A blog describing the development and related technologies involved in creating famundo.com - a family management sytem written using Ruby On Rails and postgres
Capistrano can be used for many tasks not directly related to deployment. One such task, which I use a lot, is getting content form staging servers, and putting it up on production servers. (See my post about Deploying to Staging and Production with Capistrano for how to manage a staging/production split in capistrano.)
Capistrano already has a put command for putting data on the server. The problem is that it expects a string, and so if uploading very large files (like a tar.gz of uploaded files, for example) this is VERY memory intensive. Imagine a 1GB file loaded into a string...
As capistrano uses sftp if available, I wrote a small helper that does the same thing that get_file does in capistrano (this function is new in capistrano 1.4). Only it uploads files instead of downloading. There is one slight inconvenience with the code, in that it uploads the files one by one. Capistrano's put works in parallel, uploading to all servers concurrently. Usually this isn't a problem, as the upstrean bandwidth usually limit the speed of concurrent uploads anyway. A perfect solution will be to use a modified version of put, that reads the files a chunk at a time and uploads in chunks. For my need this is overkill.
Open your deploy.rb recipies file, and add to it the following:
class Capistrano::Actor# A saner put replacement that doesn't read the whole file into memory...def put_file(path,remote_path,options={})raise"put_file can only be used with SFTP"unlessCapistrano::SFTP&&options.fetch(:sftp,true)execute_on_servers(options)do|servers|servers.eachdo|server|logger.info"uploading #{File.basename(path)} to #{server}"sftp=sessions[server].sftpsftp.connectunlesssftp.state==:opensftp.put_filepath,remote_pathlogger.debug"done uploading #{File.basename(path)} to #{server}"endendendend
To use it, call it just like get_file:
desc"Upload content to the remote server"task:upload_content,:roles=>:appdoput_file'content.tar.gz','content.tar.gz'end
Note that it requires SFTP and will raise an error if not available.
As of capistrano 1.4, you can now put shared capistrano code in /etc/capistrano.conf and it will loaded into every single project. You can put the above code in /etc/capistrano.conf and have it available everywhere.