From 97d0ddf0ae843c2095fc4671f53714c3f2d6b601 Mon Sep 17 00:00:00 2001 From: seenmyfate Date: Fri, 18 Oct 2013 11:25:59 +0100 Subject: [PATCH] Introduce `no_release` server option In order to provide a way for a server to perform tasks as part of a deploy but without being involved in the standard deploy flow, all included tasks will now prefer `release_roles` over `roles`. For example: on release_roles :all do # end This behaviour is implemented using `exclude`, a new option when selecting roles. `release_roles` is equivalent to: on roles :all, exclude: :no_release do # end Any server defined with `no_release: true` will be excluded from these tasks: server 'localhost', roles: %w{db}, no_release: true `exclude` can also be used in user defined tasks against any attribute, for example: server 'localhost', roles: %w{app web}, inactive: true on roles :app, exclude: :inactive do # end This commit resolves #686 --- lib/capistrano/configuration/server.rb | 21 ++++++++- lib/capistrano/dsl/env.rb | 10 +++++ lib/capistrano/tasks/deploy.rake | 6 +-- lib/capistrano/tasks/git.rake | 10 ++--- lib/capistrano/tasks/hg.rake | 8 ++-- spec/integration/dsl_spec.rb | 45 ++++++++++++++++++- .../capistrano/configuration/server_spec.rb | 22 +++++++++ .../capistrano/configuration/servers_spec.rb | 29 ++++++++++++ spec/lib/capistrano/dsl/env_spec.rb | 10 ----- 9 files changed, 136 insertions(+), 25 deletions(-) delete mode 100644 spec/lib/capistrano/dsl/env_spec.rb diff --git a/lib/capistrano/configuration/server.rb b/lib/capistrano/configuration/server.rb index b81402db..1f151aa7 100644 --- a/lib/capistrano/configuration/server.rb +++ b/lib/capistrano/configuration/server.rb @@ -23,7 +23,7 @@ module Capistrano end def select?(options) - selector = Selector.new(options) + selector = Selector.for(options) selector.call(self) end @@ -103,6 +103,14 @@ module Capistrano @options = options end + def self.for(options) + if options.has_key?(:exclude) + Exclusive + else + self + end.new(options) + end + def callable if key.respond_to?(:call) key @@ -126,6 +134,17 @@ module Capistrano ->(server) { :all } end + class Exclusive < Selector + + def key + options[:exclude] + end + + def call(server) + !callable.call(server) + end + end + end end diff --git a/lib/capistrano/dsl/env.rb b/lib/capistrano/dsl/env.rb index a7bb858f..e6641344 100644 --- a/lib/capistrano/dsl/env.rb +++ b/lib/capistrano/dsl/env.rb @@ -43,6 +43,16 @@ module Capistrano env.roles_for(names) end + def release_roles(*names) + options = { exclude: :no_release } + if names.last.is_a? Hash + names.last.merge(options) + else + names << options + end + roles(*names) + end + def primary(role) env.primary(role) end diff --git a/lib/capistrano/tasks/deploy.rake b/lib/capistrano/tasks/deploy.rake index 101e5662..99d05071 100644 --- a/lib/capistrano/tasks/deploy.rake +++ b/lib/capistrano/tasks/deploy.rake @@ -42,7 +42,7 @@ namespace :deploy do namespace :check do desc 'Check shared and release directories exist' task :directories do - on roles :all do + on release_roles :all do execute :mkdir, '-pv', shared_path, releases_path end end @@ -80,7 +80,7 @@ namespace :deploy do namespace :symlink do desc 'Symlink release to current' task :release do - on roles :all do + on release_roles :all do execute :rm, '-rf', current_path execute :ln, '-s', release_path, current_path end @@ -133,7 +133,7 @@ namespace :deploy do desc 'Clean up old releases' task :cleanup do - on roles :all do |host| + on release_roles :all do |host| releases = capture(:ls, '-x', releases_path).split if releases.count >= fetch(:keep_releases) info t(:keeping_releases, host: host.to_s, keep_releases: fetch(:keep_releases), releases: releases.count) diff --git a/lib/capistrano/tasks/git.rake b/lib/capistrano/tasks/git.rake index a33cc20a..5e55b2c5 100644 --- a/lib/capistrano/tasks/git.rake +++ b/lib/capistrano/tasks/git.rake @@ -7,7 +7,7 @@ namespace :git do desc 'Upload the git wrapper script, this script guarantees that we can script git without getting an interactive prompt' task :wrapper do - on roles :all do + on release_roles :all do upload! StringIO.new("#!/bin/sh -e\nexec /usr/bin/ssh -o PasswordAuthentication=no -o StrictHostKeyChecking=no \"$@\"\n"), "#{fetch(:tmp_dir)}/git-ssh.sh" execute :chmod, "+x", "#{fetch(:tmp_dir)}/git-ssh.sh" end @@ -16,7 +16,7 @@ namespace :git do desc 'Check that the repository is reachable' task check: :'git:wrapper' do fetch(:branch) - on roles :all do + on release_roles :all do with git_environmental_variables do exit 1 unless test :git, :'ls-remote', repo_url end @@ -25,7 +25,7 @@ namespace :git do desc 'Clone the repo to the cache' task clone: :'git:wrapper' do - on roles :all do + on release_roles :all do if test " [ -f #{repo_path}/HEAD ] " info t(:mirror_exists, at: repo_path) else @@ -40,7 +40,7 @@ namespace :git do desc 'Update the repo mirror to reflect the origin state' task update: :'git:clone' do - on roles :all do + on release_roles :all do within repo_path do execute :git, :remote, :update end @@ -49,7 +49,7 @@ namespace :git do desc 'Copy repo to releases' task create_release: :'git:update' do - on roles :all do + on release_roles :all do with git_environmental_variables do within repo_path do execute :mkdir, '-p', release_path diff --git a/lib/capistrano/tasks/hg.rake b/lib/capistrano/tasks/hg.rake index 4018ef1a..138a75c7 100644 --- a/lib/capistrano/tasks/hg.rake +++ b/lib/capistrano/tasks/hg.rake @@ -1,14 +1,14 @@ namespace :hg do desc 'Check that the repo is reachable' task :check do - on roles :all do + on release_roles :all do execute "hg", "id", repo_url end end desc 'Clone the repo to the cache' task :clone do - on roles :all do + on release_roles :all do if test " [ -d #{repo_path}/.hg ] " info t(:mirror_exists, at: repo_path) else @@ -21,7 +21,7 @@ namespace :hg do desc 'Pull changes from the remote repo' task :update => :'hg:clone' do - on roles :all do + on release_roles :all do within repo_path do execute "hg", "pull" end @@ -30,7 +30,7 @@ namespace :hg do desc 'Copy repo to releases' task :create_release => :'hg:update' do - on roles :all do + on release_roles :all do within repo_path do execute "hg", "archive", release_path, "--rev", fetch(:branch) end diff --git a/spec/integration/dsl_spec.rb b/spec/integration/dsl_spec.rb index 36af5d5b..3dba85c9 100644 --- a/spec/integration/dsl_spec.rb +++ b/spec/integration/dsl_spec.rb @@ -11,13 +11,33 @@ describe Capistrano::DSL do dsl.server 'example2.com', roles: %w{web} dsl.server 'example3.com', roles: %w{app web}, active: true dsl.server 'example4.com', roles: %w{app}, primary: true + dsl.server 'example5.com', roles: %w{db}, no_release: true end describe 'fetching all servers' do subject { dsl.roles(:all) } it 'returns all servers' do - expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com} + expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com example5.com} + end + end + + describe 'fetching all release servers' do + + context 'with no additional options' do + subject { dsl.release_roles(:all) } + + it 'returns all release servers' do + expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com} + end + end + + context 'with filter options' do + subject { dsl.release_roles(:all, filter: :active) } + + it 'returns all release servers that match the filter' do + expect(subject.map(&:hostname)).to eq %w{example1.com example3.com} + end end end @@ -85,16 +105,37 @@ describe Capistrano::DSL do dsl.role :app, %w{example3.com example4.com} dsl.role :app, %w{example3.com}, active: true dsl.role :app, %w{example4.com}, primary: true + dsl.role :db, %w{example5.com}, no_release: true end describe 'fetching all servers' do subject { dsl.roles(:all) } it 'returns all servers' do - expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com} + expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com example5.com} end end + describe 'fetching all release servers' do + + context 'with no additional options' do + subject { dsl.release_roles(:all) } + + it 'returns all release servers' do + expect(subject.map(&:hostname)).to eq %w{example1.com example2.com example3.com example4.com} + end + end + + context 'with filter options' do + subject { dsl.release_roles(:all, filter: :active) } + + it 'returns all release servers that match the filter' do + expect(subject.map(&:hostname)).to eq %w{example1.com example3.com} + end + end + end + + describe 'fetching servers by role' do subject { dsl.roles(:app) } diff --git a/spec/lib/capistrano/configuration/server_spec.rb b/spec/lib/capistrano/configuration/server_spec.rb index 4d98cd50..b4bb032a 100644 --- a/spec/lib/capistrano/configuration/server_spec.rb +++ b/spec/lib/capistrano/configuration/server_spec.rb @@ -159,6 +159,11 @@ module Capistrano let(:options) { { select: :active }} it { should be_true } end + + context 'with :exclude' do + let(:options) { { exclude: :active }} + it { should be_false } + end end context 'value does not match server properly' do @@ -171,6 +176,11 @@ module Capistrano let(:options) { { select: :inactive }} it { should be_false } end + + context 'with :exclude' do + let(:options) { { exclude: :inactive }} + it { should be_true } + end end end @@ -186,6 +196,12 @@ module Capistrano let(:options) { { select: ->(s) { s.properties.active } } } it { should be_true } end + + context 'with :exclude' do + let(:options) { { exclude: ->(s) { s.properties.active } } } + it { should be_false } + end + end context 'value does not match server properly' do @@ -198,6 +214,12 @@ module Capistrano let(:options) { { select: ->(s) { s.properties.inactive } } } it { should be_false } end + + context 'with :exclude' do + let(:options) { { exclude: ->(s) { s.properties.inactive } } } + it { should be_true } + end + end end diff --git a/spec/lib/capistrano/configuration/servers_spec.rb b/spec/lib/capistrano/configuration/servers_spec.rb index f250593c..03677bf8 100644 --- a/spec/lib/capistrano/configuration/servers_spec.rb +++ b/spec/lib/capistrano/configuration/servers_spec.rb @@ -141,6 +141,35 @@ module Capistrano end + describe 'excluding by property' do + + before do + servers.add_host('1', roles: :app, active: true) + servers.add_host('2', roles: :app, active: true, no_release: true) + end + + it 'is empty if the filter would remove all matching hosts' do + hosts = servers.roles_for([:app, exclude: :active]) + expect(hosts.map(&:hostname)).to be_empty + end + + it 'returns the servers without the attributes specified' do + hosts = servers.roles_for([:app, exclude: :no_release]) + expect(hosts.map(&:hostname)).to eq %w{1} + end + + it 'can exclude hosts by properties on the host using a regular proc' do + hosts = servers.roles_for([:app, exclude: ->(h) { h.properties.no_release }]) + expect(hosts.map(&:hostname)).to eq %w{1} + end + + it 'is empty if the regular proc filter would remove all matching hosts' do + hosts = servers.roles_for([:app, exclude: ->(h) { h.properties.active }]) + expect(hosts.map(&:hostname)).to be_empty + end + + end + describe 'filtering roles' do before do diff --git a/spec/lib/capistrano/dsl/env_spec.rb b/spec/lib/capistrano/dsl/env_spec.rb deleted file mode 100644 index 61414cba..00000000 --- a/spec/lib/capistrano/dsl/env_spec.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'spec_helper' - -module Capistrano - module DSL - - describe Env do - - end - end -end