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,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