Skip to content

Commit fc69657

Browse files
committed
Extracted capistrano multiconfig from deployment project
0 parents  commit fc69657

File tree

8 files changed

+202
-0
lines changed

8 files changed

+202
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.gem
2+
.bundle
3+
Gemfile.lock
4+
pkg/*

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
source "http://rubygems.org"

README.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# capistrano-multiconfig
2+
3+
## Description
4+
5+
Capistrano extension that allows to use multiple configurations.
6+
7+
Multiconfig extension is similar to [multistage](https://github.com/capistrano/capistrano-ext) extenstion.
8+
But it's not only about 'stage' configurations. It's about any configuration that you may need.
9+
Extension recursively builds configuration list from configuration root directory.
10+
Each configuration loads recursively configuration from it namespace files and own configuration file.
11+
12+
## Usage
13+
14+
Install gem
15+
16+
$ gem install capistrano-multistage
17+
18+
19+
Add to `Capfile`
20+
21+
set :config, 'path/to/your/configurations'
22+
require 'capistrano/multiconfig'
23+
24+
## Example
25+
26+
Assume we need next configurations:
27+
28+
* services:billing:production
29+
* services:billing:qa
30+
* blog:production
31+
* blog:staging
32+
* dev:wiki
33+
34+
Choose configuration root directory for example `config/deploy`
35+
36+
Create configuration files:
37+
38+
config/deploy/services/billing/production.rb
39+
config/deploy/services/billing/qa.rb
40+
config/deploy/blog/production.rb
41+
config/deploy/blog/staging.rb
42+
config/deploy/dev/wiki.rb
43+
44+
Add to `Capfile`:
45+
46+
set :config_root, 'config/deploy'
47+
require 'capistrano/multiconfig'
48+
49+
Put related capistrano configuration to each file according to file meaning.
50+
51+
Check tasks:
52+
53+
$ cap -T
54+
cap services:billing:production # Load services:billing:production configuration
55+
cap services:billing:qa # Load services:billing:qa configuration
56+
cap blog:production # Load blog:production configuration
57+
cap blog:staging # Load blog:staging configuration
58+
cap wiki # Load wiki configuration
59+
cap invoke # Invoke a single command on the remote servers.
60+
cap shell # Begin an interactive Capistrano session.
61+
62+
Let's try to run task without specified configuration:
63+
64+
$ cap shell
65+
triggering start callbacks for `shell'
66+
* executing `multiconfig:ensure'
67+
No configuration specified. Please specify one of:
68+
* wiki:production
69+
* wiki:staging
70+
* blog:production
71+
* blog:staging
72+
(e.g. `cap wiki:production shell')
73+
74+
75+
So we must provide configuration as first task:
76+
77+
$ cap services:billing:qa shell
78+
79+
## Configuration Loading
80+
81+
Configuration task loads not only configuration associated with it filename.
82+
It also recursively load configurations from all namespaces.
83+
84+
For example for *:config_root* `config/deploy` task `cap apps/blog/qa.rb` loads with **order** next configuration files (if they exist):
85+
86+
* config/deploy/apps.rb
87+
* config/deploy/apps/blog.rb
88+
* config/deploy/apps/blog/qa.rb
89+
90+
So it's easy to put shared configuration.

Rakefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require "bundler/gem_tasks"

capistrano-multiconfig.gemspec

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# -*- encoding: utf-8 -*-
2+
$:.push File.expand_path("../lib", __FILE__)
3+
4+
Gem::Specification.new do |s|
5+
s.name = "capistrano-multiconfig"
6+
s.version = "0.0.1"
7+
s.authors = ["Andriy Yanko"]
8+
s.email = ["[email protected]"]
9+
s.homepage = "https://github.com/railsware/multiconfig"
10+
s.summary = %q{Capistrano extension that allows to use multiple configurations}
11+
s.description = %q{
12+
Multiconfig extension is similar to [multistage](https://github.com/capistrano/capistrano-ext) extenstion.
13+
But it's not only about 'stage' configurations. It's about any configuration that you may need.
14+
Extension recursively builds configuration list from configuration root directory.
15+
Each configuration loads recursively configuration from namespace files and own configuration file.
16+
}
17+
18+
s.rubyforge_project = "capistrano-multiconfig"
19+
20+
s.files = `git ls-files`.split("\n")
21+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23+
s.require_paths = ["lib"]
24+
25+
s.add_runtime_dependency "capistrano", ">=2.5.5"
26+
end

lib/capistrano/multiconfig.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require 'capistrano/multiconfig/configurations'
2+
require 'capistrano/multiconfig/ensure'
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
Capistrano::Configuration.instance.load do
2+
# configurations root directory
3+
config_root = File.expand_path(fetch(:config_root, "config/deploy"))
4+
5+
# list of configurations files
6+
config_files = Dir["#{config_root}/**/*.rb"]
7+
8+
# remove configuration file if it's part of another configuration
9+
config_files.reject! do |config_file|
10+
config_dir = config_file.gsub(/\.rb$/, '/')
11+
config_files.any? { |file| file[0, config_dir.size] == config_dir }
12+
end
13+
14+
# build configuration names list
15+
config_names = config_files.map do |config_file|
16+
config_file.sub("#{config_root}/", '').sub(/\.rb$/, '').gsub('/', ':')
17+
end
18+
19+
# ensure that configuration segments don't override any method, task or namespace
20+
config_names.each do |config_name|
21+
config_name.split(':').each do |segment|
22+
if all_methods.any? { |m| m == segment }
23+
raise ArgumentError,
24+
"Config task #{config_name} name overrides #{segment.inspect} (method|task|namespace)"
25+
end
26+
end
27+
end
28+
29+
# create configuration task for each configuration name
30+
config_names.each do |config_name|
31+
segments = config_name.split(':')
32+
namespace_names = segments[0, segments.size - 1]
33+
task_name = segments.last
34+
35+
# create configuration task block
36+
block = lambda do
37+
desc "Load #{config_name} configuration"
38+
task(task_name) do
39+
# set configuration name as :config_name variable
40+
self.set(:config_name, config_name)
41+
42+
# recursively load configurations
43+
segments.size.times do |i|
44+
path = ([config_root] + segments[0..i]).join('/') + '.rb'
45+
self.load(path) if File.exists?(path)
46+
end
47+
end
48+
end
49+
50+
# wrap task block into namespace blocks
51+
block = namespace_names.reverse.inject(block) do |child, name|
52+
lambda do
53+
namespace(name, &child)
54+
end
55+
end
56+
57+
# create configuration task
58+
block.call
59+
end
60+
61+
# set configuration names list
62+
set(:config_names, config_names)
63+
end

lib/capistrano/multiconfig/ensure.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Capistrano::Configuration.instance.load do
2+
namespace :multiconfig do
3+
desc "[internal] Ensure that a configuration has been selected"
4+
task :ensure do
5+
unless exists?(:config_name)
6+
puts "No configuration specified. Please specify one of:"
7+
config_names.each { |name| puts " * #{name}" }
8+
puts "(e.g. `cap #{config_names.first} #{ARGV.last}')"
9+
abort
10+
end
11+
end
12+
end
13+
14+
on :start, 'multiconfig:ensure', :except => config_names
15+
end

0 commit comments

Comments
 (0)