sprinkle 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/CREDITS +2 -1
  2. data/Manifest.txt +11 -2
  3. data/{README.rdoc → README.txt} +7 -1
  4. data/bin/sprinkle +7 -0
  5. data/config/hoe.rb +2 -2
  6. data/lib/sprinkle/actors/actors.rb +17 -0
  7. data/lib/sprinkle/actors/capistrano.rb +41 -4
  8. data/lib/sprinkle/actors/vlad.rb +39 -4
  9. data/lib/sprinkle/configurable.rb +31 -0
  10. data/lib/sprinkle/deployment.rb +46 -6
  11. data/lib/sprinkle/extensions/arbitrary_options.rb +1 -1
  12. data/lib/sprinkle/extensions/array.rb +1 -3
  13. data/lib/sprinkle/extensions/blank_slate.rb +1 -1
  14. data/lib/sprinkle/extensions/dsl_accessor.rb +1 -1
  15. data/lib/sprinkle/extensions/string.rb +1 -1
  16. data/lib/sprinkle/extensions/symbol.rb +1 -1
  17. data/lib/sprinkle/installers/apt.rb +29 -3
  18. data/lib/sprinkle/installers/gem.rb +34 -6
  19. data/lib/sprinkle/installers/installer.rb +64 -29
  20. data/lib/sprinkle/installers/rake.rb +15 -2
  21. data/lib/sprinkle/installers/rpm.rb +20 -3
  22. data/lib/sprinkle/installers/source.rb +74 -16
  23. data/lib/sprinkle/package.rb +127 -2
  24. data/lib/sprinkle/policy.rb +42 -2
  25. data/lib/sprinkle/script.rb +11 -1
  26. data/lib/sprinkle/verifiers/directory.rb +16 -0
  27. data/lib/sprinkle/verifiers/executable.rb +36 -0
  28. data/lib/sprinkle/verifiers/file.rb +20 -0
  29. data/lib/sprinkle/verifiers/process.rb +21 -0
  30. data/lib/sprinkle/verifiers/ruby.rb +25 -0
  31. data/lib/sprinkle/verifiers/symlink.rb +30 -0
  32. data/lib/sprinkle/verify.rb +114 -0
  33. data/lib/sprinkle/version.rb +1 -1
  34. data/lib/sprinkle.rb +8 -2
  35. data/spec/sprinkle/actors/capistrano_spec.rb +21 -1
  36. data/spec/sprinkle/installers/gem_spec.rb +1 -1
  37. data/spec/sprinkle/installers/installer_spec.rb +55 -29
  38. data/spec/sprinkle/package_spec.rb +137 -0
  39. data/spec/sprinkle/verify_spec.rb +160 -0
  40. data/sprinkle.gemspec +6 -6
  41. metadata +14 -4
  42. data/examples/merb/deploy.rb +0 -5
