如何从 Capistrano 运行 rake 任务?

Posted

技术标签:

【中文标题】如何从 Capistrano 运行 rake 任务?【英文标题】:How do I run a rake task from Capistrano? 【发布时间】:2010-09-23 15:44:59 【问题描述】:

我已经有一个 deploy.rb 可以在我的生产服务器上部署我的应用程序。

我的应用程序包含一个自定义 rake 任务(lib/tasks 目录中的 .rake 文件)。

我想创建一个远程运行该 rake 任务的 cap 任务。

【问题讨论】:

有人能解释一下使用 capistrano 自己的 #rake 变量的优缺点吗?似乎它并不总是最好的选择。 【参考方案1】:

以前的答案对我没有帮助,我发现了这个: 来自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

运行你的任务使用

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

也许对某人有用

【讨论】:

【参考方案2】:

使用capistrano-rake gem

只需安装 gem 而不会弄乱自定义 capistrano 配方并在远程服务器上执行所需的 rake 任务,如下所示:

cap production invoke:rake TASK=my:rake_task

完全披露:我写的

【讨论】:

【参考方案3】:

更明确一点,在您的\config\deploy.rb 中,在任何任务或命名空间之外添加:

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

然后,从/rails_root/,你可以运行:

cap staging rake:invoke task=rebuild_table_abc

【讨论】:

最好使用 /usr/bin/env rake,这样 rvm 设置将获得正确的 rake。 如果有'bundle exec'【参考方案4】:

这对我有用:

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

然后只需运行cap production "invoke[task_name]"

【讨论】:

【参考方案5】:

Capistrano 3 通用版本(运行任何 rake 任务)

构建 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

用法示例:cap staging "invoke[db:migrate]"

请注意,deploy:set_rails_env 需要来自 capistrano-rails gem

【讨论】:

这仅支持单个参数,如果您将rake args[:command] 替换为execute :rake, "#args.command[#args.extras.join(",")]",您可以执行具有多个参数的任务,如下所示:cap production invoke["task","arg1","arg2"] @Robin Clowers 您可以传递多个参数,例如cap staging invoke['task[arg1\,arg2]']。我更喜欢这种方法而不是您提到的方法,因为它反映了 rake 的实际调用。使用这种方法,您还可以链接多个任务,这通常很有用:cap staging invoke['task1 task2[arg1] task3[arg2\,arg3]']。适用于 rake 10.2.0 或更高版本 这很棒 - 我想指出,您需要将 :app 作为您的服务器角色之一。 显然这需要是 "invoke[db:migrate]" ... 已更正。 @Abram 使用您建议的命令我得到“不知道如何构建任务 'invoke”【参考方案6】:

所以我一直在努力。它看起来运作良好。但是,您需要一个格式化程序才能真正利用代码。

如果您不想使用格式化程序,只需将日志级别设置为调试模式。这些 sema 到 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

这是我为使用上述代码而构建的格式化程序。它基于 sshkit 中内置的 :textsimple,但它不是调用自定义任务的好方法。哦,这很多不适用于最新版本的 sshkit gem。我知道它适用于 1.7.1。我这样说是因为 master 分支更改了可用的 SSHKit::Command 方法。

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

【讨论】:

【参考方案7】:

