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

Merge pull request #1368 from townsen/consolidate_servers_on_hostname

Consolidate servers on hostname
This commit is contained in:
Lee Hambley 2015-02-14 11:58:46 +01:00
commit 5c9b27e90d
12 changed files with 141 additions and 24 deletions

View file

@ -14,6 +14,15 @@ Reverse Chronological Order:
* release_roles did not honour additional property filtering (@townsen) * release_roles did not honour additional property filtering (@townsen)
* Refactored and simplified property filtering code (@townsen) * Refactored and simplified property filtering code (@townsen)
* Breaking Changes
* Hosts with the same name are now consolidated into one irrespective of the
user and port. This allows multiple declarations of a server to be made safely.
The last declared properties will win. See capistrnorb.com Properties documentation
for details.
* Inside the on() block the host variable is now a copy of the host, so changes can be
made within the block (such as dynamically overriding the user) that will not persist.
This is very convenient for switching the SSH user temporarily to 'root' for example.
* Minor changes * Minor changes
* Add role_properties() method (see capistrano.github.io PR for doc) (@townsen) * Add role_properties() method (see capistrano.github.io PR for doc) (@townsen)
* Add equality syntax ( eg. port: 1234) for property filtering (@townsen) * Add equality syntax ( eg. port: 1234) for property filtering (@townsen)

View file

@ -0,0 +1,17 @@
Feature: SSH Connection
Background:
Given a test app with the default configuration
And servers with the roles app and web
And a task which executes as root
Scenario: Switching from default user to root and back again
When I run cap "git:check"
Then the task is successful
And references in the remote repo are listed
When I run cap "am_i_root"
Then the task is successful
And contains "root" in the output
Then I run cap "git:check"
Then the task is successful
And references in the remote repo are listed

View file

@ -27,6 +27,10 @@ Given(/^a custom task to generate a file$/) do
TestApp.copy_task_to_test_app('spec/support/tasks/database.rake') TestApp.copy_task_to_test_app('spec/support/tasks/database.rake')
end end
Given(/^a task which executes as root$/) do
TestApp.copy_task_to_test_app('spec/support/tasks/root.rake')
end
Given(/config stage file has line "(.*?)"/) do |line| Given(/config stage file has line "(.*?)"/) do |line|
TestApp.append_to_deploy_file(line) TestApp.append_to_deploy_file(line)
end end

View file

@ -62,7 +62,7 @@ module Capistrano
end end
def matches?(other) def matches?(other)
user == other.user && hostname == other.hostname && port == other.port hostname == other.hostname
end end
private private
@ -98,7 +98,7 @@ module Capistrano
@properties[key] @properties[key]
end end
def respond_to?(method) def respond_to?(method, include_all=false)
@properties.has_key?(method) @properties.has_key?(method)
end end

View file

@ -8,7 +8,14 @@ module Capistrano
include Enumerable include Enumerable
def add_host(host, properties={}) def add_host(host, properties={})
servers.add server(host, properties).with(properties) new_host = Server[host]
if server = servers.find { |s| s.matches? new_host }
server.user = new_host.user if new_host.user
server.port = new_host.port if new_host.port
server.with(properties)
else
servers << new_host.with(properties)
end
end end
def add_role(role, hosts, options={}) def add_role(role, hosts, options={})
@ -50,15 +57,8 @@ module Capistrano
private private
def server(host, properties)
new_host = Server[host]
new_host.with({user: properties[:user]}) unless properties[:user].nil?
new_host.with({port: properties[:port]}) unless properties[:port].nil?
servers.find { |server| server.matches? new_host } || new_host
end
def servers def servers
@servers ||= Set.new @servers ||= []
end end
def extract_options(array) def extract_options(array)

View file

@ -51,8 +51,8 @@ module Capistrano
end end
def on(hosts, options={}, &block) def on(hosts, options={}, &block)
subset = Configuration.env.filter hosts subset_copy = Marshal.dump(Configuration.env.filter(hosts))
SSHKit::Coordinator.new(subset).each(options, &block) SSHKit::Coordinator.new(Marshal.load(subset_copy)).each(options, &block)
end end
def run_locally(&block) def run_locally(&block)

View file

