卡皮斯特拉诺 rails |在 gitlab 部署期间迁移时设置 rails_env 时 Rake::Task 的未定义方法 `already_invoked'
Capistrano rails | undefined method `already_invoked' for Rake::Task when setting rails_env while migrations during gitlab deploy
我花了很多时间对此进行调试,但似乎还没有接近答案。我也考虑过在 capistrano 存储库中创建一个问题,但我真的不认为这是 capistrano 本身的问题。
上下文:
我在托管在 Gitlab EE 中的私有存储库中有一个 rails 5 项目。我的存储库已完全配置为使用 Gitlab CI,使用 capistrano、capistrano/rails 和 capistrano/rvm 进行自动化部署。我有一份部署工作,我在其中执行 cap review deploy
(正在查看我要部署的环境)
一切运行良好,直到我到达 capistrano 钩子 deploy:migrate
,它在其中中断并出现以下错误。
错误:
(Backtrace restricted to imported tasks)
cap aborted!
NoMethodError: undefined method `already_invoked' for <Rake::Task deploy:migrating => [set_rails_env]>:Rake::Task
Tasks: TOP => deploy:migrate
(See full trace by running task with --trace)
The deploy has failed with an error: undefined method `already_invoked' for <Rake::Task deploy:migrating => [set_rails_env]>:Rake::Task
据我所知,设置 rails_env(来自 gem capistrano/rails)的 rake 任务似乎有问题.我已经跟踪查看 capistrano/rvm 是否没有从 rvm 设置正确的 ruby,但事实并非如此。 cappistrano/rvm 设置没问题。我认为这可能与 gem 版本或其他方面有关。
我的弱点:
我已将其修补为在未添加新迁移时工作,使用 capistrano/rails:
的人员提供的可选参数
set :conditionally_migrate, true
但它仍然失败(当有新的迁移时,这是大部分时间)
此外,在我的本地环境(使用 bundle exec cap review deploy
)中使用 capistrano 时,它可以正常工作。
提前致谢!希望你能帮我找到答案...
编辑:添加一些关于我的文件和堆栈的详细信息:
在我的 Rails 配置中,我使用了一个 git-ignored environment_variables.yml
文件,我在其中设置了 capistrano 配置文件中显示的所有那些 ENV['var']
配置。我还保证在执行部署作业的运行器中正确设置了这些。 (使用 Gitlab Variables)。
这些 CI 作业使用 gitlab-runner 服务运行 gitalOcean droplet 上的 docker runner 中指定的图像工作。
.gitlab-ci.yml
stages:
- test
- deploy
variables:
MYSQL_DATABASE: "test_linting"
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
DB_NAME: 'test_linting'
DB_USER: 'root'
DB_PASS: ''
DB_HOST: 'mysql'
services:
- mysql
testing:
image: heroku/ruby
stage: test
cache:
paths:
- vendor/cache
script:
- bundle install --without=development production --jobs $(nproc) --path=vendor/cache
- bundle exec rails db:create RAILS_ENV=test
- bundle exec rails db:migrate RAILS_ENV=test
- bundle exec rails test
artifacts:
paths:
- coverage/
dev-deploy:
image: ruby:2.3
stage: deploy
environment:
name: $CI_BUILD_REF_NAME
url: http://dev.linting.com
before_script:
- gem install capistrano -v '~> 3.7'
- gem install capistrano-rails
- gem install capistrano-rvm
- gem install slackistrano
script:
- cap $CI_BUILD_REF_NAME deploy
only:
- development
deploy:
image: ruby:2.3
stage: deploy
environment:
name: $CI_BUILD_REF_NAME
url: http://$CI_BUILD_REF_NAME.linting.com
before_script:
- gem install capistrano -v '~> 3.7'
- gem install capistrano-rails
- gem install capistrano-rvm
- gem install slackistrano
script:
- cap $CI_BUILD_REF_NAME deploy
only:
- qa
- staging
review_deploy:
image: ruby:2.3
stage: deploy
environment:
name: review
url: http://rev.linting.com
when: manual
before_script:
- gem install capistrano -v '~> 3.7'
- gem install capistrano-rails
- gem install capistrano-rvm
- gem install slackistrano
script:
- cap review deploy
except:
- development
- qa
- staging
- master
config/deploy.rb
lock '~> 3.7'
env_file = "./config/environment_variables.yml"
if File.exists?(env_file)
YAML.load_file(env_file)['capistrano'].each do |key, value|
ENV[key.to_s] = value
end
end
set :application, ENV['PROJECT_NAME']
set :repo_url, ENV['REPO_URL']
set :rvm_type, :user
set :rvm_ruby_version, '2.3.1'
set :conditionally_migrate, true
set :linked_files, %w{config/environment_variables.yml}
set :linked_dirs, %w{log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}
set :slackistrano, {
klass: Slackistrano::CustomMessaging,
channel: ENV['SLACK_CHANNEL'],
webhook: ENV['SLACK_HOOK']
}
namespace :deploy do
desc 'Restart application'
task :restart do
on roles(:app), in: :sequence, wait: 5 do
execute :touch, release_path.join('tmp/restart.txt')
end
end
desc 'Reset environment (rake db:reset)'
task :db_reset do
on roles(:app) do
within "#{current_path}" do
with rails_env: "#{fetch(:rails_env)}" do
execute :rake, "db:migrate:reset"
execute :rake, "db:seed"
end
end
end
end
after :published, 'deploy:restart'
after :finishing, 'deploy:cleanup'
end
config/deploy/review.rb
# Server definiton: (define ip in local env, and pass in gitlab)
server ENV['DEV_DROPLET_IP'], user: 'deploy', roles: %w{web app db}, password: ENV["SSH_DEPLOY_PASS"]
# ONLY WORKS IF IT WAS RUN BY THE GITLAB CI RUNNER
set :branch, ENV['CI_BUILD_REF_NAME'] ? ENV['CI_BUILD_REF_NAME'] : 'development'
# Capistrano Variables
set :stage, 'review'
set :rails_env, 'review'
# Variables for rev
set :commit, ENV['CI_BUILD_REF'] ? ENV['CI_BUILD_REF'][0..7] : 'local'
set :user, ENV['GITLAB_USER_EMAIL'] ? ENV['GITLAB_USER_EMAIL']: 'local user'
# Capistrano Deployment Route
set :deploy_to, "/home/deploy/rev.#{ENV['PROJECT_NAME']}"
namespace :deploy do
desc 'Set review branch in the review server'
task :set_review_branch do
on roles(:app) do
within "#{current_path}" do
execute "echo '#{fetch(:branch)}@#{fetch(:commit)}' >> #{release_path.join('tmp/rev_branch')}"
execute "echo '#{fetch(:user)}' >> #{release_path.join('tmp/rev_user')}"
end
end
end
after :publishing, 'deploy:set_review_branch'
before :finishing, 'deploy:db_reset'
end
Capfile
require "capistrano/setup"
require "capistrano/deploy"
require 'capistrano/rvm'
require 'capistrano/rails'
require 'slackistrano/capistrano'
require_relative 'lib/custom_messaging'
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git
# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
宝石文件
source 'https://rubygems.org'
gem 'rails', '~> 5.0.0', '>= 5.0.0.1'
gem 'puma', '~> 3.0'
gem 'sass-rails', '~> 5.0'
gem 'bootstrap-sass'
gem 'uglifier', '>= 1.3.0'
gem 'jquery-rails'
gem 'turbolinks', '~> 5'
gem 'jbuilder', '~> 2.5'
gem 'mysql2'
gem 'haml-rails'
gem 'devise'
gem 'paperclip', git: 'git://github.com/thoughtbot/paperclip.git'
gem 'administrate', '~> 0.3.0'
gem 'bourbon'
gem 'rails_real_favicon', '~> 0.0.6'
gem 'listen', '~> 3.0.5'
group :development, :test do
gem 'byebug', platform: :mri
gem 'minitest-rails'
gem 'minitest-reporters'
gem 'simplecov'
gem 'simplecov-json', require: false
gem 'factory_girl_rails'
gem 'faker'
end
group :development do
gem 'annotate'
gem 'better_errors'
gem 'binding_of_caller'
# Capistrano for Deployments
gem 'capistrano', '~> 3.7'
gem 'capistrano-rvm'
gem 'capistrano-rails'
gem 'slackistrano'
gem 'web-console'
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
我的猜测是因为您在 CI 环境中手动安装 Capistrano Gems,所以版本不匹配。如果您 运行 bundle install
和 bundle exec
在 CI 服务器上,或者固定安装的版本,它可能会开始工作。
我为此使用的一个解决方案是将所有必需的部署 gem 添加到我的 Gemfile 中的 :deployment
组。然后您需要将以下内容添加到您的 deploy.rb
:
set :bundle_without, %w{development test deployment}.join(' ')
然后将脚本更改为:
bundle install --except development test default production ...
bundle exec cap $CI_BUILD_REF_NAME deploy
我花了很多时间对此进行调试,但似乎还没有接近答案。我也考虑过在 capistrano 存储库中创建一个问题,但我真的不认为这是 capistrano 本身的问题。
上下文:
我在托管在 Gitlab EE 中的私有存储库中有一个 rails 5 项目。我的存储库已完全配置为使用 Gitlab CI,使用 capistrano、capistrano/rails 和 capistrano/rvm 进行自动化部署。我有一份部署工作,我在其中执行 cap review deploy
(正在查看我要部署的环境)
一切运行良好,直到我到达 capistrano 钩子 deploy:migrate
,它在其中中断并出现以下错误。
错误:
(Backtrace restricted to imported tasks)
cap aborted!
NoMethodError: undefined method `already_invoked' for <Rake::Task deploy:migrating => [set_rails_env]>:Rake::Task
Tasks: TOP => deploy:migrate
(See full trace by running task with --trace)
The deploy has failed with an error: undefined method `already_invoked' for <Rake::Task deploy:migrating => [set_rails_env]>:Rake::Task
据我所知,设置 rails_env(来自 gem capistrano/rails)的 rake 任务似乎有问题.我已经跟踪查看 capistrano/rvm 是否没有从 rvm 设置正确的 ruby,但事实并非如此。 cappistrano/rvm 设置没问题。我认为这可能与 gem 版本或其他方面有关。
我的弱点:
我已将其修补为在未添加新迁移时工作,使用 capistrano/rails:
的人员提供的可选参数set :conditionally_migrate, true
但它仍然失败(当有新的迁移时,这是大部分时间)
此外,在我的本地环境(使用 bundle exec cap review deploy
)中使用 capistrano 时,它可以正常工作。
提前致谢!希望你能帮我找到答案...
编辑:添加一些关于我的文件和堆栈的详细信息:
在我的 Rails 配置中,我使用了一个 git-ignored environment_variables.yml
文件,我在其中设置了 capistrano 配置文件中显示的所有那些 ENV['var']
配置。我还保证在执行部署作业的运行器中正确设置了这些。 (使用 Gitlab Variables)。
这些 CI 作业使用 gitlab-runner 服务运行 gitalOcean droplet 上的 docker runner 中指定的图像工作。
.gitlab-ci.yml
stages:
- test
- deploy
variables:
MYSQL_DATABASE: "test_linting"
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
DB_NAME: 'test_linting'
DB_USER: 'root'
DB_PASS: ''
DB_HOST: 'mysql'
services:
- mysql
testing:
image: heroku/ruby
stage: test
cache:
paths:
- vendor/cache
script:
- bundle install --without=development production --jobs $(nproc) --path=vendor/cache
- bundle exec rails db:create RAILS_ENV=test
- bundle exec rails db:migrate RAILS_ENV=test
- bundle exec rails test
artifacts:
paths:
- coverage/
dev-deploy:
image: ruby:2.3
stage: deploy
environment:
name: $CI_BUILD_REF_NAME
url: http://dev.linting.com
before_script:
- gem install capistrano -v '~> 3.7'
- gem install capistrano-rails
- gem install capistrano-rvm
- gem install slackistrano
script:
- cap $CI_BUILD_REF_NAME deploy
only:
- development
deploy:
image: ruby:2.3
stage: deploy
environment:
name: $CI_BUILD_REF_NAME
url: http://$CI_BUILD_REF_NAME.linting.com
before_script:
- gem install capistrano -v '~> 3.7'
- gem install capistrano-rails
- gem install capistrano-rvm
- gem install slackistrano
script:
- cap $CI_BUILD_REF_NAME deploy
only:
- qa
- staging
review_deploy:
image: ruby:2.3
stage: deploy
environment:
name: review
url: http://rev.linting.com
when: manual
before_script:
- gem install capistrano -v '~> 3.7'
- gem install capistrano-rails
- gem install capistrano-rvm
- gem install slackistrano
script:
- cap review deploy
except:
- development
- qa
- staging
- master
config/deploy.rb
lock '~> 3.7'
env_file = "./config/environment_variables.yml"
if File.exists?(env_file)
YAML.load_file(env_file)['capistrano'].each do |key, value|
ENV[key.to_s] = value
end
end
set :application, ENV['PROJECT_NAME']
set :repo_url, ENV['REPO_URL']
set :rvm_type, :user
set :rvm_ruby_version, '2.3.1'
set :conditionally_migrate, true
set :linked_files, %w{config/environment_variables.yml}
set :linked_dirs, %w{log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}
set :slackistrano, {
klass: Slackistrano::CustomMessaging,
channel: ENV['SLACK_CHANNEL'],
webhook: ENV['SLACK_HOOK']
}
namespace :deploy do
desc 'Restart application'
task :restart do
on roles(:app), in: :sequence, wait: 5 do
execute :touch, release_path.join('tmp/restart.txt')
end
end
desc 'Reset environment (rake db:reset)'
task :db_reset do
on roles(:app) do
within "#{current_path}" do
with rails_env: "#{fetch(:rails_env)}" do
execute :rake, "db:migrate:reset"
execute :rake, "db:seed"
end
end
end
end
after :published, 'deploy:restart'
after :finishing, 'deploy:cleanup'
end
config/deploy/review.rb
# Server definiton: (define ip in local env, and pass in gitlab)
server ENV['DEV_DROPLET_IP'], user: 'deploy', roles: %w{web app db}, password: ENV["SSH_DEPLOY_PASS"]
# ONLY WORKS IF IT WAS RUN BY THE GITLAB CI RUNNER
set :branch, ENV['CI_BUILD_REF_NAME'] ? ENV['CI_BUILD_REF_NAME'] : 'development'
# Capistrano Variables
set :stage, 'review'
set :rails_env, 'review'
# Variables for rev
set :commit, ENV['CI_BUILD_REF'] ? ENV['CI_BUILD_REF'][0..7] : 'local'
set :user, ENV['GITLAB_USER_EMAIL'] ? ENV['GITLAB_USER_EMAIL']: 'local user'
# Capistrano Deployment Route
set :deploy_to, "/home/deploy/rev.#{ENV['PROJECT_NAME']}"
namespace :deploy do
desc 'Set review branch in the review server'
task :set_review_branch do
on roles(:app) do
within "#{current_path}" do
execute "echo '#{fetch(:branch)}@#{fetch(:commit)}' >> #{release_path.join('tmp/rev_branch')}"
execute "echo '#{fetch(:user)}' >> #{release_path.join('tmp/rev_user')}"
end
end
end
after :publishing, 'deploy:set_review_branch'
before :finishing, 'deploy:db_reset'
end
Capfile
require "capistrano/setup"
require "capistrano/deploy"
require 'capistrano/rvm'
require 'capistrano/rails'
require 'slackistrano/capistrano'
require_relative 'lib/custom_messaging'
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git
# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
宝石文件
source 'https://rubygems.org'
gem 'rails', '~> 5.0.0', '>= 5.0.0.1'
gem 'puma', '~> 3.0'
gem 'sass-rails', '~> 5.0'
gem 'bootstrap-sass'
gem 'uglifier', '>= 1.3.0'
gem 'jquery-rails'
gem 'turbolinks', '~> 5'
gem 'jbuilder', '~> 2.5'
gem 'mysql2'
gem 'haml-rails'
gem 'devise'
gem 'paperclip', git: 'git://github.com/thoughtbot/paperclip.git'
gem 'administrate', '~> 0.3.0'
gem 'bourbon'
gem 'rails_real_favicon', '~> 0.0.6'
gem 'listen', '~> 3.0.5'
group :development, :test do
gem 'byebug', platform: :mri
gem 'minitest-rails'
gem 'minitest-reporters'
gem 'simplecov'
gem 'simplecov-json', require: false
gem 'factory_girl_rails'
gem 'faker'
end
group :development do
gem 'annotate'
gem 'better_errors'
gem 'binding_of_caller'
# Capistrano for Deployments
gem 'capistrano', '~> 3.7'
gem 'capistrano-rvm'
gem 'capistrano-rails'
gem 'slackistrano'
gem 'web-console'
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
我的猜测是因为您在 CI 环境中手动安装 Capistrano Gems,所以版本不匹配。如果您 运行 bundle install
和 bundle exec
在 CI 服务器上,或者固定安装的版本,它可能会开始工作。
我为此使用的一个解决方案是将所有必需的部署 gem 添加到我的 Gemfile 中的 :deployment
组。然后您需要将以下内容添加到您的 deploy.rb
:
set :bundle_without, %w{development test deployment}.join(' ')
然后将脚本更改为:
bundle install --except development test default production ...
bundle exec cap $CI_BUILD_REF_NAME deploy