sprinkle 0.4.2 → 0.5.0.rc1

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.
Files changed (87) hide show
  1. data/.gitignore +8 -0
  2. data/Gemfile +5 -0
  3. data/Gemfile.lock +54 -0
  4. data/README.markdown +178 -166
  5. data/Rakefile +4 -28
  6. data/bin/sprinkle +14 -1
  7. data/lib/sprinkle.rb +5 -1
  8. data/lib/sprinkle/actors/actors.rb +20 -5
  9. data/lib/sprinkle/actors/capistrano.rb +62 -36
  10. data/lib/sprinkle/actors/dummy.rb +127 -0
  11. data/lib/sprinkle/actors/local.rb +59 -17
  12. data/lib/sprinkle/actors/ssh.rb +189 -107
  13. data/lib/sprinkle/actors/vlad.rb +51 -32
  14. data/lib/sprinkle/configurable.rb +2 -1
  15. data/lib/sprinkle/deployment.rb +22 -2
  16. data/lib/sprinkle/errors/pretty_failure.rb +41 -0
  17. data/lib/sprinkle/errors/remote_command_failure.rb +24 -0
  18. data/lib/sprinkle/errors/transfer_failure.rb +28 -0
  19. data/lib/sprinkle/installers/apt.rb +17 -16
  20. data/lib/sprinkle/installers/binary.rb +23 -8
  21. data/lib/sprinkle/installers/brew.rb +17 -10
  22. data/lib/sprinkle/installers/bsd_port.rb +10 -6
  23. data/lib/sprinkle/installers/deb.rb +3 -10
  24. data/lib/sprinkle/installers/freebsd_pkg.rb +5 -11
  25. data/lib/sprinkle/installers/freebsd_portinstall.rb +8 -2
  26. data/lib/sprinkle/installers/gem.rb +9 -3
  27. data/lib/sprinkle/installers/group.rb +28 -4
  28. data/lib/sprinkle/installers/installer.rb +58 -7
  29. data/lib/sprinkle/installers/mac_port.rb +13 -6
  30. data/lib/sprinkle/installers/npm.rb +42 -0
  31. data/lib/sprinkle/installers/openbsd_pkg.rb +4 -11
  32. data/lib/sprinkle/installers/opensolaris_pkg.rb +7 -13
  33. data/lib/sprinkle/installers/package_installer.rb +33 -0
  34. data/lib/sprinkle/installers/pacman.rb +5 -13
  35. data/lib/sprinkle/installers/pear.rb +40 -0
  36. data/lib/sprinkle/installers/push_text.rb +18 -5
  37. data/lib/sprinkle/installers/rake.rb +7 -2
  38. data/lib/sprinkle/installers/reconnect.rb +29 -0
  39. data/lib/sprinkle/installers/replace_text.rb +11 -2
  40. data/lib/sprinkle/installers/rpm.rb +8 -6
  41. data/lib/sprinkle/installers/runner.rb +41 -16
  42. data/lib/sprinkle/installers/smart.rb +6 -17
  43. data/lib/sprinkle/installers/source.rb +22 -10
  44. data/lib/sprinkle/installers/thor.rb +7 -0
  45. data/lib/sprinkle/installers/transfer.rb +62 -41
  46. data/lib/sprinkle/installers/user.rb +34 -4
  47. data/lib/sprinkle/installers/yum.rb +10 -10
  48. data/lib/sprinkle/installers/zypper.rb +4 -15
  49. data/lib/sprinkle/package.rb +81 -98
  50. data/lib/sprinkle/policy.rb +11 -4
  51. data/lib/sprinkle/utility/log_recorder.rb +33 -0
  52. data/lib/sprinkle/verifiers/directory.rb +1 -1
  53. data/lib/sprinkle/verifiers/executable.rb +1 -1
  54. data/lib/sprinkle/verifiers/file.rb +11 -2
  55. data/lib/sprinkle/verifiers/package.rb +2 -14
  56. data/lib/sprinkle/verifiers/permission.rb +40 -0
  57. data/lib/sprinkle/verifiers/symlink.rb +2 -2
  58. data/lib/sprinkle/verifiers/test.rb +21 -0
  59. data/lib/sprinkle/verify.rb +3 -3
  60. data/lib/sprinkle/version.rb +3 -0
  61. data/spec/fixtures/my_file.txt +1 -0
  62. data/spec/sprinkle/actors/capistrano_spec.rb +16 -3
  63. data/spec/sprinkle/actors/local_spec.rb +24 -6
  64. data/spec/sprinkle/actors/ssh_spec.rb +38 -0
  65. data/spec/sprinkle/installers/apt_spec.rb +23 -2
  66. data/spec/sprinkle/installers/binary_spec.rb +22 -14
  67. data/spec/sprinkle/installers/brew_spec.rb +4 -4
  68. data/spec/sprinkle/installers/installer_spec.rb +36 -7
  69. data/spec/sprinkle/installers/npm_spec.rb +16 -0
  70. data/spec/sprinkle/installers/pear_spec.rb +16 -0
  71. data/spec/sprinkle/installers/push_text_spec.rb +23 -1
  72. data/spec/sprinkle/installers/rpm_spec.rb +5 -0
  73. data/spec/sprinkle/installers/runner_spec.rb +27 -11
  74. data/spec/sprinkle/installers/smart_spec.rb +60 -0
  75. data/spec/sprinkle/installers/source_spec.rb +4 -4
  76. data/spec/sprinkle/installers/transfer_spec.rb +31 -16
  77. data/spec/sprinkle/package_spec.rb +10 -2
  78. data/spec/sprinkle/policy_spec.rb +6 -0
  79. data/spec/sprinkle/verify_spec.rb +18 -4
  80. data/sprinkle.gemspec +22 -158
  81. metadata +178 -96
  82. data/TODO +0 -56
  83. data/VERSION +0 -1
  84. data/lib/sprinkle/verifiers/apt.rb +0 -21
  85. data/lib/sprinkle/verifiers/brew.rb +0 -21
  86. data/lib/sprinkle/verifiers/rpm.rb +0 -21
  87. data/lib/sprinkle/verifiers/users_groups.rb +0 -33
