I already have a deploy.rb that can deploy my app on my production server.
My app contains a custom rake task (a .rake file in the lib/tasks directory).
I'd like to create a cap task that will remotely run that rake task.
I already have a deploy.rb that can deploy my app on my production server.
My app contains a custom rake task (a .rake file in the lib/tasks directory).
I'd like to create a cap task that will remotely run that rake task.
A little bit more explicit, in your \config\deploy.rb, add outside any task or namespace:
namespace :rake do
desc "Run a task on a remote server."
# run like: cap staging rake:invoke task=a_certain_task
task :invoke do
run("cd #{deploy_to}/current; /usr/bin/env rake #{ENV['task']} RAILS_ENV=#{rails_env}")
end
end
Then, from /rails_root/, you can run:
cap staging rake:invoke task=rebuild_table_abc
Capistrano 3 Generic Version (run any rake task)
Building a generic version of Mirek Rusin's answer:
desc 'Invoke a rake command on the remote server'
task :invoke, [:command] => 'deploy:set_rails_env' do |task, args|
on primary(:app) do
within current_path do
with :rails_env => fetch(:rails_env) do
rake args[:command]
end
end
end
end
Example usage: cap staging "invoke[db:migrate]"
Note that deploy:set_rails_env requires comes from the capistrano-rails gem
...couple of years later...
Have a look at capistrano's rails plugin, you can see at https://github.com/capistrano/rails/blob/master/lib/capistrano/tasks/migrations.rake#L5-L14 it can look something like:
desc 'Runs rake db:migrate if migrations are set'
task :migrate => [:set_rails_env] do
on primary fetch(:migration_role) do
within release_path do
with rails_env: fetch(:rails_env) do
execute :rake, "db:migrate"
end
end
end
end
run("cd #{deploy_to}/current && /usr/bin/env rake `<task_name>` RAILS_ENV=production")
Found it with Google -- http://ananelson.com/said/on/2007/12/30/remote-rake-tasks-with-capistrano/
The RAILS_ENV=production was a gotcha -- I didn't think of it at first and couldn't figure out why the task wasn't doing anything.
There's a common way that'll "just work" with require 'bundler/capistrano' and other extensions that modify rake. This will also work with pre-production environments if you're using multistage. The gist? Use config vars if you can.
desc "Run the super-awesome rake task"
task :super_awesome do
rake = fetch(:rake, 'rake')
rails_env = fetch(:rails_env, 'production')
run "cd '#{current_path}' && #{rake} super_awesome RAILS_ENV=#{rails_env}"
end
capistrano-rake gemJust install the gem without messing with custom capistrano recipes and execute desired rake tasks on remote servers like this:
cap production invoke:rake TASK=my:rake_task
Full Disclosure: I wrote it
I personally use in production a helper method like this:
def run_rake(task, options={}, &block)
command = "cd #{latest_release} && /usr/bin/env bundle exec rake #{task}"
run(command, options, &block)
end
That allows to run rake task similar to using the run (command) method.
NOTE: It is similar to what Duke proposed, but I:
There's an interesting gem cape that makes your rake tasks available as Capistrano tasks, so you can run them remotely. cape is well documented, but here's a short overview on how to set i up.
After installing the gem, just add this to your config/deploy.rb file.
# config/deploy.rb
require 'cape'
Cape do
# Create Capistrano recipes for all Rake tasks.
mirror_rake_tasks
end
Now, you can run all you rake tasks locally or remotely through cap.
As an added bonus, cape lets you set how you want to run your rake task locally and remotely (no more bundle exec rake), just add this to your config/deploy.rb file:
# Configure Cape to execute Rake via Bundler, both locally and remotely.
Cape.local_rake_executable = '/usr/bin/env bundle exec rake'
Cape.remote_rake_executable = '/usr/bin/env bundle exec rake'
namespace :rake_task do
task :invoke do
if ENV['COMMAND'].to_s.strip == ''
puts "USAGE: cap rake_task:invoke COMMAND='db:migrate'"
else
run "cd #{current_path} && RAILS_ENV=production rake #{ENV['COMMAND']}"
end
end
end
This worked for me:
task :invoke, :command do |task, args|
on roles(:app) do
within current_path do
with rails_env: fetch(:rails_env) do
execute :rake, args[:command]
end
end
end
end
Then simply run cap production "invoke[task_name]"
Here's what I put in my deploy.rb to simplify running rake tasks. It's a simple wrapper around capistrano's run() method.
def rake(cmd, options={}, &block)
command = "cd #{current_release} && /usr/bin/env bundle exec rake #{cmd} RAILS_ENV=#{rails_env}"
run(command, options, &block)
end
Then I just run any rake task like so:
rake 'app:compile:jammit'
You can use this:
namespace :rails_staging_task do
desc "Create custom role"
task :create_custom_role do
on roles(:app), in: :sequence, wait: 5 do
within "#{deploy_to}/current" do
with rails_env: :staging do
rake "create_role:my_custom_role"
end
end
end
end
# other task here
end
Most of it is from above answer with a minor enhancement to run any rake task from capistrano
Run any rake task from capistrano
$ cap rake -s rake_task=$rake_task
# Capfile
task :rake do
rake = fetch(:rake, 'rake')
rails_env = fetch(:rails_env, 'production')
run "cd '#{current_path}' && #{rake} #{rake_task} RAILS_ENV=#{rails_env}"
end
If you want to be able to pass multiple arguments try this (based on marinosbern's answer):
task :invoke, [:command] => 'deploy:set_rails_env' do |task, args|
on primary(:app) do
within current_path do
with :rails_env => fetch(:rails_env) do
execute :rake, "#{args.command}[#{args.extras.join(",")}]"
end
end
end
end
Then you can run a task like so: cap production invoke["task","arg1","arg2"]
This also works:
run("cd #{release_path}/current && /usr/bin/rake <rake_task_name>", :env => {'RAILS_ENV' => rails_env})
More info: Capistrano Run
Previous answers didn't help me and i found this: From http://kenglish.co/run-rake-tasks-on-the-server-with-capistrano-3-and-rbenv/
namespace :deploy do
# ....
# @example
# bundle exec cap uat deploy:invoke task=users:update_defaults
desc 'Invoke rake task on the server'
task :invoke do
fail 'no task provided' unless ENV['task']
on roles(:app) do
within release_path do
with rails_env: fetch(:rails_env) do
execute :rake, ENV['task']
end
end
end
end
end
to run your task use
bundle exec cap uat deploy:invoke task=users:update_defaults
Maybe it will be useful for someone
So I have been working on this. it seams to work well. However you need a formater to really take advantage of the code.
If you don't want to use a formatter just set the log level to to debug mode. These semas to h
SSHKit.config.output_verbosity = Logger::DEBUG
namespace :invoke do
desc 'Run a bash task on a remote server. cap environment invoke:bash[\'ls -la\'] '
task :bash, :execute do |_task, args|
on roles(:app), in: :sequence do
SSHKit.config.format = :supersimple
execute args[:execute]
end
end
desc 'Run a rake task on a remote server. cap environment invoke:rake[\'db:migrate\'] '
task :rake, :task do |_task, args|
on primary :app do
within current_path do
with rails_env: fetch(:rails_env) do
SSHKit.config.format = :supersimple
rake args[:task]
end
end
end
end
end
This is the formatter I built to work with the code above. It is based off the :textsimple built into the sshkit but it is not a bad way to invoke custom tasks. Oh this many not works with the newest version of sshkit gem. I know it works with 1.7.1. I say this because the master branch has changed the SSHKit::Command methods that are available.
module SSHKit
module Formatter
class SuperSimple < SSHKit::Formatter::Abstract
def write(obj)
case obj
when SSHKit::Command then write_command(obj)
when SSHKit::LogMessage then write_log_message(obj)
end
end
alias :<< :write
private
def write_command(command)
unless command.started? && SSHKit.config.output_verbosity == Logger::DEBUG
original_output << "Running #{String(command)} #{command.host.user ? "as #{command.host.user}@" : "on "}#{command.host}\n"
if SSHKit.config.output_verbosity == Logger::DEBUG
original_output << "Command: #{command.to_command}" + "\n"
end
end
unless command.stdout.empty?
command.stdout.lines.each do |line|
original_output << line
original_output << "\n" unless line[-1] == "\n"
end
end
unless command.stderr.empty?
command.stderr.lines.each do |line|
original_output << line
original_output << "\n" unless line[-1] == "\n"
end
end
end
def write_log_message(log_message)
original_output << log_message.to_s + "\n"
end
end
end
end