@ -241,25 +241,45 @@ describe Capistrano::DSL do
end end
describe 'fetching all servers' do describe 'fetching all servers' do
subject { dsl.roles(:all).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" } } it 'creates one server per hostname, ignoring user and port combinations' do
expect(dsl.roles(:all).size).to eq(1)
it 'creates a server instance for each unique user@host:port combination' do
expect(subject).to eq %w{db@example1.com:1234 root@example1.com:1234 @example1.com:5678 deployer@example1.com:1234}
end end
end end
describe 'fetching servers for a role' do describe 'fetching servers for a role' do
it 'roles defined using the `server` syntax are included' do it 'roles defined using the `server` syntax are included' do
expect(dsl.roles(:web).size).to eq(2) as = dsl.roles(:web).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" }
expect(as.size).to eq(1)
expect(as[0]).to eq("deployer@example1.com:5678")
end end
it 'roles defined using the `role` syntax are included' do it 'roles defined using the `role` syntax are included' do
expect(dsl.roles(:app).size).to eq(2) as = dsl.roles(:app).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" }
expect(as.size).to eq(1)
expect(as[0]).to eq("deployer@example1.com:5678")
end end
end end
end end
describe 'when setting user and port' do
subject { dsl.roles(:all).map { |server| "#{server.user}@#{server.hostname}:#{server.port}" }.first }
describe "using the :user property" do
it "takes precedence over in the host string" do
dsl.server 'db@example1.com:1234', roles: %w{db}, active: true, user: 'brian'
expect(subject).to eq("brian@example1.com:1234")
end
end
describe "using the :port property" do
it "takes precedence over in the host string" do
dsl.server 'db@example1.com:9090', roles: %w{db}, active: true, port: 1234
expect(subject).to eq("db@example1.com:1234")
end
end
end
end end
describe 'setting and fetching variables' do describe 'setting and fetching variables' do
@ -538,6 +558,26 @@ describe Capistrano::DSL do
recipient.doit(host, role, props) recipient.doit(host, role, props)
end end
end end
it 'yields the merged properties for multiple roles' do
recipient = mock('recipient')
recipient.expects(:doit).with('example1.com', :redis, { port: 6379, type: :slave})
recipient.expects(:doit).with('example2.com', :redis, { port: 6379, type: :master})
recipient.expects(:doit).with('example1.com', :web, { port: 80 })
recipient.expects(:doit).with('example2.com', :web, { port: 81 })
dsl.role_properties(:redis, :web) do |host, role, props|
recipient.doit(host, role, props)
end
end
it 'honours a property filter before yielding' do
recipient = mock('recipient')
recipient.expects(:doit).with('example1.com', :redis, { port: 6379, type: :slave})
recipient.expects(:doit).with('example1.com', :web, { port: 80 })
dsl.role_properties(:redis, :web, select: :active) do |host, role, props|
recipient.doit(host, role, props)
end
end
end end
end end

View file

@ -33,7 +33,7 @@ module Capistrano
end end
describe 'comparing identity' do describe 'comparing identity' do
subject { server.matches? Server[hostname] } subject { server.hostname == Server[hostname].hostname }
context 'with the same user, hostname and port' do context 'with the same user, hostname and port' do
let(:hostname) { 'root@hostname:1234' } let(:hostname) { 'root@hostname:1234' }
@ -42,12 +42,12 @@ module Capistrano
context 'with a different user' do context 'with a different user' do
let(:hostname) { 'deployer@hostname:1234' } let(:hostname) { 'deployer@hostname:1234' }
it { expect(subject).to be_falsey } it { expect(subject).to be_truthy }
end end
context 'with a different port' do context 'with a different port' do
let(:hostname) { 'root@hostname:5678' } let(:hostname) { 'root@hostname:5678' }
it { expect(subject).to be_falsey } it { expect(subject).to be_truthy }
end end
context 'with a different hostname' do context 'with a different hostname' do
@ -94,6 +94,10 @@ module Capistrano
it 'sets the user' do it 'sets the user' do
expect(server.user).to eq 'tomc' expect(server.user).to eq 'tomc'
end end
it 'sets the netssh_options user' do
expect(server.netssh_options[:user]).to eq 'tomc'
end
end end
context 'properties contains port' do context 'properties contains port' do
@ -285,6 +289,9 @@ module Capistrano
it 'contains correct user' do it 'contains correct user' do
expect(server.netssh_options[:user]).to eq 'another_user' expect(server.netssh_options[:user]).to eq 'another_user'
end end
it 'does not affect server user in host' do
expect(server.user).to eq 'user_name'
end
it 'contains keys' do it 'contains keys' do
expect(server.netssh_options[:keys]).to eq %w(/home/another_user/.ssh/id_rsa) expect(server.netssh_options[:keys]).to eq %w(/home/another_user/.ssh/id_rsa)
end end

View file

@ -18,6 +18,12 @@ module Capistrano
expect(servers.count).to eq 1 expect(servers.count).to eq 1
end end
it 'handles de-duplification within roles with users' do
servers.add_role(:app, %w{1}, user: 'nick')
servers.add_role(:app, %w{1}, user: 'fred')
expect(servers.count).to eq 1
end
it 'accepts instances of server objects' do it 'accepts instances of server objects' do
servers.add_role(:app, [Capistrano::Configuration::Server.new('example.net'), 'example.com']) servers.add_role(:app, [Capistrano::Configuration::Server.new('example.net'), 'example.com'])
expect(servers.roles_for([:app]).length).to eq 2 expect(servers.roles_for([:app]).length).to eq 2
@ -134,7 +140,23 @@ module Capistrano
servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'root', port: 34) servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'root', port: 34)
servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'deployer', port: 34) servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'deployer', port: 34)
servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'deployer', port: 56) servers.add_host('1', roles: [:app, 'web'], test: :value, user: 'deployer', port: 56)
expect(servers.count).to eq(8) expect(servers.count).to eq(1)
end
describe "with a :user property" do
it 'sets the server ssh username' do
servers.add_host('1', roles: [:app, 'web'], user: 'nick')
expect(servers.count).to eq(1)
expect(servers.roles_for([:all]).first.user).to eq 'nick'
end
it 'overwrites the value of a user specified in the hostname' do
servers.add_host('brian@1', roles: [:app, 'web'], user: 'nick')
expect(servers.count).to eq(1)
expect(servers.roles_for([:all]).first.user).to eq 'nick'
end
end end
it 'overwrites the value of a previously defined scalar property' do it 'overwrites the value of a previously defined scalar property' do

