Skip to content

Commit

Permalink
Introduce no_release server option
Browse files Browse the repository at this point in the history
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
  • Loading branch information
seenmyfate committed Oct 18, 2013
1 parent e7fe616 commit 97d0ddf
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 25 deletions.
21 changes: 20 additions & 1 deletion lib/capistrano/configuration/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def matches?(host)
end

def select?(options)
selector = Selector.new(options)
selector = Selector.for(options)
selector.call(self)
end

Expand Down Expand Up @@ -103,6 +103,14 @@ def initialize(options)
@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
Expand All @@ -126,6 +134,17 @@ def all
->(server) { :all }
end

class Exclusive < Selector

def key
options[:exclude]
end

def call(server)
!callable.call(server)
end
end

end

end
Expand Down
10 changes: 10 additions & 0 deletions lib/capistrano/dsl/env.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ def roles(*names)
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
Expand Down
6 changes: 3 additions & 3 deletions lib/capistrano/tasks/deploy.rake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
10 changes: 5 additions & 5 deletions lib/capistrano/tasks/git.rake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions lib/capistrano/tasks/hg.rake
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
45 changes: 43 additions & 2 deletions spec/integration/dsl_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,33 @@
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

Expand Down Expand Up @@ -85,16 +105,37 @@
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) }

Expand Down
22 changes: 22 additions & 0 deletions spec/lib/capistrano/configuration/server_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ class Configuration
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
Expand All @@ -171,6 +176,11 @@ class Configuration
let(:options) { { select: :inactive }}
it { should be_false }
end

context 'with :exclude' do
let(:options) { { exclude: :inactive }}
it { should be_true }
end
end
end

Expand All @@ -186,6 +196,12 @@ class Configuration
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
Expand All @@ -198,6 +214,12 @@ class Configuration
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

Expand Down
29 changes: 29 additions & 0 deletions spec/lib/capistrano/configuration/servers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,35 @@ class Configuration

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
Expand Down
10 changes: 0 additions & 10 deletions spec/lib/capistrano/dsl/env_spec.rb

This file was deleted.

0 comments on commit 97d0ddf

Please sign in to comment.