data/bin/sprinkle CHANGED
@@ -20,7 +20,10 @@ ARGV.each do |arg|
20
20
  ENV[$1] = $2 if arg =~ /^(\w+)=(.*)$/
21
21
  end
22
22
 
23
+ require File.dirname(__FILE__) + '/../lib/sprinkle/version'
24
+
23
25
  parser = OptionParser.new do |opts|
26
+ opts.version = Sprinkle::Version
24
27
  opts.banner = <<BANNER
25
28
  Sprinkle
26
29
  ========
@@ -44,6 +47,8 @@ BANNER
44
47
  opts.separator ""
45
48
  opts.on("-s", "--script=PATH", String,
46
49
  "Path to a sprinkle script to run") { |v| OPTIONS[:path] = v }
50
+ opts.on("--only [ROLE]", String,
51
+ "Only run sprinkle policies for the specified role") { |v| OPTIONS[:only_role] = v }
47
52
  opts.on("-t", "--test",
48
53
  "Process but don't perform any actions") { |v| OPTIONS[:testing] = v }
49
54
  opts.on("-v", "--verbose",
@@ -61,6 +66,12 @@ BANNER
61
66
  end
62
67
  end
63
68
 
69
+ def only_role(options)
70
+ role=OPTIONS[:only_role]
71
+ Sprinkle::OPTIONS[:only_role] = role
72
+ puts "Only running policies for :#{role}"
73
+ end
74
+
64
75
  def force_mode(options)
65
76
  Sprinkle::OPTIONS[:force] = OPTIONS[:force] || false
66
77
  end
@@ -78,7 +89,8 @@ def verbosity(options)
78
89
  end
79
90
 
80
91
  def log_level(options)
81
- Object.logger.level = ActiveSupport::BufferedLogger::Severity::DEBUG if options[:verbose]
92
+ severity = ActiveSupport::BufferedLogger::Severity
93
+ Object.logger.level = options[:verbose] ? severity::DEBUG : severity::INFO
82
94
  end
83
95
 
84
96
  require File.dirname(__FILE__) + '/../lib/sprinkle'
@@ -91,5 +103,6 @@ operation_mode(OPTIONS)
91
103
  powder_cloud(OPTIONS)
92
104
  log_level(OPTIONS)
93
105
  verbosity(OPTIONS)
106
+ only_role(OPTIONS)
94
107
 
95
108
  Sprinkle::Script.sprinkle File.read(powder), powder
data/lib/sprinkle.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'rubygems'
2
- require 'active_support'
2
+ require 'active_support/all'
3
3
 
4
4
  # Use active supports auto load mechanism
5
5
  require 'active_support/version'
@@ -14,10 +14,14 @@ end
14
14
  #ActiveSupport::Dependencies::RAILS_DEFAULT_LOGGER = Logger.new($stdout)
15
15
  #ActiveSupport::Dependencies.log_activity = true
16
16
 
17
+ require File.dirname(__FILE__) + "/sprinkle/version.rb"
18
+
17
19
  # Load up extensions to existing classes
18
20
  Dir[File.dirname(__FILE__) + '/sprinkle/extensions/*.rb'].each { |e| require e }
19
21
  # Load up the verifiers so they can register themselves
20
22
  Dir[File.dirname(__FILE__) + '/sprinkle/verifiers/*.rb'].each { |e| require e }
23
+ # Load up the installers so they can register themselves
24
+ Dir[File.dirname(__FILE__) + '/sprinkle/installers/*.rb'].each { |e| require e }
21
25
 
22
26
  # Configuration options
23
27
  module Sprinkle
@@ -6,12 +6,27 @@
6
6
  #++
7
7
 
8
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.
9
+ # An actor is a method of command delivery to a remote machine. Actors are the
10
+ # layer setting between Sprinkle and the systems you and wanting to apply
11
+ # policies to.
12
12
  #
13
- # 99% of the time, however, the two built-in actors Sprinkle::Actors::Capistrano
14
- # and Sprinkle::Actors::Vlad will be enough.
13
+ # Sprinkle ships with actors for Capistrano, Vlad, localhost and pure SSH.
14
+ # 99% of the time these should be sufficient but you can always write your
15
+ # own actor otherwise.
16
+ #
17
+ # == Writing an actor
18
+ #
19
+ # Actors must provide only 3 methods:
20
+ #
21
+ # * install (installer, roles, options)
22
+ # * verify (verifier, roles, options)
23
+ # * transfer (source, destination, roles, options)
24
+ #
25
+ # Hopefully these methods are kind of fairly obvious. They should return true
26
+ # to indicate success and false to indicate failure.
27
+ # The actual commands you need to execute can be retrived from
28
+ # +installer.install_sequence+ and +verifier.commands+.
29
+
15
30
  module Actors
16
31
  end
17
32
  end
@@ -2,16 +2,15 @@ require 'capistrano/cli'
2
2
 
3
3
  module Sprinkle
4
4
  module Actors
5
- # = Capistrano Delivery Method
5
+ # The Capistrano actor uses Capistrano to define your roles and deliver
6
+ # commands to your remote servers. You'll need the capistrano gem installed.
6
7
  #
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:
8
+ # The only configuration option is to specify a recipe.
11
9
  #
12
10
  # deployment do
13
11
  # delivery :capistrano do
14
- # recipes 'deploy'
12
+ # recipe 'deploy'
13
+ # recipe 'more'
15
14
  # end
16
15
  # end
17
16
  #
@@ -47,52 +46,79 @@ module Sprinkle
47
46
  #
48
47
  # deployment do
49
48
  # delivery :capistrano do
50
- # recipes 'deploy'
51
- # recipes 'magic_beans'
49
+ # recipe 'deploy'
50
+ # recipes 'magic_beans', 'normal_beans'
52
51
  # end
53
52
  # end
54
- def recipes(script)
53
+ def recipe(scripts)
55
54
  @loaded_recipes ||= []
56
- @config.load script
57
- @loaded_recipes << script
55
+ Array(scripts).each do |script|
56
+ @config.load script
57
+ @loaded_recipes << script
58
+ end
58
59
  end
59
-
60
- def process(name, commands, roles, suppress_and_return_failures = false) #:nodoc:
60
+
61
+ def recipes(scripts) #:nodoc:
62
+ recipe(scripts)
63
+ end
64
+
65
+ def install(installer, roles, opts = {}) #:nodoc:
66
+ @installer = installer
67
+ process(installer.package.name, installer.install_sequence, roles, opts)
68
+ rescue ::Capistrano::CommandError => e
69
+ raise_error(e)
70
+ ensure
71
+ @installer = nil
72
+ end
73
+
74
+ def verify(verifier, roles, opts = {}) #:nodoc:
75
+ process(verifier.package.name, verifier.commands, roles,
76
+ :suppress_and_return_failures => true)
77
+ end
78
+
79
+ def process(name, commands, roles, opts = {}) #:nodoc:
80
+ inst=@installer
81
+ @log_recorder = log_recorder = Sprinkle::Utility::LogRecorder.new
61
82
  define_task(name, roles) do
62
83
  via = fetch(:run_method, :sudo)
63
84
  commands.each do |command|
64
- invoke_command command, :via => via
85
+ if command == :TRANSFER
86
+ opts.reverse_merge!(:recursive => true)
87
+ upload inst.sourcepath, inst.destination, :via => :scp,
88
+ :recursive => opts[:recursive]
89
+ elsif command == :RECONNECT
90
+ teardown_connections_to(sessions.keys)
91
+ else
92
+ # this reset the log
93
+ log_recorder.reset command
94
+ invoke_command(command, {:via => via}) do |c,s,d|
95
+ # record the stream and data
96
+ log_recorder.log(s, d)
97
+ end
98
+ end
65
99
  end
66
100
  end
67
-
68
- begin
69
- run(name)
70
- return true
71
- rescue ::Capistrano::CommandError => e
72
- return false if suppress_and_return_failures
73
-
74
- # Reraise error if we're not suppressing it
75
- raise
76
- end
101
+ run_task(name, opts)
77
102
  end
78
-
79
- def transfer(name, source, destination, roles, recursive = true, suppress_and_return_failures = false)
80
- define_task(name, roles) do
81
- upload source, destination, :via => :scp, :recursive => recursive
103
+
104
+ private
105
+
106
+ def raise_error(e)
107
+ details={:command => @log_recorder.command, :code => "??",
108
+ :message => e.message,
109
+ :hosts => e.hosts,
110
+ :error => @log_recorder.err, :stdout => @log_recorder.out}
111
+ raise Sprinkle::Errors::RemoteCommandFailure.new(@installer, details, e)
82
112
  end
83
-
84
- begin
85
- run(name)
113
+
114
+ def run_task(task, opts={})
115
+ run(task)
86
116
  return true
87
117
  rescue ::Capistrano::CommandError => e
88
- return false if suppress_and_return_failures
89
-
118
+ return false if opts[:suppress_and_return_failures]
90
119
  # Reraise error if we're not suppressing it
91
120
  raise
92
121
  end
93
- end
94
-
95
- private
96
122
 
97
123
  # REVISIT: can we set the description somehow?
98
124
  def define_task(name, roles, &block)
@@ -0,0 +1,127 @@
1
+ require 'capistrano/cli'
2
+ require 'pp'
3
+
4
+ module Sprinkle
5
+ module Actors
6
+ # = Capistrano Delivery Method
7
+ #
8
+ # Capistrano is one of the delivery method options available out of the
9
+ # box with Sprinkle. If you have the capistrano gem install, you may use
10
+ # this delivery. The only configuration option available, and which is
11
+ # mandatory to include is +recipes+. An example:
12
+ #
13
+ # deployment do
14
+ # delivery :capistrano do
15
+ # recipes 'deploy'
16
+ # end
17
+ # end
18
+ #
19
+ # Recipes is given a list of files which capistrano will include and load.
20
+ # These recipes are mainly to set variables such as :user, :password, and to
21
+ # set the app domain which will be sprinkled.
22
+ class Dummy #:nodoc:
23
+ attr_accessor :config, :loaded_recipes #:nodoc:
24
+
25
+ def initialize(&block) #:nodoc:
26
+ # @config.set(:_sprinkle_actor, self)
27
+ @roles={}
28
+ self.instance_eval &block
29
+ end
30
+
31
+ # Defines a recipe file which will be included by capistrano. Use these
32
+ # recipe files to set capistrano specific configurations. Default recipe
33
+ # included is "deploy." But if any other recipe is specified, it will
34
+ # include that instead. Multiple recipes may be specified through multiple
35
+ # recipes calls, an example:
36
+ #
37
+ # deployment do
38
+ # delivery :capistrano do
39
+ # recipes 'deploy'
40
+ # recipes 'magic_beans'
41
+ # end
42
+ # end
43
+ # def recipes(script)
44
+ # end
45
+
46
+ def role(role, server, opts={})
47
+ @roles[role]||=[]
48
+ @roles[role] << [ server, opts ]
49
+ end
50
+
51
+ def install(installer, roles, opts={})
52
+ if per_host=opts.delete(:per_host)
53
+ servers_per_role(roles).each do |server|
54
+ installer.reconfigure_for(server)
55
+ installer.announce
56
+ process(installer.package.name, installer.install_sequence, server, opts)
57
+ end
58
+ else
59
+ process(installer.package, installer.install_sequence, roles, opts)
60
+ end
61
+ end
62
+
63
+ def verify(verifier, roles, opts = {})
64
+ process(verifier.package.name, verifier.commands, roles, opts = {})
65
+ end
66
+
67
+ def servers_per_role(role)
68
+ @roles[role]
69
+ end
70
+
71
+ def process(name, commands, roles, opts = {}) #:nodoc:
72
+ # puts "PROCESS: #{name} on #{roles}"
73
+ pp commands
74
+ # return false if suppress_and_return_failures
75
+ true
76
+ end
77
+
78
+ def transfer(name, source, destination, roles, recursive = true, suppress_and_return_failures = false)
79
+
80
+ end
81
+
82
+ private
83
+
84
+ # REVISIT: can we set the description somehow?
85
+ def define_task(name, roles, &block)
86
+ @config.task task_sym(name), :roles => roles, &block
87
+ end
88
+
89
+ def run(task)
90
+ @config.send task_sym(task)
91
+ end
92
+
93
+ def task_sym(name)
94
+ "install_#{name.to_task_name}".to_sym
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+
101
+ =begin
102
+
103
+ # channel: the SSH channel object used for this response
104
+ # stream: either :err or :out, for stderr or stdout responses
105
+ # output: the text that the server is sending, might be in chunks
106
+ run "apt-get update" do |channel, stream, output|
107
+ if output =~ /Are you sure?/
108
+ answer = Capistrano::CLI.ui.ask("Are you sure: ")
109
+ channel.send_data(answer + "\n")
110
+ else
111
+ # allow the default callback to be processed
112
+ Capistrano::Configuration.default_io_proc.call[channel, stream, output]
113
+ end
114
+ end
115
+
116
+
117
+
118
+ You can tell subversion to use a different username+password by
119
+ setting a couple variables:
120
+ set :svn_username, "my svn username"
121
+ set :svn_password, "my svn password"
122
+ If you don't want to set the password explicitly in your recipe like
123
+ that, you can make capistrano prompt you for it like this:
124
+ set(:svn_password) { Capistrano::CLI.password_prompt("Subversion
125
+ password: ") }
126
+ - Jamis
127
+ =end
@@ -1,37 +1,79 @@
1
+ require 'open4'
2
+
1
3
  module Sprinkle
2
4
  module Actors
3
- # = Local Delivery Method
5
+ # The local actor executes all commands on your local system, as opposed to other
6
+ # implementations that generally run commands on a remote system over the
7
+ # network.
4
8
  #
5
- # This actor implementation performs any given commands on your local system, as
6
- # opposed to other implementations that generally run commands on a remote system
7
- # via the network.
8
- #
9
- # This is useful if you'd like to use Sprinkle to provision your local machine.
10
- # To enable this actor, in your Sprinkle script specify the :local delivery mechanism.
9
+ # This could be useful if you'd like to use Sprinkle to provision your
10
+ # local machine. To enable this actor, in your Sprinkle script specify
11
+ # the :local delivery mechanism.
11
12
  #
12
13
  # deployment do
13
14
  # delivery :local
14
15
  # end
15
16
  #
16
- # Note, your local machine will be assumed to be a member of all roles when applying policies
17
- #
17
+ # Note: The local actor completely ignores roles and behaves as if your
18
+ # local system was a member of all roles defined.
18
19
  class Local
19
20
 
20
- def process(name, commands, roles, suppress_and_return_failures = false) #:nodoc:
21
+ class LocalCommandError < StandardError; end
22
+
23
+ def install(installer, roles, opts = {}) #:nodoc:
24
+ # all local installer cares about is the commands
25
+ @installer = installer
26
+ process(installer.package.name, installer.install_sequence, roles)
27
+ rescue LocalCommandError => e
28
+ raise_error(e)
29
+ ensure
30
+ @installer = nil
31
+ end
32
+
33
+ def verify(verifier, roles, opts = {}) #:nodoc:
34
+ process(verifier.package.name, verifier.commands, roles, :suppress_and_return_failures => true)
35
+ end
36
+
37
+ protected
38
+
39
+ def process(name, commands, roles, opts = {}) #:nodoc:
40
+ @log_recorder = Sprinkle::Utility::LogRecorder.new
21
41
  commands.each do |command|
22
- system command
23
- return false if $?.to_i != 0
42
+ if command == :RECONNECT
43
+ return true
44
+ elsif command == :TRANSFER
45
+ res = transfer(@installer.sourcepath, @installer.destination, roles,
46
+ :recursive => @installer.options[:recursive])
47
+ raise LocalCommandError if res != 0
48
+ else
49
+ res = run_command command
50
+ raise LocalCommandError if res != 0 and not opts[:suppress_and_return_failures]
51
+ end
24
52
  end
25
53
  return true
26
54
  end
27
55
 
28
- def transfer(name, source, destination, roles, recursive = true, suppress_and_return_failures = false)
29
- if recursive
30
- flags = "-R "
31
- end
56
+ def run_command(cmd)
57
+ @log_recorder.reset cmd
58
+ pid, stdin, out, err = Open4.popen4(cmd)
59
+ ignored, status = Process::waitpid2 pid
60
+ @log_recorder.log :err, err.read
61
+ @log_recorder.log :out, out.read
62
+ @log_recorder.code = status.to_i
63
+ end
64
+
65
+ def raise_error(e)
66
+ raise Sprinkle::Errors::RemoteCommandFailure.new(@installer, @log_recorder.hash, e)
67
+ end
68
+
69
+ def transfer(source, destination, roles, opts ={}) #:nodoc:
70
+ opts.reverse_merge!(:recursive => true)
71
+ flags = "-R " if opts[:recursive]
32
72
 
33
- system "cp #{flags}#{source} #{destination}"
73
+ run_command "cp #{flags}#{source} #{destination}"
34
74
  end
75
+
76
+
35
77
  end
36
78
  end
37
79
  end