sprinkle 0.4.2 → 0.5.0.rc1

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