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 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