zool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,82 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ module Zool
4
+ describe KeyfileWriter do
5
+ context "dumping keys to files" do
6
+ before :each do
7
+ @writer = KeyfileWriter.new
8
+ end
9
+
10
+ context "#write" do
11
+ context "with no filename provided" do
12
+ before :each do
13
+ sorted_keys = [
14
+ key_fixtures[:pascal],
15
+ key_fixtures[:pascal_private],
16
+ key_fixtures[:pascal_laptop],
17
+ key_fixtures[:bob],
18
+ key_fixtures[:upcase],
19
+ ]
20
+ sorted_keys.each do |key|
21
+ @writer.write key
22
+ end
23
+ end
24
+
25
+ it "should replace special characters with underscores in filename" do
26
+ it_should_generate_keyfile 'bob_schneider.pub'
27
+ it_should_generate_keyfile 'pascal_friederich.pub'
28
+ end
29
+
30
+ it "should not fail if keyfile name is not parsable" do
31
+ key_without_host = "ssh-dsa asdfkasdlfjasdlfkjsdf="
32
+ @writer.write key_without_host
33
+ it_should_generate_keyfile '1__not_parsable.pub'
34
+ end
35
+
36
+ it "should not fail if the name in the keyfile is unusual" do
37
+ key_with_only_username = "ssh-dsa asdfasdfasdfkjkj= Peter"
38
+ @writer.write key_with_only_username
39
+ it_should_generate_keyfile "peter.pub"
40
+ end
41
+
42
+ it "should write the ssh key in the appropriate keyfile" do
43
+ File.read('keys/bob_schneider.pub').chomp.should == key_fixtures[:bob]
44
+ File.read('keys/pascal_friederich.pub').chomp.should == key_fixtures[:pascal]
45
+ end
46
+
47
+ it "should turn the filenames to underscores" do
48
+ it_should_generate_keyfile 'upcase_van.pub'
49
+ end
50
+
51
+ it "should number dublicate keynames" do
52
+ it_should_generate_keyfile 'pascal_friederich_2.pub'
53
+ it_should_generate_keyfile 'pascal_friederich_3.pub'
54
+ end
55
+ end
56
+
57
+ context "with a filename provided" do
58
+ it "should write the ssh key to the file named as given (with .pub added to the name)" do
59
+ @writer.write('a key', 'customname')
60
+ it_should_generate_keyfile('customname.pub')
61
+ end
62
+ end
63
+ end
64
+
65
+ context "#write_keys" do
66
+ it "should write every key" do
67
+ @writer.should_receive(:write).exactly(3).times
68
+ keys = [
69
+ key_fixtures[:pascal],
70
+ key_fixtures[:pascal_private],
71
+ key_fixtures[:pascal_laptop]
72
+ ]
73
+ @writer.write_keys(keys)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ def it_should_generate_keyfile(keyfile)
81
+ Dir['keys/*.pub'].map {|path| path.split('/').last }.should include keyfile
82
+ end
@@ -0,0 +1,133 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ module Zool
4
+ describe ServerPool do
5
+ context "parsing from a hosts file" do
6
+ context "with a user argument and/or password" do
7
+ before :each do
8
+ hostsfile = <<-HOSTS
9
+ 12.21.4.1 servername
10
+ HOSTS
11
+ @server = ServerPool.from_hostfile(hostsfile, :user => 'peter', :password => 'peters1234').servers.first
12
+ end
13
+
14
+ it "should pass the user argument to the servers" do
15
+ @server.user.should == 'peter'
16
+ end
17
+
18
+ it "should pass the password argument to the servers" do
19
+ @server.send(:instance_variable_get, :@options)[:password].should == 'peters1234'
20
+ end
21
+ end
22
+
23
+ context "when given a String" do
24
+ it "should return a Serverpool object with the servers from the hosts file" do
25
+ hostsfile = StringIO.new <<-HOSTS
26
+ 12.21.4.1 servername
27
+ 12.21.4.2 servername2
28
+ 12.21.4.3 servername3
29
+ 12.21.4.4 servername4
30
+ HOSTS
31
+ pool = ServerPool.from_hostfile(hostsfile)
32
+ pool.servers.map {|server| server.hostname }.should == ['12.21.4.1', '12.21.4.2', '12.21.4.3', '12.21.4.4']
33
+ end
34
+
35
+ it "should remove duplicates from the list of servers" do
36
+ hostsfile = StringIO.new <<-HOSTS
37
+ 12.21.4.1 servername1
38
+ 12.21.4.2 servername2
39
+ 12.21.4.2 fancy_alias
40
+ HOSTS
41
+ pool = ServerPool.from_hostfile(hostsfile)
42
+ pool.servers.map {|server| server.hostname }.should == ['12.21.4.1', '12.21.4.2']
43
+ end
44
+
45
+ it "should remove localhost and networks from the list of servers" do
46
+ hostfile = StringIO.new <<-HOSTS
47
+ 12.34.45.56 validserver
48
+ localhost localhost
49
+ 127.0.0.1 localhost
50
+ 255.255.255.255 network
51
+ ::1 localhost
52
+ HOSTS
53
+ pool = ServerPool.from_hostfile(hostfile)
54
+ pool.servers.map {|server| server.hostname }.should == ['12.34.45.56']
55
+ end
56
+
57
+ it "should ignore malformed lines" do
58
+ hostsfile = StringIO.new <<-HOSTS
59
+ # asdfasdfasdf comment
60
+ 19023912u0194h odd_line
61
+ 12.21.4.1 servername1
62
+
63
+ 10.257.2.1 invalid_ip
64
+ 12.21.4.2 servername2
65
+ myhost.de
66
+ HOSTS
67
+ pool = ServerPool.from_hostfile(hostsfile)
68
+ pool.servers.map {|server| server.hostname }.should == ['12.21.4.1', '12.21.4.2', 'myhost.de']
69
+ end
70
+ end
71
+ end
72
+ before :each do
73
+ @pool = ServerPool.new
74
+ end
75
+
76
+ it "should only take Server objects" do
77
+ lambda { @pool << Object.new }.should raise_error TypeError
78
+ lambda { @pool.add Object.new }.should raise_error TypeError
79
+ end
80
+
81
+ it "should delegate methods to the server objects" do
82
+ server = mock('server')
83
+ server.should_receive(:keys)
84
+ server.should_receive(:fetch_keys)
85
+ server.should_receive(:upload_keys)
86
+ @pool.push server
87
+
88
+ @pool.keys
89
+ @pool.fetch_keys
90
+ @pool.upload_keys
91
+ end
92
+
93
+ context "delegating methods to the servers" do
94
+ it "should aggregated values as an hash" do
95
+ server1 = stub
96
+ server1_keys = ['first key', 'second key']
97
+ server1.stub!(:keys).and_return(server1_keys)
98
+
99
+ server2 = stub
100
+ server2_keys = ['third key', 'forth key']
101
+ server2.stub!(:keys).and_return(server2_keys)
102
+
103
+ @pool.push server1
104
+ @pool.push server2
105
+
106
+ @pool.keys.should == server1_keys + server2_keys
107
+ end
108
+ end
109
+
110
+ context "dumping the servers keys to files" do
111
+ it "should write a keyfile for every key" do
112
+ FileUtils.rm_r 'keys' # cleanup old keyfiles
113
+ @pool.stub!(:keys).and_return(key_fixtures.values.join("\n"))
114
+ @pool.dump_keyfiles
115
+ Dir['keys/*'].should have(key_fixtures.size).keys
116
+ end
117
+ end
118
+
119
+ context "adding a key to the serverpool" do
120
+ it "should add the key to every server in the pool" do
121
+ new_key = 'a key'
122
+ keys = mock('keys')
123
+ server1 = mock('server1', :keys => keys)
124
+ server2 = mock('server2', :keys => keys)
125
+ @pool.push server1
126
+ @pool.push server2
127
+
128
+ keys.should_receive(:<<).twice.with(new_key)
129
+ @pool.keys << new_key
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,178 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'ruby-debug'
3
+ Debugger.start
4
+
5
+ module Zool
6
+ describe Server do
7
+ before :each do
8
+ @server = Server.new("somehost")
9
+ end
10
+
11
+ it "should have a getter for the user attribute" do
12
+ @server.user.should == @server.send(:instance_variable_get, :@options)[:user]
13
+ end
14
+
15
+ context "fetching a servers keys" do
16
+ it "should use a password if provided" do
17
+ server = Server.new('somehost', :user => 'root', :password => 'a password')
18
+ Net::SCP.should_receive(:download!).with(anything, anything, anything, anything, :ssh => {:password => 'a password'})
19
+ server.keys
20
+ end
21
+
22
+ it "should use the default server location" do
23
+ Server.new('somehost').keyfile_location.should == '~/.ssh/authorized_keys'
24
+ Server.new('somehost', :user => 'peter').keyfile_location.should == '~/.ssh/authorized_keys'
25
+ end
26
+
27
+ context "with a custom keyfile location set" do
28
+ it "should use the custom keyfile location" do
29
+ @server = Server.new('somehost')
30
+ custom_keyfile_location = '/some/custom/path'
31
+ @server.keyfile_location = custom_keyfile_location
32
+ Net::SCP.should_receive(:download!).with(anything, anything, custom_keyfile_location, anything, anything)
33
+
34
+ @server.fetch_keys
35
+ end
36
+ end
37
+
38
+ context "when the keyfile is not presetn" do
39
+ it "should return an empty list of keys" do
40
+ Net::SCP.should_receive(:download!).and_raise(Net::SCP::Error)
41
+ @server.keys.should be_empty
42
+ end
43
+ end
44
+
45
+ it "should load the authorized_keys file from the server" do
46
+ @server = Server.new('somehost')
47
+ Net::SCP.should_receive(:download!).with(anything, anything, @server.keyfile_location, anything, anything)
48
+
49
+ @server.fetch_keys
50
+ end
51
+
52
+ it "should make the keys available through a keys array" do
53
+ @server.stub!(:load_remote_file).and_return("#{key_fixtures[:pascal]}\n#{key_fixtures[:bob]}")
54
+ @server.keys.should == [key_fixtures[:pascal], key_fixtures[:bob]]
55
+ end
56
+
57
+ it "should remove unneccasarry whitespace from the output" do
58
+ @server = Server.new('somehost')
59
+ @server.stub!(:load_remote_file).and_return(" #{key_fixtures[:pascal]} ")
60
+ @server.keys.should == [key_fixtures[:pascal]]
61
+ @server.keys.should have(1).key
62
+ end
63
+
64
+ it "should remove blank lines" do
65
+ @server = Server.new('somehost')
66
+ @server.stub!(:load_remote_file).and_return(" #{key_fixtures[:pascal]} \n ")
67
+ @server.keys.should == [key_fixtures[:pascal]]
68
+ @server.keys.should have(1).key
69
+ end
70
+
71
+ it "should remove duplicate keys from the list" do
72
+ @server = Server.new('somehost')
73
+ @server.stub!(:load_remote_file).and_return("#{key_fixtures[:pascal]}\n#{key_fixtures[:pascal]}")
74
+ @server.keys.should have(1).key
75
+ @server.keys.should == [key_fixtures[:pascal]]
76
+ end
77
+
78
+ context "requesting the keys several times" do
79
+ it "should not fetch the keys again" do
80
+ @server = Server.new('somehost')
81
+ @server.should_receive(:load_remote_file).once.and_return("n#{key_fixtures[:pascal]}")
82
+ @server.keys
83
+ @server.keys
84
+ end
85
+ end
86
+
87
+ context "fetching the keys again after they have already been fetched" do
88
+ it "should return the new list of keys" do
89
+ @server = Server.new('somehost')
90
+ @server.should_receive(:load_remote_file).ordered.and_return("#{key_fixtures[:pascal]}")
91
+ @server.should_receive(:load_remote_file).ordered.and_return("#{key_fixtures[:bob]}")
92
+ @server.keys
93
+ @server.keys.should == [key_fixtures[:pascal]]
94
+ @server.fetch_keys
95
+ @server.keys.should == [key_fixtures[:bob]]
96
+ end
97
+ end
98
+ end
99
+
100
+ context "dumping the keys to files" do
101
+ it "should write a keyfile for every key" do
102
+ FileUtils.rm_r 'keys' # cleanup old keyfiles
103
+ @server.stub!(:load_remote_file).and_return(key_fixtures.values.join("\n"))
104
+ @server.dump_keyfiles
105
+ Dir['keys/*'].should have(key_fixtures.size).keys
106
+ end
107
+ end
108
+
109
+ context "setting a servers keys" do
110
+ before :each do
111
+ @server = Server.new('somehost')
112
+ @server.stub!(:load_remote_file).and_return("")
113
+ end
114
+
115
+ context "by replacing all of them" do
116
+ it "should take a array of keys" do
117
+ @server.keys = [key_fixtures[:pascal], key_fixtures[:bob]]
118
+ @server.keys.should == [key_fixtures[:pascal], key_fixtures[:bob]]
119
+ end
120
+
121
+ it "should not fetch the servers existing keys" do
122
+ @server.should_not_receive(:load_remote_file)
123
+ @server.keys = [key_fixtures[:pascal]]
124
+ end
125
+ end
126
+
127
+ context "by adding keys to the existing keys" do
128
+ it "should fetch the servers current keys if not done before" do
129
+ @server.should_receive(:load_remote_file)
130
+ @server.keys << "asdf"
131
+ end
132
+ end
133
+
134
+ context "and uploading them" do
135
+ before :each do
136
+ @server.keys = [key_fixtures[:pascal], key_fixtures[:bob]]
137
+ @backup_keys = 'original keys'
138
+ @server.stub!(:load_remote_file).and_return(@backup_keys)
139
+
140
+ Net::SCP.stub(:download!)
141
+ end
142
+
143
+ it "should write a authorized_keys file with all the keys" do
144
+ channel_stub = stub('ssh channel stub', :null_object => true)
145
+ Net::SSH.stub!(:start).and_return(channel_stub)
146
+ Net::SCP.stub!(:upload!) # the backup
147
+ channel_stub.stub(:scp).and_return(channel_stub)
148
+
149
+ channel_stub.should_receive(:upload!).with(stringbuffer_with(@server.keys.join("\n")), @server.keyfile_location).ordered
150
+ @server.upload_keys
151
+ end
152
+
153
+ it "should backup the existing authorized_keys file" do
154
+ @server.should_receive(:load_remote_file).and_return(@backup_keys)
155
+ Net::SSH.stub!(:start).and_return(stub('ssh channel stub', :null_object => true))
156
+
157
+ Net::SCP.should_receive(:upload!).with('somehost', 'root', stringbuffer_with(@backup_keys), /authorized_keys_\d+$/, anything)
158
+ @server.upload_keys
159
+ end
160
+
161
+ context "with an exception during backup of the original keys" do
162
+ before :each do
163
+ Net::SCP.stub!(:download!).and_raise(Exception)
164
+ end
165
+
166
+ it "should not upload the new keys" do
167
+ Net::SCP.should_not_receive(:upload!)
168
+ lambda { @server.upload_keys }.should raise_error
169
+ end
170
+ end
171
+
172
+ context "providing a fallback if something goes wrong" do
173
+
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
metadata ADDED
@@ -0,0 +1,266 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zool
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Pascal Friederich
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-06 00:00:00 +01:00
13
+ default_executable: zool
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: net-scp
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.0.2
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: net-ssh
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.0.17
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: net-scp
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 1.0.2
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: net-ssh
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 2.0.17
54
+ version:
55
+ - !ruby/object:Gem::Dependency
56
+ name: treetop
57
+ type: :runtime
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 1.4.3
64
+ version:
65
+ - !ruby/object:Gem::Dependency
66
+ name: polyglot
67
+ type: :runtime
68
+ version_requirement:
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 0.2.9
74
+ version:
75
+ - !ruby/object:Gem::Dependency
76
+ name: highline
77
+ type: :runtime
78
+ version_requirement:
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: 1.5.1
84
+ version:
85
+ - !ruby/object:Gem::Dependency
86
+ name: builder
87
+ type: :development
88
+ version_requirement:
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: 2.1.2
94
+ version:
95
+ - !ruby/object:Gem::Dependency
96
+ name: columnize
97
+ type: :development
98
+ version_requirement:
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 0.3.1
104
+ version:
105
+ - !ruby/object:Gem::Dependency
106
+ name: cucumber
107
+ type: :development
108
+ version_requirement:
109
+ version_requirements: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: 0.5.3
114
+ version:
115
+ - !ruby/object:Gem::Dependency
116
+ name: diff-lcs
117
+ type: :development
118
+ version_requirement:
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: 1.1.2
124
+ version:
125
+ - !ruby/object:Gem::Dependency
126
+ name: fakefs
127
+ type: :development
128
+ version_requirement:
129
+ version_requirements: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: 0.2.1
134
+ version:
135
+ - !ruby/object:Gem::Dependency
136
+ name: json_pure
137
+ type: :development
138
+ version_requirement:
139
+ version_requirements: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: 1.2.0
144
+ version:
145
+ - !ruby/object:Gem::Dependency
146
+ name: linecache
147
+ type: :development
148
+ version_requirement:
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: "0.43"
154
+ version:
155
+ - !ruby/object:Gem::Dependency
156
+ name: rake
157
+ type: :development
158
+ version_requirement:
159
+ version_requirements: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: 0.8.7
164
+ version:
165
+ - !ruby/object:Gem::Dependency
166
+ name: rspec
167
+ type: :development
168
+ version_requirement:
169
+ version_requirements: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: 1.2.9
174
+ version:
175
+ - !ruby/object:Gem::Dependency
176
+ name: ruby-debug
177
+ type: :development
178
+ version_requirement:
179
+ version_requirements: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - ">="
182
+ - !ruby/object:Gem::Version
183
+ version: 0.10.3
184
+ version:
185
+ - !ruby/object:Gem::Dependency
186
+ name: ruby-debug-base
187
+ type: :development
188
+ version_requirement:
189
+ version_requirements: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ version: 0.10.3
194
+ version:
195
+ - !ruby/object:Gem::Dependency
196
+ name: term-ansicolor
197
+ type: :development
198
+ version_requirement:
199
+ version_requirements: !ruby/object:Gem::Requirement
200
+ requirements:
201
+ - - ">="
202
+ - !ruby/object:Gem::Version
203
+ version: 1.0.4
204
+ version:
205
+ description: Zool allows you to manage authorized_keys files on servers. It comes with a command-line client 'zool'. The configuration can be done in a pyconfig/gitosis like configuration file. See README.md for further details
206
+ email: paukul@gmail.com
207
+ executables:
208
+ - zool
209
+ extensions: []
210
+
211
+ extra_rdoc_files:
212
+ - LICENSE
213
+ - README.md
214
+ files:
215
+ - LICENSE
216
+ - Rakefile
217
+ - Readme.md
218
+ - bin/zool
219
+ - lib/py_config_parser/py_config_parser.tt
220
+ - lib/zool.rb
221
+ - lib/zool/configuration.rb
222
+ - lib/zool/key_file_writer.rb
223
+ - lib/zool/server.rb
224
+ - lib/zool/server_pool.rb
225
+ - README.md
226
+ has_rdoc: true
227
+ homepage: http://github.com/paukul/zool
228
+ licenses: []
229
+
230
+ post_install_message:
231
+ rdoc_options:
232
+ - --charset=UTF-8
233
+ require_paths:
234
+ - lib
235
+ required_ruby_version: !ruby/object:Gem::Requirement
236
+ requirements:
237
+ - - ">="
238
+ - !ruby/object:Gem::Version
239
+ version: "0"
240
+ version:
241
+ required_rubygems_version: !ruby/object:Gem::Requirement
242
+ requirements:
243
+ - - ">="
244
+ - !ruby/object:Gem::Version
245
+ version: "0"
246
+ version:
247
+ requirements: []
248
+
249
+ rubyforge_project:
250
+ rubygems_version: 1.3.5
251
+ signing_key:
252
+ specification_version: 3
253
+ summary: Library and command-line client to manage authorized_keys files
254
+ test_files:
255
+ - spec/py_config_parser_spec.rb
256
+ - spec/spec_helper.rb
257
+ - spec/zool/configuration_spec.rb
258
+ - spec/zool/key_file_writer_spec.rb
259
+ - spec/zool/server_pool_spec.rb
260
+ - spec/zool/server_spec.rb
261
+ - spec/zool.rb
262
+ - features/config_parser.feature
263
+ - features/fetching_ssh_keys.feature
264
+ - features/step_definitions/ssh_keys_steps.rb
265
+ - features/store_ssh_keys.feature
266
+ - features/support/env.rb