trooper 0.6.0.alpha1

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2011 Richard Adams
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.md ADDED
@@ -0,0 +1,96 @@
1
+ # Trooper [![Build Status](https://secure.travis-ci.org/madwire/trooper.png?branch=master)](http://travis-ci.org/madwire/trooper)
2
+
3
+ Trooper is designed to give you the flexibility to deploy your code to any server in any way you like.
4
+ You design your deployment strategy to best fit your application and its needs.
5
+ Trooper comes with some built in actions that you can use in your own strategies or come up with entirely new ones.
6
+
7
+ ## Installation
8
+
9
+ 1. Super easy! `gem install trooper` or add it to your Gemfile `gem 'trooper'`
10
+ 2. Pop into your terminal and run `=> trooper init`
11
+ 3. Start building you deployment strategy :)
12
+
13
+ ## Usage
14
+
15
+ `=> trooper deploy -e staging` or `=> trooper update`
16
+
17
+ #### Define your own strategies and actions
18
+
19
+ ```ruby
20
+ action :restart_server, 'Restarting the server' do
21
+ run 'touch tmp/restart.txt'
22
+ run "echo 'Restarted'"
23
+ end
24
+
25
+ strategy :restart, 'Restart application' do
26
+ actions :restart_server
27
+ end
28
+
29
+ strategy :update, 'Update the code base on the server' do
30
+ actions :update_repository, :install_gems
31
+ call :restart # call another strategy
32
+ end
33
+ ```
34
+
35
+ #### Example Troopfile
36
+
37
+ ```ruby
38
+ user 'my_user'
39
+ hosts 'production1.example.com', 'production2.example.com'
40
+ repository 'git@git.foo.co.uk:bar.git'
41
+ path "/fullpath/to/data/folder"
42
+
43
+ set :my_value => 'something'
44
+
45
+ env :stage do
46
+ hosts 'stage.example.com',
47
+ set :my_value => 'something_else'
48
+
49
+ action :restart do
50
+ run 'touch tmp/restert.txt'
51
+ end
52
+ end
53
+
54
+ action :restart do
55
+ run 'touch tmp/restert.txt'
56
+ run "echo 'Restarted'"
57
+ end
58
+
59
+ strategy :update, 'Update the code base on the server' do
60
+ actions :update_repository, :install_gems
61
+ call :restart
62
+ end
63
+
64
+ strategy :deploy, 'Full deployment to the server' do
65
+ call :update
66
+ actions :migrate_database
67
+ call :restart
68
+ end
69
+
70
+ strategy :restart, 'Restart application' do
71
+ actions :restart
72
+ end
73
+ ```
74
+
75
+ ## LICENSE
76
+
77
+ Copyright 2011 Richard Adams
78
+
79
+ Permission is hereby granted, free of charge, to any person obtaining
80
+ a copy of this software and associated documentation files (the
81
+ "Software"), to deal in the Software without restriction, including
82
+ without limitation the rights to use, copy, modify, merge, publish,
83
+ distribute, sublicense, and/or sell copies of the Software, and to
84
+ permit persons to whom the Software is furnished to do so, subject to
85
+ the following conditions:
86
+
87
+ The above copyright notice and this permission notice shall be
88
+ included in all copies or substantial portions of the Software.
89
+
90
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
91
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
92
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
93
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
94
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
95
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
96
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ begin
9
+ require 'rdoc/task'
10
+ require 'sdoc'
11
+ rescue LoadError
12
+ require 'rdoc/rdoc'
13
+ require 'rake/rdoctask'
14
+ RDoc::Task = Rake::RDocTask
15
+ end
16
+
17
+ RDoc::Task.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = 'Trooper'
20
+ rdoc.options << '--line-numbers'
21
+ rdoc.options << '-f' << 'sdoc'
22
+ rdoc.options << '-c' << 'utf-8'
23
+ rdoc.options << '-t' << 'rails'
24
+ #rdoc.options << '-g' # SDoc flag, link methods to GitHub
25
+ rdoc.rdoc_files.include('README.md')
26
+ rdoc.rdoc_files.include('lib/**/*.rb')
27
+ end
28
+
29
+
30
+ Bundler::GemHelper.install_tasks
31
+
32
+
33
+ ENV["COVERAGE"] = "true"
34
+
35
+ require 'rspec/core'
36
+ require 'rspec/core/rake_task'
37
+
38
+ RSpec::Core::RakeTask.new(:spec) do |spec|
39
+ spec.pattern = FileList['spec/**/*_spec.rb']
40
+ end
41
+
42
+ desc 'Default: Run all specs.'
43
+ task :default => :spec
44
+
data/bin/trooper ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ $:.push File.expand_path("../../lib", __FILE__)
5
+
6
+ require 'trooper'
7
+ require 'trooper/cli'
8
+
9
+ Trooper::CLI.start
@@ -0,0 +1,56 @@
1
+ require 'trooper/dsl/folders'
2
+ require 'trooper/dsl/rake'
3
+ require 'trooper/dsl/bundler'
4
+
5
+ module Trooper
6
+ class Action
7
+ include Trooper::DSL::Folders
8
+ include Trooper::DSL::Rake
9
+ include Trooper::DSL::Bundler
10
+
11
+ attr_reader :name, :description, :config, :block
12
+ attr_accessor :commands
13
+
14
+ # expects a name, description and a block
15
+ # Action.new(:my_action, 'Does great things') { run 'touch file' }
16
+ def initialize(name, description, &block)
17
+ @name, @description, @config = name, description, {}
18
+ @commands, @block = [], block
19
+ end
20
+
21
+ # eval's the block passed on initialize and returns the command array
22
+ def call(configuration)
23
+ @config = configuration
24
+ eval_block(&block)
25
+ commands
26
+ end
27
+ alias :execute :call
28
+
29
+ # validates the action
30
+ def ok?
31
+ true
32
+ end
33
+
34
+ # run is the base run command used by the dsl
35
+ def run(command)
36
+ commands << command if command != ''
37
+ end
38
+
39
+ def method_missing(method_sym, *arguments, &block) # :nodoc:
40
+ config[method_sym] || super
41
+ end
42
+
43
+ private
44
+
45
+ def eval_block(&block) # :nodoc:
46
+ if block_given?
47
+ if block.arity == 1
48
+ block.call self
49
+ else
50
+ instance_eval &block
51
+ end
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,31 @@
1
+ require 'trooper/action'
2
+
3
+ module Trooper
4
+ module Actions
5
+
6
+ class CloneRepositoryAction < Action
7
+
8
+ def initialize(config = {})
9
+ @name = :clone_repository
10
+ @description = "Cloning repository as 'application'"
11
+ @config = config
12
+ @commands = []
13
+ end
14
+
15
+ def call(configuration)
16
+ @config = configuration
17
+ build_commands
18
+ commands
19
+ end
20
+
21
+ private
22
+
23
+ def build_commands
24
+ cd path
25
+ run "git clone #{repository} application"
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ require 'trooper/action'
2
+
3
+ module Trooper
4
+ module Actions
5
+
6
+ class InstallGemsAction < Action
7
+
8
+ def initialize(config = {})
9
+ @name = :install_gems
10
+ @description = "Installing Gems with Bundler"
11
+ @config = config
12
+ @commands = []
13
+ end
14
+
15
+ def call(configuration)
16
+ @config = configuration
17
+ build_commands
18
+ commands
19
+ end
20
+
21
+ private
22
+
23
+ def build_commands
24
+ cd application_path
25
+ bundle_install
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ require 'trooper/action'
2
+
3
+ module Trooper
4
+ module Actions
5
+
6
+ class MigrateDatabaseAction < Action
7
+
8
+ def initialize(config = {})
9
+ @name = :migrate_database
10
+ @description = "Migrating database"
11
+ @config = config
12
+ @commands = []
13
+ end
14
+
15
+ def call(configuration)
16
+ @config = configuration
17
+ build_commands
18
+ commands
19
+ end
20
+
21
+ private
22
+
23
+ def build_commands
24
+ cd application_path
25
+ rake "db:migrate RAILS_ENV=#{environment}"
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ require 'trooper/action'
2
+
3
+ module Trooper
4
+ module Actions
5
+
6
+ class RestartServerAction < Action
7
+
8
+ def initialize(config = {})
9
+ @name = :restart_server
10
+ @description = "Restart server"
11
+ @config = config
12
+ @commands = []
13
+ end
14
+
15
+ def call(configuration)
16
+ @config = configuration
17
+ build_commands
18
+ commands
19
+ end
20
+
21
+ private
22
+
23
+ def build_commands
24
+ cd application_path
25
+ create_folder 'tmp'
26
+ run 'touch tmp/restart.txt'
27
+ run 'echo "Server restarted!"'
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,31 @@
1
+ require 'trooper/action'
2
+
3
+ module Trooper
4
+ module Actions
5
+
6
+ class RollbackMigrateAction < Action
7
+
8
+ def initialize(config = {})
9
+ @name = :rollback_migrate
10
+ @description = "Rollback database"
11
+ @config = config
12
+ @commands = []
13
+ end
14
+
15
+ def call(configuration)
16
+ @config = configuration
17
+ build_commands
18
+ commands
19
+ end
20
+
21
+ private
22
+
23
+ def build_commands
24
+ cd application_path
25
+ rake "db:rollback RAILS_ENV=#{environment}"
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ require 'trooper/action'
2
+
3
+ module Trooper
4
+ module Actions
5
+
6
+ class SetupDatabaseAction < Action
7
+
8
+ def initialize(config = {})
9
+ @name = :setup_database
10
+ @description = "Setting up database"
11
+ @config = config
12
+ @commands = []
13
+ end
14
+
15
+ def call(configuration)
16
+ @config = configuration
17
+ build_commands
18
+ commands
19
+ end
20
+
21
+ private
22
+
23
+ def build_commands
24
+ cd application_path
25
+ rake "db:setup RAILS_ENV=#{environment}"
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ require 'trooper/action'
2
+
3
+ module Trooper
4
+ module Actions
5
+
6
+ class UpdateRepositoryAction < Action
7
+
8
+ def initialize(config = {})
9
+ @name = :update_repository
10
+ @description = "Updating repository"
11
+ @config = config
12
+ @commands = []
13
+ end
14
+
15
+ def call(configuration)
16
+ @config = configuration
17
+ build_commands
18
+ commands
19
+ end
20
+
21
+ private
22
+
23
+ def build_commands
24
+ cd application_path
25
+ run "git checkout #{branch} -q"
26
+ run "git pull origin #{branch} -q"
27
+ run "git gc --aggressive"
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,51 @@
1
+ module Trooper
2
+
3
+ class Arsenal < Array
4
+
5
+ # find an arsenal by its name
6
+ def find_by_name(name)
7
+ detect { |weapon| weapon.name == name }
8
+ end
9
+ alias :[] :find_by_name
10
+
11
+ # add a weapon to the arsenal
12
+ def add(weapon)
13
+ if weapon.ok?
14
+ remove weapon.name
15
+ self << weapon
16
+ end
17
+ weapon
18
+ end
19
+
20
+ # remove a weapon from the arsenal
21
+ def remove(name)
22
+ self.delete_if {|w| w.name == name}
23
+ end
24
+
25
+ # clears arsenal
26
+ def clear!
27
+ self.clear
28
+ end
29
+
30
+ class << self
31
+
32
+ # returns the strategies arsenal
33
+ def strategies
34
+ @strategies ||= new
35
+ end
36
+
37
+ # returns the strategies arsenal
38
+ def actions
39
+ @actions ||= new
40
+ end
41
+
42
+ # clears all default arsenal
43
+ def reset!
44
+ @strategies, @actions = nil
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,67 @@
1
+ require 'optparse'
2
+ require 'trooper/configuration'
3
+
4
+ module Trooper
5
+ class CLI
6
+
7
+ def self.start(argv = ARGV)
8
+ cli = self.new(argv)
9
+ cli.execute
10
+ cli
11
+ end
12
+
13
+ attr_reader :command, :options, :argv
14
+ attr_accessor :config
15
+
16
+ def initialize(argv)
17
+ @argv = argv
18
+ @options = { :environment => :production }
19
+
20
+ @command = option_parser.parse!(@argv)[0]
21
+ raise CliArgumentError, "You didnt pass an action" if @command == nil
22
+ end
23
+
24
+ def execute
25
+ case command.to_sym
26
+ when :init
27
+ Configuration.init
28
+ else
29
+ config = Configuration.new(options)
30
+ config.execute action
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def option_parser
37
+ @option_parser ||= ::OptionParser.new do |op|
38
+ op.banner = 'Usage: trooper <command> [options]'
39
+ op.separator ''
40
+
41
+ op.on "-d", "--debug", "Debuy" do
42
+ @options[:debug] = true
43
+ end
44
+
45
+ op.on "-e", "--env ENV", "Environment" do |e|
46
+ @options[:environment] = e.to_sym
47
+ end
48
+
49
+ op.on "-f", "--file TROOPFILE", "Load a different Troopfile" do |f|
50
+ @options[:file_name] = f || 'Troopfile'
51
+ end
52
+
53
+ op.on_tail "-h", "--help", "Help" do
54
+ puts @option_parser
55
+ exit
56
+ end
57
+
58
+ op.on_tail "-v", "--version", "Show version" do
59
+ puts "Trooper v#{Trooper::Version::STRING}"
60
+ exit
61
+ end
62
+
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,34 @@
1
+ require 'trooper/action'
2
+
3
+ require 'trooper/actions/clone_repository_action'
4
+ require 'trooper/actions/install_gems_action'
5
+ require 'trooper/actions/migrate_database_action'
6
+ require 'trooper/actions/restart_server_action'
7
+ require 'trooper/actions/rollback_migrate_action'
8
+ require 'trooper/actions/setup_database_action'
9
+ require 'trooper/actions/update_repository_action'
10
+
11
+ module Trooper
12
+ module Config
13
+ module Action
14
+
15
+ DEFAULT_ACTIONS = [Actions::CloneRepositoryAction, Actions::InstallGemsAction,
16
+ Actions::MigrateDatabaseAction, Actions::RestartServerAction,
17
+ Actions::RollbackMigrateAction, Actions::SetupDatabaseAction,
18
+ Actions::UpdateRepositoryAction]
19
+
20
+ def action(name, description = "No Description", &block)
21
+ action = Trooper::Action.new name, description, &block
22
+ Trooper::Arsenal.actions.add action
23
+ end
24
+
25
+
26
+ def load_default_actions!
27
+ DEFAULT_ACTIONS.each do |klass|
28
+ Trooper::Arsenal.actions.add klass.new
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ module Trooper
2
+ module Config
3
+ module Environment
4
+
5
+ def load_environment!
6
+ instance_variable = instance_variable_get("@#{self[:environment].to_s}_configuration")
7
+ unless instance_variable.nil?
8
+ instance_eval(&instance_variable)
9
+ end
10
+ end
11
+
12
+ def env(environment_name, &block)
13
+ instance_variable_set "@#{environment_name.to_s}_configuration", block
14
+ end
15
+
16
+ def user(arg)
17
+ set :user => arg
18
+ end
19
+
20
+ def hosts(*arg)
21
+ set :hosts => [*arg]
22
+ end
23
+
24
+ def repository(arg)
25
+ set :repository => arg
26
+ end
27
+
28
+ def path(arg)
29
+ set :application_path => "#{arg}/application"
30
+ set :trooper_path => "#{arg}/trooper"
31
+ set :path => arg
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,14 @@
1
+ require 'trooper/strategy'
2
+
3
+ module Trooper
4
+ module Config
5
+ module Strategy
6
+
7
+ def strategy(name, description = "No Description", &block)
8
+ strategy = Trooper::Strategy.new name, description, self, &block
9
+ Trooper::Arsenal.strategies.add strategy
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,88 @@
1
+ # encoding: utf-8
2
+
3
+ require 'trooper/arsenal'
4
+
5
+ require 'trooper/config/environment'
6
+ require 'trooper/config/strategy'
7
+ require 'trooper/config/action'
8
+
9
+ module Trooper
10
+ class Configuration < Hash
11
+ FILE_NAME = "Troopfile"
12
+
13
+ include Trooper::Config::Environment
14
+ include Trooper::Config::Strategy
15
+ include Trooper::Config::Action
16
+
17
+ def self.init
18
+ gem_dir = File.dirname(__FILE__)
19
+
20
+ if RUBY_VERSION == /1.8/
21
+ require 'ftools'
22
+ File.copy("#{gem_dir}/template/troopfile.rb", "./Troopfile")
23
+ else
24
+ require 'fileutils'
25
+ ::FileUtils.copy("#{gem_dir}/template/troopfile.rb", "./Troopfile")
26
+ end
27
+ end
28
+
29
+ # initialize a new configuration object, will parse the given troopfile.
30
+ # Configuration.new({:my_override => 'settings'})
31
+ def initialize(options = {})
32
+ @loaded = false
33
+ @file_name = options[:file_name] || FILE_NAME
34
+ self[:environment] = options[:environment] || :production
35
+
36
+ load_default_actions!
37
+ load_troopfile! options
38
+ end
39
+
40
+ # returns a terminal friendly version of the configuration
41
+ def to_s
42
+ self.map {|k,v| "#{k}: #{v}" }.join("\n")
43
+ end
44
+
45
+ # will find and execute the strategy name passed
46
+ # @config.execute(:my_strategy_name)
47
+ def execute(strategy_name)
48
+ Arsenal.strategies[strategy_name].execute self
49
+ end
50
+
51
+ # a way to set variables that will be available to all actions
52
+ # set(:my_variable => 'sdsd') # => available as method in an action
53
+ def set(hash)
54
+ self.merge! hash
55
+ end
56
+
57
+ # returns true if the troopfile was loaded
58
+ def loaded?
59
+ @loaded
60
+ end
61
+
62
+ private
63
+
64
+ # loads the troopfile and sets the environment up
65
+ def load_troopfile!(options)
66
+ if troopfile?
67
+ eval troopfile.read
68
+ @loaded = true
69
+
70
+ load_environment!
71
+ set options
72
+ else
73
+ raise Trooper::NoConfigurationFileError, "No Configuration file (#{@file_name}) can be found!"
74
+ end
75
+ end
76
+
77
+ # returns the troopfile file object
78
+ def troopfile
79
+ File.open(@file_name)
80
+ end
81
+
82
+ # returns true if the troopfile exists
83
+ def troopfile?
84
+ File.exists?(@file_name)
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,26 @@
1
+ module Trooper
2
+ module DSL
3
+ module Bundler
4
+
5
+ def bundle_exec(command)
6
+ use_bundle = using_bundler? ? "bundle exec " : ""
7
+ run use_bundle + command
8
+ end
9
+
10
+ def bundle_install
11
+ run "bundle install --path #{trooper_path}/bundle --deployment --without development test" if using_bundler?
12
+ end
13
+
14
+ def rake(command)
15
+ bundle_exec "rake #{command}"
16
+ end
17
+
18
+ private
19
+
20
+ def using_bundler?
21
+ File.exists? "Gemfile"
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ module Trooper
2
+ module DSL
3
+ module Folders
4
+
5
+ def cd(path)
6
+ run "cd #{path}"
7
+ end
8
+
9
+ def create_folder(path)
10
+ run "mkdir -p #{path}"
11
+ end
12
+ alias :mkdir :create_folder
13
+
14
+ def delete_folder(path)
15
+ run "rm -rf #{path}"
16
+ end
17
+
18
+ def create_folders(*folders)
19
+ folders.each { |folder| create_folder folder }
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ module Trooper
2
+ module DSL
3
+ module Rake
4
+
5
+ def rake(command)
6
+ run "rake #{command}"
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,86 @@
1
+ # encoding: utf-8
2
+
3
+ require 'logger'
4
+
5
+ module Trooper
6
+
7
+ class Logger < ::Logger
8
+ ACTION = 6
9
+ SUCCESS = 7
10
+
11
+ # DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN < ACTION < SUCCESS
12
+ LABELS = %w(DEBUG INFO WARN ERROR FATAL ANY ACTION SUCCESS)
13
+
14
+ def action(progname = nil, &block)
15
+ add(ACTION, nil, progname, &block)
16
+ end
17
+
18
+ def success(progname = nil, &block)
19
+ add(SUCCESS, nil, progname, &block)
20
+ end
21
+
22
+ private
23
+
24
+ def format_severity(severity)
25
+ LABELS[severity] || 'ANY'
26
+ end
27
+
28
+ end
29
+
30
+
31
+ class LogFormat
32
+
33
+ COLOURS = {
34
+ :black => 30,
35
+ :red => 31,
36
+ :green => 32,
37
+ :yellow => 33,
38
+ :blue => 34,
39
+ :magenta => 35,
40
+ :cyan => 36,
41
+ :white => 37
42
+ }
43
+
44
+ def call(severity, datetime, progname, message)
45
+ # DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN < ACTION < SUCCESS
46
+ case severity
47
+ when "DEBUG"
48
+ colour("#{progname} => [#{severity}] #{message}\n", :yellow)
49
+ when "WARN"
50
+ colour("#{progname} => [#{severity}] #{message}\n", :yellow)
51
+ when "ACTION"
52
+ colour("#{progname} => [#{severity}] #{message}\n", :magenta)
53
+ when "SUCCESS"
54
+ colour("#{progname} => [#{severity}] #{message}\n", :green)
55
+ when "ERROR", "FATAL"
56
+ colour("#{progname} => [#{severity}] #{message}\n", :red)
57
+ else
58
+ "#{progname} => [#{severity}] #{message}\n"
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def colour(msg, clr = :black)
65
+ "\e[#{COLOURS[clr]}m#{msg}\e[0m"
66
+ end
67
+
68
+ def underline
69
+ "\e[4m"
70
+ end
71
+
72
+ end
73
+
74
+
75
+ def self.logger
76
+ @logger ||= begin
77
+ logger = Logger.new($stdout)
78
+ logger.level = ::Logger::DEBUG
79
+ logger.progname = 'Trooper'
80
+ logger.formatter = LogFormat.new
81
+ logger
82
+ end
83
+ end
84
+
85
+
86
+ end
@@ -0,0 +1,57 @@
1
+ # encoding: utf-8
2
+
3
+ require "net/ssh"
4
+
5
+ module Trooper
6
+ class Runner
7
+
8
+ attr_reader :host, :user, :connection
9
+
10
+ # pass the host, user and any net/ssh config options
11
+ # Runner.new('my.example.com', 'admin', :forward_agent => false)
12
+ def initialize(host, user, options = { :forward_agent => true })
13
+ @host = host
14
+ @user = user
15
+ @connection = Net::SSH.start(host, user, options)
16
+ end
17
+
18
+ # returns user@host as a string
19
+ def to_s
20
+ "#{user}@#{host}"
21
+ end
22
+
23
+ # execute a set of commands via net/ssh, returns and array or raises an exception
24
+ # runner.execute(['cd to/path', 'touch file']) # => ['cd to/path && touch file', :stdout, '']
25
+ # runner.execute('cat file') # => ['cat file', :stdout, 'file content']
26
+ def execute(command, options = {})
27
+ commands = parse command
28
+ Trooper.logger.debug commands
29
+ connection.exec! commands do |ch, stream, data|
30
+ raise Trooper::StdError, "#{data}\n[ERROR INFO] #{commands}" if stream == :stderr
31
+ ch.wait
32
+ return [commands, stream, data]
33
+ end
34
+ end
35
+
36
+ # close net/ssh connection
37
+ def close
38
+ connection.close
39
+ end
40
+
41
+ private
42
+
43
+ # parse command, expects a string or array
44
+ # parse(['cd to/path', 'touch file']) # => 'cd to/path && touch file'
45
+ def parse(command)
46
+ case command.class.name.downcase.to_sym #Array => :array
47
+ when :array
48
+ command.compact.join(' && ')
49
+ when :string
50
+ command.chomp
51
+ else
52
+ raise Trooper::MalformedCommandError, "Command Not a String or Array: #{command.inspect}"
53
+ end
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,104 @@
1
+ require 'trooper/runner'
2
+
3
+ module Trooper
4
+ class Strategy
5
+
6
+ attr_reader :name, :description, :config, :run_list
7
+
8
+ def initialize(name, description, config = {}, &block)
9
+ @name, @description, @config = name, description, config
10
+ @run_list = []
11
+ eval_block(&block)
12
+ end
13
+
14
+ def execute(config = {})
15
+ @config = config
16
+ Trooper.logger.debug "Configuration\n#{config}"
17
+ successful = nil
18
+
19
+ runners.each do |runner|
20
+ begin
21
+ Trooper.logger.info "\e[4mRunning on #{runner}\n"
22
+
23
+ run_list.each do |item, name|
24
+ self.send("#{item}_execute", runner, name)
25
+ end
26
+
27
+ successful = true
28
+ Trooper.logger.success "\e[4mAll Actions Completed\n"
29
+ rescue Exception => e
30
+ Trooper.logger.error "#{e.class.to_s} : #{e.message}\n\n#{e.backtrace.join("\n")}"
31
+
32
+ successful = false
33
+ break #stop commands running on other servers
34
+ ensure
35
+ runner.close
36
+ end
37
+ end
38
+
39
+ successful
40
+ end
41
+
42
+ def ok?
43
+ true
44
+ end
45
+
46
+ def call(strategy_name)
47
+ if Arsenal.strategies[strategy_name]
48
+ Arsenal.strategies[strategy_name].run_list.each do |action|
49
+ @run_list << action
50
+ end
51
+ end
52
+ end
53
+
54
+ def actions(*action_names)
55
+ [*action_names].each do |name|
56
+ @run_list << [:action, name]
57
+ end
58
+ end
59
+
60
+ def method_missing(method_sym, *arguments, &block)
61
+ config[method_sym] || super
62
+ end
63
+
64
+ private
65
+
66
+ def eval_block(&block)
67
+ if block_given?
68
+ if block.arity == 1
69
+ block.call self
70
+ else
71
+ instance_eval &block
72
+ end
73
+ end
74
+ end
75
+
76
+ def action_execute(runner, name)
77
+ action = Arsenal.actions[name]
78
+
79
+ if action
80
+ commands = action.execute config
81
+ Trooper.logger.action action.description
82
+ runner_execute!(runner, commands)
83
+ end
84
+ end
85
+
86
+ def runners
87
+ @runners ||= begin
88
+ r, h, u = [], (hosts rescue nil), (user rescue nil)
89
+ h.each {|host| r << Runner.new(host, user) } if h && u; r
90
+ end
91
+ end
92
+
93
+ def runner_execute!(runner, commands, options = {})
94
+ result = runner.execute commands, options
95
+ if result && result[1] == :stdout
96
+ Trooper.logger.info "#{result[2]}\n"
97
+ true
98
+ else
99
+ false
100
+ end
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,34 @@
1
+ user 'my_user'
2
+ hosts 'stage.example.com'
3
+ repository 'git@git.bar.co.uk:whatever.git'
4
+ path "/path/to/data/folder"
5
+
6
+ set :my_value => 'something'
7
+
8
+ env :production do
9
+ hosts 'production1.example.com', 'production2.example.com'
10
+ set :my_value => 'something_else'
11
+
12
+ action :restart do
13
+ run 'touch tmp/restert.txt'
14
+ end
15
+ end
16
+
17
+ action :restart do
18
+ run 'touch tmp/restert.txt'
19
+ end
20
+
21
+ strategy :update, 'Update the code base on the server' do
22
+ actions :update_repository, :install_gems
23
+ call :restart
24
+ end
25
+
26
+ strategy :deploy, 'Full deployment to the server' do
27
+ call :update
28
+ actions :migrate_database
29
+ call :restart
30
+ end
31
+
32
+ strategy :restart, 'Restart application' do
33
+ actions :restart
34
+ end
@@ -0,0 +1,9 @@
1
+ module Trooper
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 6
5
+ TINY = 0
6
+ PRE = 'alpha1'
7
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
8
+ end
9
+ end
data/lib/trooper.rb ADDED
@@ -0,0 +1,29 @@
1
+ require 'trooper/version'
2
+ require 'trooper/logger'
3
+
4
+ module Trooper
5
+
6
+ # Base class for all Trooper exceptions
7
+ class TrooperError < StandardError
8
+ end
9
+
10
+ # When a strategy is not defined
11
+ class MissingStrategyError < TrooperError
12
+ end
13
+
14
+ # When a CLI argument is missing or badly formed
15
+ class CliArgumentError < TrooperError
16
+ end
17
+
18
+ # When a command(s) return a stderr stream
19
+ class StdError < TrooperError
20
+ end
21
+
22
+ # When a command is not formed corrently arrays or strings are exceptal commands
23
+ class MalformedCommandError < TrooperError
24
+ end
25
+
26
+ class NoConfigurationFileError < TrooperError
27
+ end
28
+
29
+ end
metadata ADDED
@@ -0,0 +1,220 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: trooper
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.0.alpha1
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Richard Adams
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: net-ssh
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 2.3.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 2.3.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '2.8'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '2.8'
46
+ - !ruby/object:Gem::Dependency
47
+ name: guard-rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '0.6'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '0.6'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rb-fsevent
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: yard
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '0.7'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '0.7'
94
+ - !ruby/object:Gem::Dependency
95
+ name: pry
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: '0.9'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '0.9'
110
+ - !ruby/object:Gem::Dependency
111
+ name: sdoc
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 0.3.16
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: 0.3.16
126
+ - !ruby/object:Gem::Dependency
127
+ name: simplecov
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ~>
132
+ - !ruby/object:Gem::Version
133
+ version: 0.5.4
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: 0.5.4
142
+ - !ruby/object:Gem::Dependency
143
+ name: rake
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ~>
148
+ - !ruby/object:Gem::Version
149
+ version: 0.9.2
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ~>
156
+ - !ruby/object:Gem::Version
157
+ version: 0.9.2
158
+ description: Simple but powerful deployment
159
+ email:
160
+ - richard@madwire.co.uk
161
+ executables: []
162
+ extensions: []
163
+ extra_rdoc_files: []
164
+ files:
165
+ - bin/trooper
166
+ - lib/trooper/action.rb
167
+ - lib/trooper/actions/clone_repository_action.rb
168
+ - lib/trooper/actions/install_gems_action.rb
169
+ - lib/trooper/actions/migrate_database_action.rb
170
+ - lib/trooper/actions/restart_server_action.rb
171
+ - lib/trooper/actions/rollback_migrate_action.rb
172
+ - lib/trooper/actions/setup_database_action.rb
173
+ - lib/trooper/actions/update_repository_action.rb
174
+ - lib/trooper/arsenal.rb
175
+ - lib/trooper/cli.rb
176
+ - lib/trooper/config/action.rb
177
+ - lib/trooper/config/environment.rb
178
+ - lib/trooper/config/strategy.rb
179
+ - lib/trooper/configuration.rb
180
+ - lib/trooper/dsl/bundler.rb
181
+ - lib/trooper/dsl/folders.rb
182
+ - lib/trooper/dsl/rake.rb
183
+ - lib/trooper/logger.rb
184
+ - lib/trooper/runner.rb
185
+ - lib/trooper/strategy.rb
186
+ - lib/trooper/template/troopfile.rb
187
+ - lib/trooper/version.rb
188
+ - lib/trooper.rb
189
+ - LICENSE
190
+ - Rakefile
191
+ - README.md
192
+ homepage: http://www.madwire.co.uk
193
+ licenses: []
194
+ post_install_message:
195
+ rdoc_options: []
196
+ require_paths:
197
+ - lib
198
+ required_ruby_version: !ruby/object:Gem::Requirement
199
+ none: false
200
+ requirements:
201
+ - - ! '>='
202
+ - !ruby/object:Gem::Version
203
+ version: '0'
204
+ segments:
205
+ - 0
206
+ hash: 3543094362386461389
207
+ required_rubygems_version: !ruby/object:Gem::Requirement
208
+ none: false
209
+ requirements:
210
+ - - ! '>'
211
+ - !ruby/object:Gem::Version
212
+ version: 1.3.1
213
+ requirements: []
214
+ rubyforge_project:
215
+ rubygems_version: 1.8.22
216
+ signing_key:
217
+ specification_version: 3
218
+ summary: Deploy like a 'Trooper'
219
+ test_files: []
220
+ has_rdoc: