trooper 0.6.0.beta → 0.6.0
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/README.md +35 -16
- data/lib/trooper/action.rb +33 -3
- data/lib/trooper/arsenal.rb +57 -7
- data/lib/trooper/config/action.rb +2 -2
- data/lib/trooper/configuration.rb +52 -9
- data/lib/trooper/host.rb +110 -0
- data/lib/trooper/logger.rb +1 -1
- data/lib/trooper/runner.rb +91 -46
- data/lib/trooper/strategy.rb +106 -87
- data/lib/trooper/template/troopfile.rb +29 -10
- data/lib/trooper/version.rb +1 -1
- data/lib/trooper.rb +2 -1
- metadata +10 -6
data/README.md
CHANGED
@@ -4,39 +4,52 @@ Trooper is designed to give you the flexibility to deploy your code to any serve
|
|
4
4
|
You design your deployment strategy to best fit your application and its needs.
|
5
5
|
Trooper comes with some built in actions that you can use in your own strategies or come up with entirely new ones.
|
6
6
|
|
7
|
-
#### Please give me a try on your small project for now
|
8
|
-
|
9
|
-
We have been using earlier versions of trooper were I work for the best part of 2 years,
|
10
|
-
but I re-wrote it for public release and so has lost a lot of its maturity.
|
11
|
-
|
12
7
|
## Installation
|
13
8
|
|
14
|
-
1. Super easy! `gem install trooper` or add it to your Gemfile `gem
|
9
|
+
1. Super easy! `gem install trooper` or add it to your Gemfile `gem "trooper", "~> 0.6.0"`
|
15
10
|
2. Pop into your terminal and run `=> trooper init`
|
16
11
|
3. Start building you deployment strategy :)
|
17
12
|
|
18
13
|
## Usage
|
19
14
|
|
20
|
-
`=> trooper deploy -e
|
15
|
+
`=> trooper deploy -e stage` or `=> trooper update`
|
16
|
+
|
17
|
+
#### Action
|
21
18
|
|
22
|
-
|
19
|
+
An Action is a set of commands to be run in sequence, they should be small units of work. Trooper
|
20
|
+
comes with some build in actions but you can of course overwrite them or ignore all together.
|
21
|
+
Actions can also be run locally by adding the local: true option
|
23
22
|
|
24
23
|
```ruby
|
25
|
-
action :
|
26
|
-
|
27
|
-
|
24
|
+
action :migrate_database, 'Migrating database' do
|
25
|
+
cd application_path
|
26
|
+
rake "db:migrate RAILS_ENV=#{environment}"
|
28
27
|
end
|
29
28
|
|
30
|
-
|
31
|
-
|
29
|
+
action :precompile_assets, 'Precompile assets', local: true do
|
30
|
+
run 'do_stuff'
|
31
|
+
rake "assets:precompile"
|
32
32
|
end
|
33
|
+
```
|
33
34
|
|
34
|
-
|
35
|
-
|
36
|
-
|
35
|
+
#### Strategy
|
36
|
+
|
37
|
+
A Strategy is collection of actions to be executed in sequence, A Strategy can call other
|
38
|
+
strategies and have prerequisites.
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
strategy :deploy, "Deploy application" do
|
42
|
+
actions :clone_repository, :install_gems, :migrate_database
|
37
43
|
end
|
38
44
|
```
|
39
45
|
|
46
|
+
Once you've defined the your strategy you can call it e.g. `=> trooper my_strategy_name`
|
47
|
+
|
48
|
+
#### Prerequisite
|
49
|
+
|
50
|
+
A Prerequisite is a Strategy that can only run once per host
|
51
|
+
|
52
|
+
|
40
53
|
#### Example Troopfile
|
41
54
|
|
42
55
|
```ruby
|
@@ -61,12 +74,18 @@ action :restart do
|
|
61
74
|
run "echo 'Restarted'"
|
62
75
|
end
|
63
76
|
|
77
|
+
strategy :bootstrap, "Bootstrap application" do
|
78
|
+
actions :setup_trooper, :clone_repository
|
79
|
+
end
|
80
|
+
|
64
81
|
strategy :update, 'Update the code base on the server' do
|
82
|
+
prerequisites :bootstrap
|
65
83
|
actions :update_repository, :install_gems
|
66
84
|
call :restart
|
67
85
|
end
|
68
86
|
|
69
87
|
strategy :deploy, 'Full deployment to the server' do
|
88
|
+
prerequisites :bootstrap
|
70
89
|
call :update
|
71
90
|
actions :migrate_database
|
72
91
|
call :restart
|
data/lib/trooper/action.rb
CHANGED
@@ -8,13 +8,15 @@ module Trooper
|
|
8
8
|
include Trooper::DSL::Rake
|
9
9
|
include Trooper::DSL::Bundler
|
10
10
|
|
11
|
-
attr_reader :name, :description, :config, :block
|
11
|
+
attr_reader :name, :description, :config, :block, :options
|
12
12
|
attr_accessor :commands
|
13
13
|
|
14
14
|
# Public: Define a new action.
|
15
15
|
#
|
16
16
|
# name - The name of the action.
|
17
17
|
# description - A description of action to be used in the cli output.
|
18
|
+
# options - The Hash options used to refine the selection (default: {}):
|
19
|
+
# :local - A boolean of whether this action should be run locally (optional).
|
18
20
|
# block - A block containing the tasks to run in this action.
|
19
21
|
#
|
20
22
|
# Examples
|
@@ -22,8 +24,8 @@ module Trooper
|
|
22
24
|
# Action.new(:my_action, 'Does great things') { run 'touch file' }
|
23
25
|
#
|
24
26
|
# Returns a new action object.
|
25
|
-
def initialize(name, description, &block)
|
26
|
-
@name, @description, @config = name, description, {}
|
27
|
+
def initialize(name, description, options = {}, &block)
|
28
|
+
@name, @description, @options, @config = name, description, options, {}
|
27
29
|
@commands, @block = [], block
|
28
30
|
end
|
29
31
|
|
@@ -42,6 +44,22 @@ module Trooper
|
|
42
44
|
commands
|
43
45
|
end
|
44
46
|
alias :execute :call
|
47
|
+
|
48
|
+
# Public: Modifies the commands list to include the prerequisite list checker .
|
49
|
+
#
|
50
|
+
# configuration - The configuration object to be used for block eval.
|
51
|
+
#
|
52
|
+
# Examples
|
53
|
+
#
|
54
|
+
# @action.call(config_object) # => "..."
|
55
|
+
#
|
56
|
+
# Returns a command String.
|
57
|
+
def prerequisite_call(configuration)
|
58
|
+
original_commands = call configuration
|
59
|
+
original_commands << "echo '#{self.name}' >> #{prerequisite_list}"
|
60
|
+
original_commands << "echo '#{self.description}'"
|
61
|
+
"touch #{prerequisite_list}; if grep -vz #{self.name} #{prerequisite_list}; then #{original_commands.join(' && ')}; else echo 'Already Done'; fi"
|
62
|
+
end
|
45
63
|
|
46
64
|
# Public: Validates the action object. (NOT WORKING)
|
47
65
|
#
|
@@ -54,6 +72,18 @@ module Trooper
|
|
54
72
|
true
|
55
73
|
end
|
56
74
|
|
75
|
+
# Public: What type of action this is.
|
76
|
+
#
|
77
|
+
# Examples
|
78
|
+
#
|
79
|
+
# @action.type # => :action
|
80
|
+
# @action.type # => :local_action
|
81
|
+
#
|
82
|
+
# Returns a Symbol.
|
83
|
+
def type
|
84
|
+
options && options[:local] ? :local_action : :action
|
85
|
+
end
|
86
|
+
|
57
87
|
# run is the base run command used by the dsl
|
58
88
|
|
59
89
|
# Public: Appends command(string) to commands(array).
|
data/lib/trooper/arsenal.rb
CHANGED
@@ -2,13 +2,30 @@ module Trooper
|
|
2
2
|
|
3
3
|
class Arsenal < Array
|
4
4
|
|
5
|
-
#
|
5
|
+
# Public: Find an item in the arsenal.
|
6
|
+
#
|
7
|
+
# name - The name of the weapon object, weapon object must respond to name.
|
8
|
+
#
|
9
|
+
# Examples
|
10
|
+
#
|
11
|
+
# Arsenal.strategies.find_by_name(:my_stratergy) # => <Strategy>
|
12
|
+
# Arsenal.strategies[:my_stratergy] # => <Strategy>
|
13
|
+
#
|
14
|
+
# Returns the duplicated String.
|
6
15
|
def find_by_name(name)
|
7
16
|
detect { |weapon| weapon.name == name }
|
8
17
|
end
|
9
18
|
alias :[] :find_by_name
|
10
19
|
|
11
|
-
#
|
20
|
+
# Public: Add a 'weapon' to the arsenal.
|
21
|
+
#
|
22
|
+
# weapon - An object that responds to a name method e.g 'weapon.name' .
|
23
|
+
#
|
24
|
+
# Examples
|
25
|
+
#
|
26
|
+
# Arsenal.actions.add(<Action>) # => <Action>
|
27
|
+
#
|
28
|
+
# Returns the weapon passed.
|
12
29
|
def add(weapon)
|
13
30
|
if weapon.ok?
|
14
31
|
remove weapon.name
|
@@ -17,31 +34,64 @@ module Trooper
|
|
17
34
|
weapon
|
18
35
|
end
|
19
36
|
|
20
|
-
#
|
37
|
+
# Public: Removes a 'weapon' from the arsenal.
|
38
|
+
#
|
39
|
+
# name - The name of the arsenal to delete.
|
40
|
+
#
|
41
|
+
# Examples
|
42
|
+
#
|
43
|
+
# Arsenal.actions.remove(:my_action) # => [<Action>]
|
44
|
+
#
|
45
|
+
# Returns self.
|
21
46
|
def remove(name)
|
22
47
|
self.delete_if {|w| w.name == name}
|
23
48
|
end
|
24
49
|
|
25
|
-
#
|
50
|
+
# Public: Clears the arsenals storage array.
|
51
|
+
#
|
52
|
+
# Examples
|
53
|
+
#
|
54
|
+
# Arsenal.strategies.clear! # => []
|
55
|
+
#
|
56
|
+
# Returns an empty array.
|
26
57
|
def clear!
|
27
58
|
self.clear
|
28
59
|
end
|
29
60
|
|
30
61
|
class << self
|
31
62
|
|
32
|
-
#
|
63
|
+
# Public: Storage for the defined strategies.
|
64
|
+
#
|
65
|
+
# Examples
|
66
|
+
#
|
67
|
+
# Arsenal.strategies # => [<Strategy>, <Strategy>]
|
68
|
+
#
|
69
|
+
# Returns the strategies arsenal.
|
33
70
|
def strategies
|
34
71
|
@strategies ||= new
|
35
72
|
end
|
36
73
|
|
37
|
-
#
|
74
|
+
# Public: Storage for the defined actions.
|
75
|
+
#
|
76
|
+
# Examples
|
77
|
+
#
|
78
|
+
# Arsenal.actions # => [<Action>, <Action>]
|
79
|
+
#
|
80
|
+
# Returns the actions arsenal.
|
38
81
|
def actions
|
39
82
|
@actions ||= new
|
40
83
|
end
|
41
84
|
|
42
|
-
#
|
85
|
+
# Public: Clears the arsenals storage of all strategies and actions.
|
86
|
+
#
|
87
|
+
# Examples
|
88
|
+
#
|
89
|
+
# Arsenal.reset! # => true
|
90
|
+
#
|
91
|
+
# Returns true.
|
43
92
|
def reset!
|
44
93
|
@strategies, @actions = nil
|
94
|
+
true
|
45
95
|
end
|
46
96
|
|
47
97
|
end
|
@@ -20,8 +20,8 @@ module Trooper
|
|
20
20
|
Actions::RollbackMigrateAction, Actions::SetupDatabaseAction,
|
21
21
|
Actions::UpdateRepositoryAction]
|
22
22
|
|
23
|
-
def action(name, description = "No Description", &block)
|
24
|
-
action = Trooper::Action.new name, description, &block
|
23
|
+
def action(name, description = "No Description", options = {}, &block)
|
24
|
+
action = Trooper::Action.new name, description, options, &block
|
25
25
|
Trooper::Arsenal.actions.add action
|
26
26
|
end
|
27
27
|
|
@@ -6,6 +6,8 @@ require 'trooper/config/environment'
|
|
6
6
|
require 'trooper/config/strategy'
|
7
7
|
require 'trooper/config/defaults'
|
8
8
|
|
9
|
+
require 'trooper/runner'
|
10
|
+
|
9
11
|
module Trooper
|
10
12
|
class Configuration < Hash
|
11
13
|
|
@@ -13,6 +15,13 @@ module Trooper
|
|
13
15
|
include Trooper::Config::Strategy
|
14
16
|
include Trooper::Config::Defaults
|
15
17
|
|
18
|
+
# Public: Copies the template troopfile to current dir
|
19
|
+
#
|
20
|
+
# Examples
|
21
|
+
#
|
22
|
+
# Configuration.init # => nil
|
23
|
+
#
|
24
|
+
# Returns nil.
|
16
25
|
def self.init
|
17
26
|
gem_dir = File.dirname(__FILE__)
|
18
27
|
|
@@ -25,8 +34,15 @@ module Trooper
|
|
25
34
|
end
|
26
35
|
end
|
27
36
|
|
28
|
-
# initialize a new configuration object, will parse the given troopfile
|
29
|
-
#
|
37
|
+
# Public: initialize a new configuration object, will parse the given troopfile
|
38
|
+
#
|
39
|
+
# options - Default options to combine with troopfile
|
40
|
+
#
|
41
|
+
# Examples
|
42
|
+
#
|
43
|
+
# Configuration.new({:my_override => 'settings'}) # => <Configuration>
|
44
|
+
#
|
45
|
+
# Returns a configuration object.
|
30
46
|
def initialize(options = {})
|
31
47
|
@loaded = false
|
32
48
|
|
@@ -34,31 +50,58 @@ module Trooper
|
|
34
50
|
load_troopfile! options
|
35
51
|
end
|
36
52
|
|
37
|
-
#
|
53
|
+
# Public: Terminal Friendly version of the configuration.
|
54
|
+
#
|
55
|
+
# Examples
|
56
|
+
#
|
57
|
+
# @configuration.to_s # => '...'
|
58
|
+
#
|
59
|
+
# Returns a String.
|
38
60
|
def to_s
|
39
61
|
config.map {|k,v| "#{k}: #{v}" }.join("\n")
|
40
62
|
end
|
41
63
|
|
42
|
-
#
|
64
|
+
# Public: Find and Execute the Strategy.
|
65
|
+
#
|
66
|
+
# strategy_name - The name of the strategy as a symbol.
|
67
|
+
#
|
68
|
+
# Examples
|
69
|
+
#
|
43
70
|
# @config.execute(:my_strategy_name)
|
71
|
+
#
|
72
|
+
# Returns boolean.
|
44
73
|
def execute(strategy_name)
|
45
|
-
Arsenal.strategies[strategy_name]
|
74
|
+
strategy = Arsenal.strategies[strategy_name]
|
75
|
+
Runner.new(strategy, self).execute
|
46
76
|
end
|
47
77
|
|
48
|
-
#
|
49
|
-
#
|
78
|
+
# Public: Set variables that will be available to all actions.
|
79
|
+
#
|
80
|
+
# hash - A key value hash to merge with config
|
81
|
+
#
|
82
|
+
# Examples
|
83
|
+
#
|
84
|
+
# @config.set(:my_variable => 'sdsd') # => available as method in an action
|
85
|
+
#
|
86
|
+
# Returns self.
|
50
87
|
def set(hash)
|
51
88
|
config.merge! hash
|
52
89
|
end
|
53
90
|
|
54
|
-
#
|
91
|
+
# Public: Check to see if troopfile is loaded.
|
92
|
+
#
|
93
|
+
# Examples
|
94
|
+
#
|
95
|
+
# @config.loaded? # => true
|
96
|
+
#
|
97
|
+
# Returns boolean.
|
55
98
|
def loaded?
|
56
99
|
@loaded
|
57
100
|
end
|
58
101
|
|
59
102
|
private
|
60
103
|
|
61
|
-
def config
|
104
|
+
def config # :nodoc:
|
62
105
|
self
|
63
106
|
end
|
64
107
|
|
data/lib/trooper/host.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "net/ssh"
|
4
|
+
require 'open3'
|
5
|
+
|
6
|
+
module Trooper
|
7
|
+
class Host
|
8
|
+
|
9
|
+
attr_reader :host, :user, :connection
|
10
|
+
|
11
|
+
# Public: Initialize a new Host object.
|
12
|
+
#
|
13
|
+
# host - The String of the host location
|
14
|
+
# user - The String of the user name.
|
15
|
+
# options - The Hash of options to pass into Net::SSH
|
16
|
+
# see Net::SSH for details (default: { :forward_agent => true })
|
17
|
+
#
|
18
|
+
# Examples
|
19
|
+
#
|
20
|
+
# Host.new('my.example.com', 'admin', :forward_agent => false)
|
21
|
+
#
|
22
|
+
# Returns a Host object.
|
23
|
+
def initialize(host, user, options = { :forward_agent => true })
|
24
|
+
@host = host
|
25
|
+
@user = user
|
26
|
+
@connection = Net::SSH.start(host, user, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Public: Friendly version of the object.
|
30
|
+
#
|
31
|
+
# Examples
|
32
|
+
#
|
33
|
+
# @host.to_s # => 'foo@bar.com'
|
34
|
+
#
|
35
|
+
# Returns user@host as a String.
|
36
|
+
def to_s
|
37
|
+
"#{user}@#{host}"
|
38
|
+
end
|
39
|
+
|
40
|
+
# Public: Execute a set of commands via net/ssh.
|
41
|
+
#
|
42
|
+
# command - A String or Array of command to run on a remote server
|
43
|
+
# options - The Hash options used to refine the selection (default: {}):
|
44
|
+
# :local - Run the commands on the local machine (optional).
|
45
|
+
#
|
46
|
+
# Examples
|
47
|
+
#
|
48
|
+
# runner.execute(['cd to/path', 'touch file']) # => ['cd to/path && touch file', :stdout, '']
|
49
|
+
# runner.execute('cat file') # => ['cat file', :stdout, 'file content']
|
50
|
+
# runner.execute('cat file', :local => true) # => ['cat file', :stdout, 'file content']
|
51
|
+
#
|
52
|
+
# Returns an array or raises an exception.
|
53
|
+
def execute(command, options = {})
|
54
|
+
options = {} if options == nil
|
55
|
+
|
56
|
+
commands = parse command
|
57
|
+
Trooper.logger.debug commands
|
58
|
+
|
59
|
+
if !options[:local]
|
60
|
+
|
61
|
+
connection.exec! commands do |ch, stream, data|
|
62
|
+
raise Trooper::StdError, "#{data}\n[ERROR INFO] #{commands}" if stream == :stderr
|
63
|
+
ch.wait
|
64
|
+
return [commands, stream, data]
|
65
|
+
end
|
66
|
+
|
67
|
+
else
|
68
|
+
|
69
|
+
if commands != ''
|
70
|
+
begin
|
71
|
+
stdin, stdout, stderr = Open3.popen3(commands)
|
72
|
+
raise Trooper::StdError, "#{stderr.read}\n[ERROR INFO] #{commands}" if stderr.read != ''
|
73
|
+
|
74
|
+
return [commands, :stdout, stdout.read]
|
75
|
+
rescue Exception => e
|
76
|
+
raise Trooper::StdError, "#{stderr}\n[ERROR INFO] #{commands}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Public: Close net/ssh connection.
|
84
|
+
#
|
85
|
+
# Examples
|
86
|
+
#
|
87
|
+
# @host.close # => true
|
88
|
+
#
|
89
|
+
# Returns boolean.
|
90
|
+
def close
|
91
|
+
connection.close
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# parse command, expects a string or array
|
97
|
+
# parse(['cd to/path', 'touch file']) # => 'cd to/path && touch file'
|
98
|
+
def parse(command)
|
99
|
+
case command.class.name.downcase.to_sym #Array => :array
|
100
|
+
when :array
|
101
|
+
command.compact.join(' && ')
|
102
|
+
when :string
|
103
|
+
command.chomp
|
104
|
+
else
|
105
|
+
raise Trooper::MalformedCommandError, "Command Not a String or Array: #{command.inspect}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
data/lib/trooper/logger.rb
CHANGED
@@ -47,7 +47,7 @@ module Trooper
|
|
47
47
|
}
|
48
48
|
|
49
49
|
def call(severity, datetime, progname, message)
|
50
|
-
# DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN < ACTION < SUCCESS
|
50
|
+
# DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN < ACTION < SUCCESS < STRATEGY
|
51
51
|
case severity
|
52
52
|
when "DEBUG"
|
53
53
|
colour("#{progname} => [#{severity}] #{message}\n", :yellow)
|
data/lib/trooper/runner.rb
CHANGED
@@ -1,57 +1,102 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require "net/ssh"
|
4
|
-
|
5
1
|
module Trooper
|
6
2
|
class Runner
|
7
|
-
|
8
|
-
attr_reader :
|
9
|
-
|
10
|
-
#
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
3
|
+
|
4
|
+
attr_reader :strategy, :config, :list
|
5
|
+
|
6
|
+
# Public: initialize a new Runner.
|
7
|
+
#
|
8
|
+
# strategy - A Trooper::Strategy object to execute.
|
9
|
+
# config - A Trooper::Configuration object to use for deployment.
|
10
|
+
#
|
11
|
+
# Examples
|
12
|
+
#
|
13
|
+
# Runner.new(<Strategy>, <Configuration>) # => <Runner>
|
14
|
+
#
|
15
|
+
# Returns a new Runner object.
|
16
|
+
def initialize(strategy, config)
|
17
|
+
@strategy, @config = strategy, config
|
18
|
+
@list = strategy.list config
|
16
19
|
end
|
17
20
|
|
18
|
-
#
|
19
|
-
|
20
|
-
|
21
|
+
# Public: Executes the strategy across mutiple hosts logging output as it goes.
|
22
|
+
#
|
23
|
+
# Examples
|
24
|
+
#
|
25
|
+
# @runner.execute # => true
|
26
|
+
#
|
27
|
+
# Returns a boolean.
|
28
|
+
def execute
|
29
|
+
Trooper.logger.debug "Configuration\n#{config}"
|
30
|
+
Trooper.logger.strategy strategy.description
|
31
|
+
successful = nil
|
32
|
+
|
33
|
+
hosts.each do |host|
|
34
|
+
begin
|
35
|
+
Trooper.logger.info "\e[4mRunning on #{host}\n"
|
36
|
+
|
37
|
+
list.each do |strategy_name, type, name|
|
38
|
+
# strategy_name, type, name
|
39
|
+
commands, options = build_commands strategy_name, type, name
|
40
|
+
runner_execute! host, commands, options if commands
|
41
|
+
end
|
42
|
+
|
43
|
+
successful = true
|
44
|
+
Trooper.logger.success "\e[4mAll Actions Completed\n"
|
45
|
+
rescue Exception => e
|
46
|
+
Trooper.logger.error "#{e.class.to_s} : #{e.message}\n\n#{e.backtrace.join("\n")}"
|
47
|
+
|
48
|
+
successful = false
|
49
|
+
break #stop commands running on other servers
|
50
|
+
ensure
|
51
|
+
host.close
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
successful
|
21
56
|
end
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
#
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# build the commands to be sent to the host object
|
61
|
+
def build_commands(strategy_name, type, action_name)
|
62
|
+
action = Arsenal.actions[action_name]
|
63
|
+
|
64
|
+
if action
|
65
|
+
options = action.options
|
66
|
+
|
67
|
+
case type
|
68
|
+
when :prerequisite
|
69
|
+
commands = action.prerequisite_call config
|
70
|
+
Trooper.logger.action "Prerequisite: #{action.description}"
|
71
|
+
else
|
72
|
+
commands = action.call config
|
73
|
+
Trooper.logger.action action.description
|
74
|
+
end
|
75
|
+
|
76
|
+
[commands, options]
|
77
|
+
else
|
78
|
+
raise MissingActionError, "Cant find action: #{action_name}"
|
33
79
|
end
|
34
80
|
end
|
35
|
-
|
36
|
-
#
|
37
|
-
def
|
38
|
-
|
81
|
+
|
82
|
+
# returns an array of host objects
|
83
|
+
def hosts
|
84
|
+
@hosts ||= begin
|
85
|
+
r, h, u = [], (config[:hosts] rescue nil), (config[:user] rescue nil)
|
86
|
+
h.each {|host| r << Host.new(host, u) } if h && u; r
|
87
|
+
end
|
39
88
|
end
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
when :string
|
50
|
-
command.chomp
|
51
|
-
else
|
52
|
-
raise Trooper::MalformedCommandError, "Command Not a String or Array: #{command.inspect}"
|
89
|
+
|
90
|
+
# runs the commands on a host and deals with output
|
91
|
+
def runner_execute!(host, commands, options = {})
|
92
|
+
result = host.execute commands, options
|
93
|
+
if result && result[1] == :stdout
|
94
|
+
Trooper.logger.info "#{result[2]}\n"
|
95
|
+
true
|
96
|
+
else
|
97
|
+
false
|
53
98
|
end
|
54
99
|
end
|
55
|
-
|
100
|
+
|
56
101
|
end
|
57
|
-
end
|
102
|
+
end
|
data/lib/trooper/strategy.rb
CHANGED
@@ -1,62 +1,53 @@
|
|
1
|
-
require 'trooper/
|
1
|
+
require 'trooper/host'
|
2
2
|
|
3
3
|
module Trooper
|
4
4
|
class Strategy
|
5
5
|
|
6
|
-
attr_reader :name, :description, :config, :run_list
|
6
|
+
attr_reader :name, :description, :config, :run_list, :prereq_run_list, :block
|
7
7
|
|
8
|
+
# Public: Initialize a new Strategy object.
|
9
|
+
#
|
10
|
+
# name - A Symbol of the strategy name
|
11
|
+
# description - A String of what this strategy will do.
|
12
|
+
# config - A Hash of config options expects a Trooper::Configuration object
|
13
|
+
# block - A block to be eval with the strategy object
|
14
|
+
#
|
15
|
+
# Examples
|
16
|
+
#
|
17
|
+
# Strategy.new :my_strategy, 'Does something cool' do
|
18
|
+
# actions :my_action
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# Returns a Host object.
|
8
22
|
def initialize(name, description, config = {}, &block)
|
9
23
|
@name, @description, @config = name, description, config
|
10
|
-
@run_list = []
|
11
|
-
@block = block if block_given?
|
12
|
-
end
|
13
|
-
|
14
|
-
def execute(config = {})
|
15
|
-
@config = config
|
16
|
-
|
17
|
-
eval_block
|
18
|
-
|
19
|
-
Trooper.logger.debug "Configuration\n#{config}"
|
20
|
-
Trooper.logger.strategy description
|
21
|
-
successful = nil
|
22
|
-
|
23
|
-
runners.each do |runner|
|
24
|
-
begin
|
25
|
-
Trooper.logger.info "\e[4mRunning on #{runner}\n"
|
26
|
-
|
27
|
-
run_list.each do |strategy, type, name|
|
28
|
-
commands = build_commands strategy, type, name
|
29
|
-
runner_execute! runner, commands if commands
|
30
|
-
end
|
31
|
-
|
32
|
-
successful = true
|
33
|
-
Trooper.logger.success "\e[4mAll Actions Completed\n"
|
34
|
-
rescue Exception => e
|
35
|
-
Trooper.logger.error "#{e.class.to_s} : #{e.message}\n\n#{e.backtrace.join("\n")}"
|
36
|
-
|
37
|
-
successful = false
|
38
|
-
break #stop commands running on other servers
|
39
|
-
ensure
|
40
|
-
runner.close
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
successful
|
45
|
-
end
|
46
|
-
|
47
|
-
def build_run_list
|
48
|
-
eval_block
|
49
|
-
run_list
|
24
|
+
@run_list, @prereq_run_list, @block = [], [], block
|
50
25
|
end
|
51
26
|
|
27
|
+
# Public: Validates the strategy object. (NOT WORKING)
|
28
|
+
#
|
29
|
+
# Examples
|
30
|
+
#
|
31
|
+
# @strategy.ok? # => true
|
32
|
+
#
|
33
|
+
# Returns true.
|
52
34
|
def ok?
|
53
35
|
true
|
54
36
|
end
|
55
37
|
|
38
|
+
# Public: Add other strategies actions to the run list.
|
39
|
+
#
|
40
|
+
# strategy_names - A list of other Strategy names.
|
41
|
+
#
|
42
|
+
# Examples
|
43
|
+
#
|
44
|
+
# @strategy.call(:my_other_strategy) # => nil
|
45
|
+
#
|
46
|
+
# Returns nil.
|
56
47
|
def call(*strategy_names)
|
57
48
|
[*strategy_names].each do |strategy_name|
|
58
49
|
if Arsenal.strategies[strategy_name]
|
59
|
-
Arsenal.strategies[strategy_name].
|
50
|
+
Arsenal.strategies[strategy_name].list(config).each do |action|
|
60
51
|
# strategy_name, type, name
|
61
52
|
@run_list << action
|
62
53
|
end
|
@@ -64,75 +55,103 @@ module Trooper
|
|
64
55
|
end
|
65
56
|
end
|
66
57
|
|
58
|
+
# Public: Add other strategies actions to the prerequisite run list.
|
59
|
+
#
|
60
|
+
# strategy_names - A list of other Strategy names.
|
61
|
+
#
|
62
|
+
# Examples
|
63
|
+
#
|
64
|
+
# @strategy.prerequisites(:my_other_strategy) # => nil
|
65
|
+
#
|
66
|
+
# Returns nil.
|
67
67
|
def prerequisites(*strategy_names)
|
68
|
-
|
69
|
-
@
|
68
|
+
if @prereq_run_list == []
|
69
|
+
@prereq_run_list = [[@name, :action, :prepare_prerequisite]]
|
70
70
|
end
|
71
|
+
|
71
72
|
[*strategy_names].each do |strategy_name|
|
72
73
|
if Arsenal.strategies[strategy_name]
|
73
|
-
Arsenal.strategies[strategy_name].
|
74
|
+
Arsenal.strategies[strategy_name].list(config).each do |action|
|
74
75
|
# strategy_name, type, name
|
75
|
-
@
|
76
|
+
@prereq_run_list << [action[0], :prerequisite, action[2]]
|
76
77
|
end
|
77
78
|
end
|
78
79
|
end
|
79
80
|
end
|
80
81
|
|
82
|
+
# Public: Add actions to the run list.
|
83
|
+
#
|
84
|
+
# action_names - A list of Action names.
|
85
|
+
#
|
86
|
+
# Examples
|
87
|
+
#
|
88
|
+
# @strategy.actions(:my_action) # => nil
|
89
|
+
#
|
90
|
+
# Returns nil.
|
81
91
|
def actions(*action_names)
|
82
92
|
[*action_names].each do |name|
|
83
|
-
|
84
|
-
|
93
|
+
if Arsenal.actions[name]
|
94
|
+
# strategy_name, type, name
|
95
|
+
@run_list << [self.name, Arsenal.actions[name].type, name]
|
96
|
+
end
|
85
97
|
end
|
86
98
|
end
|
87
99
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
100
|
+
# Public: Add an Action to the Task list but scoped to this Strategy
|
101
|
+
# (Action Not available outside this object).
|
102
|
+
#
|
103
|
+
# name - The name of the action.
|
104
|
+
# description - A description of action to be used in the cli output.
|
105
|
+
# block - A block containing the tasks to run in this action.
|
106
|
+
#
|
107
|
+
# Examples
|
108
|
+
#
|
109
|
+
# @strategy.action(:my_action, 'Does great things') { run 'touch file' }
|
110
|
+
#
|
111
|
+
# Returns an Action object.
|
112
|
+
def action(name, description = "No Description", &block)
|
113
|
+
action_name = "#{self.name}_#{name}".to_sym
|
114
|
+
|
115
|
+
action = Trooper::Action.new action_name, description, &block
|
116
|
+
Arsenal.actions.add action
|
117
|
+
actions action_name
|
118
|
+
|
119
|
+
action
|
120
|
+
end
|
93
121
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
raise MissingActionError, "Cant find action: #{action_name}"
|
107
|
-
end
|
122
|
+
# Public: The Task List.
|
123
|
+
#
|
124
|
+
# configuration - A optional Trooper::Configuration object.
|
125
|
+
#
|
126
|
+
# Examples
|
127
|
+
#
|
128
|
+
# @strategy.list() # => [[:my_strategy, :action, :my_strategy_my_new_action]]
|
129
|
+
#
|
130
|
+
# Returns and Array of Arrays.
|
131
|
+
def list(configuration = {})
|
132
|
+
build_list(configuration) if run_list == []
|
133
|
+
prereq_run_list + run_list
|
108
134
|
end
|
109
135
|
|
110
|
-
def method_missing(method_sym, *arguments, &block)
|
136
|
+
def method_missing(method_sym, *arguments, &block) # :nodoc:
|
111
137
|
config[method_sym] || super
|
112
138
|
end
|
113
139
|
|
114
140
|
private
|
115
141
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
142
|
+
# builds the task list by evaling the block passed on initialize
|
143
|
+
def build_list(configuration)
|
144
|
+
@config = configuration
|
145
|
+
eval_block(&block)
|
120
146
|
end
|
121
147
|
|
122
|
-
def
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
def runner_execute!(runner, commands, options = {})
|
130
|
-
result = runner.execute commands, options
|
131
|
-
if result && result[1] == :stdout
|
132
|
-
Trooper.logger.info "#{result[2]}\n"
|
133
|
-
true
|
134
|
-
else
|
135
|
-
false
|
148
|
+
def eval_block(&block) # :nodoc:
|
149
|
+
if block_given?
|
150
|
+
if block.arity == 1
|
151
|
+
block.call self
|
152
|
+
else
|
153
|
+
instance_eval &block
|
154
|
+
end
|
136
155
|
end
|
137
156
|
end
|
138
157
|
|
@@ -10,29 +10,24 @@ repository 'git@git.bar.co.uk:whatever.git'
|
|
10
10
|
# Set environment specific settings and or actions
|
11
11
|
# env :stage do
|
12
12
|
# hosts 'stage.example.com', 'stage.example.com'
|
13
|
-
# set :my_value => 'something_else'
|
14
|
-
|
15
|
-
# action :my_action do
|
16
|
-
# #some action
|
17
|
-
# end
|
18
13
|
# end
|
19
14
|
|
20
|
-
# action :my_action do
|
15
|
+
# action :my_action, 'Do something locally', local: true do
|
21
16
|
# #some action
|
22
17
|
# end
|
23
18
|
|
24
|
-
strategy :
|
19
|
+
strategy :bootstrap, 'Bootstraps the Application Server' do
|
25
20
|
actions :setup_trooper, :clone_repository, :setup_database
|
26
21
|
end
|
27
22
|
|
28
23
|
strategy :update, 'Update the code base on the server' do
|
29
|
-
prerequisites :
|
24
|
+
prerequisites :bootstrap
|
30
25
|
actions :update_repository, :install_gems
|
31
26
|
call :restart
|
32
27
|
end
|
33
28
|
|
34
29
|
strategy :deploy, 'Full deployment to the server' do
|
35
|
-
prerequisites :
|
30
|
+
prerequisites :bootstrap
|
36
31
|
call :update
|
37
32
|
actions :migrate_database
|
38
33
|
call :restart
|
@@ -40,4 +35,28 @@ end
|
|
40
35
|
|
41
36
|
strategy :restart, 'Restart application' do
|
42
37
|
actions :restart
|
43
|
-
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Set environment specific settings and or actions
|
41
|
+
# env :stage do
|
42
|
+
# hosts 'stage.example.com', 'stage.example.com'
|
43
|
+
# set :my_value => 'something_else'
|
44
|
+
|
45
|
+
# action :my_action do
|
46
|
+
# #something different for the stage env
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
|
50
|
+
# action :my_action do
|
51
|
+
# #some action
|
52
|
+
# end
|
53
|
+
|
54
|
+
# strategy :my_strategy, 'It does something cool' do
|
55
|
+
# prerequisites :bootstrap
|
56
|
+
# actions :update_repository, :install_gems # use some built in actions
|
57
|
+
# #define my own action
|
58
|
+
# action :my_other_action, 'Only avaliable in this strategy scope' do
|
59
|
+
# rake 'sometask'
|
60
|
+
# end
|
61
|
+
# call :restart
|
62
|
+
# end
|
data/lib/trooper/version.rb
CHANGED
data/lib/trooper.rb
CHANGED
@@ -23,10 +23,11 @@ module Trooper
|
|
23
23
|
class StdError < TrooperError
|
24
24
|
end
|
25
25
|
|
26
|
-
# When a command is not formed corrently arrays or strings are exceptal commands
|
26
|
+
# When a command is not formed corrently, arrays or strings are exceptal commands
|
27
27
|
class MalformedCommandError < TrooperError
|
28
28
|
end
|
29
29
|
|
30
|
+
# When a configuration file is missing
|
30
31
|
class NoConfigurationFileError < TrooperError
|
31
32
|
end
|
32
33
|
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trooper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.0
|
5
|
-
prerelease:
|
4
|
+
version: 0.6.0
|
5
|
+
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Richard Adams
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-07-
|
12
|
+
date: 2012-07-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: net-ssh
|
@@ -184,6 +184,7 @@ files:
|
|
184
184
|
- lib/trooper/dsl/bundler.rb
|
185
185
|
- lib/trooper/dsl/folders.rb
|
186
186
|
- lib/trooper/dsl/rake.rb
|
187
|
+
- lib/trooper/host.rb
|
187
188
|
- lib/trooper/logger.rb
|
188
189
|
- lib/trooper/runner.rb
|
189
190
|
- lib/trooper/strategy.rb
|
@@ -208,13 +209,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
208
209
|
version: '0'
|
209
210
|
segments:
|
210
211
|
- 0
|
211
|
-
hash: -
|
212
|
+
hash: -566483630194322455
|
212
213
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
213
214
|
none: false
|
214
215
|
requirements:
|
215
|
-
- - ! '
|
216
|
+
- - ! '>='
|
216
217
|
- !ruby/object:Gem::Version
|
217
|
-
version:
|
218
|
+
version: '0'
|
219
|
+
segments:
|
220
|
+
- 0
|
221
|
+
hash: -566483630194322455
|
218
222
|
requirements: []
|
219
223
|
rubyforge_project:
|
220
224
|
rubygems_version: 1.8.22
|