View file

@ -1,3 +1,5 @@
require 'open-uri'
Vagrant.configure("2") do |config| Vagrant.configure("2") do |config|
config.ssh.insert_key = false config.ssh.insert_key = false
@ -8,6 +10,15 @@ Vagrant.configure("2") do |config|
config.vm.box = 'hashicorp/precise64' config.vm.box = 'hashicorp/precise64'
config.vm.network "forwarded_port", guest: 22, host: "222#{i}".to_i config.vm.network "forwarded_port", guest: 22, host: "222#{i}".to_i
config.vm.provision :shell, inline: 'sudo apt-get -y install git-core' config.vm.provision :shell, inline: 'sudo apt-get -y install git-core'
vagrantkey = open("https://raw.githubusercontent.com/mitchellh/vagrant/master/keys/vagrant.pub", "r",&:read)
config.vm.provision :shell,
inline: <<-INLINE
install -d -m 700 /root/.ssh
echo -e "#{vagrantkey}" > /root/.ssh/authorized_keys
chmod 0600 /root/.ssh/authorized_keys
INLINE
end end
end end
end end

View file

@ -0,0 +1,7 @@
task :am_i_root do
on roles(:all) do |host|
host.user = 'root'
ident = capture :id, '-a'
info "I am #{ident}"
end
end

View file

@ -13,7 +13,7 @@ module TestApp
set :deploy_to, '#{deploy_to}' set :deploy_to, '#{deploy_to}'
set :repo_url, 'git://github.com/capistrano/capistrano.git' set :repo_url, 'git://github.com/capistrano/capistrano.git'
set :branch, 'master' set :branch, 'master'
set :ssh_options, { keys: "\#{ENV['HOME']}/.vagrant.d/insecure_private_key" } set :ssh_options, { keys: "\#{ENV['HOME']}/.vagrant.d/insecure_private_key", auth_methods: ['publickey'] }
server 'vagrant@localhost:2220', roles: %w{web app} server 'vagrant@localhost:2220', roles: %w{web app}
set :linked_files, #{linked_files} set :linked_files, #{linked_files}
set :linked_dirs, #{linked_dirs} set :linked_dirs, #{linked_dirs}