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.
@@ -0,0 +1,72 @@
1
+ Feature: Fetching SSH Keys
2
+ In order to see a list of all used ssh servers
3
+ As a xing techie
4
+ I want to be able to fetch all authorized keys from remote servers
5
+
6
+ Scenario: Fetching all keys from a server
7
+ Given the following keys are on the servers
8
+ | server | key |
9
+ | preview_server | ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO |
10
+ When I run the fetch_keys command for the server "preview_server"
11
+ Then It should fetch the following keys
12
+ | key |
13
+ | ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO |
14
+
15
+ Scenario: Fetching all keys from all servers
16
+ Given the following hosts
17
+ """
18
+ 13.9.1.41 preview_server
19
+ 13.9.1.42 edge_server
20
+ 10.53.1.41 production_server
21
+ """
22
+ And the following keys are on the servers
23
+ | server | key |
24
+ | 13.9.1.41 | ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO |
25
+ | 13.9.1.41 | ssh-rsa key4== abel.fernandez@nb-afernandez.local |
26
+ | 13.9.1.41 | ssh-dss key2== christian.kvalheim@nb-ckvalheim.local |
27
+ | 13.9.1.42 | ssh-rsa key3== lee.hambley@xing.com |
28
+ | 10.53.1.41 | ssh-rsa key4== abel.fernandez@nb-afernandez.local |
29
+ | 10.53.1.41 | ssh-rsa key5== pascal.friederich@nb-pfriederich.local |
30
+ When I run the fetch_keys command
31
+ Then It should fetch the following keys
32
+ | key |
33
+ | ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO |
34
+ | ssh-rsa key4== abel.fernandez@nb-afernandez.local |
35
+ | ssh-dss key2== christian.kvalheim@nb-ckvalheim.local |
36
+ | ssh-rsa key3== lee.hambley@xing.com |
37
+ | ssh-rsa key5== pascal.friederich@nb-pfriederich.local |
38
+
39
+ Scenario: Dumping a single servers keys to files
40
+ Given the following keys have been fetched
41
+ | key |
42
+ | ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO |
43
+ | ssh-rsa key4== abelfernandez@nb-afernandez.local |
44
+ | ssh-dss key2== christian.kvalheim@nb-ckvalheim.local |
45
+ | ssh-rsa key3== lee.hambley@xing.com |
46
+ | ssh-rsa key5== lee.hambley@private |
47
+ When I run the dump_keyfiles command
48
+ Then It should generate the following files
49
+ | name | key |
50
+ | abelfernandez.pub | ssh-rsa key4== abelfernandez@nb-afernandez.local |
51
+ | adem_deliceoglu.pub | ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO |
52
+ | christian_kvalheim.pub | ssh-dss key2== christian.kvalheim@nb-ckvalheim.local |
53
+ | lee_hambley.pub | ssh-rsa key3== lee.hambley@xing.com |
54
+ | lee_hambley_2.pub | ssh-rsa key5== lee.hambley@private |
55
+
56
+ Scenario: Dumping all servers keys to files
57
+ Given the following hosts
58
+ """
59
+ 13.9.1.41 preview_server
60
+ 10.53.1.42 production_server
61
+ """
62
+ And the following keys are on the servers
63
+ | server | key |
64
+ | 13.9.1.41 | ssh-rsa key4== abel.fernandez@nb-afernandez.local |
65
+ | 13.9.1.41 | ssh-dss key2== christian.kvalheim@nb-ckvalheim.local |
66
+ | 10.53.1.42 | ssh-rsa key4== abel.fernandez@nb-afernandez.local |
67
+ When I run the fetch_keys command
68
+ And I run the dump_keyfiles command
69
+ Then It should generate the following files
70
+ | name | key |
71
+ | abel_fernandez.pub | ssh-rsa key4== abel.fernandez@nb-afernandez.local |
72
+ | christian_kvalheim.pub | ssh-dss key2== christian.kvalheim@nb-ckvalheim.local |
@@ -0,0 +1,136 @@
1
+ require 'fileutils'
2
+ require 'ruby-debug'
3
+ Debugger.start
4
+
5
+ #########
6
+ # GIVEN
7
+ #########
8
+
9
+ Given /^the local keyfiles$/ do |table|
10
+ writer = Zool::KeyfileWriter.new
11
+ table.hashes.each {|keyfile| writer.write(keyfile['key'], keyfile['name'])}
12
+ end
13
+
14
+ Given /^the config$/ do |string|
15
+ @config = string
16
+ end
17
+
18
+ Given /^the following hosts$/ do |string|
19
+ hosts = StringIO.new(string)
20
+ @zool = Zool::ServerPool.from_hostfile(hosts)
21
+ end
22
+
23
+ Given /^the following keys are on the servers$/ do |table|
24
+ keys = server_with_keys_from_table(table)
25
+
26
+ keys.each do |host, keys|
27
+ File.open(fake_server_dir!(host) + '/authorized_keys', 'w+') do |file|
28
+ file.write(keys.join("\n"))
29
+ end
30
+ end
31
+ end
32
+
33
+ Given /^the following keys have been fetched$/ do |table|
34
+ Given 'the server "localhost"'
35
+ @zool.keys = table.rows.flatten
36
+ end
37
+
38
+ Given /^the server "([^\"]*)" without a key file$/ do |servername|
39
+ Given "the server \"localhost\""
40
+ end
41
+
42
+ Given /^the server "([^\"]*)"$/ do |servername|
43
+ @zool = Zool::Server.new(servername)
44
+ end
45
+
46
+ #########
47
+ # WHEN
48
+ #########
49
+
50
+ When /^I parse the config and run the upload_keys command$/ do
51
+ @zool = Zool::Configuration.parse(@config)
52
+ @zool.upload_keys
53
+ end
54
+
55
+ When /^I build the config from scratch$/ do
56
+ @generated_config = Zool::Configuration.build(@zool)
57
+ end
58
+
59
+ When /^I run the fetch_keys command for the server "([^\"]*)"$/ do |hostname|
60
+ @zool = Zool::Server.new(hostname)
61
+ @zool.fetch_keys
62
+ end
63
+
64
+ When /^I add the key "([^\"]*)"$/ do |key|
65
+ @zool.keys << key
66
+ end
67
+
68
+ When /^I run the (.*) command$/ do |command|
69
+ @zool.send(command)
70
+ end
71
+
72
+ When /^I upload the keys to the server "([^\"]*)"$/ do |servername, table|
73
+ @zool = Zool::Server.new(servername)
74
+ @zool.keys = table.rows
75
+ @zool.upload_keys
76
+ end
77
+
78
+ #########
79
+ # THEN
80
+ #########
81
+
82
+ Then /^It should fetch the following keys$/ do |table|
83
+ actual_keys = [['key']] | @zool.keys.map {|key| [key] }
84
+ table.diff!(actual_keys)
85
+ end
86
+
87
+ Then /^It should generate the following files$/ do |keyfiles|
88
+ actual_keyfiles = [['name', 'key']]
89
+ Dir[TEST_TMP_PATH + '/keys/*.pub'].each do |keyfile|
90
+ File.open(keyfile) do |file|
91
+ actual_keyfiles << [File.basename(keyfile), file.read.strip]
92
+ end
93
+ end
94
+
95
+ keyfiles.diff!(actual_keyfiles)
96
+ end
97
+
98
+ Then /^the server "([^\"]*)" should have the authorized_keys file with the content$/ do |server, expected_content|
99
+ File.read(TEST_TMP_PATH + "/servers/#{server}/authorized_keys").should == expected_content
100
+ end
101
+
102
+ Then /^the following keys should be on the servers$/ do |table|
103
+ actual_keys_from_server = [['server', 'key']]
104
+ server_with_keys_from_table(table).each do |server, keys|
105
+ entries = File.read(TEST_TMP_PATH + "/servers/#{server}/authorized_keys").split("\n").map {|key| [server, key]}
106
+ actual_keys_from_server += entries
107
+ end
108
+ table.diff!(actual_keys_from_server)
109
+ end
110
+
111
+ Then /^I should have the following config$/ do |string|
112
+ @generated_config.should == string
113
+ end
114
+
115
+ #########
116
+ # HELPER
117
+ #########
118
+ def fake_server_dir!(server)
119
+ path = fake_server_dir(server)
120
+ return path if File.directory?(path)
121
+ FileUtils.mkdir_p path
122
+ path
123
+ end
124
+
125
+ def fake_server_dir(host)
126
+ TEST_TMP_PATH + "/servers/#{host}"
127
+ end
128
+
129
+ def server_with_keys_from_table(table)
130
+ keys = {}
131
+ table.hashes.each do |values|
132
+ keys[values["server"]] ||= []
133
+ keys[values["server"]] << values["key"]
134
+ end
135
+ keys
136
+ end
@@ -0,0 +1,62 @@
1
+ Feature: Store ssh keys on servers
2
+ In order to simplify uploading of keys to servers
3
+ As a xing techie
4
+ I want to be able to compose key lists and upload them to servers
5
+
6
+ Scenario: uploading keys to a server
7
+ Given the server "preview_server" without a key file
8
+ When I upload the keys to the server "preview_server"
9
+ | key |
10
+ | ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO |
11
+ | ssh-rsa key4== abel.fernandez@nb-afernandez.local |
12
+ | ssh-dss key2== christian.kvalheim@nb-ckvalheim.local |
13
+ | ssh-rsa key3== lee.hambley@xing.com |
14
+ | ssh-rsa key5== pascal.friederich@nb-pfriederich.local |
15
+
16
+ Then the server "preview_server" should have the authorized_keys file with the content
17
+ """
18
+ ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO
19
+ ssh-rsa key4== abel.fernandez@nb-afernandez.local
20
+ ssh-dss key2== christian.kvalheim@nb-ckvalheim.local
21
+ ssh-rsa key3== lee.hambley@xing.com
22
+ ssh-rsa key5== pascal.friederich@nb-pfriederich.local
23
+ """
24
+
25
+ Scenario: adding a single key to a servers keyfile
26
+ Given the server "13.9.1.41"
27
+ And the following keys are on the servers
28
+ | server | key |
29
+ | 13.9.1.41 | ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO |
30
+ | 13.9.1.41 | ssh-dss key2== christian.kvalheim@nb-ckvalheim.local |
31
+ When I add the key "ssh-rsa key4== abel.fernandez@nb-afernandez.local"
32
+ And I run the upload_keys command
33
+ Then the server "13.9.1.41" should have the authorized_keys file with the content
34
+ """
35
+ ssh-rsa key1== Adem.Deliceoglu@PC-ADELICEO
36
+ ssh-dss key2== christian.kvalheim@nb-ckvalheim.local
37
+ ssh-rsa key4== abel.fernandez@nb-afernandez.local
38
+ """
39
+
40
+ Scenario: adding a single key to a serverpools keyfiles
41
+ Given the following hosts
42
+ """
43
+ 13.9.1.41 preview_server
44
+ 13.9.1.42 edge_server
45
+ """
46
+ And the following keys are on the servers
47
+ | server | key |
48
+ | 13.9.1.41 | ssh-rsa key1== some.key@somehost |
49
+ | 13.9.1.41 | ssh-rsa key4== anotherkey@ahost.local |
50
+ | 13.9.1.41 | ssh-dss key2== thiskey@thishost.local |
51
+ | 13.9.1.42 | ssh-rsa key3== snafu@bar.com |
52
+ When I add the key "ssh-rsa key5== additionalkey@host"
53
+ And I run the upload_keys command
54
+ Then the following keys should be on the servers
55
+ | server | key |
56
+ | 13.9.1.41 | ssh-rsa key1== some.key@somehost |
57
+ | 13.9.1.41 | ssh-rsa key4== anotherkey@ahost.local |
58
+ | 13.9.1.41 | ssh-dss key2== thiskey@thishost.local |
59
+ | 13.9.1.41 | ssh-rsa key5== additionalkey@host |
60
+ | 13.9.1.42 | ssh-rsa key3== snafu@bar.com |
61
+ | 13.9.1.42 | ssh-rsa key5== additionalkey@host |
62
+
@@ -0,0 +1,39 @@
1
+ $:.unshift(File.expand_path(File.dirname(__FILE__) + '/../..'))
2
+
3
+ require 'spec'
4
+ require 'lib/zool'
5
+
6
+ TEST_TMP_PATH = File.expand_path(File.dirname(__FILE__) + "/../tmp")
7
+
8
+ class Zool::Server
9
+ def initialize(host, options = {})
10
+ @options = {
11
+ :password => '',
12
+ :user => `whoami`.chomp
13
+ }
14
+ @hostname = 'localhost'
15
+ @fake_hostname = host
16
+
17
+ temp_server_path = TEST_TMP_PATH + "/servers/#{host}"
18
+ @keyfile_location = temp_server_path + '/authorized_keys'
19
+ FileUtils.mkdir_p temp_server_path unless File.directory? temp_server_path
20
+ end
21
+
22
+ def hostname
23
+ @fake_hostname
24
+ end
25
+ end
26
+
27
+ class Zool::KeyfileWriter
28
+ def initialize(out_directory = 'keys')
29
+ @out_directory = TEST_TMP_PATH + "/#{out_directory}"
30
+ FileUtils.mkdir_p @out_directory unless File.directory? @out_directory
31
+ end
32
+ end
33
+
34
+ Before do
35
+ FileUtils.rm_r TEST_TMP_PATH if File.directory? TEST_TMP_PATH
36
+ @zool = nil
37
+ @servers = nil
38
+ end
39
+
@@ -0,0 +1,85 @@
1
+ grammar PyConfig
2
+ rule main
3
+ section*
4
+ {
5
+ def build
6
+ @config = {}
7
+ elements.each do |section|
8
+ @config[section.name] = section.build
9
+ end
10
+ @config
11
+ end
12
+ }
13
+ end
14
+
15
+ rule section
16
+ space* '[' sectionname ']' eol
17
+ pairs:(sectionvalue eol?)+
18
+ white*
19
+ {
20
+ def name
21
+ sectionname.text_value
22
+ end
23
+
24
+ def build
25
+ return pairs.elements.inject({}) do |pairs, elt|
26
+ pairs.merge!(elt.sectionvalue.build)
27
+ pairs
28
+ end
29
+ end
30
+ }
31
+ end
32
+
33
+ rule sectionname
34
+ (string / space / '.')+
35
+ end
36
+
37
+ rule sectionvalue
38
+ space* key separator value
39
+ {
40
+ def build
41
+ separator.build(key, value)
42
+ end
43
+ }
44
+ end
45
+
46
+ rule separator
47
+ space* ':' space*
48
+ {
49
+ def build(key, value)
50
+ {key.text_value => value.text_value.strip}
51
+ end
52
+ }
53
+ /
54
+ space* '=' space*
55
+ {
56
+ def build(key, value)
57
+ {key.text_value => value.text_value.split(',').map {|val| val.strip }}
58
+ end
59
+ }
60
+ end
61
+
62
+ rule key
63
+ string
64
+ end
65
+
66
+ rule value
67
+ [^\n]*
68
+ end
69
+
70
+ rule string
71
+ [a-zA-Z_0-9]+
72
+ end
73
+
74
+ rule white
75
+ (space / eol)
76
+ end
77
+
78
+ rule space
79
+ [ \t]
80
+ end
81
+
82
+ rule eol
83
+ "\n" / ("\r" "\n"?)
84
+ end
85
+ end
data/lib/zool.rb ADDED
@@ -0,0 +1,24 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ begin
3
+ require File.expand_path(File.dirname(__FILE__) + '/../vendor/gems/environment')
4
+ rescue LoadError
5
+ # seems to be the gem version
6
+ end
7
+
8
+ require 'treetop'
9
+ require 'py_config_parser/py_config_parser'
10
+ require 'zool/server'
11
+ require 'zool/server_pool'
12
+ require 'zool/key_file_writer'
13
+ require 'zool/configuration'
14
+
15
+ module Zool
16
+ DEFAULT_LOGGER = begin
17
+ if defined?(Spec)
18
+ # we are in test environment
19
+ Logger.new('test.log')
20
+ else
21
+ Logger.new('zool.log')
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,151 @@
1
+ module Zool
2
+ class Configuration
3
+ class ParseError < Exception; end
4
+ attr_reader :servers, :roles, :groups
5
+
6
+ def self.parse(configuration)
7
+ parser = PyConfigParser.new
8
+ if raw_config = parser.parse(configuration)
9
+ self.new(raw_config.build)
10
+ else
11
+ raise ParseError.new(parser.failure_reason)
12
+ end
13
+ end
14
+
15
+ def self.build(pool)
16
+ conf = ""
17
+ pool.servers.each do |server|
18
+ next if server.keys == []
19
+ conf << "\n" unless conf == ""
20
+ conf << "[server #{server.hostname}]\n"
21
+ keynames = server.keys.map {|key| KeyfileWriter.keyname_for_key(key)}
22
+ conf << " keys = #{keynames.join(', ')}\n"
23
+ end
24
+ conf
25
+ end
26
+
27
+ def initialize(raw_config)
28
+ @raw_config = raw_config
29
+ parse
30
+ end
31
+
32
+ def keys
33
+ @keys ||= read_keys
34
+ end
35
+
36
+ def upload_keys
37
+ @servers.each { |servername, server| server.upload_keys }
38
+ end
39
+
40
+ private
41
+ def parse
42
+ @roles = {}
43
+ @servers = {}
44
+ @groups = {}
45
+
46
+ parse_groups
47
+ parse_servers
48
+ parse_roles
49
+ end
50
+
51
+ def parse_groups
52
+ raw_groups.each do |raw_group|
53
+ @groups[raw_group[/^group\s(.*)/, 1]] = @raw_config[raw_group]['members']
54
+ end
55
+ end
56
+
57
+ def parse_servers
58
+ raw_servers.each do |raw_server|
59
+ server = server(raw_server[/^server\s(.*)/, 1], @raw_config[raw_server])
60
+ @raw_config[raw_server]['keys'].each do |key|
61
+ server.keys << fetch_key(key)
62
+ end
63
+ end
64
+ end
65
+
66
+ def raw_servers
67
+ raw(:server)
68
+ end
69
+
70
+ def raw_groups
71
+ raw(:group)
72
+ end
73
+
74
+ def parse_roles
75
+ raw_roles.each do |raw_role|
76
+ @roles[raw_role[/^role\s(.*)/, 1]] = server_pool(raw_role)
77
+ end
78
+ end
79
+
80
+ def raw_roles
81
+ raw(:role)
82
+ end
83
+
84
+ def raw(raw_type)
85
+ @raw_config.select {|k, v| k =~ /^#{raw_type}/}.map {|role_arrey| role_arrey[0]}
86
+ end
87
+
88
+ def server(hostname, raw_object)
89
+ return @servers[hostname] if @servers[hostname]
90
+ options = server_options_from_configuration(raw_object)
91
+
92
+ new_server = Server.new(hostname, options)
93
+ new_server.keys = []
94
+ @servers[hostname] = new_server
95
+ new_server
96
+ end
97
+
98
+ def server_options_from_configuration(raw_object)
99
+ user = raw_object['user']
100
+ password = raw_object['password']
101
+ options = {}
102
+ options.update({:user => user}) if user
103
+ options.update({:password => password}) if password
104
+ options
105
+ end
106
+
107
+ def read_keys
108
+ hash = {}
109
+ key_directory = KeyfileWriter.new.out_directory # FIXME: terrible and lazy hack!
110
+ keyfiles = Dir["#{key_directory}/*.pub"]
111
+ keyfiles.each do |keyfile|
112
+ keyname = File.basename(keyfile)[/(.*)\.pub/, 1]
113
+ hash[keyname] = File.read(keyfile).chomp
114
+ end
115
+ hash
116
+ end
117
+
118
+ def server_pool(raw_role)
119
+ pool = ServerPool.new()
120
+
121
+ @raw_config[raw_role]['servers'].each do |hostname|
122
+ pool << server(hostname, @raw_config[raw_role])
123
+ end
124
+
125
+ @raw_config[raw_role]['keys'].each do |key|
126
+ if key =~ /^&/
127
+ add_group_keys(key[1..-1], pool)
128
+ else
129
+ pool.keys << fetch_key(key)
130
+ end
131
+ end
132
+
133
+ pool
134
+ end
135
+
136
+ def fetch_key(key)
137
+ return keys[key] unless keys[key].nil?
138
+ raise ParseError.new("missing ssh key '#{key}'")
139
+ end
140
+
141
+ def add_group_keys(group, pool)
142
+ begin
143
+ @groups[group].each do |key|
144
+ pool.keys << fetch_key(key)
145
+ end
146
+ rescue NoMethodError => e
147
+ raise ParseError.new("missing referenced group '#{group}'")
148
+ end
149
+ end
150
+ end
151
+ end