Permalink
Browse files

Extracted capistrano multiconfig from deployment project

  • Loading branch information...
0 parents commit fc69657ee41979e614e0555f1888dc2479686118 @ayanko ayanko committed Oct 31, 2011
@@ -0,0 +1,4 @@
+*.gem
+.bundle
+Gemfile.lock
+pkg/*
@@ -0,0 +1 @@
+source "http://rubygems.org"
@@ -0,0 +1,90 @@
+# capistrano-multiconfig
+
+## Description
+
+Capistrano extension that allows to use multiple configurations.
+
+Multiconfig extension is similar to [multistage](https://github.com/capistrano/capistrano-ext) extenstion.
+But it's not only about 'stage' configurations. It's about any configuration that you may need.
+Extension recursively builds configuration list from configuration root directory.
+Each configuration loads recursively configuration from it namespace files and own configuration file.
+
+## Usage
+
+Install gem
+
+ $ gem install capistrano-multistage
+
+
+Add to `Capfile`
+
+ set :config, 'path/to/your/configurations'
+ require 'capistrano/multiconfig'
+
+## Example
+
+Assume we need next configurations:
+
+* services:billing:production
+* services:billing:qa
+* blog:production
+* blog:staging
+* dev:wiki
+
+Choose configuration root directory for example `config/deploy`
+
+Create configuration files:
+
+ config/deploy/services/billing/production.rb
+ config/deploy/services/billing/qa.rb
+ config/deploy/blog/production.rb
+ config/deploy/blog/staging.rb
+ config/deploy/dev/wiki.rb
+
+Add to `Capfile`:
+
+ set :config_root, 'config/deploy'
+ require 'capistrano/multiconfig'
+
+Put related capistrano configuration to each file according to file meaning.
+
+Check tasks:
+
+ $ cap -T
+ cap services:billing:production # Load services:billing:production configuration
+ cap services:billing:qa # Load services:billing:qa configuration
+ cap blog:production # Load blog:production configuration
+ cap blog:staging # Load blog:staging configuration
+ cap wiki # Load wiki configuration
+ cap invoke # Invoke a single command on the remote servers.
+ cap shell # Begin an interactive Capistrano session.
+
+Let's try to run task without specified configuration:
+
+ $ cap shell
+ triggering start callbacks for `shell'
+ * executing `multiconfig:ensure'
+ No configuration specified. Please specify one of:
+ * wiki:production
+ * wiki:staging
+ * blog:production
+ * blog:staging
+ (e.g. `cap wiki:production shell')
+
+
+So we must provide configuration as first task:
+
+ $ cap services:billing:qa shell
+
+## Configuration Loading
+
+Configuration task loads not only configuration associated with it filename.
+It also recursively load configurations from all namespaces.
+
+For example for *:config_root* `config/deploy` task `cap apps/blog/qa.rb` loads with **order** next configuration files (if they exist):
+
+* config/deploy/apps.rb
+* config/deploy/apps/blog.rb
+* config/deploy/apps/blog/qa.rb
+
+So it's easy to put shared configuration.
@@ -0,0 +1 @@
+require "bundler/gem_tasks"
@@ -0,0 +1,26 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+
+Gem::Specification.new do |s|
+ s.name = "capistrano-multiconfig"
+ s.version = "0.0.1"
+ s.authors = ["Andriy Yanko"]
+ s.email = ["andriy.yanko@gmail.com"]
+ s.homepage = "https://github.com/railsware/multiconfig"
+ s.summary = %q{Capistrano extension that allows to use multiple configurations}
+ s.description = %q{
+Multiconfig extension is similar to [multistage](https://github.com/capistrano/capistrano-ext) extenstion.
+But it's not only about 'stage' configurations. It's about any configuration that you may need.
+Extension recursively builds configuration list from configuration root directory.
+Each configuration loads recursively configuration from namespace files and own configuration file.
+ }
+
+ s.rubyforge_project = "capistrano-multiconfig"
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+
+ s.add_runtime_dependency "capistrano", ">=2.5.5"
+end
@@ -0,0 +1,2 @@
+require 'capistrano/multiconfig/configurations'
+require 'capistrano/multiconfig/ensure'
@@ -0,0 +1,63 @@
+Capistrano::Configuration.instance.load do
+ # configurations root directory
+ config_root = File.expand_path(fetch(:config_root, "config/deploy"))
+
+ # list of configurations files
+ config_files = Dir["#{config_root}/**/*.rb"]
+
+ # remove configuration file if it's part of another configuration
+ config_files.reject! do |config_file|
+ config_dir = config_file.gsub(/\.rb$/, '/')
+ config_files.any? { |file| file[0, config_dir.size] == config_dir }
+ end
+
+ # build configuration names list
+ config_names = config_files.map do |config_file|
+ config_file.sub("#{config_root}/", '').sub(/\.rb$/, '').gsub('/', ':')
+ end
+
+ # ensure that configuration segments don't override any method, task or namespace
+ config_names.each do |config_name|
+ config_name.split(':').each do |segment|
+ if all_methods.any? { |m| m == segment }
+ raise ArgumentError,
+ "Config task #{config_name} name overrides #{segment.inspect} (method|task|namespace)"
+ end
+ end
+ end
+
+ # create configuration task for each configuration name
+ config_names.each do |config_name|
+ segments = config_name.split(':')
+ namespace_names = segments[0, segments.size - 1]
+ task_name = segments.last
+
+ # create configuration task block
+ block = lambda do
+ desc "Load #{config_name} configuration"
+ task(task_name) do
+ # set configuration name as :config_name variable
+ self.set(:config_name, config_name)
+
+ # recursively load configurations
+ segments.size.times do |i|
+ path = ([config_root] + segments[0..i]).join('/') + '.rb'
+ self.load(path) if File.exists?(path)
+ end
+ end
+ end
+
+ # wrap task block into namespace blocks
+ block = namespace_names.reverse.inject(block) do |child, name|
+ lambda do
+ namespace(name, &child)
+ end
+ end
+
+ # create configuration task
+ block.call
+ end
+
+ # set configuration names list
+ set(:config_names, config_names)
+end
@@ -0,0 +1,15 @@
+Capistrano::Configuration.instance.load do
+ namespace :multiconfig do
+ desc "[internal] Ensure that a configuration has been selected"
+ task :ensure do
+ unless exists?(:config_name)
+ puts "No configuration specified. Please specify one of:"
+ config_names.each { |name| puts " * #{name}" }
+ puts "(e.g. `cap #{config_names.first} #{ARGV.last}')"
+ abort
+ end
+ end
+ end
+
+ on :start, 'multiconfig:ensure', :except => config_names
+end

0 comments on commit fc69657

Please sign in to comment.