capistrano-provisioning 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
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
@@ -0,0 +1,3 @@
1
+ require 'capistrano-provisioning/cluster'
2
+ require 'capistrano-provisioning/user'
3
+ require 'capistrano-provisioning/namespaces'
@@ -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
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format specdoc
@@ -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