diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 15c5779..1dfda55 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,6 @@ jobs: - '3.2' - '3.3' gemfile: - - gemfiles/Gemfile.rails61 - gemfiles/Gemfile.rails70 - gemfiles/Gemfile.rails71 - gemfiles/Gemfile.rails72 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5efd83a..a47295e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ ## [Unreleased] ### Fixed * Capistrano: Add missing `tmpdir` requirement to deploy application secrets +* Capistrano: cap deploy:setup should be safe on existing deployments + +### Added +* Capistrano: add task deploy:preinstall to preinstall ruby and bundled gems + +## Changed +* Drop support for Rails 6.1 ## 7.3.1 / 2025-01-02 ### Added diff --git a/config/rubocop/ndr.yml b/config/rubocop/ndr.yml index 853f50a..e843553 100644 --- a/config/rubocop/ndr.yml +++ b/config/rubocop/ndr.yml @@ -4,7 +4,7 @@ # # See the README for instructions on using in a project. -require: +plugins: - rubocop-rails - rubocop-rake diff --git a/gemfiles/Gemfile.rails61 b/gemfiles/Gemfile.rails61 deleted file mode 100644 index 6083b3b..0000000 --- a/gemfiles/Gemfile.rails61 +++ /dev/null @@ -1,7 +0,0 @@ -source 'https://rubygems.org' -gemspec path: '..' - -gem 'activesupport', '~> 6.1.0' - -# Latest concurrent-ruby breaks Rails < 7.1. See https://github.com/rails/rails/issues/54260 -gem 'concurrent-ruby', '1.3.4' diff --git a/lib/ndr_dev_support/capistrano/ndr_model.rb b/lib/ndr_dev_support/capistrano/ndr_model.rb index e423af9..b229a01 100644 --- a/lib/ndr_dev_support/capistrano/ndr_model.rb +++ b/lib/ndr_dev_support/capistrano/ndr_model.rb @@ -4,6 +4,7 @@ require_relative 'assets' require_relative 'deploy_secrets' require_relative 'install_ruby' +require_relative 'preinstall' require_relative 'restart' require_relative 'revision_logger' require_relative 'ruby_version' @@ -53,11 +54,15 @@ # sticky; all deployments made within it should be owned by the deployer group too. This # means that e.g. a deployment by "bob.smith" can then be rolled back by "tom.jones". run "mkdir -p #{deploy_to}" - run "chgrp -R deployer #{deploy_to}" + # Set deployer group for everything created by this user + # run "chgrp -R deployer #{deploy_to}" + run "find #{deploy_to} -group #{fetch(:user)} -print0 |xargs -r0 chgrp -h deployer" # The sticky group will apply automatically to new subdirectories, but # any existing subdirectories will need it manually applying via `-R`. - run "chmod -R g+s #{deploy_to}" + # run "chmod -R g+s #{deploy_to}" + run "find #{deploy_to} -user #{fetch(:user)} -type d " \ + '-not -perm -2000 -print0 |xargs -r0 chmod g+s' end desc 'Custom tasks to be run once, after the initial `cap setup`' @@ -67,8 +72,12 @@ run "mkdir -p #{full_path}" # Allow the application to write into here: - run "chgrp -R #{application_group} #{full_path}" - run "chmod -R g+s #{full_path}" + # run "chgrp -R #{application_group} #{full_path}" + # run "chmod -R g+s #{full_path}" + run "find #{full_path} -user #{fetch(:user)} -not -group #{application_group} " \ + "-print0 |xargs -r0 chgrp -h #{application_group}" + run "find #{full_path} -user #{fetch(:user)} -type d " \ + '-not -perm -2000 -print0 |xargs -r0 chmod g+s' end fetch(:shared_paths, []).each do |path| @@ -185,6 +194,14 @@ def target_ruby_version_for(env) match ? match[:version] : raise('Unrecognized Ruby version!') end +def log_deployment_message(msg) + name = fetch(:deployer_name, capture('id -un').chomp) + log = File.join(shared_path, 'revisions.log') + msg = "[#{Time.now}] #{name} #{msg}" # rubocop:disable Rails/TimeZone + + run "(test -e #{log} || (touch #{log} && chmod 664 #{log})) && echo #{Shellwords.escape(msg)} >> #{log};" +end + def add_target(env, name, app, port, app_user, is_web_server) desc "Deploy to #{env} service #{app_user || 'you'}@#{app}:#{port}" task(name) do diff --git a/lib/ndr_dev_support/capistrano/preinstall.rb b/lib/ndr_dev_support/capistrano/preinstall.rb new file mode 100644 index 0000000..f71c995 --- /dev/null +++ b/lib/ndr_dev_support/capistrano/preinstall.rb @@ -0,0 +1,37 @@ +Capistrano::Configuration.instance(:must_exist).load do + namespace :deploy do + desc <<~DESC + Preinstall ruby and gems, then abort and rollback cleanly, leaving the + current installation unchanged. + + This is particularly useful for ruby version bumps: installing the new + ruby version and all the bundled gems can take a long time. + + This aborts before updating out-of-bundle gems, in case that causes + issues when restarting the currently installed version. + + Usage: + cap target deploy:preinstall + DESC + task :preinstall do + # Running this task sets a flag, to make ndr_dev_support:check_preinstall abort. + # We do this in a roundabout way on Capistrano 2, because deploy:update_code + # explicitly runs deploy:finalize_update, instead of using task dependencies. + set :preinstall, true + end + end + + namespace :ndr_dev_support do + desc 'Hook to abort capistrano installation early after preinstalling ruby and in-bundle gems' + task :check_preinstall do + next unless fetch(:preinstall, false) + + log_deployment_message("preinstalled #{real_revision}") + warn Rainbow("Successful preinstall for target: #{fetch(:name)}") + abort 'Aborting after successful preinstall' + end + end + + after 'deploy:preinstall', 'deploy:update' + before 'ndr_dev_support:update_out_of_bundle_gems', 'ndr_dev_support:check_preinstall' +end diff --git a/lib/ndr_dev_support/capistrano/revision_logger.rb b/lib/ndr_dev_support/capistrano/revision_logger.rb index 97af468..8178b74 100644 --- a/lib/ndr_dev_support/capistrano/revision_logger.rb +++ b/lib/ndr_dev_support/capistrano/revision_logger.rb @@ -2,11 +2,7 @@ namespace :ndr_dev_support do desc 'Append to the log of deployments the user and revision.' task :log_deployment, except: { no_release: true } do - name = fetch(:deployer_name, capture('id -un')) - log = File.join(shared_path, 'revisions.log') - msg = "[#{Time.now}] #{name} deployed #{latest_revision}" - - run "(test -e #{log} || (touch #{log} && chmod 664 #{log})) && echo #{msg} >> #{log};" + log_deployment_message("deployed #{latest_revision}") end end