trooper 0.6.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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: