fabric 0.2.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/.document +5 -0
- data/.gitignore +26 -0
- data/Gemfile +18 -0
- data/LICENSE +20 -0
- data/README.rdoc +91 -0
- data/Rakefile +8 -0
- data/VERSION +1 -0
- data/bin/fab +7 -0
- data/fabric.gemspec +97 -0
- data/features/support/env.rb +6 -0
- data/lib/extensions/narrator.rb +19 -0
- data/lib/fabric-test.rb +10 -0
- data/lib/fabric.rb +57 -0
- data/lib/fabric/account.rb +36 -0
- data/lib/fabric/capture.rb +21 -0
- data/lib/fabric/grant.rb +8 -0
- data/lib/fabric/group.rb +8 -0
- data/lib/fabric/key.rb +8 -0
- data/lib/fabric/map.rb +102 -0
- data/lib/fabric/role.rb +24 -0
- data/lib/fabric/server.rb +148 -0
- data/lib/fabric/user.rb +33 -0
- data/lib/initialisers/data_mapper.rb +4 -0
- data/spec/classes/account_spec.rb +92 -0
- data/spec/classes/capture_spec.rb +28 -0
- data/spec/classes/map_spec.rb +212 -0
- data/spec/classes/role_spec.rb +4 -0
- data/spec/classes/server_spec.rb +104 -0
- data/spec/classes/user_spec.rb +34 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +27 -0
- data/tasks/features.rake +15 -0
- data/tasks/jeweler.rake +14 -0
- data/tasks/rdoc.rake +9 -0
- data/tasks/spec.rake +15 -0
- data/tasks/test.rake +6 -0
- data/test.yml.example +5 -0
- metadata +116 -0
@@ -0,0 +1,212 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
|
3
|
+
describe Map do
|
4
|
+
before(:each) do
|
5
|
+
Fabric.options[:map_root] = File.join(File.dirname(__FILE__), '..')
|
6
|
+
@map = Map.create
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "when created" do
|
10
|
+
it "should be valid when created" do
|
11
|
+
@map.should be_a Map
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should have a blank collection attribute for roles" do
|
15
|
+
@map.roles.should == []
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should have a blank collection attribute for users" do
|
19
|
+
@map.users.should == []
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should default the key repository to 'keys'" do
|
23
|
+
@map.key_repository.should == 'keys'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "when read" do
|
28
|
+
it "should return true" do
|
29
|
+
@map.read!.should be_true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "'draw' called with a block" do
|
34
|
+
it "should return the map" do
|
35
|
+
|
36
|
+
map = Map.draw do |map|
|
37
|
+
1
|
38
|
+
end
|
39
|
+
|
40
|
+
map.should be_a Map
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "when passed a non-existent directory as key repository" do
|
45
|
+
it "should raise a LoadError" do
|
46
|
+
lambda { @map.key_repository('blah') }.should raise_error LoadError
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "with a mock key repository" do
|
51
|
+
it "should update the key repository without raising an error" do
|
52
|
+
lambda { @map.key_repository('support/mock_key_repo') }.should_not raise_error LoadError
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should know the full path of the repository" do
|
56
|
+
@map.key_repository 'support/mock_key_repo'
|
57
|
+
@map.expanded_key_repository_path.should == File.join(File.dirname(__FILE__), '..','support', 'mock_key_repo')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "when given a role" do
|
62
|
+
it "should add that role to the role list" do
|
63
|
+
@map.role 'test'
|
64
|
+
@map.roles.first(:name => 'test').should be_a Role
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "with host(s)" do
|
68
|
+
it "should add a server object when passed one host" do
|
69
|
+
@map.role 'test', '127.0.0.1'
|
70
|
+
@map.roles.first(:name =>'test').servers.first.should be_a Server
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should add multiple server objects when passed multiple hosts" do
|
74
|
+
hosts = []
|
75
|
+
(rand(4) + 1).times { hosts << '127.0.0.1' }
|
76
|
+
|
77
|
+
@map.role 'test', *hosts
|
78
|
+
|
79
|
+
servers = @map.roles.first(:name => 'test').servers
|
80
|
+
servers.count.should == hosts.count
|
81
|
+
servers.each do |server|
|
82
|
+
server.should be_a Server
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "with user(s)" do
|
89
|
+
it "should add user to the user list" do
|
90
|
+
@map.user 'test'
|
91
|
+
@map.users.first(:name => 'test').should be_a User
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "with a user with an ssh key in the mock key repository" do
|
95
|
+
before(:each) do
|
96
|
+
@map.key_repository 'support/mock_key_repo'
|
97
|
+
@key_file = File.join(@map.expanded_key_repository_path, 'fabric_test_user.pub')
|
98
|
+
|
99
|
+
File.open(@key_file, 'w+') do |f|
|
100
|
+
f.write('test key')
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
after(:each) do
|
105
|
+
File.delete(@key_file)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should add the user's ssh key from the key repository" do
|
109
|
+
@map.user 'fabric_test_user'
|
110
|
+
@map.users.first(:name => 'fabric_test_user').keys.first.public_key.should == 'test key'
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should add multiple users to the user list" do
|
115
|
+
users = []
|
116
|
+
(rand(4) + 1).times { |i| users << "user_#{i}" }
|
117
|
+
|
118
|
+
@map.user *users
|
119
|
+
|
120
|
+
@map.users.count.should == users.count
|
121
|
+
@map.users.all.each do |user|
|
122
|
+
user.should be_a User
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe "when given a grant" do
|
128
|
+
before(:each) do
|
129
|
+
@map.role 'test', '127.0.0.1'
|
130
|
+
@map.user 'test_user'
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should add the map's users to the role" do
|
134
|
+
@map.roles.first(:name => 'test').users.count.should == 0
|
135
|
+
@map.grant 'test_user', 'test'
|
136
|
+
@map.roles.first(:name => 'test').users.count.should == 1
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "for multiple users" do
|
140
|
+
before(:each) do
|
141
|
+
@map.user 'another test'
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should add multiple users to the role" do
|
145
|
+
@map.grant ['test_user', 'another test'], 'test'
|
146
|
+
@map.roles.first(:name => 'test').users.count.should == 2
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should add all the users to the role when passed :all" do
|
150
|
+
@map.grant :all, 'test'
|
151
|
+
@map.roles.first(:name => 'test').users.count.should == 2
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "for all roles" do
|
156
|
+
before(:each) do
|
157
|
+
@map.role 'test again', '127.0.0.1'
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should add the user to multiple roles" do
|
161
|
+
@map.grant 'test_user', ['test', 'test again']
|
162
|
+
@map.roles.each do |role|
|
163
|
+
role.users.count.should == 1
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should add the user to all the roles when passed :all" do
|
168
|
+
@map.grant 'test_user', :all
|
169
|
+
@map.roles.each do |role|
|
170
|
+
role.users.count.should == 1
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should raise a Object Not Found if an invalid user identifier is passed" do
|
176
|
+
lambda { @map.grant 'fake_user', 'test' }.should raise_error(DataMapper::ObjectNotFoundError)
|
177
|
+
end
|
178
|
+
|
179
|
+
it "should raise a Object Not Found if an invalid role identifier is passed" do
|
180
|
+
lambda { @map.grant 'user', 'fake_test' }.should raise_error(DataMapper::ObjectNotFoundError)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe "with a namespace" do
|
185
|
+
it "should be an instance of Map when created" do
|
186
|
+
namespace = @map.namespace
|
187
|
+
namespace.should be_a Map
|
188
|
+
end
|
189
|
+
|
190
|
+
it "should know its parent when created" do
|
191
|
+
namespace = @map.namespace
|
192
|
+
namespace.parent.should == @map
|
193
|
+
end
|
194
|
+
|
195
|
+
describe "with users" do
|
196
|
+
before(:each) do
|
197
|
+
@map.users.create(:name => 'test')
|
198
|
+
@namespace = @map.namespace
|
199
|
+
end
|
200
|
+
|
201
|
+
it "should inherit its parents users" do
|
202
|
+
@namespace.users.should == @map.users
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should add users without adding them to the parent" do
|
206
|
+
@namespace.users.create(:name => 'test two')
|
207
|
+
@namespace.users.length.should == 2
|
208
|
+
@map.users.length.should == 1
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
|
3
|
+
describe Server do
|
4
|
+
before(:each) do
|
5
|
+
@server = Server.create(:host => TEST_SERVER)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should be valid when created" do
|
9
|
+
@server.should be_a Server
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "'create_group' method" do
|
13
|
+
before(:each) do
|
14
|
+
@group = Group.new(:name => TEST_GROUP)
|
15
|
+
end
|
16
|
+
|
17
|
+
after(:each) do
|
18
|
+
@server.execute_command("sudo /usr/sbin/groupdel #{TEST_GROUP}")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should create a group" do
|
22
|
+
@server.create_group(@group)
|
23
|
+
|
24
|
+
@server.execute_command("cat /etc/group | grep #{TEST_GROUP}") # Not the strongest test - grep not ideal for this
|
25
|
+
@server.output.should_not == ''
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "running plesk" do
|
30
|
+
before(:each) do
|
31
|
+
# mock a plesk server
|
32
|
+
@server.execute_command("sudo mkdir /usr/local/psa")
|
33
|
+
end
|
34
|
+
|
35
|
+
after(:each) do
|
36
|
+
# how can we tear this down safely?
|
37
|
+
@server.execute_command("sudo rm -rf /usr/local/psa")
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should know it is a plesk server" do
|
41
|
+
@server.is_running_plesk?.should be_true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "not running plesk" do
|
46
|
+
it "should know it is not a plesk server" do
|
47
|
+
@server.is_running_plesk?.should be_false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "with accounts" do
|
52
|
+
before(:each) do
|
53
|
+
@role = Role.create(:name => 'test')
|
54
|
+
@role.servers << @server
|
55
|
+
@role.users.create(:name => TEST_USER)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should have an account object for each of its role's users" do
|
59
|
+
@server.accounts.first.user.should == @role.users.first
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should check if an account exists" do
|
63
|
+
# Runner has to exist for the login to have worked
|
64
|
+
runner = User.new(:name => TEST_RUNNER)
|
65
|
+
|
66
|
+
@server.account_exists_for?(runner).should be_true
|
67
|
+
@server.account_exists_for?(@role.users.first).should be_false
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should identify account that require adding" do
|
71
|
+
@server.accounts_to_add.should == Array(@role.users.first)
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "with an account that should be deleted" do
|
77
|
+
before(:each) do
|
78
|
+
@role = Role.create(:name => 'test')
|
79
|
+
@role.servers << @server
|
80
|
+
|
81
|
+
# We'll add the test user too; just to check it doesn't get deleted
|
82
|
+
@role.users.create(:name => TEST_USER)
|
83
|
+
@server.accounts.first.add_user
|
84
|
+
|
85
|
+
@doomed_user = User.create(:name => TEST_DOOMED_USER)
|
86
|
+
@server.execute_command("sudo /usr/sbin/useradd -m #{@doomed_user.name}")
|
87
|
+
end
|
88
|
+
|
89
|
+
after(:each) do
|
90
|
+
@server.execute_command("sudo /usr/sbin/userdel --remove #{TEST_USER}")
|
91
|
+
@server.execute_command("sudo /usr/sbin/userdel --remove #{TEST_DOOMED_USER}")
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should identify accounts that require removing" do
|
95
|
+
@server.accounts_to_remove.should == Array(@doomed_user)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should delete the account" do
|
99
|
+
@server.account_exists_for?(@doomed_user).should be_true
|
100
|
+
@server.delete_account_for(@doomed_user)
|
101
|
+
@server.account_exists_for?(@doomed_user).should be_false
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
|
3
|
+
describe User do
|
4
|
+
before :each do
|
5
|
+
@user = User.new(:name => 'bob')
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should merge multiple keys into the authorized_keys file" do
|
9
|
+
user = User.create
|
10
|
+
key1 = 'this is'
|
11
|
+
key2 = ' a key.'
|
12
|
+
|
13
|
+
user.keys.create(:public_key => key1)
|
14
|
+
user.keys.create(:public_key => key2)
|
15
|
+
|
16
|
+
user.authorized_keys_file.should == key1 + key2
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should raise an error if the authorized_keys_file is blank" do
|
20
|
+
lambda { @user.authorized_keys_file }.should raise_error
|
21
|
+
end
|
22
|
+
|
23
|
+
it "can generate the user's home directory path" do
|
24
|
+
@user.home_directory_path.should == '/home/bob/'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "can generate the path to the user's .ssh directory" do
|
28
|
+
@user.ssh_config_directory_path.should == '/home/bob/.ssh/'
|
29
|
+
end
|
30
|
+
|
31
|
+
it "can generate the path to the user's authorized_keys file" do
|
32
|
+
@user.authorized_keys_file_path.should == '/home/bob/.ssh/authorized_keys'
|
33
|
+
end
|
34
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'vendor','gems','environment')
|
2
|
+
|
3
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
4
|
+
|
5
|
+
require 'fabric'
|
6
|
+
require 'fabric-test'
|
7
|
+
require 'database_cleaner'
|
8
|
+
|
9
|
+
TEST_SERVER = Fabric::Test.settings['server']
|
10
|
+
TEST_RUNNER = Fabric::Test.settings['runner']
|
11
|
+
TEST_USER = Fabric::Test.settings['user']
|
12
|
+
TEST_DOOMED_USER = Fabric::Test.settings['doomed_user']
|
13
|
+
TEST_GROUP = Fabric::Test.settings['group']
|
14
|
+
|
15
|
+
Spec::Runner.configure do |config|
|
16
|
+
config.before(:suite) do
|
17
|
+
DatabaseCleaner.strategy = :transaction
|
18
|
+
end
|
19
|
+
|
20
|
+
config.before(:each) do
|
21
|
+
DatabaseCleaner.start
|
22
|
+
end
|
23
|
+
|
24
|
+
config.after(:each) do
|
25
|
+
DatabaseCleaner.clean
|
26
|
+
end
|
27
|
+
end
|
data/tasks/features.rake
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
namespace :test do
|
2
|
+
begin
|
3
|
+
require 'cucumber/rake/task'
|
4
|
+
Cucumber::Rake::Task.new(:features)
|
5
|
+
|
6
|
+
task :features => :check_dependencies
|
7
|
+
rescue LoadError
|
8
|
+
task :features do
|
9
|
+
abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
desc 'synonym for test:features'
|
15
|
+
task 'features' => 'test:features'
|
data/tasks/jeweler.rake
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gem|
|
4
|
+
gem.name = "fabric"
|
5
|
+
gem.summary = %Q{Fabric is a small ruby app to perform tasks on servers via SSH.}
|
6
|
+
gem.description = %Q{Fabric is a small ruby app to perform tasks on servers via SSH. Built around net/ssh and taking heavy inspiration from Capistrano, it allows you to create policies for server management and perform sysadmin tasks without the need for a process/daemon/dependency or even ruby being installed on the remote server.}
|
7
|
+
gem.email = "sam.phillips@setfiremedia.com"
|
8
|
+
gem.homepage = "http://github.com/setfire/fabric"
|
9
|
+
gem.authors = ["Sam Phillips"]
|
10
|
+
gem.add_development_dependency "cucumber", ">= 0.3.104"
|
11
|
+
end
|
12
|
+
rescue LoadError
|
13
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
14
|
+
end
|
data/tasks/rdoc.rake
ADDED
data/tasks/spec.rake
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
|
4
|
+
namespace 'test' do
|
5
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
6
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
7
|
+
t.spec_opts = ['--options', "\"spec/spec.opts\""]
|
8
|
+
t.ruby_opts = ['-Ilib']
|
9
|
+
t.rcov = false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'synonym for test:spec'
|
14
|
+
task 'spec' => 'test:spec'
|
15
|
+
|