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