From 4bac3bfaed8f89c07e78734b513a6b91827db53a Mon Sep 17 00:00:00 2001 From: Jordan Huizenga Date: Wed, 14 Dec 2022 11:46:37 +1100 Subject: [PATCH] allow skipping tenants when running patches --- docs/usage.md | 18 +++++++++++++ lib/patches.rb | 1 + lib/patches/tenant_finder.rb | 28 +++++++++++++++++++++ lib/patches/tenant_runner.rb | 5 ++-- lib/tasks/patches.rake | 4 --- spec/tenant_finder_spec.rb | 49 ++++++++++++++++++++++++++++++++++++ spec/tenant_runner_spec.rb | 9 ++----- 7 files changed, 100 insertions(+), 14 deletions(-) create mode 100644 lib/patches/tenant_finder.rb create mode 100644 spec/tenant_finder_spec.rb diff --git a/docs/usage.md b/docs/usage.md index 7ccc040..87ec87a 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -63,6 +63,24 @@ end *Note:* Make sure your sidekiq queue is able to process concurrent jobs. You can use ```config.sidekiq_options``` to customise it. +### Skipping tenants when running patches for multiple tenants + +If you are using the Apartment gem, the patches will run across all tenants by default. If you wish to only run patches against a subset of tenants, you can use the `ONLY_TENANTS` env var, like so + +```bash +# This will only run for my_tenant and other_tenant, provided they are listed as tenants by the Apartment gem +ONLY_TENANTS=my_tenant,other_tenant bundle exec rake patches:run +``` + +Similarly if you want to run patches against all tenants _except_ for a select few, you can use the `SKIP_TENANTS` env var, like so + +```bash +# This will run for all tenants EXCEPT my_tenant and other_tenant +SKIP_TENANTS=my_tenant,other_tenant bundle exec rake patches:run +``` + +If you specify both env vars, the `ONLY_TENANTS` env var will take precedence + ### Application version verification In environments where a rolling update of sidekiq workers is performed during the deployment, multiple versions of the application run at the same time. If a Patches job is scheduled by the new application version during the rolling update, there is a possibility that it can be executed by the old application version, which will not have all the required patch files. diff --git a/lib/patches.rb b/lib/patches.rb index cd06ba4..5a599cd 100644 --- a/lib/patches.rb +++ b/lib/patches.rb @@ -31,3 +31,4 @@ def self.logger=(log) require "patches/tenant_runner" require "patches/notifier" require "patches/worker" if defined?(Sidekiq) +require "patches/tenant_finder" diff --git a/lib/patches/tenant_finder.rb b/lib/patches/tenant_finder.rb new file mode 100644 index 0000000..338acef --- /dev/null +++ b/lib/patches/tenant_finder.rb @@ -0,0 +1,28 @@ +class Patches::TenantFinder + + def tenant_names + @tenant_names ||= begin + if only_tenant_names.any? + apartment_tenant_names.select { |tenant_name| only_tenant_names.include?(tenant_name) } + elsif skip_tenant_names.any? + apartment_tenant_names.reject { |tenant_name| skip_tenant_names.include?(tenant_name) } + else + apartment_tenant_names + end + end + end + + private + + def apartment_tenant_names + Apartment.tenant_names || [] + end + + def only_tenant_names + ENV['ONLY_TENANTS'] ? ENV['ONLY_TENANTS'].split(',').map { |s| s.strip } : [] + end + + def skip_tenant_names + ENV['SKIP_TENANTS'] ? ENV['SKIP_TENANTS'].split(',').map { |s| s.strip } : [] + end +end diff --git a/lib/patches/tenant_runner.rb b/lib/patches/tenant_runner.rb index 77043e8..fe273ff 100644 --- a/lib/patches/tenant_runner.rb +++ b/lib/patches/tenant_runner.rb @@ -2,9 +2,8 @@ class Patches::TenantRunner include Patches::TenantRunConcern attr_accessor :path - def initialize(path: nil, tenants: nil) + def initialize(path: nil) @path = path - @tenants = tenants end def perform @@ -23,7 +22,7 @@ def perform end def tenants - @tenants ||= (Apartment.tenant_names || []) + @tenants ||= Patches::TenantFinder.new.tenant_names end private diff --git a/lib/tasks/patches.rake b/lib/tasks/patches.rake index fa67c80..e6ca307 100644 --- a/lib/tasks/patches.rake +++ b/lib/tasks/patches.rake @@ -17,10 +17,6 @@ namespace :patches do end end - def tenants - ENV['DB'] ? ENV['DB'].split(',').map { |s| s.strip } : Apartment.tenant_names || [] - end - task :pending => [:environment] do Patches::Pending.new.each do |patch| puts patch diff --git a/spec/tenant_finder_spec.rb b/spec/tenant_finder_spec.rb new file mode 100644 index 0000000..f2d3f95 --- /dev/null +++ b/spec/tenant_finder_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' +require 'patches/tenant_finder' + +describe Patches::TenantFinder do + describe '#tenant_names' do + subject { tenant_finder.tenant_names } + let(:tenant_finder) { Patches::TenantFinder.new } + let(:apartment_tenant_names) { ['test1', 'test2', 'test3'] } + let(:only_tenants_env_var) { nil } + let(:skip_tenants_env_var) { nil } + + before do + allow(Apartment).to receive(:tenant_names).and_return(apartment_tenant_names) + allow(ENV).to receive(:[]).with('ONLY_TENANTS').and_return(only_tenants_env_var) + allow(ENV).to receive(:[]).with('SKIP_TENANTS').and_return(skip_tenants_env_var) + end + + context 'when no env vars are set' do + it 'returns the list from Apartment' do + expect(subject).to match_array(['test1', 'test2', 'test3']) + end + end + + context 'when ONLY_TENANTS is set' do + let(:only_tenants_env_var) { 'test1,test2' } + + it 'returns a subset of the Apartment list filtered by the env var' do + expect(subject).to match_array(['test1', 'test2']) + end + end + + context 'when SKIP_TENANTS is set' do + let(:skip_tenants_env_var) { 'test1,test2' } + + it 'returns a subset of the Apartment list excluding names set by the env var' do + expect(subject).to match_array(['test3']) + end + end + + context 'when ONLY_TENANTS and SKIP_TENANTS are set' do + let(:only_tenants_env_var) { 'test1' } + let(:skip_tenants_env_var) { 'test1' } + + it 'prioritises the ONLY_TENANTS env var' do + expect(subject).to match_array(['test1']) + end + end + end +end diff --git a/spec/tenant_runner_spec.rb b/spec/tenant_runner_spec.rb index c456732..a7b5516 100644 --- a/spec/tenant_runner_spec.rb +++ b/spec/tenant_runner_spec.rb @@ -20,16 +20,11 @@ module Tenant allow(Patches::Config.configuration).to receive(:application_version) { application_version } end - context 'with tenants' do - let(:tenants) { ['tenants'] } - subject { described_class.new(tenants: tenants) } - specify { expect(subject.tenants).to eql(tenants) } - end - context 'perform' do let(:tenant_names) { ['test'] } + let(:tenant_finder) { double(Patches::TenantFinder, tenant_names: tenant_names) } - before { expect(Apartment).to receive(:tenant_names).and_return(tenant_names) } + before { allow(Patches::TenantFinder).to receive(:new).and_return(tenant_finder) } specify do expect(subject.tenants).to eql(['test'])