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 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 'trooper'`
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 staging` or `=> trooper update`
15
+ `=> trooper deploy -e stage` or `=> trooper update`
16
+
17
+ #### Action
21
18
 
22
- #### Define your own strategies and actions
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 :restart_server, 'Restarting the server' do
26
- run 'touch tmp/restart.txt'
27
- run "echo 'Restarted'"
24
+ action :migrate_database, 'Migrating database' do
25
+ cd application_path
26
+ rake "db:migrate RAILS_ENV=#{environment}"
28
27
  end
29
28
 
30
- strategy :restart, 'Restart application' do
31
- actions :restart_server
29
+ action :precompile_assets, 'Precompile assets', local: true do
30
+ run 'do_stuff'
31
+ rake "assets:precompile"
32
32
  end
33
+ ```
33
34
 
34
- strategy :update, 'Update the code base on the server' do
35
- actions :update_repository, :install_gems
36
- call :restart # call another strategy
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
@@ -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).
@@ -2,13 +2,30 @@ module Trooper
2
2
 
3
3
  class Arsenal < Array
4
4
 
5
- # find an arsenal by its name
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
- # add a weapon to the arsenal
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
- # remove a weapon from the arsenal
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
- # clears arsenal
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
- # returns the strategies arsenal
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
- # returns the strategies arsenal
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
- # clears all default arsenal
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
- # Configuration.new({:my_override => 'settings'})
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
- # returns a terminal friendly version of the configuration
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
- # will find and execute the strategy name passed
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].execute self
74
+ strategy = Arsenal.strategies[strategy_name]
75
+ Runner.new(strategy, self).execute
46
76
  end
47
77
 
48
- # a way to set variables that will be available to all actions
49
- # set(:my_variable => 'sdsd') # => available as method in an action
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
- # returns true if the troopfile was loaded
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
 
@@ -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
@@ -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)
@@ -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 :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)
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
- # returns user@host as a string
19
- def to_s
20
- "#{user}@#{host}"
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
- # 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]
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
- # close net/ssh connection
37
- def close
38
- connection.close
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
- 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}"
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
@@ -1,62 +1,53 @@
1
- require 'trooper/runner'
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].build_run_list.each do |action|
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
- unless @run_list.detect { |a| a[2] == :prepare_prerequisite }
69
- @run_list << [@name, :action, :prepare_prerequisite]
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].build_run_list.each do |action|
74
+ Arsenal.strategies[strategy_name].list(config).each do |action|
74
75
  # strategy_name, type, name
75
- @run_list << [action[0], :prerequisite, action[2]]
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
- # strategy_name, type, name
84
- @run_list << [self.name, :action, name]
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
- def build_commands(strategy_name, type, action_name)
89
- action = Arsenal.actions[action_name]
90
-
91
- if action
92
- commands = action.call config
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
- case type
95
- when :prerequisite
96
- commands << "echo '#{action.name}' >> #{prerequisite_list}"
97
- commands << "echo '#{action.description}'"
98
- commands = "touch #{prerequisite_list}; if grep -vz #{action_name} #{prerequisite_list}; then #{commands.join(' && ')}; else echo 'Already Done'; fi"
99
- Trooper.logger.action "Prerequisite: #{action.description}"
100
- else
101
- Trooper.logger.action action.description
102
- end
103
-
104
- commands
105
- else
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
- def eval_block
117
- if @block
118
- instance_eval &@block
119
- end
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 runners
123
- @runners ||= begin
124
- r, h, u = [], (hosts rescue nil), (user rescue nil)
125
- h.each {|host| r << Runner.new(host, user) } if h && u; r
126
- end
127
- end
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 :setup, 'Setting up Application on the Server' do
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 :setup
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 :setup
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
@@ -3,7 +3,7 @@ module Trooper
3
3
  MAJOR = 0
4
4
  MINOR = 6
5
5
  TINY = 0
6
- PRE = 'beta'
6
+ PRE = nil
7
7
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
8
8
  end
9
9
  end
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.beta
5
- prerelease: 6
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-05 00:00:00.000000000 Z
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: -2382425093214391347
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: 1.3.1
218
+ version: '0'
219
+ segments:
220
+ - 0
221
+ hash: -566483630194322455
218
222
  requirements: []
219
223
  rubyforge_project:
220
224
  rubygems_version: 1.8.22