如果您希望能够传递多个参数,试试这个(基于 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

然后你可以像这样运行一个任务:cap production invoke["task","arg1","arg2"]

【讨论】:

【参考方案8】:

这也有效:

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

更多信息:Capistrano Run

【讨论】:

deploy_to/current 在这里不起作用。符号链接没有改变。如果您更新 rake 任务,这将运行旧代码。考虑改用 release_path。 垃圾信息越多?【参考方案9】:

...几年后...

看看 capistrano 的 rails 插件,你可以在 https://github.com/capistrano/rails/blob/master/lib/capistrano/tasks/migrations.rake#L5-L14 看到它看起来像:

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

【讨论】:

这仅适用于 capistrano v3。 帮助很大。谢谢! @Mirek Rusin 其他使用 run 的回复将在 capistrano 上工作,直到版本 2。从版本 3 开始,这是要走的路。【参考方案10】:

其中大部分来自above answer,并进行了小幅增强,可以从 capistrano 运行任何 rake 任务

从 capistrano 运行任何 rake 任务

$ 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

【讨论】:

【参考方案11】:
run("cd #deploy_to/current && /usr/bin/env rake `<task_name>` RAILS_ENV=production")

通过 Google 找到它 -- http://ananelson.com/said/on/2007/12/30/remote-rake-tasks-with-capistrano/

RAILS_ENV=production 是一个陷阱——我一开始并没有想到它,也无法弄清楚为什么任务没有做任何事情。

【讨论】:

一个小改进:如果用 && 替换分号,那么如果第一个语句(更改目录)失败,则第二个语句(运行 rake 任务)将不会运行。 如果您要部署到多台服务器,这将不起作用。它将多次运行 rake 任务。 应该尊重 capistrano 的 rake 设置 "cd #deploy_to/current &amp;&amp; #rake &lt;task_name&gt; RAILS_ENV=production" @Mark Redding:您能否将其中一台服务器置于其自己的角色以执行 rake 任务,并将您的 capistrano 任务限制为仅在具有该角色的服务器上运行? 我在 deploy.rb 中创建了一个任务。该任务上有一个 :roles => :db ,因此它只会在我定义为 db:migrate 的主要服务器上执行。【参考方案12】:
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 

【讨论】:

好。将其从 RAILS_ENV=production 更改为 RAILS_ENV=#rails_env 使其也可以在我的登台服务器上工作。【参考方案13】:

使用 Capistrano 风格的 rake 调用

有一种常见的方法可以“正常工作”require 'bundler/capistrano' 和其他修改 rake 的扩展。如果您使用多阶段,这也适用于预生产环境。要点?如果可以,请使用配置变量。

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 值 可能值得补充的是,如果您的任务是命名空间(即未在***命名空间中定义),您可能必须使用 top.run 而不仅仅是 run 谢谢@dolzenko。刚刚找到docs for the top method。在我们在同一个命名空间中定义run 的情况下,top.run 是必需的,否则即使任务在命名空间中,它仍然应该找到***run。我错过了什么吗?你的情况发生了什么? 我显然没有在同一个命名空间中定义任何运行方法,所以不确定我为什么需要它。无论如何,Capistrano 2.0 已经成为历史,而下一个版本是基于 Rake 的(希望让事情更可预测)【参考方案14】:

有一个有趣的 gem cape 可以让您的 rake 任务作为 Capistrano 任务使用,因此您可以远程运行它们。 cape 有据可查,但这里是关于如何设置 i 的简短概述。

安装 gem 后,只需将其添加到您的 config/deploy.rb 文件中即可。

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

现在,您可以在本地或通过cap 远程运行所有rake 任务。

作为额外的奖励,cape 可让您设置本地和远程运行 rake 任务的方式(不再是 bundle exec rake),只需将其添加到您的 config/deploy.rb 文件中即可:

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

【讨论】:

注意:仅适用于 Capistrano v2.x。与 Capistrano v3 不兼容。【参考方案15】:

我个人在生产中使用这样的辅助方法:

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

这允许运行 rake 任务,类似于使用 run(命令)方法。


注意:这类似于Duke 提出的建议,但我:

使用 latest_release 而不是 current_release - 根据我的经验,它更符合您在运行 rake 命令时的期望; 遵循 Rake 和 Capistrano 的命名约定(而不是:cmd -> task 和 rake -> run_rake) 不要设置 RAILS_ENV=#​​rails_env 因为设置它的正确位置是 default_run_options 变量。例如 default_run_options[:env] = 'RAILS_ENV' => 'production' # -> DRY!

【讨论】:

【参考方案16】:

这是我在 deploy.rb 中添加的内容,以简化正在运行的 rake 任务。这是对 capistrano 的 run() 方法的简单包装。

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

然后我像这样运行任何 rake 任务:

rake 'app:compile:jammit'

【讨论】:

这种冲突是因为 capistrano 定义了它自己的 rake 变量(用于确定使用哪个 rake),因此会破坏内置的接收器,例如预编译资产的接收器

以上是关于如何从 Capistrano 运行 rake 任务?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Rake 任务中运行 Rake 任务?

如何从控制台运行 rake 任务?

如何在 Ruby 脚本中运行 Rake 任务?

Capistrano - rake assets:precompile - 应用程序已经初始化

如何从另一个调用 Capistrano 任务?

如何运行 rake 任务,未使用 cron [重复]