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:
commit
5c9b27e90d
12 changed files with 141 additions and 24 deletions
|
@ -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)
|
||||||
|
|
17
features/sshconnect.feature
Normal file
17
features/sshconnect.feature
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
11
spec/support/Vagrantfile
vendored
11
spec/support/Vagrantfile
vendored
|
@ -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
|
||||||
|
|
7
spec/support/tasks/root.rake
Normal file
7
spec/support/tasks/root.rake
Normal 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
|
|
@ -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}
|
||||||
|
|
Loading…
Reference in a new issue