Como executo uma tarefa rake a partir do Capistrano?


105

Já tenho um deploy.rb que pode implantar meu aplicativo em meu servidor de produção.

Meu aplicativo contém uma tarefa rake personalizada (um arquivo .rake no diretório lib / tasks).

Eu gostaria de criar uma tarefa de limite que executará remotamente essa tarefa de rake.


2
Alguém pode explicar os prós / contras de usar a #{rake}variável do próprio capistrano ? Parece que nem sempre é a melhor opção.
lulalala

Respostas:


59

Um pouco mais explícito, em seu \config\deploy.rb, adicione fora de qualquer tarefa ou 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

Então, /rails_root/você pode executar:

cap staging rake:invoke task=rebuild_table_abc

1
melhor usar / usr / bin / env rake para que as configurações do rvm selecionem o rake correto.
DGM

8
Com 'bundle exec' se disponível
Bogdan Gusiev,

44

... alguns anos depois ...

Dê uma olhada no plug-in rails do capistrano, você pode ver em https://github.com/capistrano/rails/blob/master/lib/capistrano/tasks/migrations.rake#L5-L14 ele pode ser parecido com:

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

3
Isso é apenas para capistrano v3.
phillbaker

Ajudou muito. Obrigado! @Mirek Rusin
Nishant Shrivastava

o outro responde, esse uso runfuncionará no capistrano até a versão 2. a partir da versão 3 esse é o caminho a percorrer.
Don Giulio

44

Versão Genérica Capistrano 3 (execute qualquer tarefa de rake)

Construindo uma versão genérica da resposta de Mirek Rusin:

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

Exemplo de uso: cap staging "invoke[db:migrate]"

Observe que deploy:set_rails_envrequer vem da joia capistrano-rails


1
Isso suporta apenas um único argumento, se você substituir rake args[:command] por, execute :rake, "#{args.command}[#{args.extras.join(",")}]" pode executar uma tarefa com vários argumentos, como: cap production invoke["task","arg1","arg2"]
Robin Clowers

1
@ Robin Clowers Você pode passar vários argumentos, por exemplo cap staging invoke['task[arg1\,arg2]']. Eu prefiro essa abordagem ao que você mencionou porque ela reflete a invocação real do rake. Com essa abordagem, você também pode encadear múltiplas tarefas, o que é frequentemente útil: cap staging invoke['task1 task2[arg1] task3[arg2\,arg3]']. Funciona para rake 10.2.0 ou mais recente
marinosb de

isso é ótimo - gostaria de observar, você precisa incluir: app como uma de suas funções de servidor.
lfender6445

Aparentemente, isso precisava ser "invoke [db: migrate]" ... Correção feita.
Abrão

@Abram com o comando que você sugeriu que recebo "Não sei como criar tarefa 'invocar"
dc10

41
run("cd #{deploy_to}/current && /usr/bin/env rake `<task_name>` RAILS_ENV=production")

Encontrei com o Google - http://ananelson.com/said/on/2007/12/30/remote-rake-tasks-with-capistrano/

O RAILS_ENV=productionfoi uma pegadinha - Eu não penso nisso em primeiro lugar e não conseguia descobrir por que a tarefa não estava fazendo nada.


2
Uma pequena melhoria: se você substituir o ponto-e-vírgula por &&, a segunda instrução (executando a tarefa rake) não será executada se a primeira instrução (alterando o diretório) falhar.
Teflon Ted

2
Isso não funcionará se você estiver implantando em vários servidores. Ele executará a tarefa de rake várias vezes.
Mark Redding

4
deve-se respeitar a configuração de rake do capistrano"cd #{deploy_to}/current && #{rake} <task_name> RAILS_ENV=production"
kares

@Mark Redding: Você poderia colocar um dos servidores em sua própria função para tarefas de rake e restringir sua tarefa capistrano para rodar apenas em servidores com essa função?
mj1531

Eu fiz algo onde criei uma tarefa no meu deploy.rb. Essa tarefa tem um: roles =>: db nela de forma que só será executada no mesmo servidor que eu defini como meu principal para db: migrate.
Mark Redding

20

Use invocações de rake no estilo Capistrano

Há uma maneira comum que "simplesmente funciona" com require 'bundler/capistrano'outras extensões que modificam o rake. Isso também funcionará com ambientes de pré-produção se você estiver usando vários estágios. A essência? Use config vars, se puder.

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

2
Esta é a melhor solução, usa os valores capistrano quando disponíveis
loopj

2
Provavelmente, vale a pena adicionar que se sua tarefa tiver um namespace (ou seja, definida não no namespace de nível superior), você pode ter que usar em top.runvez de apenasrun
dolzenko

