capistrano-provisioning 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/LICENSE +20 -0
- data/README.mdown +229 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/capistrano-provisioning.gemspec +70 -0
- data/lib/capistrano-provisioning/cluster.rb +79 -0
- data/lib/capistrano-provisioning/namespaces.rb +152 -0
- data/lib/capistrano-provisioning/recipes.rb +40 -0
- data/lib/capistrano-provisioning/user.rb +114 -0
- data/lib/capistrano-provisioning.rb +3 -0
- data/pkg/capistrano-provisioning-0.0.0.gem +0 -0
- data/pkg/capistrano-provisioning-0.0.1.gem +0 -0
- data/pkg/capistrano-provisioning-0.0.3.gem +0 -0
- data/spec/cluster_spec.rb +82 -0
- data/spec/namespaces_spec.rb +227 -0
- data/spec/recipes_spec.rb +17 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/user_spec.rb +47 -0
- metadata +113 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Sam Phillips
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.mdown
ADDED
@@ -0,0 +1,229 @@
|
|
1
|
+
# Capistrano Provisioning
|
2
|
+
|
3
|
+
Capistrano Provisioning is an extension to Capistrano that allows you to define clusters of servers and run provisioning tasks on them, such as installing users. It is a replacement for the fabric gem (http://rubygems.org/gems/fabric).
|
4
|
+
|
5
|
+
## A word of caution
|
6
|
+
|
7
|
+
This software is in alpha - we're releasing early so we can start using this gem in anger.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
### With bundler
|
12
|
+
|
13
|
+
Add the following to your Gemfile:
|
14
|
+
|
15
|
+
gem 'capistrano-provisioning'
|
16
|
+
|
17
|
+
If your bundler environment isn't initialised yet, you'll need to add something like this to your deploy.rb/Capfile:
|
18
|
+
|
19
|
+
require 'rubygems'
|
20
|
+
require 'bundler'
|
21
|
+
|
22
|
+
Bundler.setup
|
23
|
+
require 'capistrano-provisioning/recipes'
|
24
|
+
|
25
|
+
### Without bundler
|
26
|
+
|
27
|
+
gem install capistrano-provisioning
|
28
|
+
|
29
|
+
Either within your Capfile, or your deploy.rb, add:
|
30
|
+
|
31
|
+
require 'capistrano-provisioning/recipes'
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
Once you have required the gem, the `cluster` command is available to you within capistrano recipes.
|
36
|
+
|
37
|
+
The simplest usage of this would be something like:
|
38
|
+
|
39
|
+
cluster :web, 'web1.example.com'
|
40
|
+
|
41
|
+
In fact, this is shorthand for the following:
|
42
|
+
|
43
|
+
cluster :web do
|
44
|
+
servers 'web1.example.com'
|
45
|
+
end
|
46
|
+
|
47
|
+
Both of these would add a cap task called 'web'; calling this will mean any task specified later in the chain will run on the 'web' cluster. Eg:
|
48
|
+
|
49
|
+
cap web invoke COMMAND='uptime'
|
50
|
+
|
51
|
+
Which will run the 'uptime' command on 'web1.example.com'.
|
52
|
+
|
53
|
+
You can specify multiple remote hosts like so:
|
54
|
+
|
55
|
+
cluster :web, 'web1.example.com', 'web2.example.com'
|
56
|
+
|
57
|
+
So... this far it's quite similar to cap's own 'role' syntax. After this, however, life gets interesting...
|
58
|
+
|
59
|
+
### Chaining
|
60
|
+
|
61
|
+
If you want to run a task on multiple clusters, simply chain them as so:
|
62
|
+
|
63
|
+
cap web db invoke COMMAND='uptime'
|
64
|
+
|
65
|
+
... Assuming that you've defined a web and a db cluster!
|
66
|
+
|
67
|
+
This will also work with namespaced clusters.
|
68
|
+
|
69
|
+
### Installing users
|
70
|
+
|
71
|
+
Specify the users that belong on a cluster as so:
|
72
|
+
|
73
|
+
cluster :web do
|
74
|
+
servers 'web1.example.com'
|
75
|
+
users 'bob', 'joe'
|
76
|
+
end
|
77
|
+
|
78
|
+
And put these users' public ssh keys in config/keys directory as so `config/keys/bob.pub` and `config/keys/joe.pub`
|
79
|
+
|
80
|
+
Running:
|
81
|
+
|
82
|
+
cap web install_users
|
83
|
+
|
84
|
+
... will now create these users on the servers if they don't exist, and add the keys to their authorized_keys file.
|
85
|
+
|
86
|
+
You can specify groups as so:
|
87
|
+
|
88
|
+
cluster :web do
|
89
|
+
servers 'web1.example.com'
|
90
|
+
users 'bob', 'joe', :groups => ['some_group']
|
91
|
+
users 'rupert'
|
92
|
+
end
|
93
|
+
|
94
|
+
This will add Bob and Joe to some_group (:groups is an array, so add as many as you like), but won't add Rupert.
|
95
|
+
|
96
|
+
### Bootstrapping
|
97
|
+
|
98
|
+
To specify a block of code that is to be run to 'bootstrap' a server, do this:
|
99
|
+
|
100
|
+
cluster :web do
|
101
|
+
servers 'web1.example.com'
|
102
|
+
bootstrap do
|
103
|
+
run 'some_command'
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
Run the following:
|
108
|
+
|
109
|
+
cap web run_bootstrap
|
110
|
+
|
111
|
+
And `some_command` will be run on the servers.
|
112
|
+
|
113
|
+
### Namespaces and default users
|
114
|
+
|
115
|
+
Namespaces work as you would expect:
|
116
|
+
|
117
|
+
namespace :system1 do
|
118
|
+
cluster :web do
|
119
|
+
# ...
|
120
|
+
end
|
121
|
+
|
122
|
+
cluster :db do
|
123
|
+
# ...
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
This block will create the following tasks:
|
128
|
+
|
129
|
+
cap system1:web
|
130
|
+
cap system1:db
|
131
|
+
|
132
|
+
As well as
|
133
|
+
|
134
|
+
cap system1:all
|
135
|
+
|
136
|
+
Which will load all of the clusters defined in `system1`.
|
137
|
+
|
138
|
+
It is also possible to define a default group of users for a namespace:
|
139
|
+
|
140
|
+
namespace :system1 do
|
141
|
+
default_users 'bob', 'joe', :groups => ['some_group']
|
142
|
+
default_users 'rupert'
|
143
|
+
cluster :web do
|
144
|
+
# ...
|
145
|
+
end
|
146
|
+
|
147
|
+
cluster :db do
|
148
|
+
# ...
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
Which would mean that running:
|
153
|
+
|
154
|
+
cap system1:web install_users
|
155
|
+
|
156
|
+
... would add Bob, Joe and Rupert to the web cluster, with the appropriate groups.
|
157
|
+
|
158
|
+
#### Inheriting default users
|
159
|
+
|
160
|
+
By default, namespaces do _not_ inherit default users from the namespace above. If you want this inheritance, it's easy:
|
161
|
+
|
162
|
+
namespace :nested_namespace do
|
163
|
+
inherit_default_users
|
164
|
+
end
|
165
|
+
|
166
|
+
If you want those users to have an additional group within this namespace only, use the following:
|
167
|
+
|
168
|
+
namespace :nested_namespace do
|
169
|
+
inherit_default_users :additional_groups => 'additional_group'
|
170
|
+
end
|
171
|
+
|
172
|
+
You can also pass an array of additional groups:
|
173
|
+
|
174
|
+
namespace :nested_namespace do
|
175
|
+
inherit_default_users :additional_groups => ['additional_group_1', 'additional_group_2']
|
176
|
+
end
|
177
|
+
|
178
|
+
#### Inheriting specific default users
|
179
|
+
|
180
|
+
These groups of default users can also be named, so you can atomically specify inheritance:
|
181
|
+
|
182
|
+
namespace :system1 do
|
183
|
+
default_users 'bob', 'joe', :groups => ['some_group']
|
184
|
+
default_users 'rupert'
|
185
|
+
cluster :web do
|
186
|
+
# ...
|
187
|
+
end
|
188
|
+
|
189
|
+
cluster :db do
|
190
|
+
# ...
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
Clusters can also inherit these named groups:
|
195
|
+
|
196
|
+
namespace :system1 do
|
197
|
+
default_users :admins, 'bob', 'joe'
|
198
|
+
cluster :web do
|
199
|
+
user :admins
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
These can be mixed in with normal user names:
|
204
|
+
|
205
|
+
namespace :system1 do
|
206
|
+
default_users :admins, 'bob', 'joe'
|
207
|
+
cluster :web do
|
208
|
+
user :admins, 'sam'
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
## Features we guess we're probably going to need
|
213
|
+
|
214
|
+
* Ability to output business-friendly documentation of how the clusters are set up (and what user access exists)
|
215
|
+
* A way to remove user accounts that don't belong
|
216
|
+
|
217
|
+
## Note on Patches/Pull Requests
|
218
|
+
|
219
|
+
* Fork the project.
|
220
|
+
* Make your feature addition or bug fix.
|
221
|
+
* Add tests for it. This is important so I don't break it in a
|
222
|
+
future version unintentionally.
|
223
|
+
* Commit, do not mess with rakefile, version, or history.
|
224
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
225
|
+
* Send me a pull request. Bonus points for topic branches.
|
226
|
+
|
227
|
+
## Copyright
|
228
|
+
|
229
|
+
Copyright (c) 2010 Sam Phillips. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "capistrano-provisioning"
|
8
|
+
gem.summary = %Q{Capistrano provisioning}
|
9
|
+
gem.description = %Q{Capistrano provisioning}
|
10
|
+
gem.email = "sam@samdanavia.com"
|
11
|
+
gem.homepage = "http://github.com/samdanavia/capistrano-provisioning"
|
12
|
+
gem.authors = ["Sam Phillips"]
|
13
|
+
gem.add_dependency "capistrano", ">= 2.5.18"
|
14
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'spec/rake/spectask'
|
23
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
24
|
+
spec.libs << 'lib' << 'spec'
|
25
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
26
|
+
end
|
27
|
+
|
28
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
29
|
+
spec.libs << 'lib' << 'spec'
|
30
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
31
|
+
spec.rcov = true
|
32
|
+
end
|
33
|
+
|
34
|
+
task :spec => :check_dependencies
|
35
|
+
|
36
|
+
task :default => :spec
|
37
|
+
|
38
|
+
require 'rake/rdoctask'
|
39
|
+
Rake::RDocTask.new do |rdoc|
|
40
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
41
|
+
|
42
|
+
rdoc.rdoc_dir = 'rdoc'
|
43
|
+
rdoc.title = "capistrano-provisioning #{version}"
|
44
|
+
rdoc.rdoc_files.include('README*')
|
45
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
46
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.3
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{capistrano-provisioning}
|
8
|
+
s.version = "0.0.3"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Sam Phillips"]
|
12
|
+
s.date = %q{2010-06-16}
|
13
|
+
s.description = %q{Capistrano provisioning}
|
14
|
+
s.email = %q{sam@samdanavia.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.mdown"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
"LICENSE",
|
22
|
+
"README.mdown",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"capistrano-provisioning.gemspec",
|
26
|
+
"lib/capistrano-provisioning.rb",
|
27
|
+
"lib/capistrano-provisioning/cluster.rb",
|
28
|
+
"lib/capistrano-provisioning/namespaces.rb",
|
29
|
+
"lib/capistrano-provisioning/recipes.rb",
|
30
|
+
"lib/capistrano-provisioning/user.rb",
|
31
|
+
"pkg/capistrano-provisioning-0.0.0.gem",
|
32
|
+
"pkg/capistrano-provisioning-0.0.1.gem",
|
33
|
+
"pkg/capistrano-provisioning-0.0.3.gem",
|
34
|
+
"spec/cluster_spec.rb",
|
35
|
+
"spec/namespaces_spec.rb",
|
36
|
+
"spec/recipes_spec.rb",
|
37
|
+
"spec/spec.opts",
|
38
|
+
"spec/spec_helper.rb",
|
39
|
+
"spec/user_spec.rb"
|
40
|
+
]
|
41
|
+
s.homepage = %q{http://github.com/samdanavia/capistrano-provisioning}
|
42
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
43
|
+
s.require_paths = ["lib"]
|
44
|
+
s.rubygems_version = %q{1.3.6}
|
45
|
+
s.summary = %q{Capistrano provisioning}
|
46
|
+
s.test_files = [
|
47
|
+
"spec/cluster_spec.rb",
|
48
|
+
"spec/namespaces_spec.rb",
|
49
|
+
"spec/recipes_spec.rb",
|
50
|
+
"spec/spec_helper.rb",
|
51
|
+
"spec/user_spec.rb"
|
52
|
+
]
|
53
|
+
|
54
|
+
if s.respond_to? :specification_version then
|
55
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
56
|
+
s.specification_version = 3
|
57
|
+
|
58
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
59
|
+
s.add_runtime_dependency(%q<capistrano>, [">= 2.5.18"])
|
60
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
61
|
+
else
|
62
|
+
s.add_dependency(%q<capistrano>, [">= 2.5.18"])
|
63
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
64
|
+
end
|
65
|
+
else
|
66
|
+
s.add_dependency(%q<capistrano>, [">= 2.5.18"])
|
67
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module CapistranoProvisioning
|
2
|
+
class Cluster
|
3
|
+
attr_accessor :name, :servers, :bootstrap, :config
|
4
|
+
|
5
|
+
def initialize(name, opts = {})
|
6
|
+
self.name = name
|
7
|
+
self.servers = opts[:servers]
|
8
|
+
self.bootstrap = opts[:bootstrap]
|
9
|
+
self.config = opts[:config]
|
10
|
+
|
11
|
+
@users = []
|
12
|
+
|
13
|
+
add_cluster_cap_task
|
14
|
+
end
|
15
|
+
|
16
|
+
def install_users
|
17
|
+
ensure_users
|
18
|
+
|
19
|
+
self.servers.each do |server|
|
20
|
+
self.users.each do |user|
|
21
|
+
user.install(:server => server)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def preview_users
|
27
|
+
ensure_users
|
28
|
+
|
29
|
+
self.servers.each do |server|
|
30
|
+
puts "#{server}: "
|
31
|
+
self.users.each do |user|
|
32
|
+
groups = user.groups.empty? ? '' : "(#{user.groups.join(', ')})"
|
33
|
+
puts "\t#{user.name} #{groups}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def ensure_users
|
39
|
+
if @users.empty? and config.respond_to?(:default_users)
|
40
|
+
@users = config.default_users
|
41
|
+
end
|
42
|
+
|
43
|
+
abort "No users found" unless @users
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_users(users, opts = {})
|
47
|
+
@users += users.collect do |user|
|
48
|
+
if user.is_a? CapistranoProvisioning::User
|
49
|
+
user.config = self.config
|
50
|
+
user
|
51
|
+
else
|
52
|
+
opts.merge!(:name => user, :config => self.config)
|
53
|
+
CapistranoProvisioning::User.new(opts) # This dependency should be injected, really.
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def unique_name
|
59
|
+
self.config.send(:unique_name) + ":" + self.name.to_s
|
60
|
+
end
|
61
|
+
|
62
|
+
def users
|
63
|
+
ensure_users
|
64
|
+
@users
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
69
|
+
def add_cluster_cap_task
|
70
|
+
cluster = self
|
71
|
+
|
72
|
+
self.config.task(name.to_sym, :desc => "Set the current cluster to '#{name}'") do
|
73
|
+
logger.info "Setting servers to #{cluster.servers.join(', ')}"
|
74
|
+
current_cluster = fetch(:clusters, [])
|
75
|
+
set(:clusters, current_cluster.push(cluster))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module CapistranoProvisioning
|
2
|
+
module NamespaceExtension
|
3
|
+
|
4
|
+
attr_accessor :clusters
|
5
|
+
def cluster(name, *servers, &block)
|
6
|
+
# A note on initialising. At the moment I can't figure out how to initialize stuff from this module,
|
7
|
+
# hence the untidy defaulting here.
|
8
|
+
self.clusters ||= {}
|
9
|
+
self.create_namespace_all_task
|
10
|
+
|
11
|
+
cluster = CapistranoProvisioning::Cluster.new(name, :config => self)
|
12
|
+
|
13
|
+
self.clusters[cluster.unique_name] = cluster
|
14
|
+
self.clusters[cluster.unique_name].servers = servers
|
15
|
+
|
16
|
+
return unless block_given?
|
17
|
+
|
18
|
+
@current_cluster = cluster
|
19
|
+
yield block
|
20
|
+
@current_cluster = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def servers(*servers)
|
24
|
+
@current_cluster.servers = servers
|
25
|
+
end
|
26
|
+
alias :server :servers
|
27
|
+
|
28
|
+
def default_users(*args)
|
29
|
+
return @users || [] if args.empty?
|
30
|
+
|
31
|
+
name, users, options = parse_name_collection_and_options_args(args)
|
32
|
+
new_users = users.collect do |user|
|
33
|
+
CapistranoProvisioning::User.new(options.merge!(:name => user, :config => self))
|
34
|
+
end
|
35
|
+
|
36
|
+
if name
|
37
|
+
@user_groups ||= {}
|
38
|
+
@user_groups[name] = new_users
|
39
|
+
end
|
40
|
+
|
41
|
+
@users ||= []
|
42
|
+
@users += new_users
|
43
|
+
end
|
44
|
+
alias :default_user :default_users
|
45
|
+
|
46
|
+
def default_user_group(group)
|
47
|
+
@user_groups[group]
|
48
|
+
end
|
49
|
+
|
50
|
+
def inherit_default_users(*args)
|
51
|
+
groups, options = parse_collection_and_options_args(args)
|
52
|
+
|
53
|
+
options[:additional_groups] = options[:additional_groups].to_a
|
54
|
+
parent_users = clone_parent_users(groups)
|
55
|
+
|
56
|
+
if options[:additional_groups]
|
57
|
+
parent_users.collect! do |user|
|
58
|
+
user.groups += options[:additional_groups]
|
59
|
+
user
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
@users ||= []
|
64
|
+
@users += parent_users
|
65
|
+
end
|
66
|
+
alias :inherit_default_user :inherit_default_users
|
67
|
+
|
68
|
+
def users(*args)
|
69
|
+
users, options = parse_collection_and_options_args(args)
|
70
|
+
|
71
|
+
users_to_add = users.collect do |user|
|
72
|
+
if user.is_a? Symbol
|
73
|
+
group_users = @current_cluster.config.default_user_group(user).flatten
|
74
|
+
abort "Can't find user group for #{user} (in #{self.name})" unless group_users
|
75
|
+
group_users
|
76
|
+
else
|
77
|
+
user
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
users_to_add.flatten!
|
82
|
+
@current_cluster.add_users(users_to_add, options)
|
83
|
+
end
|
84
|
+
alias :user :users
|
85
|
+
|
86
|
+
def bootstrap(&block)
|
87
|
+
@current_cluster.bootstrap = block
|
88
|
+
end
|
89
|
+
|
90
|
+
def unique_name
|
91
|
+
if self.parent.respond_to?(:unique_name)
|
92
|
+
self.parent.unique_name + ":" + self.name.to_s
|
93
|
+
else
|
94
|
+
self.name.to_s
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
protected
|
99
|
+
def create_namespace_all_task
|
100
|
+
return if self.tasks.keys.include?(:all)
|
101
|
+
|
102
|
+
task(:all, :desc => "Set the current clusters '#{name}'") do
|
103
|
+
clusters = self.clusters
|
104
|
+
clusters.merge!(self.offspring_clusters)
|
105
|
+
|
106
|
+
logger.info "Setting clusters to #{clusters.keys.collect(&:to_s).sort.join(', ')}"
|
107
|
+
set(:clusters, clusters.values)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def offspring_clusters
|
112
|
+
clusters = self.clusters
|
113
|
+
|
114
|
+
self.namespaces.each do |name, namespace|
|
115
|
+
clusters.merge!(namespace.offspring_clusters)
|
116
|
+
end
|
117
|
+
|
118
|
+
clusters
|
119
|
+
end
|
120
|
+
|
121
|
+
def clone_parent_users(groups = [])
|
122
|
+
unless groups.empty?
|
123
|
+
groups.collect { |group| self.parent.default_user_group(group).collect(&:dup) }.flatten
|
124
|
+
else
|
125
|
+
self.parent.default_users.collect(&:dup)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def parse_name_collection_and_options_args(args)
|
130
|
+
args = args.dup
|
131
|
+
|
132
|
+
name = (args.first.is_a? Symbol) ? args.shift : nil
|
133
|
+
collection, options = parse_collection_and_options_args(args)
|
134
|
+
return name, collection, options
|
135
|
+
end
|
136
|
+
|
137
|
+
def parse_collection_and_options_args(args)
|
138
|
+
args = args.dup
|
139
|
+
|
140
|
+
if args.last.is_a? Hash
|
141
|
+
options = args.pop
|
142
|
+
else
|
143
|
+
options = {}
|
144
|
+
end
|
145
|
+
|
146
|
+
collection = args
|
147
|
+
return collection, options
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
Capistrano::Configuration::Namespaces::Namespace.send(:include, CapistranoProvisioning::NamespaceExtension)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'capistrano-provisioning'
|
2
|
+
|
3
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
4
|
+
on :before, "cluster_ensure", :only => ["run_bootstrap", "install_users", "preview_users"]
|
5
|
+
desc "[Internal] Ensures that a cluster has been specified"
|
6
|
+
task :cluster_ensure do
|
7
|
+
@clusters = fetch(:clusters, false)
|
8
|
+
unless @clusters
|
9
|
+
abort("No cluster specified - please use one of '#{self.clusters.keys.join(', ')}'")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Will set up a 'cluster' role, that will be set by the current provision.
|
14
|
+
set :servers, []
|
15
|
+
role(:cluster) {
|
16
|
+
fetch(:clusters).collect(&:servers).flatten
|
17
|
+
}
|
18
|
+
|
19
|
+
desc "Runs the bootstrap comamnds on the cluster"
|
20
|
+
task :run_bootstrap do
|
21
|
+
@clusters.each do |cluster|
|
22
|
+
abort "No bootstrap block given for '#{cluster.name}' cluster" unless cluster.bootstrap
|
23
|
+
cluster.bootstrap.call
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "Installs the specified users on the cluster"
|
28
|
+
task :install_users do
|
29
|
+
@clusters.each do |cluster|
|
30
|
+
cluster.install_users
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
task :preview_users do
|
35
|
+
puts "The following users will be added: "
|
36
|
+
@clusters.each do |cluster|
|
37
|
+
cluster.preview_users
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module CapistranoProvisioning
|
2
|
+
class User
|
3
|
+
attr_accessor :name, :config
|
4
|
+
attr_writer :groups, :key
|
5
|
+
|
6
|
+
def initialize(opts = {})
|
7
|
+
self.name = opts[:name]
|
8
|
+
self.config = opts[:config]
|
9
|
+
self.groups = opts[:groups].to_a || []
|
10
|
+
end
|
11
|
+
|
12
|
+
def install(opts = {})
|
13
|
+
abort "Aborting - Cannot install user #{self.name} as no server specified" unless opts[:server]
|
14
|
+
abort "Aborting - Could not find key for #{self.name} at #{local_key_file_path}" unless key
|
15
|
+
|
16
|
+
logger.debug "installing #{self.name} on #{opts[:server]}"
|
17
|
+
|
18
|
+
self.create_account_on_server(opts[:server]) unless self.account_exists?(opts[:server])
|
19
|
+
self.create_ssh_config_directory(opts[:server])
|
20
|
+
self.update_authorized_keys(opts[:server])
|
21
|
+
self.add_account_to_groups(opts[:server])
|
22
|
+
end
|
23
|
+
|
24
|
+
def groups
|
25
|
+
@groups.uniq
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
def account_exists?(server)
|
30
|
+
begin
|
31
|
+
if capture("id #{self.name}", :hosts => server)
|
32
|
+
true
|
33
|
+
else
|
34
|
+
false
|
35
|
+
end
|
36
|
+
rescue
|
37
|
+
# Must figure out a better way to capture that the command has failed
|
38
|
+
false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def key
|
43
|
+
File.read(local_key_file_path) if File.exists?(local_key_file_path)
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_account_on_server(server)
|
47
|
+
run "#{sudo} /usr/sbin/useradd -m #{name}", :pty => true, :hosts => server
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_account_to_groups(server)
|
51
|
+
self.groups.each do |group|
|
52
|
+
run "#{sudo} /usr/sbin/usermod -a -G#{group} #{self.name}", :pty => true, :hosts => server
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def create_ssh_config_directory(server)
|
57
|
+
# Actual dirt
|
58
|
+
commands = <<-COMMANDS
|
59
|
+
sudo su root -c 'if [ ! -d #{ssh_config_directory_path} ]; then
|
60
|
+
sudo mkdir #{ssh_config_directory_path};
|
61
|
+
fi';
|
62
|
+
#{sudo} chown #{name} #{ssh_config_directory_path} &&
|
63
|
+
#{sudo} chmod 700 #{ssh_config_directory_path}
|
64
|
+
COMMANDS
|
65
|
+
|
66
|
+
run commands, :pty => true, :hosts => server
|
67
|
+
end
|
68
|
+
|
69
|
+
def update_authorized_keys(server)
|
70
|
+
commands = <<-COMMANDS
|
71
|
+
#{sudo} touch #{authorized_keys_file_path} &&
|
72
|
+
echo '#{key}' | sudo tee #{authorized_keys_file_path}
|
73
|
+
COMMANDS
|
74
|
+
run commands, :pty => true, :hosts => server
|
75
|
+
end
|
76
|
+
|
77
|
+
def authorized_keys_file_path
|
78
|
+
"/home/#{self.name}/.ssh/authorized_keys"
|
79
|
+
end
|
80
|
+
|
81
|
+
def home_directory_path
|
82
|
+
"/home/#{self.name}/"
|
83
|
+
end
|
84
|
+
|
85
|
+
def ssh_config_directory_path
|
86
|
+
"/home/#{self.name}/.ssh/"
|
87
|
+
end
|
88
|
+
|
89
|
+
def local_key_file_path
|
90
|
+
"config/keys/#{self.name}.pub"
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
protected
|
95
|
+
# This isn't the best way to get to the run/logger stuff in this class -
|
96
|
+
# need to figure out a better way to do this, or a way to not need to.
|
97
|
+
|
98
|
+
def sudo(*parameters, &block)
|
99
|
+
self.config.sudo(*parameters, &block)
|
100
|
+
end
|
101
|
+
|
102
|
+
def logger
|
103
|
+
self.config.logger
|
104
|
+
end
|
105
|
+
|
106
|
+
def run(cmd, options={}, &block)
|
107
|
+
self.config.run(cmd, options, &block)
|
108
|
+
end
|
109
|
+
|
110
|
+
def capture(command, options={})
|
111
|
+
self.config.capture(command, options)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CapistranoProvisioning::Cluster do
|
4
|
+
let(:config) { Capistrano::Configuration.new }
|
5
|
+
let(:user) { CapistranoProvisioning::User.new }
|
6
|
+
let(:namespace) { Capistrano::Configuration::Namespaces::Namespace.new(:test_namespace, config) }
|
7
|
+
let(:cluster) { CapistranoProvisioning::Cluster.new(cluster_name, :servers => [], :config => config) }
|
8
|
+
let(:cluster_name) { '16nHElbcaf' }
|
9
|
+
|
10
|
+
it "should add a capistrano task" do
|
11
|
+
config_mock = mock(Capistrano::Configuration)
|
12
|
+
config_mock.should_receive(:task).with(cluster_name.to_sym, anything)
|
13
|
+
CapistranoProvisioning::Cluster.new(cluster_name, :servers => [], :config => config_mock)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should return an empty of array of users when it has none" do
|
17
|
+
cluster.users.should == []
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should add users from their names" do
|
21
|
+
# For some reason `expect {}.to change` didn't work here - reverting to old style for now
|
22
|
+
cluster.add_users ['test1', 'test2']
|
23
|
+
cluster.users.length.should == 2
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should add users from objects" do
|
27
|
+
cluster.add_users [user]
|
28
|
+
cluster.users.length.should == 1
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should have a unique name" do
|
32
|
+
cluster = CapistranoProvisioning::Cluster.new(cluster_name, :servers => [], :config => namespace)
|
33
|
+
cluster.unique_name.should == "test_namespace:#{cluster_name}"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should take a bootstrap block" do
|
37
|
+
block = Proc.new {}
|
38
|
+
cluster.bootstrap = block
|
39
|
+
cluster.bootstrap.should == block
|
40
|
+
end
|
41
|
+
|
42
|
+
context "and users" do
|
43
|
+
it "should use the namespace's default users if no users are specified" do
|
44
|
+
namespace.default_users 'sam', 'david'
|
45
|
+
cluster = CapistranoProvisioning::Cluster.new(cluster_name, :config => namespace)
|
46
|
+
|
47
|
+
cluster.users.collect(&:name).should include('sam', 'david')
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should not add users to the namespace" do
|
51
|
+
cluster = CapistranoProvisioning::Cluster.new(cluster_name, :config => namespace)
|
52
|
+
cluster.add_users ['bob', 'juan']
|
53
|
+
|
54
|
+
namespace.default_users.should == []
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should use the clusters's users if users are specified" do
|
58
|
+
namespace.default_users 'sam', 'david'
|
59
|
+
cluster = CapistranoProvisioning::Cluster.new(cluster_name, :config => namespace)
|
60
|
+
|
61
|
+
cluster.add_users ['bob', 'juan']
|
62
|
+
|
63
|
+
user_names = cluster.users.collect(&:name)
|
64
|
+
user_names.should include('bob', 'juan')
|
65
|
+
user_names.should_not include('sam', 'david')
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should install users" do
|
69
|
+
test_user, test_user_2 = user.dup, user.dup
|
70
|
+
|
71
|
+
test_user.stub!(:key => 'test key')
|
72
|
+
test_user.should_receive(:install)
|
73
|
+
|
74
|
+
test_user_2.stub!(:key => 'test key 2')
|
75
|
+
test_user_2.should_receive(:install)
|
76
|
+
|
77
|
+
cluster.servers = 'host1.example.com'
|
78
|
+
cluster.add_users [test_user, test_user_2]
|
79
|
+
cluster.install_users
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Capistrano::Configuration do
|
4
|
+
let(:config) { Capistrano::Configuration.new }
|
5
|
+
let(:namespace) { Capistrano::Configuration::Namespaces::Namespace.new(:test_namespace, config) }
|
6
|
+
|
7
|
+
context "default users" do
|
8
|
+
it "should take a list of default users" do
|
9
|
+
namespace.default_users 'sam', 'chris'
|
10
|
+
namespace.default_users.length.should == 2
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should take a name for the list of users" do
|
14
|
+
namespace.default_users :admin, 'sam', 'chris'
|
15
|
+
namespace.default_users.length.should == 2
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return an empty array if there are no default users" do
|
19
|
+
namespace.default_users.length.should == 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "default user inheritance" do
|
24
|
+
it "should inherit the users" do
|
25
|
+
namespace.default_users 'chris', 'sam'
|
26
|
+
namespace.namespace :nested do
|
27
|
+
inherit_default_users
|
28
|
+
end
|
29
|
+
|
30
|
+
namespace.namespaces[:nested].default_users.length.should == 2
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should inherit a named group of users" do
|
34
|
+
namespace.default_users :admin, 'chris', 'sam'
|
35
|
+
namespace.namespace :nested do
|
36
|
+
inherit_default_users :admin
|
37
|
+
end
|
38
|
+
|
39
|
+
namespace.namespaces[:nested].default_users.length.should == 2
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should inherit multiple named groups of users" do
|
43
|
+
namespace.default_users :admin, 'chris', 'sam'
|
44
|
+
namespace.default_users :developers, 'david', 'rob'
|
45
|
+
|
46
|
+
namespace.namespace :nested do
|
47
|
+
inherit_default_users :admin, :developers
|
48
|
+
end
|
49
|
+
|
50
|
+
namespace.namespaces[:nested].default_users.length.should == 4
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should inherit the users' groups" do
|
54
|
+
namespace.default_users 'sam', 'chris', :groups => 'test_group'
|
55
|
+
namespace.namespace :nested do
|
56
|
+
inherit_default_users
|
57
|
+
end
|
58
|
+
|
59
|
+
namespace.namespaces[:nested].default_users.each do |user|
|
60
|
+
user.groups.should include('test_group')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "additional groups" do
|
65
|
+
|
66
|
+
it "should add one additional groups from a string" do
|
67
|
+
namespace.default_users 'sam', 'chris', :groups => 'test_group'
|
68
|
+
namespace.namespace :nested do
|
69
|
+
inherit_default_users :additional_groups => 'test_group_2'
|
70
|
+
end
|
71
|
+
|
72
|
+
namespace.namespaces[:nested].default_users.each do |user|
|
73
|
+
user.groups.should include('test_group')
|
74
|
+
user.groups.should include('test_group_2')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should add any additional groups from an array" do
|
79
|
+
namespace.default_users 'sam', 'chris', :groups => 'test_group'
|
80
|
+
namespace.namespace :nested do
|
81
|
+
inherit_default_users :additional_groups => ['test_group_2']
|
82
|
+
end
|
83
|
+
|
84
|
+
namespace.namespaces[:nested].default_users.each do |user|
|
85
|
+
user.groups.should include('test_group')
|
86
|
+
user.groups.should include('test_group_2')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should not add additional groups to the original users" do
|
91
|
+
config.namespace :test_namespace do
|
92
|
+
default_users 'sam', 'chris', :groups => 'test_group'
|
93
|
+
namespace :nested do
|
94
|
+
inherit_default_users :additional_groups => 'test_group_2'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
config.namespaces[:test_namespace].default_users.each do |user|
|
99
|
+
user.groups.should_not include('test_group_2')
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should create a cluster" do
|
106
|
+
namespace.cluster :test_cluster
|
107
|
+
namespace.clusters["test_namespace:test_cluster"].should be_a CapistranoProvisioning::Cluster
|
108
|
+
end
|
109
|
+
|
110
|
+
it "cluster should take a list of servers inline" do
|
111
|
+
namespace.cluster :test_cluster, 'server_1', 'server_2'
|
112
|
+
namespace.clusters["test_namespace:test_cluster"].servers.length.should == 2
|
113
|
+
end
|
114
|
+
|
115
|
+
context "within a cluster" do
|
116
|
+
it "should take a list of users" do
|
117
|
+
config.namespace :test_namespace do
|
118
|
+
cluster :test_cluster do
|
119
|
+
users 'sam', 'chris'
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
users = config.namespaces[:test_namespace].clusters["test_namespace:test_cluster"].users
|
124
|
+
user_names = users.collect(&:name)
|
125
|
+
user_names.should include('sam','chris')
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should inherit named users" do
|
129
|
+
config.namespace :test_namespace do
|
130
|
+
default_users :chaps, 'joe', 'bob'
|
131
|
+
cluster :test_cluster do
|
132
|
+
users :chaps
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
users = config.namespaces[:test_namespace].clusters["test_namespace:test_cluster"].users
|
137
|
+
users.each do |user|
|
138
|
+
user.should be_a CapistranoProvisioning::User
|
139
|
+
end
|
140
|
+
|
141
|
+
user_names = users.collect(&:name)
|
142
|
+
user_names.should include('joe', 'bob')
|
143
|
+
user_names.should_not include(:chaps)
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should raise an error if passed named users that do not exist" do
|
148
|
+
expect {
|
149
|
+
config.namespace :test_namespace do
|
150
|
+
default_users :chaps, 'joe', 'bob'
|
151
|
+
cluster :test_cluster do
|
152
|
+
users :non_existent_group
|
153
|
+
end
|
154
|
+
end
|
155
|
+
}.to raise_error
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should inherit named users mixed in with user names" do
|
159
|
+
config.namespace :test_namespace do
|
160
|
+
default_users :chaps, 'joe', 'bob'
|
161
|
+
cluster :test_cluster do
|
162
|
+
users :chaps, 'sam'
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
user_names = config.namespaces[:test_namespace].clusters["test_namespace:test_cluster"].users.collect(&:name)
|
167
|
+
user_names.should include('joe', 'bob', 'sam')
|
168
|
+
user_names.should_not include(:chaps)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe "unique name" do
|
173
|
+
it "should give a unique name on the top-tier namespace" do
|
174
|
+
namespace.send(:unique_name).should == 'test_namespace'
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should give a unique name on a second-tier namespace" do
|
178
|
+
namespace.namespace :foo do; end
|
179
|
+
namespace.namespaces[:foo].send(:unique_name).should == "test_namespace:foo"
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should give a unique name on a third-tier namespace" do
|
183
|
+
namespace.namespace :foo do
|
184
|
+
namespace :bar do; end
|
185
|
+
end
|
186
|
+
namespace.namespaces[:foo].namespaces[:bar].send(:unique_name).should == "test_namespace:foo:bar"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Shouldn't necessarily be testing these protected methods,
|
191
|
+
# however it is complex and worthy of a spec, I feel.
|
192
|
+
context "argument parsing" do
|
193
|
+
it "should handle a named collection" do
|
194
|
+
args = [:test, 'sam', 'bob']
|
195
|
+
name, collection, options = namespace.send(:parse_name_collection_and_options_args, args)
|
196
|
+
|
197
|
+
name.should == :test
|
198
|
+
collection.should == ['sam', 'bob']
|
199
|
+
options.should be_empty
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should handle a named collection with options" do
|
203
|
+
args = [:test, 'sam', 'bob', { :option => true } ]
|
204
|
+
name, collection, options = namespace.send(:parse_name_collection_and_options_args, args)
|
205
|
+
|
206
|
+
name.should == args.first
|
207
|
+
collection.should == args.slice(1,2)
|
208
|
+
options.should == args.last
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should handle a collection with no options" do
|
212
|
+
args = ['sam', 'bob']
|
213
|
+
users, options = namespace.send(:parse_collection_and_options_args, args)
|
214
|
+
|
215
|
+
users.should == args
|
216
|
+
options.should be_empty
|
217
|
+
end
|
218
|
+
|
219
|
+
it "should handle a collection with options" do
|
220
|
+
args = ['sam', 'bob', { :option => true } ]
|
221
|
+
users, options = namespace.send(:parse_collection_and_options_args, args)
|
222
|
+
|
223
|
+
users.should == args.slice(0,2)
|
224
|
+
options.should == args.last
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# TODO: Not sure how to test this yet
|
4
|
+
|
5
|
+
describe "Recipes" do
|
6
|
+
context "namespace" do
|
7
|
+
it "should create an :all task which loads all servers in that namespace"
|
8
|
+
end
|
9
|
+
|
10
|
+
context "cluster definition" do
|
11
|
+
it "should create a task to set the servers"
|
12
|
+
|
13
|
+
it "should create a task to bootstrap the servers"
|
14
|
+
|
15
|
+
it "should create a task to install users on the servers"
|
16
|
+
end
|
17
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
|
4
|
+
# This might not be a good idea... http://gist.github.com/54177
|
5
|
+
require 'rubygems'
|
6
|
+
|
7
|
+
require 'spec'
|
8
|
+
require 'spec/autorun'
|
9
|
+
|
10
|
+
require 'capistrano'
|
11
|
+
require 'capistrano-provisioning'
|
12
|
+
|
13
|
+
Spec::Runner.configure do |config|
|
14
|
+
|
15
|
+
end
|
data/spec/user_spec.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CapistranoProvisioning::User do
|
4
|
+
let(:username) { 'z6MeKbLOXPqi5P' }
|
5
|
+
let(:server) { 'test1.example.com' }
|
6
|
+
let(:config_mock) { mock(Capistrano::Configuration) }
|
7
|
+
let(:user) { CapistranoProvisioning::User.new(:name => username, :server => server, :config => config_mock) }
|
8
|
+
|
9
|
+
describe "install" do
|
10
|
+
it "should require a server" do
|
11
|
+
expect { user.install }.to raise_error(SystemExit)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should error if the user's ssh key cannot be loaded" do
|
15
|
+
expect { user.install(:server => server) }.to raise_error(SystemExit)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should create an account on the server" do
|
20
|
+
config_mock.should_receive(:run).with(/#{username}/, anything()).once
|
21
|
+
config_mock.should_receive(:sudo).with(no_args()).once
|
22
|
+
|
23
|
+
user.send(:create_account_on_server, server)
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "and groups" do
|
27
|
+
it "should return an empty array if there are no groups" do
|
28
|
+
user.groups.should == []
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should de-duplify groups" do
|
32
|
+
user.groups = ['test_group', 'test_group2', 'test_group']
|
33
|
+
user.groups.should == ['test_group','test_group2']
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should add a user to one group" do
|
37
|
+
user = CapistranoProvisioning::User.new(:name => username, :server => server, :config => config_mock, :groups => 'test_group')
|
38
|
+
user.groups.should include("test_group")
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should add a user to an array of groups" do
|
42
|
+
user = CapistranoProvisioning::User.new(:name => username, :server => server, :config => config_mock, :groups => ['test_group', 'test_group_2'])
|
43
|
+
user.groups.should include("test_group")
|
44
|
+
user.groups.should include("test_group_2")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: capistrano-provisioning
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 3
|
9
|
+
version: 0.0.3
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Sam Phillips
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-06-16 00:00:00 +01:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: capistrano
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 2
|
29
|
+
- 5
|
30
|
+
- 18
|
31
|
+
version: 2.5.18
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rspec
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 1
|
43
|
+
- 2
|
44
|
+
- 9
|
45
|
+
version: 1.2.9
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
description: Capistrano provisioning
|
49
|
+
email: sam@samdanavia.com
|
50
|
+
executables: []
|
51
|
+
|
52
|
+
extensions: []
|
53
|
+
|
54
|
+
extra_rdoc_files:
|
55
|
+
- LICENSE
|
56
|
+
- README.mdown
|
57
|
+
files:
|
58
|
+
- .document
|
59
|
+
- LICENSE
|
60
|
+
- README.mdown
|
61
|
+
- Rakefile
|
62
|
+
- VERSION
|
63
|
+
- capistrano-provisioning.gemspec
|
64
|
+
- lib/capistrano-provisioning.rb
|
65
|
+
- lib/capistrano-provisioning/cluster.rb
|
66
|
+
- lib/capistrano-provisioning/namespaces.rb
|
67
|
+
- lib/capistrano-provisioning/recipes.rb
|
68
|
+
- lib/capistrano-provisioning/user.rb
|
69
|
+
- pkg/capistrano-provisioning-0.0.0.gem
|
70
|
+
- pkg/capistrano-provisioning-0.0.1.gem
|
71
|
+
- pkg/capistrano-provisioning-0.0.3.gem
|
72
|
+
- spec/cluster_spec.rb
|
73
|
+
- spec/namespaces_spec.rb
|
74
|
+
- spec/recipes_spec.rb
|
75
|
+
- spec/spec.opts
|
76
|
+
- spec/spec_helper.rb
|
77
|
+
- spec/user_spec.rb
|
78
|
+
has_rdoc: true
|
79
|
+
homepage: http://github.com/samdanavia/capistrano-provisioning
|
80
|
+
licenses: []
|
81
|
+
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options:
|
84
|
+
- --charset=UTF-8
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
version: "0"
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
segments:
|
99
|
+
- 0
|
100
|
+
version: "0"
|
101
|
+
requirements: []
|
102
|
+
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 1.3.6
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: Capistrano provisioning
|
108
|
+
test_files:
|
109
|
+
- spec/cluster_spec.rb
|
110
|
+
- spec/namespaces_spec.rb
|
111
|
+
- spec/recipes_spec.rb
|
112
|
+
- spec/spec_helper.rb
|
113
|
+
- spec/user_spec.rb
|