1
0
Fork 0
mirror of https://github.com/capistrano/capistrano synced 2023-03-27 23:21:18 -04:00

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
This commit is contained in:
seenmyfate 2013-10-18 11:25:59 +01:00
parent e7fe616c64
commit 97d0ddf0ae
9 changed files with 136 additions and 25 deletions

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -11,16 +11,36 @@ 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 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 multiple roles' do
it "does not confuse the last role with options" do
expect(dsl.roles(:app, :web).count).to eq 4
@ -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 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) }

View file

@ -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

View file

@ -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

View file

@ -1,10 +0,0 @@
require 'spec_helper'
module Capistrano
module DSL
describe Env do
end
end
end