zool 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +96 -0
- data/Rakefile +57 -0
- data/Readme.md +96 -0
- data/bin/zool +156 -0
- data/features/config_parser.feature +67 -0
- data/features/fetching_ssh_keys.feature +72 -0
- data/features/step_definitions/ssh_keys_steps.rb +136 -0
- data/features/store_ssh_keys.feature +62 -0
- data/features/support/env.rb +39 -0
- data/lib/py_config_parser/py_config_parser.tt +85 -0
- data/lib/zool.rb +24 -0
- data/lib/zool/configuration.rb +151 -0
- data/lib/zool/key_file_writer.rb +48 -0
- data/lib/zool/server.rb +116 -0
- data/lib/zool/server_pool.rb +74 -0
- data/spec/py_config_parser_spec.rb +55 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/zool.rb +4 -0
- data/spec/zool/configuration_spec.rb +170 -0
- data/spec/zool/key_file_writer_spec.rb +82 -0
- data/spec/zool/server_pool_spec.rb +133 -0
- data/spec/zool/server_spec.rb +178 -0
- metadata +266 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'ruby-debug'
|
2
|
+
Debugger.start
|
3
|
+
|
4
|
+
module Zool
|
5
|
+
class KeyfileWriter
|
6
|
+
attr_accessor :out_directory
|
7
|
+
|
8
|
+
def self.keyname_for_key(key)
|
9
|
+
temp_name = key[/^\S*\s\S*\s([^@]+)\S*$/, 1]
|
10
|
+
if temp_name.nil?
|
11
|
+
logger.warn "key not parsable"
|
12
|
+
'1__not_parsable'
|
13
|
+
else
|
14
|
+
temp_name.gsub(/[^A-Z|^a-z|^0-9]/, '_').downcase
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(out_directory = 'keys')
|
19
|
+
@out_directory = out_directory
|
20
|
+
end
|
21
|
+
|
22
|
+
def write_keys(keys)
|
23
|
+
keys.each do |key|
|
24
|
+
write key
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def write(key, outname = nil)
|
29
|
+
key_name = outname || self.class.keyname_for_key(key)
|
30
|
+
key_count = Dir["#{out_directory}/#{key_name}*.pub"].size
|
31
|
+
|
32
|
+
key_name += "_#{key_count + 1}" if key_count > 0
|
33
|
+
key_path = "#{out_directory}/#{key_name}.pub"
|
34
|
+
|
35
|
+
File.open(key_path, 'w+') do |file|
|
36
|
+
file.puts key
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.logger
|
41
|
+
DEFAULT_LOGGER
|
42
|
+
end
|
43
|
+
|
44
|
+
def logger
|
45
|
+
DEFAULT_LOGGER
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/zool/server.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'net/sftp'
|
2
|
+
require 'net/scp'
|
3
|
+
|
4
|
+
module Zool
|
5
|
+
class Server
|
6
|
+
class ConnectionVerificationExecption < Exception; end
|
7
|
+
attr_reader :hostname
|
8
|
+
attr_accessor :keyfile_location
|
9
|
+
|
10
|
+
def initialize(hostname, options = {})
|
11
|
+
@options = {
|
12
|
+
:user => 'root',
|
13
|
+
:password => ''
|
14
|
+
}.update(options)
|
15
|
+
@hostname = hostname
|
16
|
+
@keyfile_location = default_keyfile_location
|
17
|
+
end
|
18
|
+
|
19
|
+
def fetch_keys
|
20
|
+
@keys = nil
|
21
|
+
@raw_authorized_keys = load_remote_file
|
22
|
+
end
|
23
|
+
|
24
|
+
def keys
|
25
|
+
@keys ||= begin
|
26
|
+
@raw_authorized_keys ||= fetch_keys
|
27
|
+
@raw_authorized_keys.split("\n").map {|key| key.strip}.uniq.reject {|key| key == ""}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def keys=(new_keys)
|
32
|
+
@keys = new_keys
|
33
|
+
end
|
34
|
+
|
35
|
+
def dump_keyfiles
|
36
|
+
key_writer = KeyfileWriter.new
|
37
|
+
key_writer.write_keys keys
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_backup
|
41
|
+
begin
|
42
|
+
backup = load_remote_file
|
43
|
+
backup_filename = "#{@keyfile_location}_#{Time.now.to_i}"
|
44
|
+
Net::SCP.upload!(@hostname, @options[:user], StringIO.new(backup), backup_filename, :ssh => {:password => @options[:password]})
|
45
|
+
backup_filename
|
46
|
+
rescue Net::SCP::Error => e
|
47
|
+
logger.fatal "Error during backup of authorized keys file: #{e.message}"
|
48
|
+
raise
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def upload_keys
|
53
|
+
remote_backup_file = create_backup
|
54
|
+
begin
|
55
|
+
backup_channel = Net::SSH.start(@hostname, @options[:user], :password => @options[:password])
|
56
|
+
main_channel = Net::SSH.start(@hostname, @options[:user], :password => @options[:password])
|
57
|
+
main_channel.scp.upload!(StringIO.new(keys.join("\n")), @keyfile_location)
|
58
|
+
main_channel.close
|
59
|
+
begin
|
60
|
+
logger.info "Trying to connect to #{@hostname} to see if I still have access"
|
61
|
+
Net::SSH.start(@hostname, @options[:user], :password => '')
|
62
|
+
logger.info "Backup channel connection succeeded. Assuming everything went fine!"
|
63
|
+
rescue Net::SSH::AuthenticationFailed => e
|
64
|
+
if !@rolled_back
|
65
|
+
logger.warn "!!!!!! Could not login to server after upload operation! Rolling back !!!!!!"
|
66
|
+
backup_channel.exec "mv #{remote_backup_file} #{@keyfile_location}"
|
67
|
+
backup_channel.loop
|
68
|
+
@rolled_back = true
|
69
|
+
retry
|
70
|
+
else
|
71
|
+
logger.fatal "Tried to role back... didnt work... giving up... sorry :("
|
72
|
+
raise e
|
73
|
+
end
|
74
|
+
end
|
75
|
+
ensure
|
76
|
+
main_channel.close unless main_channel.closed?
|
77
|
+
backup_channel.close unless backup_channel.closed?
|
78
|
+
end
|
79
|
+
raise ConnectionVerificationExecption.new("Error after uploading the keyfile to #{@hostname}") if @rolled_back
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_s
|
83
|
+
"<Zool::Server #{hostname}>"
|
84
|
+
end
|
85
|
+
|
86
|
+
def user
|
87
|
+
@options[:user]
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
def load_remote_file
|
92
|
+
downloaded_file = StringIO.new
|
93
|
+
begin
|
94
|
+
Timeout::timeout(2) do
|
95
|
+
logger.info "Fetching key from #{@hostname}"
|
96
|
+
Net::SCP.download!(@hostname, @options[:user], @keyfile_location, downloaded_file, :ssh => {:password => @options[:password]})
|
97
|
+
end
|
98
|
+
rescue Net::SCP::Error
|
99
|
+
logger.warn "Warning! Empty keyfile" # logging? later... :P
|
100
|
+
rescue Net::SSH::AuthenticationFailed
|
101
|
+
logger.warn "No access to Server #{@hostname}"
|
102
|
+
rescue Errno::ETIMEDOUT, Timeout::Error
|
103
|
+
logger.warn "Access to server #{@hostname} timed out"
|
104
|
+
end
|
105
|
+
downloaded_file.string
|
106
|
+
end
|
107
|
+
|
108
|
+
def default_keyfile_location
|
109
|
+
'~/.ssh/authorized_keys'
|
110
|
+
end
|
111
|
+
|
112
|
+
def logger
|
113
|
+
DEFAULT_LOGGER
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Zool
|
2
|
+
class ServerPool < Array
|
3
|
+
IP_FORMAT = /^(?:25[0-5]|(?:2[0-4]|1\d|[1-9])?\d)(?:\.(?:25[0-5]|(?:2[0-4]|1\d|[1-9])?\d)){3}$|^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+(?:[a-z]{2}|com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum)$/
|
4
|
+
|
5
|
+
def self.from_hostfile(hostsfile, options = {})
|
6
|
+
hosts = hostsfile.to_a.map { |host| host.split[0] }
|
7
|
+
hosts.uniq!
|
8
|
+
invalid_hosts = %w(127.0.0.1 255.255.255.255)
|
9
|
+
hosts.reject! { |host| host !~ IP_FORMAT }
|
10
|
+
hosts.reject! { |host| invalid_hosts.include?(host) }
|
11
|
+
pool = self.new
|
12
|
+
|
13
|
+
hosts.each do |host|
|
14
|
+
# puts host
|
15
|
+
server = Server.new(host, options)
|
16
|
+
# puts server.hostname
|
17
|
+
pool << server
|
18
|
+
end
|
19
|
+
pool
|
20
|
+
end
|
21
|
+
|
22
|
+
alias servers entries
|
23
|
+
|
24
|
+
def keys
|
25
|
+
@keys_proxy ||= KeysProxy.new(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
def fetch_keys
|
29
|
+
call_for_pool(:fetch_keys)
|
30
|
+
@keys_proxy = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def upload_keys
|
34
|
+
call_for_pool(:upload_keys)
|
35
|
+
end
|
36
|
+
|
37
|
+
def <<(object)
|
38
|
+
raise TypeError.new 'Invalid Argument' unless object.instance_of?(Server)
|
39
|
+
super
|
40
|
+
end
|
41
|
+
alias add <<
|
42
|
+
|
43
|
+
def dump_keyfiles
|
44
|
+
writer = KeyfileWriter.new
|
45
|
+
keys.each do |key|
|
46
|
+
writer.write(key)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def inspect
|
51
|
+
"#<Zool::ServerPool @servers=[#{servers.join(', ')}]>"
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def call_for_pool(method)
|
56
|
+
servers.map do |server|
|
57
|
+
server.send(method)
|
58
|
+
end.flatten.uniq
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class KeysProxy < Array
|
63
|
+
def initialize(pool)
|
64
|
+
@pool = pool
|
65
|
+
super @pool.send(:call_for_pool, :keys)
|
66
|
+
end
|
67
|
+
|
68
|
+
def <<(key)
|
69
|
+
@pool.each do |server|
|
70
|
+
server.keys << key
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe PyConfigParser do
|
4
|
+
include PyConfigParserHelper
|
5
|
+
|
6
|
+
before :all do
|
7
|
+
@config_string = <<-CONF
|
8
|
+
[without_whitespace]
|
9
|
+
key:value
|
10
|
+
multiple_values=foo, bar, baz
|
11
|
+
sticky_values=blim,blam,blum
|
12
|
+
|
13
|
+
[with whitespace]
|
14
|
+
key2: value2
|
15
|
+
key3 : value3
|
16
|
+
multiple_values = foo, bar, baz
|
17
|
+
CONF
|
18
|
+
end
|
19
|
+
|
20
|
+
context "parsing a config" do
|
21
|
+
context "when invalid" do
|
22
|
+
it "should compile to nil" do
|
23
|
+
parse('strange stuff').should be_nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "when valid" do
|
28
|
+
it "should compile to a hash" do
|
29
|
+
parse("").build.should == {}
|
30
|
+
end
|
31
|
+
|
32
|
+
context "with sections" do
|
33
|
+
before :all do
|
34
|
+
@sections = parse(@config_string).build
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should recognize sections" do
|
38
|
+
@sections["with whitespace"].should be_a(Hash)
|
39
|
+
@sections["without_whitespace"].should be_a(Hash)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should map key/value pairs separated by colons" do
|
43
|
+
@sections["with whitespace"]['key2'].should == 'value2'
|
44
|
+
@sections["without_whitespace"]['key'].should == 'value'
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should map key/value pairs separated by an equal sign and devided by commas" do
|
48
|
+
@sections["with whitespace"]['multiple_values'].should == %w(foo bar baz)
|
49
|
+
@sections["without_whitespace"]['multiple_values'].should == %w(foo bar baz)
|
50
|
+
@sections["without_whitespace"]['sticky_values'].should == %w(blim blam blum)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'lib/zool'
|
2
|
+
require 'spec'
|
3
|
+
require 'fakefs'
|
4
|
+
|
5
|
+
module Net::SSH
|
6
|
+
def self.start
|
7
|
+
raise("unexpected call to SCP in test environment, see #{__FILE__}:#{__LINE__}")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Net::SCP
|
12
|
+
class << self
|
13
|
+
def disallow_file_operation(a, b, c, d)
|
14
|
+
raise("unexpected call to SCP in test environment, see #{__FILE__}:#{__LINE__}")
|
15
|
+
end
|
16
|
+
alias upload! disallow_file_operation
|
17
|
+
alias upload disallow_file_operation
|
18
|
+
alias download! disallow_file_operation
|
19
|
+
alias download disallow_file_operation
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class StringbufferMatcher
|
24
|
+
def initialize(expected)
|
25
|
+
@expected = expected
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(actual)
|
29
|
+
actual.string == @expected
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def stringbuffer_with(content)
|
34
|
+
StringbufferMatcher.new(content)
|
35
|
+
end
|
36
|
+
|
37
|
+
def key_fixtures
|
38
|
+
@key_fixtures ||= {
|
39
|
+
:pascal => 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0YllcgPG3lFhW1R6g1zHIOOZhW8fl5MsBxNQYFJnkNUvwcqcH1CLFr5ybdEwgOfjqT2YDLt9qY/cn4Wa1xLvPEph7nkdx6NW7VzcxcIiakgtEEGI+F6K0ux/3bXPIEIDZcaAmlfcnw+OkoqyQR1PWppT/74mc+6+GkCoewqgIhxuajPmjLK9eAtDjNGnwsN1t0+gZkc9HNWOxWGGGNyfoSgRPlIzr4cTDnfuRPzxZDKJXLd75RJIAhr2PQwQTrdhPurCG2+48AHul/D1mg+BzWeaXifl3pd8on/Buo97A6iLM+jcx1VjDzhVil6esS/+30XSEUANh974PlIECZnIFw== pascal.friederich@nb-pfriederich.local',
|
40
|
+
:pascal_private => 'ssh-rsa fajfoijewaofjewofjaweofnlwkaenfakdjngkaldsjgndkjsnflkjdsfnjsadfkjlasdfnasnfamlfaj9efj09waj09j3f029j3029j3f2j3f2uhfuhgkashgkljdsagkjeahh3iuf2h398fh329f8h32f983h2fh3n29unfup3fhapw39fhpa93fha9w3fh983bf2fubkbawekjbfabf,ebfa,menbfiufbawefuwefiweafiubewafibefbiuwbgiu4gbiueraghaeiuhfsdiofuhasdifuhaw9e8fh9f8h238fh239fhpawh3fp9ahwpfhawp39fhp490f8hawf8ha9ef8hawp9haugbs== pascal.friederich@private',
|
41
|
+
:pascal_laptop => 'ssh-rsa fajfoijewaofjewofjaweofnlwkaenfakdjngkaldsjgndkjsnflkjdsfnjsadfkjlasdfnasnfamlfaj9efj09waj09j3f029j3029j3f2j3f2uhfuhgkashgkljdsagkjeahh3iuf2h398fh329f8h32f983h2fh3n29unfup3fhapw39fhpa93fha9w3fh983bf2fubkbawekjbfabf,ebfa,menbfiufbawefuwefiweafiubewafibefbiuwbgiu4gbiueraghaeiuhfsdiofuhasdifuhaw9e8fh9f8h238fh239fhpawh3fp9ahwpfhawp39fhp490f8hawf8ha9ef8hawp9haugbs== pascal.friederich@laptop',
|
42
|
+
:bob => 'ssh-rsa LKASJFLASJFLKASJFLAKSFNALSKVNasdfj0fj0Jf0j09Jf90jw0fj9w0fjJFIWJLFNlnfLNlknflewknaflefawelfhweaf8932y98ry239f832hfh3fh3fiuhkljdsfkjasbdfwhefhewkjfhenkhfnkejfhhdskfjhdskfjhsdkjfhskdjfhalksdjhfkjdfhalsdkfhklasdfhdskfhjdkfjhqufheufwhewiuf38h9fh3298fh2938fh9283hf9823hf9823hfk2j3hfkj23fkj23fkjh23kjfhljhaasdfsadfsadf90usdf90saudf09jas0f9jas0fj09wjf0932hf0923hf0h320f9h230f9h329h== bob.schneider@nb-pfriederich.local',
|
43
|
+
:upcase => 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0YllcgPG3lFhW1R6g1zHIOOZhW8fl5MsBxNQYFJnkNUvwcqcH1CLFr5ybdEwgOfjqT2YDLt9qY/cn4Wa1xLvPEph7nkdx6NW7VzcxcIiakgtEEGI+F6K0ux/3bXPIEIDZcaAmlfcnw+OkoqyQR1PWppT/74mc+6+GkCoewqgIhxuajPmjLK9eAtDjNGnwsN1t0+gZkc9HNWOxWGGGNyfoSgRPlIzr4cTDnfuRPzxZDKJXLd75RJIAhr2PQwQTrdhPurCG2+48AHul/D1mg+BzWeaXifl3pd8on/Buo97A6iLM+jcx1VjDzhVil6esS/+30XSEUANh974PlIECZnIFw== upcase.VaN@nb-UPCASE.StuFF'
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
module PyConfigParserHelper
|
48
|
+
def parse(string)
|
49
|
+
PyConfigParser.new.parse(string)
|
50
|
+
end
|
51
|
+
end
|
data/spec/zool.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
module Zool
|
4
|
+
describe Configuration do
|
5
|
+
before :all do
|
6
|
+
@keyfile_stub_data = {
|
7
|
+
'peter' => 'ssh-dsa adfsdfafef00if0i23f== peter@localhost',
|
8
|
+
'paul' => 'ssh-dsa adfsdfafef00if0i23f== paul@horst',
|
9
|
+
'system' => 'ssh-dsa adfsdfafef00if0i23f== system@admins',
|
10
|
+
'log' => 'ssh-dsa adfsdfafef00if0i23f== log@admins',
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
context "building a configuration file from a serverpool" do
|
15
|
+
before :each do
|
16
|
+
server1_keys = [@keyfile_stub_data['peter'], @keyfile_stub_data['paul']]
|
17
|
+
server1 = stub(:hostname => 'server1', :keys => server1_keys)
|
18
|
+
server2_keys = [@keyfile_stub_data['system'], @keyfile_stub_data['log']]
|
19
|
+
server2 = stub(:hostname => 'server2', :keys => server2_keys)
|
20
|
+
@pool = ServerPool.new([server1, server2])
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should write a server entry for every server in the pool and add it's keys" do
|
24
|
+
Configuration.build(@pool).should == <<-EXPECTED_CONF
|
25
|
+
[server server1]
|
26
|
+
keys = peter, paul
|
27
|
+
|
28
|
+
[server server2]
|
29
|
+
keys = system, log
|
30
|
+
EXPECTED_CONF
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context ".parse" do
|
35
|
+
context "an invalid configuration" do
|
36
|
+
it "should raise an exception" do
|
37
|
+
lambda { Configuration.parse('asdf') }.should raise_error(Zool::Configuration::ParseError)
|
38
|
+
end
|
39
|
+
|
40
|
+
context "pointing the reason why the configuration is invalid" do
|
41
|
+
it "should complain about missing groups that are referenced in roles" do
|
42
|
+
conf = <<-CONF
|
43
|
+
[role app]
|
44
|
+
servers = 12.3.4.5
|
45
|
+
keys = &snafu
|
46
|
+
CONF
|
47
|
+
lambda { Configuration.parse(conf) }.should raise_error(Zool::Configuration::ParseError, /missing referenced group 'snafu'/)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should complain about missing keys" do
|
51
|
+
conf = <<-CONF
|
52
|
+
[role app]
|
53
|
+
servers = 12.3.4.5
|
54
|
+
keys = i_am_not_there
|
55
|
+
CONF
|
56
|
+
lambda { Configuration.parse(conf) }.should raise_error(Zool::Configuration::ParseError, /missing ssh key 'i_am_not_there'/)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "a valid configuration" do
|
62
|
+
it "should return a configuration object with the parsed configuration hash" do
|
63
|
+
conf = <<-CONF
|
64
|
+
[role app]
|
65
|
+
servers = 13.9.6.1, 13.9.6.2
|
66
|
+
keys = &qa, peter
|
67
|
+
|
68
|
+
[group qa]
|
69
|
+
members = david
|
70
|
+
password : cleartext10)9292@*=-.?
|
71
|
+
CONF
|
72
|
+
writer = KeyfileWriter.new
|
73
|
+
FileUtils.rm_r(writer.out_directory)
|
74
|
+
|
75
|
+
writer.write 'davids key', 'david'
|
76
|
+
writer.write 'peters key', 'peter'
|
77
|
+
configuration = Configuration.parse(conf)
|
78
|
+
configuration.should be_a(Configuration)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "instanciating a configuration" do
|
84
|
+
before :all do
|
85
|
+
writer = KeyfileWriter.new
|
86
|
+
FileUtils.rm_r(writer.out_directory)
|
87
|
+
|
88
|
+
@keyfile_stub_data.each do |key, value|
|
89
|
+
writer.write value, key
|
90
|
+
end
|
91
|
+
|
92
|
+
@conf_hash = {
|
93
|
+
"role app" => {
|
94
|
+
'servers' => ['preview_server', 'production_server', 'edge_server'],
|
95
|
+
'keys' => ['&qa'],
|
96
|
+
'password' => "123456"
|
97
|
+
},
|
98
|
+
"role cron servers" => {
|
99
|
+
'servers' => ['crn1', 'crn2', 'edge_server'],
|
100
|
+
'keys' => ['system', 'log']
|
101
|
+
},
|
102
|
+
"group qa" => {
|
103
|
+
'members' => ['peter', 'paul']
|
104
|
+
},
|
105
|
+
"server 13.9.6.1" => {
|
106
|
+
'keys' => ['system'],
|
107
|
+
'password' => "123456"
|
108
|
+
},
|
109
|
+
"server 13.9.6.2" => {
|
110
|
+
'keys' => ['peter'],
|
111
|
+
'user' => "admin"
|
112
|
+
}
|
113
|
+
|
114
|
+
}
|
115
|
+
@configuration = Configuration.new(@conf_hash)
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should create a server for every server section" do
|
119
|
+
@configuration.servers['13.9.6.1'].keys.should include(@keyfile_stub_data['system'])
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should create a serverpool for every role" do
|
123
|
+
@configuration.roles['app'].should be_a(ServerPool)
|
124
|
+
@configuration.roles['cron servers'].should be_a(ServerPool)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should read the keys from the key files" do
|
128
|
+
@configuration.keys.should have(4).keys
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should add a groups keys to the serverpool" do
|
132
|
+
@configuration.servers['preview_server'].keys.should include(@keyfile_stub_data['peter'])
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should have only one server object per hostname shared between groups" do
|
136
|
+
edge_servers_keys = @configuration.servers['edge_server'].keys
|
137
|
+
edge_servers_keys.should include(@keyfile_stub_data['system'])
|
138
|
+
edge_servers_keys.should include(@keyfile_stub_data['paul'])
|
139
|
+
edge_servers_keys.should include(@keyfile_stub_data['peter'])
|
140
|
+
edge_servers_keys.should include(@keyfile_stub_data['log'])
|
141
|
+
edge_servers_keys.should have(4).keys
|
142
|
+
end
|
143
|
+
|
144
|
+
context "with a configured user and/or a password" do
|
145
|
+
it "should use the user for servers" do
|
146
|
+
@configuration.servers['13.9.6.1'].send(:instance_variable_get, :@options)[:password].should == '123456'
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should use the password for servers" do
|
150
|
+
@configuration.servers['13.9.6.2'].user.should == 'admin'
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context "calling the upload_keys method" do
|
155
|
+
it "should upload the keys to every server in the configuration" do
|
156
|
+
@configuration.servers.values.each do |server|
|
157
|
+
server.should_receive(:upload_keys).and_return(nil)
|
158
|
+
end
|
159
|
+
@configuration.upload_keys
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
Spec::Matchers.define :have_role do |role|
|
167
|
+
match do |configuration|
|
168
|
+
!configuration.roles[role].nil?
|
169
|
+
end
|
170
|
+
end
|