data/CREDITS CHANGED
@@ -13,4 +13,5 @@ Matt Allen (http://blog.allen.com.au)
13
13
  Eric Hodel (http://blog.segment7.net)
14
14
  Pete Yandell (http://notahat.com)
15
15
  Adam Meehan (http://duckpunching.com)
16
- Mitchell Hashimoto (http://mitchellhashimoto.com)
16
+ Mitchell Hashimoto (http://mitchellhashimoto.com)
17
+ Ari Lerner (http://www.citrusbyte.com)
data/Manifest.txt CHANGED
@@ -2,12 +2,11 @@ CREDITS
2
2
  History.txt
3
3
  MIT-LICENSE
4
4
  Manifest.txt
5
- README.rdoc
5
+ README.txt
6
6
  Rakefile
7
7
  bin/sprinkle
8
8
  config/hoe.rb
9
9
  config/requirements.rb
10
- examples/merb/deploy.rb
11
10
  examples/rails/README
12
11
  examples/rails/deploy.rb
13
12
  examples/rails/packages/database.rb
@@ -18,8 +17,10 @@ examples/rails/packages/server.rb
18
17
  examples/rails/rails.rb
19
18
  examples/sprinkle/sprinkle.rb
20
19
  lib/sprinkle.rb
20
+ lib/sprinkle/actors/actors.rb
21
21
  lib/sprinkle/actors/capistrano.rb
22
22
  lib/sprinkle/actors/vlad.rb
23
+ lib/sprinkle/configurable.rb
23
24
  lib/sprinkle/deployment.rb
24
25
  lib/sprinkle/extensions/arbitrary_options.rb
25
26
  lib/sprinkle/extensions/array.rb
@@ -36,6 +37,13 @@ lib/sprinkle/installers/source.rb
36
37
  lib/sprinkle/package.rb
37
38
  lib/sprinkle/policy.rb
38
39
  lib/sprinkle/script.rb
40
+ lib/sprinkle/verifiers/directory.rb
41
+ lib/sprinkle/verifiers/executable.rb
42
+ lib/sprinkle/verifiers/file.rb
43
+ lib/sprinkle/verifiers/process.rb
44
+ lib/sprinkle/verifiers/ruby.rb
45
+ lib/sprinkle/verifiers/symlink.rb
46
+ lib/sprinkle/verify.rb
39
47
  lib/sprinkle/version.rb
40
48
  script/destroy
41
49
  script/generate
@@ -54,6 +62,7 @@ spec/sprinkle/package_spec.rb
54
62
  spec/sprinkle/policy_spec.rb
55
63
  spec/sprinkle/script_spec.rb
56
64
  spec/sprinkle/sprinkle_spec.rb
65
+ spec/sprinkle/verify_spec.rb
57
66
  sprinkle.gemspec
58
67
  tasks/deployment.rake
59
68
  tasks/environment.rake
@@ -20,10 +20,16 @@ An example package description follows:
20
20
  version '1.8.6'
21
21
  source "ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-#{version}-p111.tar.gz"
22
22
  requires :ruby_dependencies
23
+
24
+ verify do
25
+ has_file '/usr/bin/ruby'
26
+ end
23
27
  end
24
28
 
25
29
  This defines a package called 'ruby', that uses the source based installer to build Ruby 1.8.6 from source,
26
- installing the package 'ruby_dependencies' beforehand.
30
+ installing the package 'ruby_dependencies' beforehand. Additionally, the package verifies it was installed
31
+ correctly by verifying the file '/usr/bin/ruby' exists after installation. If this verification fails, the
32
+ sprinkle script will gracefully stop.
27
33
 
28
34
  Reasonable defaults are set by sprinkle, such as the install prefix, download area, etc, but can be customized
29
35
  globally or per package (see below for an example).
data/bin/sprinkle CHANGED
@@ -46,6 +46,8 @@ BANNER
46
46
  "Verbose output") { |OPTIONS[:verbose]| }
47
47
  opts.on("-c", "--cloud",
48
48
  "Show powder cloud, ie. package hierarchy and installation order") { |OPTIONS[:cloud]| }
49
+ opts.on("-f", "--force",
50
+ "Force installation of all packages even if it is detected that it has been previously installed") { |OPTIONS[:force]| }
49
51
  opts.on("-h", "--help",
50
52
  "Show this help message.") { puts opts; exit }
51
53
  opts.parse!(ARGV)
@@ -55,6 +57,10 @@ BANNER
55
57
  end
56
58
  end
57
59
 
60
+ def force_mode(options)
61
+ Sprinkle::OPTIONS[:force] = OPTIONS[:force] || false
62
+ end
63
+
58
64
  def operation_mode(options)
59
65
  Sprinkle::OPTIONS[:testing] = OPTIONS[:testing] || false
60
66
  end
@@ -72,6 +78,7 @@ require 'sprinkle'
72
78
  powder = OPTIONS[:path]
73
79
  raise "Sprinkle script is not readable: #{powder}" unless File.readable?(powder)
74
80
 
81
+ force_mode(OPTIONS)
75
82
  operation_mode(OPTIONS)
76
83
  powder_cloud(OPTIONS)
77
84
  log_level(OPTIONS)
data/config/hoe.rb CHANGED
@@ -35,7 +35,7 @@ VERS = Sprinkle::VERSION::STRING + (REV ? ".#{REV}" : "")
35
35
  RDOC_OPTS = ['--quiet', '--title', 'sprinkle documentation',
36
36
  "--opname", "index.html",
37
37
  "--line-numbers",
38
- "--main", "README",
38
+ "--main", "README.txt",
39
39
  "--inline-source"]
40
40
 
41
41
  class Hoe
@@ -59,7 +59,7 @@ hoe = Hoe.new(GEM_NAME, VERS) do |p|
59
59
  # == Optional
60
60
  p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
61
61
  p.extra_deps = [ ['activesupport', '>= 2.0.2'], ['highline', '>= 1.4.0'], ['capistrano', '>= 2.2.0'] ] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
62
-
62
+
63
63
  #p.spec_extras = {} # A hash of extra values to set in the gemspec.
64
64
 
65
65
  end
@@ -0,0 +1,17 @@
1
+ #--
2
+ # The only point of this file is to give RDoc a definition for
3
+ # Sprinkle::Actors. This file in production is never actually included
4
+ # since ActiveSupport only on-demand loads classes which are needed
5
+ # and this module is never explicitly needed.
6
+ #++
7
+
8
+ module Sprinkle
9
+ # An actor is a method of command delivery to a remote machine. It is the
10
+ # layer between sprinkle and the SSH connection to run commands. This gives
11
+ # you the flexibility to define custom actors, for whatever purpose you need.
12
+ #
13
+ # 99% of the time, however, the two built-in actors Sprinkle::Actors::Capistrano
14
+ # and Sprinkle::Actors::Vlad will be enough.
15
+ module Actors
16
+ end
17
+ end
@@ -2,10 +2,26 @@ require 'capistrano/cli'
2
2
 
3
3
  module Sprinkle
4
4
  module Actors
5
+ # = Capistrano Delivery Method
6
+ #
7
+ # Capistrano is one of the delivery method options available out of the
8
+ # box with Sprinkle. If you have the capistrano gem install, you may use
9
+ # this delivery. The only configuration option available, and which is
10
+ # mandatory to include is +recipes+. An example:
11
+ #
12
+ # deployment do
13
+ # delivery :capistrano do
14
+ # recipes 'deploy'
15
+ # end
16
+ # end
17
+ #
18
+ # Recipes is given a list of files which capistrano will include and load.
19
+ # These recipes are mainly to set variables such as :user, :password, and to
20
+ # set the app domain which will be sprinkled.
5
21
  class Capistrano
6
- attr_accessor :config, :loaded_recipes
22
+ attr_accessor :config, :loaded_recipes #:nodoc:
7
23
 
8
- def initialize(&block)
24
+ def initialize(&block) #:nodoc:
9
25
  @config = ::Capistrano::Configuration.new
10
26
  @config.logger.level = Sprinkle::OPTIONS[:verbose] ? ::Capistrano::Logger::INFO : ::Capistrano::Logger::IMPORTANT
11
27
  @config.set(:password) { ::Capistrano::CLI.password_prompt }
@@ -16,20 +32,41 @@ module Sprinkle
16
32
  end
17
33
  end
18
34
 
35
+ # Defines a recipe file which will be included by capistrano. Use these
36
+ # recipe files to set capistrano specific configurations. Default recipe
37
+ # included is "deploy." But if any other recipe is specified, it will
38
+ # include that instead. Multiple recipes may be specified through multiple
39
+ # recipes calls, an example:
40
+ #
41
+ # deployment do
42
+ # delivery :capistrano do
43
+ # recipes 'deploy'
44
+ # recipes 'magic_beans'
45
+ # end
46
+ # end
19
47
  def recipes(script)
20
48
  @loaded_recipes ||= []
21
49
  @config.load script
22
50
  @loaded_recipes << script
23
51
  end
24
52
 
25
- def process(name, commands, roles)
53
+ def process(name, commands, roles, suppress_and_return_failures = false) #:nodoc:
26
54
  define_task(name, roles) do
27
55
  via = fetch(:run_method, :sudo)
28
56
  commands.each do |command|
29
57
  invoke_command command, :via => via
30
58
  end
31
59
  end
32
- run(name)
60
+
61
+ begin
62
+ run(name)
63
+ return true
64
+ rescue ::Capistrano::CommandError => e
65
+ return false if suppress_and_return_failures
66
+
67
+ # Reraise error if we're not suppressing it
68
+ raise
69
+ end
33
70
  end
34
71
 
35
72
  private
@@ -1,23 +1,58 @@
1
1
  module Sprinkle
2
2
  module Actors
3
+ # = Vlad Delivery Method
4
+ #
5
+ # Vlad is one of the delivery method options available out of the
6
+ # box with Sprinkle. If you have the vlad the deployer gem install, you
7
+ # may use this delivery. The only configuration option available, and
8
+ # which is mandatory to include is +script+. An example:
9
+ #
10
+ # deployment do
11
+ # delivery :vlad do
12
+ # script 'deploy'
13
+ # end
14
+ # end
15
+ #
16
+ # script is given a list of files which capistrano will include and load.
17
+ # These recipes are mainly to set variables such as :user, :password, and to
18
+ # set the app domain which will be sprinkled.
3
19
  class Vlad
4
20
  require 'vlad'
5
- attr_accessor :loaded_recipes
21
+ attr_accessor :loaded_recipes #:nodoc:
6
22
 
7
- def initialize(&block)
23
+ def initialize(&block) #:nodoc:
8
24
  self.instance_eval &block if block
9
25
  end
10
26
 
27
+ # Defines a script file which will be included by vlad. Use these
28
+ # script files to set vlad specific configurations. Multiple scripts
29
+ # may be specified through multiple script calls, an example:
30
+ #
31
+ # deployment do
32
+ # delivery :vlad do
33
+ # script 'deploy'
34
+ # script 'magic_beans'
35
+ # end
36
+ # end
11
37
  def script(name)
12
38
  @loaded_recipes ||= []
13
39
  self.load name
14
40
  @loaded_recipes << script
15
41
  end
16
42
 
17
- def process(name, commands, roles)
43
+ def process(name, commands, roles, suppress_and_return_failures = false) #:nodoc:
18
44
  commands = commands.join ' && ' if commands.is_a? Array
19
45
  t = remote_task(task_sym(name), :roles => roles) { run commands }
20
- t.invoke
46
+
47
+ begin
48
+ t.invoke
49
+ return true
50
+ rescue ::Vlad::CommandFailedError => e
51
+ return false if suppress_and_return_failures
52
+
53
+ # Reraise error if we're not suppressing it
54
+ raise
55
+ end
21
56
  end
22
57
 
23
58
  private
@@ -0,0 +1,31 @@
1
+ module Sprinkle
2
+ #--
3
+ # TODO: Possible documentation?
4
+ #++
5
+ module Configurable #:nodoc:
6
+ attr_accessor :delivery
7
+
8
+ def defaults(deployment)
9
+ defaults = deployment.defaults[self.class.name.split(/::/).last.downcase.to_sym]
10
+ self.instance_eval(&defaults) if defaults
11
+ @delivery = deployment.style
12
+ end
13
+
14
+ def assert_delivery
15
+ raise 'Unknown command delivery target' unless @delivery
16
+ end
17
+
18
+ def method_missing(sym, *args, &block)
19
+ unless args.empty? # mutate if not set
20
+ @options ||= {}
21
+ @options[sym] = *args unless @options[sym]
22
+ end
23
+
24
+ @options[sym] || @package.send(sym, *args, &block) # try the parents options if unknown
25
+ end
26
+
27
+ def option?(sym)
28
+ !@options[sym].nil?
29
+ end
30
+ end
31
+ end
@@ -1,29 +1,69 @@
1
1
  module Sprinkle
2
+ # Deployment blocks specify deployment specific information about a
3
+ # sprinkle script. An example:
4
+ #
5
+ # deployment do
6
+ # # mechanism for deployment
7
+ # delivery :capistrano do
8
+ # recipes 'deploy'
9
+ # end
10
+ #
11
+ # # source based package installer defaults
12
+ # source do
13
+ # prefix '/usr/local'
14
+ # archives '/usr/local/sources'
15
+ # builds '/usr/local/build'
16
+ # end
17
+ # end
18
+ #
19
+ # What the above example does is tell sprinkle that we will be using
20
+ # *capistrano* (Sprinkle::Actors::Capistrano) for deployment and
21
+ # everything within the block is capistrano specific configuration.
22
+ # For more information on what options are available, check the corresponding
23
+ # Sprinkle::Actors doc page.
24
+ #
25
+ # In addition to what delivery mechanism we're using, we specify some
26
+ # configuration options for the "source" command. The only things
27
+ # configurable, at this time, in the deployment block other than
28
+ # the delivery method are installers. If installers are configurable,
29
+ # they will say so on their corresponding documentation page. See
30
+ # Sprinkle::Installers
31
+ #
32
+ # <b>Only one deployment block is on any given sprinkle script</b>
2
33
  module Deployment
34
+ # The method outlined above which specifies deployment specific information
35
+ # for a sprinkle script. For more information, read the header of this module.
3
36
  def deployment(&block)
4
37
  @deployment = Deployment.new(&block)
5
38
  end
6
39
 
7
40
  class Deployment
8
- attr_accessor :style, :defaults
41
+ attr_accessor :style, :defaults #:nodoc:
9
42
 
10
- def initialize(&block)
43
+ def initialize(&block) #:nodoc:
11
44
  @defaults = {}
12
45
  self.instance_eval(&block)
13
46
  raise 'No delivery mechanism defined' unless @style
14
47
  end
15
48
 
16
- def delivery(type, &block)
49
+ # Specifies which Sprinkle::Actors to use for delivery. Although all
50
+ # actors jobs are the same: to run remote commands on a server, you
51
+ # may have a personal preference. The block you pass is used to configure
52
+ # the actor. For more information on what configuration options are
53
+ # available, view the corresponding Sprinkle::Actors page.
54
+ def delivery(type, &block) #:doc:
17
55
  @style = Actors.const_get(type.to_s.titleize).new &block
18
56
  end
19
57
 
20
- def method_missing(sym, *args, &block)
58
+ def method_missing(sym, *args, &block) #:nodoc:
21
59
  @defaults[sym] = block
22
60
  end
23
61
 
24
- def respond_to?(sym); !!@defaults[sym]; end
62
+ def respond_to?(sym) #:nodoc:
63
+ !!@defaults[sym]
64
+ end
25
65
 
26
- def process
66
+ def process #:nodoc:
27
67
  POLICIES.each do |policy|
28
68
  policy.process(self)
29
69
  end
@@ -1,4 +1,4 @@
1
- module ArbitraryOptions
1
+ module ArbitraryOptions #:nodoc:
2
2
  def self.included(base)
3
3
  base.alias_method_chain :method_missing, :arbitrary_options
4
4
  end
@@ -1,7 +1,5 @@
1
- class Array
2
-
1
+ class Array #:nodoc:
3
2
  def to_task_name
4
3
  collect(&:to_task_name).join('_')
5
4
  end
6
-
7
5
  end
@@ -1,4 +1,4 @@
1
- class BlankSlate
1
+ class BlankSlate #:nodoc:
2
2
  instance_methods.each do |m|
3
3
  undef_method(m) unless %w( __send__ __id__ send class inspect instance_eval instance_variables ).include?(m)
4
4
  end
@@ -1,4 +1,4 @@
1
- class Module
1
+ class Module #:nodoc:
2
2
  def dsl_accessor(*symbols)
3
3
  symbols.each do |sym|
4
4
  class_eval %{
@@ -1,4 +1,4 @@
1
- class String
1
+ class String #:nodoc:
2
2
 
3
3
  # REVISIT: what chars shall we allow in task names?
4
4
  def to_task_name
@@ -1,4 +1,4 @@
1
- class Symbol
1
+ class Symbol #:nodoc:
2
2
 
3
3
  def to_task_name
4
4
  to_s.to_task_name
@@ -1,9 +1,35 @@
1
1
  module Sprinkle
2
2
  module Installers
3
+ # = Apt Package Installer
4
+ #
5
+ # The Apt package installer uses the +apt-get+ command to install
6
+ # packages. The apt installer has only one option which can be
7
+ # modified which is the +dependencies_only+ option. When this is
8
+ # set to true, the installer uses +build-dep+ instead of +install+
9
+ # to only build the dependencies.
10
+ #
11
+ # == Example Usage
12
+ #
13
+ # First, a simple installation of the magic_beans package:
14
+ #
15
+ # package :magic_beans do
16
+ # description "Beans beans they're good for your heart..."
17
+ # apt 'magic_beans_package'
18
+ # end
19
+ #
20
+ # Second, only build the magic_beans dependencies:
21
+ #
22
+ # package :magic_beans_depends do
23
+ # apt 'magic_beans_package' { dependencies_only true }
24
+ # end
25
+ #
26
+ # As you can see, setting options is as simple as creating a
27
+ # block and calling the option as a method with the value as
28
+ # its parameter.
3
29
  class Apt < Installer
4
- attr_accessor :packages
30
+ attr_accessor :packages #:nodoc:
5
31
 
6
- def initialize(parent, *packages, &block)
32
+ def initialize(parent, *packages, &block) #:nodoc:
7
33
  super parent, &block
8
34
  packages.flatten!
9
35
 
@@ -16,7 +42,7 @@ module Sprinkle
16
42
 
17
43
  protected
18
44
 
19
- def install_commands
45
+ def install_commands #:nodoc:
20
46
  "DEBCONF_TERSE='yes' DEBIAN_PRIORITY='critical' DEBIAN_FRONTEND=noninteractive apt-get -qyu #{@command} #{@packages.join(' ')}"
21
47
  end
22
48
 
@@ -1,14 +1,42 @@
1
1
  module Sprinkle
2
2
  module Installers
3
+ # = Ruby Gem Package Installer
4
+ #
5
+ # The gem package installer installs ruby gems.
6
+ #
7
+ # The installer has a single optional configuration: source.
8
+ # By changing source you can specify a given ruby gems
9
+ # repository from which to install.
10
+ #
11
+ # == Example Usage
12
+ #
13
+ # First, a simple installation of the magic_beans gem:
14
+ #
15
+ # package :magic_beans do
16
+ # description "Beans beans they're good for your heart..."
17
+ # gem 'magic_beans'
18
+ # end
19
+ #
20
+ # Second, install magic_beans gem from github:
21
+ #
22
+ # package :magic_beans do
23
+ # gem 'magic_beans_package' do
24
+ # source 'http://gems.github.com'
25
+ # end
26
+ # end
27
+ #
28
+ # As you can see, setting options is as simple as creating a
29
+ # block and calling the option as a method with the value as
30
+ # its parameter.
3
31
  class Gem < Installer
4
- attr_accessor :gem
32
+ attr_accessor :gem #:nodoc:
5
33
 
6
- def initialize(parent, gem, options = {}, &block)
34
+ def initialize(parent, gem, options = {}, &block) #:nodoc:
7
35
  super parent, options, &block
8
36
  @gem = gem
9
37
  end
10
38
 
11
- def source(location = nil)
39
+ def source(location = nil) #:nodoc:
12
40
  # package defines an installer called source so here we specify a method directly
13
41
  # rather than rely on the automatic options processing since packages' method missing
14
42
  # won't be run
@@ -20,14 +48,14 @@ module Sprinkle
20
48
 
21
49
  # rubygems 0.9.5+ installs dependencies by default, and does platform selection
22
50
 
23
- def install_commands
51
+ def install_commands #:nodoc:
24
52
  cmd = "gem install #{gem}"
25
53
  cmd << " --version '#{version}'" if version
26
54
  cmd << " --source #{source}" if source
27
- cmd << " --install-dir #{repository}" if repository
55
+ cmd << " --install-dir #{repository}" if option?(:repository)
28
56
  cmd
29
57
  end
30
-
58
+
31
59
  end
32
60
  end
33
61
  end
@@ -1,9 +1,49 @@
1
1
  module Sprinkle
2
2
  module Installers
3
+ # The base class which all installers must subclass, this class makes
4
+ # sure all installers share some general features, which are outlined
5
+ # below.
6
+ #
7
+ # = Pre/Post Installation Hooks
8
+ #
9
+ # With all intallation methods you have the ability to specify multiple
10
+ # pre/post installation hooks. This gives you the ability to specify
11
+ # commands to run before and after an installation takes place. All
12
+ # commands by default are sudo'd so there is no need to include "sudo"
13
+ # in the command itself. There are three ways to specify a pre/post hook.
14
+ #
15
+ # First, a single command:
16
+ #
17
+ # pre :install, 'echo "Hello, World!"'
18
+ # post :install, 'rm -rf /'
19
+ #
20
+ # Second, an array of commands:
21
+ #
22
+ # commands = ['echo "First"', 'echo "Then Another"']
23
+ # pre :install, commands
24
+ # post :install, commands
25
+ #
26
+ # Third, a block which returns either a single or multiple commands:
27
+ #
28
+ # pre :install do
29
+ # amount = 7 * 3
30
+ # "echo 'Before we install, lets plant #{amount} magic beans...'"
31
+ # end
32
+ # post :install do
33
+ # ['echo "Now... let's hope they sprout!", 'echo "Indeed they have!"']
34
+ # end
35
+ #
36
+ # = Other Pre/Post Hooks
37
+ #
38
+ # Some installation methods actually grant you more fine grained
39
+ # control of when commands are run rather than a blanket pre :install
40
+ # or post :install. If this is the case, it will be documented on
41
+ # the installation method's corresponding documentation page.
3
42
  class Installer
4
- attr_accessor :delivery, :package, :options, :pre, :post
43
+ include Sprinkle::Configurable
44
+ attr_accessor :delivery, :package, :options, :pre, :post #:nodoc:
5
45
 
6
- def initialize(package, options = {}, &block)
46
+ def initialize(package, options = {}, &block) #:nodoc:
7
47
  @package = package
8
48
  @options = options
9
49
  @pre = {}; @post = {}
@@ -13,21 +53,17 @@ module Sprinkle
13
53
  def pre(stage, *commands)
14
54
  @pre[stage] ||= []
15
55
  @pre[stage] += commands
56
+ @pre[stage] += [yield] if block_given?
16
57
  end
17
58
 
18
59
  def post(stage, *commands)
19
60
  @post[stage] ||= []
20
61
  @post[stage] += commands
62
+ @post[stage] += [yield] if block_given?
21
63
  end
22
64
 
23
- def defaults(deployment)
24
- defaults = deployment.defaults[self.class.name.split(/::/).last.downcase.to_sym]
25
- self.instance_eval(&defaults) if defaults
26
- @delivery = deployment.style
27
- end
28
-
29
- def process(roles)
30
- raise 'Unknown command delivery target' unless @delivery
65
+ def process(roles) #:nodoc:
66
+ assert_delivery
31
67
 
32
68
  if logger.debug?
33
69
  sequence = install_sequence; sequence = sequence.join('; ') if sequence.is_a? Array
@@ -40,44 +76,43 @@ module Sprinkle
40
76
  end
41
77
  end
42
78
 
43
- def method_missing(sym, *args, &block)
44
- unless args.empty? # mutate if not set
45
- @options[sym] = *args unless @options[sym]
46
- end
47
-
48
- @options[sym] || @package.send(sym, *args, &block) # try the parents options if unknown
49
- end
50
-
51
79
  protected
52
-
53
- # Installation is separated into two styles that concrete derivative installer classes
54
- # can implement.
55
- #
56
- # Simple installers that issue a single or set of commands can overwride
57
- # install_commands (eg. apt, gem, rpm). Pre/post install commands are included in this
58
- # style for free.
59
- #
60
80
  # More complicated installers that have different stages, and require pre/post commands
61
81
  # within stages can override install_sequence and take complete control of the install
62
82
  # command sequence construction (eg. source based installer).
63
-
64
83
  def install_sequence
65
84
  commands = pre_commands(:install) + [ install_commands ] + post_commands(:install)
66
85
  commands.flatten
67
86
  end
68
87
 
88
+ # A concrete installer (subclass of this virtual class) must override this method
89
+ # and return the commands it needs to run as either a string or an array.
90
+ #
91
+ # <b>Overriding this method is required.</b>
69
92
  def install_commands
70
93
  raise 'Concrete installers implement this to specify commands to run to install their respective packages'
71
94
  end
72
95
 
73
- def pre_commands(stage)
96
+ def pre_commands(stage) #:nodoc:
74
97
  dress @pre[stage] || [], :pre
75
98
  end
76
99
 
77
- def post_commands(stage)
100
+ def post_commands(stage) #:nodoc:
78
101
  dress @post[stage] || [], :post
79
102
  end
80
103
 
104
+ # Concrete installers (subclasses of this virtual class) can override this method to
105
+ # specify stage-specific (pre-installation, post-installation, etc.) modifications
106
+ # of commands.
107
+ #
108
+ # An example usage of overriding this would be to prefix all commands for a
109
+ # certain stage to change to a certain directory. An example is given below:
110
+ #
111
+ # def dress(commands, stage)
112
+ # commands.collect { |x| "cd #{magic_beans_path} && #{x}" }
113
+ # end
114
+ #
115
+ # By default, no modifications are made to the commands.
81
116
  def dress(commands, stage); commands; end
82
117
 
83
118
  end