capistrano-provisioning 0.0.3
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/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
|