Obrigado @dolzenko. Acabei de encontrar os documentos do topmétodo . No caso em que definimos runno mesmo namespace, top.runé obrigatório, caso contrário, ele ainda deve encontrar o nível superior, runmesmo onde a tarefa tem namespace. Eu perdi alguma coisa? O que aconteceu no seu caso?
capitainpete,

1
Eu claramente não tinha nenhum método de execução definido no mesmo namespace, então não sei por que precisava disso. Em qualquer caso, Capistrano 2.0 é uma história e a próxima versão é baseada em Rake (tornando as coisas mais previsíveis, espero)
dolzenko

16

Use a capistrano-rakegema

Basta instalar a gema sem mexer nas receitas personalizadas de capistrano e executar as tarefas de rake desejadas em servidores remotos como este:

cap production invoke:rake TASK=my:rake_task

Divulgação completa: eu escrevi


7

Eu pessoalmente uso na produção um método auxiliar como este:

def run_rake(task, options={}, &block)
  command = "cd #{latest_release} && /usr/bin/env bundle exec rake #{task}"
  run(command, options, &block)
end

Isso permite executar a tarefa rake semelhante ao uso do método run (command).


NOTA: É semelhante ao que Duke propôs, mas eu:

  • use latest_release em vez de current_release - pela minha experiência, é mais o que você espera ao executar um comando rake;
  • siga a convenção de nomenclatura de Rake e Capistrano (em vez de: cmd -> tarefa e rake -> run_rake)
  • não configure RAILS_ENV = # {rails_env} porque o lugar certo para configurá-lo é a variável default_run_options. Ex: default_run_options [: env] = {'RAILS_ENV' => 'produção'} # -> SECO!

5

Há uma capa de joia interessante que disponibiliza suas tarefas de rake como tarefas do Capistrano, para que você possa executá-las remotamente. capeestá bem documentado, mas aqui está uma breve visão geral sobre como configurá-lo.

Depois de instalar a gem, basta adicionar isso ao seu config/deploy.rbarquivo.

# config/deploy.rb
require 'cape'
Cape do
  # Create Capistrano recipes for all Rake tasks.
  mirror_rake_tasks
end

Agora, você pode executar todas as suas raketarefas localmente ou remotamente cap.

Como um bônus adicional, capepermite que você defina como deseja executar sua tarefa de rake localmente e remotamente (não mais bundle exec rake), basta adicionar isso ao seu config/deploy.rbarquivo:

# 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'

Nota: funciona apenas para Capistrano v2.x. Não compatível com Capistrano v3.
nayiaw,

3
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 

1
Boa. Alterá-lo de RAILS_ENV=productionpara RAILS_ENV=#{rails_env}permite que ele funcione no meu servidor de teste também.
evanrmurphy

2

Aqui está o que coloquei em meu deploy.rb para simplificar a execução de tarefas rake. É um invólucro simples em torno do método run () de capistrano.

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

Então eu apenas executo qualquer tarefa rake como esta:

rake 'app:compile:jammit'

isso entra em conflito quando o capistrano define sua própria variável de rake (usada para determinar qual rake usar) e, portanto, quebra os recibos embutidos, por exemplo, aquele que pré-compila ativos
Michael,

2

Isso funcionou para mim:

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

Então simplesmente execute cap production "invoke[task_name]"


1

A maior parte é da resposta acima com um pequeno aprimoramento para executar qualquer tarefa de rake do capistrano

Execute qualquer tarefa de rake de 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

1

Isso também funciona:

run("cd #{release_path}/current && /usr/bin/rake <rake_task_name>", :env => {'RAILS_ENV' => rails_env})

Mais informações: Capistrano Run


1
{deploy_to} / current não funcionará aqui. O link simbólico não mudou. Se você atualizar a tarefa rake, o código antigo será executado. Considere usar {release_path} em vez disso.
Mark Redding

quanto mais informação é spam?
hcarreras

1

Se você deseja passar vários argumentos, tente isto (com base na resposta de marinosbern):

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

Então você pode executar uma tarefa como esta: cap production invoke["task","arg1","arg2"]


0

Então, tenho trabalhado nisso. parece funcionar bem. No entanto, você precisa de um formador para realmente tirar vantagem do código.

Se você não quiser usar um formatador, defina o nível de log como modo de depuração. Esses semas para h

SSHKit.config.output_verbosity = Logger::DEBUG

Cap Stuff

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

Este é o formatador que construí para trabalhar com o código acima. É baseado no: textimple integrado ao sshkit, mas não é uma maneira ruim de invocar tarefas personalizadas. Oh, isso não funciona com a versão mais recente do sshkit gem. Eu sei que funciona com 1.7.1. Digo isso porque o branch master mudou os métodos SSHKit :: Command que estão disponíveis.

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

0

Respostas anteriores não me ajudaram e eu achei isso: De 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

para executar sua tarefa, use

bundle exec cap uat deploy:invoke task=users:update_defaults

Talvez seja útil para alguém

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.