zool 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|