sprinkle 0.5.1.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ * Sprinkle `sudo_cmd` and Capistrino should work together instead of getting in each others way
2
+
3
+ When using the capistrano actor `sudo_cmd` will now use the capistrano
4
+ generated sudo command and therefore automatically deal with password
5
+ prompts, etc. This should fix hangs when installers try to call `sudo` on
6
+ the other side of a pipe operation and capistrano can not recognize the
7
+ password prompt.
8
+
9
+ * Sprinkle executable should return an error code if there was a failure
10
+
11
+ *Michael Nigh*
12
+
13
+ * verify of local actor was never returning false so installers would never be executed
14
+
15
+ *Edgars Beigarts*
@@ -15,6 +15,8 @@ module Sprinkle
15
15
  # * install (installer, roles, options)
16
16
  # * verify (verifier, roles, options)
17
17
  # * servers_for_role? (roles)
18
+ # * sudo?
19
+ # * sudo_command
18
20
  #
19
21
  # Hopefully these methods are kind of fairly obvious. They should return true
20
22
  # to indicate success and false to indicate failure.
@@ -35,6 +37,19 @@ module Sprinkle
35
37
  raise "you must define install"
36
38
  end
37
39
 
40
+ # an actor must define this and let the installers know if it plans
41
+ # to try and add sudo to all of their commands or not since some
42
+ # installers might need to handle sudo their own special way
43
+ def sudo?
44
+ raise "you must define sudo?"
45
+ end
46
+
47
+ # if an installer needs to call sudo this is the command the actor
48
+ # would prefer the installers to use
49
+ def sudo_command
50
+ raise "you must define sudo_command"
51
+ end
52
+
38
53
  def verify(*args)
39
54
  raise "you must define verify"
40
55
  end
@@ -38,6 +38,14 @@ module Sprinkle
38
38
  end
39
39
  end
40
40
 
41
+ def sudo?
42
+ @config.fetch(:run_method, :sudo) == :sudo
43
+ end
44
+
45
+ def sudo_command
46
+ @config.sudo
47
+ end
48
+
41
49
  # Determines if there are any servers for the given roles
42
50
  def servers_for_role?(roles)
43
51
  roles=Array(roles)
@@ -79,13 +87,15 @@ module Sprinkle
79
87
  end
80
88
 
81
89
  def verify(verifier, roles, opts = {}) #:nodoc:
82
- process(verifier.package.name, verifier.commands, roles,
83
- :suppress_and_return_failures => true)
90
+ process(verifier.package.name, verifier.commands, roles)
91
+ rescue ::Capistrano::CommandError
92
+ return false
84
93
  end
85
94
 
86
95
  def process(name, commands, roles, opts = {}) #:nodoc:
87
96
  inst=@installer
88
97
  @log_recorder = log_recorder = Sprinkle::Utility::LogRecorder.new
98
+ commands = commands.map {|x| rewrite_command(x)}
89
99
  define_task(name, roles) do
90
100
  via = fetch(:run_method, :sudo)
91
101
  commands.each do |command|
@@ -110,6 +120,17 @@ module Sprinkle
110
120
 
111
121
  private
112
122
 
123
+ # rip out any double sudos from the beginning of the command
124
+ def rewrite_command(cmd)
125
+ return cmd if cmd.is_a?(Symbol)
126
+ via = @config.fetch(:run_method, :sudo)
127
+ if via == :sudo and cmd =~ /^#{sudo_command}/
128
+ cmd.gsub(/^#{sudo_command}\s?/,"")
129
+ else
130
+ cmd
131
+ end
132
+ end
133
+
113
134
  def raise_error(e)
114
135
  details={:command => @log_recorder.command, :code => "??",
115
136
  :message => e.message,
@@ -120,11 +141,7 @@ module Sprinkle
120
141
 
121
142
  def run_task(task, opts={})
122
143
  run(task)
123
- return true
124
- rescue ::Capistrano::CommandError => e
125
- return false if opts[:suppress_and_return_failures]
126
- # Reraise error if we're not suppressing it
127
- raise
144
+ true
128
145
  end
129
146
 
130
147
  # REVISIT: can we set the description somehow?
@@ -24,6 +24,9 @@ module Sprinkle
24
24
  true
25
25
  end
26
26
 
27
+ def sudo?; false; end
28
+ def sudo_command; nil; end
29
+
27
30
  def install(installer, roles, opts = {}) #:nodoc:
28
31
  # all local installer cares about is the commands
29
32
  @installer = installer
@@ -35,7 +38,10 @@ module Sprinkle
35
38
  end
36
39
 
37
40
  def verify(verifier, roles, opts = {}) #:nodoc:
38
- process(verifier.package.name, verifier.commands, roles, :suppress_and_return_failures => true)
41
+ process(verifier.package.name, verifier.commands, roles)
42
+ true
43
+ rescue LocalCommandError
44
+ false
39
45
  end
40
46
 
41
47
  protected
@@ -44,15 +50,14 @@ module Sprinkle
44
50
  @log_recorder = Sprinkle::Utility::LogRecorder.new
45
51
  commands.each do |command|
46
52
  if command == :RECONNECT
47
- return true
53
+ res = 0
48
54
  elsif command == :TRANSFER
49
55
  res = transfer(@installer.sourcepath, @installer.destination, roles,
50
56
  :recursive => @installer.options[:recursive])
51
- raise LocalCommandError if res != 0
52
57
  else
53
58
  res = run_command command
54
- raise LocalCommandError if res != 0 and not opts[:suppress_and_return_failures]
55
59
  end
60
+ raise LocalCommandError if res != 0
56
61
  end
57
62
  return true
58
63
  end
@@ -88,9 +88,17 @@ module Sprinkle
88
88
  end
89
89
 
90
90
  # Set this to true to prepend 'sudo' to every command.
91
- def use_sudo(value)
91
+ def use_sudo(value=true)
92
92
  @options[:use_sudo] = value
93
93
  end
94
+
95
+ def sudo?
96
+ @options[:use_sudo]
97
+ end
98
+
99
+ def sudo_command
100
+ "sudo"
101
+ end
94
102
 
95
103
  def setup_gateway #:nodoc:
96
104
  @gateway ||= Net::SSH::Gateway.new(@options[:gateway], @options[:user]) if @options[:gateway]
@@ -104,8 +112,9 @@ module Sprinkle
104
112
  @verifier = verifier
105
113
  # issue all the verification steps in a single SSH command
106
114
  commands=[verifier.commands.join(" && ")]
107
- process(verifier.package.name, commands, roles,
108
- :suppress_and_return_failures => true)
115
+ process(verifier.package.name, commands, roles)
116
+ rescue SSHCommandFailure => e
117
+ false
109
118
  ensure
110
119
  @verifier = nil
111
120
  end
@@ -126,9 +135,7 @@ module Sprinkle
126
135
  end
127
136
 
128
137
  def process(name, commands, roles, opts = {}) #:nodoc:
129
- opts.reverse_merge!(:suppress_and_return_failures => false)
130
138
  setup_gateway
131
- @suppress = opts[:suppress_and_return_failures]
132
139
  r=execute_on_role(commands, roles)
133
140
  logger.debug green "process returning #{r}"
134
141
  return r
@@ -143,10 +150,10 @@ module Sprinkle
143
150
  end
144
151
 
145
152
  def prepare_commands(commands)
146
- return commands unless @options[:use_sudo]
153
+ return commands unless sudo?
147
154
  commands.map do |command|
148
155
  next command if command.is_a?(Symbol)
149
- command.match(/^sudo/) ? command : "sudo #{command}"
156
+ command.match(/^#{sudo_command}/) ? command : "#{sudo_command} #{command}"
150
157
  end
151
158
  end
152
159
 
@@ -166,13 +173,9 @@ module Sprinkle
166
173
  @log_recorder.reset cmd
167
174
  res = ssh(session, cmd)
168
175
  if res != 0
169
- if @suppress
170
- return false
171
- else
172
- fail=SSHCommandFailure.new
173
- fail.details = @log_recorder.hash.merge(:hosts => host)
174
- raise fail, "#{cmd} failed with error code #{res[:code]}"
175
- end
176
+ fail=SSHCommandFailure.new
177
+ fail.details = @log_recorder.hash.merge(:hosts => host)
178
+ raise fail, "#{cmd} failed with error code #{res[:code]}"
176
179
  end
177
180
  end
178
181
  true
@@ -27,6 +27,15 @@ module Sprinkle
27
27
  raise "The vlad actor needs a maintainer. "+
28
28
  "Please file an issue on github.com/sprinkle-tool/sprinkle if you can help."
29
29
  end
30
+
31
+ def sudo?
32
+ # TODO
33
+ raise
34
+ end
35
+
36
+ def sudo_command
37
+ "sudo"
38
+ end
30
39
 
31
40
  # Defines a script file which will be included by vlad. Use these
32
41
  # script files to set vlad specific configurations. Multiple scripts
@@ -66,7 +75,7 @@ module Sprinkle
66
75
  protected
67
76
 
68
77
  def process(name, commands, roles, opts ={}) #:nodoc:
69
- commands = commands.map{|x| "sudo #{x}"} if use_sudo
78
+ commands = commands.map{|x| "#{sudo_command} #{x}"} if sudo?
70
79
  commands = commands.join(' && ')
71
80
  puts "executing #{commands}"
72
81
  task = remote_task(task_sym(name), :roles => roles) { run commands }
@@ -75,7 +84,7 @@ module Sprinkle
75
84
 
76
85
  def process_with_transfer(name, commands, roles, opts ={}) #:nodoc:
77
86
  raise "cant do non recursive file transfers, sorry" if opts[:recursive] == false
78
- commands = commands.map{|x| x == :TRANSFER : x ? "sudo #{x}" } if use_sudo
87
+ commands = commands.map{|x| x == :TRANSFER ? x : "sudo #{x}" } if sudo?
79
88
  i = commands.index(:TRANSFER)
80
89
  before = commands.first(i).join(" && ")
81
90
  after = commands.last(commands.size-i+1).join(" && ")
@@ -80,10 +80,10 @@ module Sprinkle
80
80
  end
81
81
  rescue Sprinkle::Errors::RemoteCommandFailure => e
82
82
  e.print_summary
83
- exit
83
+ exit 1
84
84
  rescue Sprinkle::Errors::TransferFailure => e
85
85
  e.print_summary
86
- exit
86
+ exit 2
87
87
  ensure
88
88
  # do any cleanup our actor may need to close network sockets, etc
89
89
  @style.teardown if @style.respond_to?(:teardown)
@@ -80,11 +80,11 @@ module Sprinkle
80
80
  end
81
81
 
82
82
  def sudo_cmd
83
- "sudo " if sudo?
83
+ return "#{@delivery.try(:sudo_command) || "sudo"} " if sudo?
84
84
  end
85
85
 
86
86
  def sudo?
87
- options[:sudo] or package.sudo?
87
+ options[:sudo] or package.sudo? or @delivery.try(:sudo?)
88
88
  end
89
89
 
90
90
  def escape_shell_arg(str)
@@ -34,7 +34,7 @@ module Sprinkle
34
34
  protected
35
35
 
36
36
  def install_commands #:nodoc:
37
- sudo_cmd ?
37
+ sudo? ?
38
38
  @cmds.map { |cmd| "#{sudo_cmd}#{cmd}"} :
39
39
  @cmds
40
40
  end
@@ -16,6 +16,12 @@ module Sprinkle
16
16
  # package :magic_beans do
17
17
  # yum "magic_beans", "magic_sauce"
18
18
  # end
19
+ #
20
+ # To install a specific version just add that version after the name
21
+ #
22
+ # package :magic_beans do
23
+ # yum "magic_beans-3.0"
24
+ # end
19
25
  class Yum < PackageInstaller
20
26
 
21
27
  auto_api
@@ -222,78 +222,66 @@ module Sprinkle
222
222
  v.process(roles)
223
223
  end
224
224
  end
225
-
226
- def dependencies
227
- @dependencies.map {|a,b| a }
228
- end
229
-
225
+
230
226
  def requires(*packages)
231
- opts = packages.extract_options!
232
- packages.each do |pack|
233
- @dependencies << [pack, opts]
234
- end
227
+ add_dependencies packages, :dependencies
235
228
  end
236
229
 
237
230
  def recommends(*packages)
238
- opts = packages.extract_options!
239
- packages.each do |pack|
240
- @recommends << [pack, opts]
241
- end
242
- @recommends.map {|a,b| a }
231
+ add_dependencies packages, :recommends
243
232
  end
244
233
 
245
234
  def optional(*packages)
246
- opts = packages.extract_options!
247
- packages.each do |pack|
248
- @optional << [pack, opts]
249
- end
250
- @optional.map {|a,b| a }
235
+ add_dependencies packages, :optional
251
236
  end
252
-
237
+
238
+ def dependencies
239
+ @dependencies.map {|a,b| a }
240
+ end
241
+
253
242
  def tree(depth = 1, &block)
254
243
  packages = []
255
-
256
- @recommends.each do |dep, config|
257
- package = PACKAGES[dep]
258
- next unless package # skip missing recommended packages as they're allowed to not exist
259
- package=package.instance(config)
260
- block.call(self, package, depth) if block
261
- packages << package.tree(depth + 1, &block)
262
- end
263
-
264
- @dependencies.each do |dep, config|
265
- package = PACKAGES[dep]
266
- package = select_package(dep, package) if package.is_a? Array
267
-
268
- raise "Package definition not found for key: #{dep}" unless package
269
- package = package.instance(config)
270
- block.call(self, package, depth) if block
271
- packages << package.tree(depth + 1, &block)
272
- end
273
-
244
+ packages << tree_for_packages(@recommends, :depth => depth, &block)
245
+ packages << tree_for_packages(@dependencies, :depth => depth, :required => true, &block)
274
246
  packages << self
275
-
276
- @optional.each do |dep, config|
277
- package = PACKAGES[dep]
278
- next unless package # skip missing optional packages as they're allow to not exist
279
- package = package.instance(config)
280
- block.call(self, package, depth) if block
281
- packages << package.tree(depth + 1, &block)
282
- end
283
-
247
+ packages << tree_for_packages(@optional, :depth => depth, &block)
284
248
  packages
285
249
  end
286
250
 
287
251
  def to_s; @name; end
288
252
 
289
- protected
253
+ protected
290
254
 
291
255
  def install(i)
292
256
  @installers << i
293
257
  i
294
258
  end
295
259
 
296
- private
260
+ private
261
+
262
+ def add_dependencies(packages, kind)
263
+ opts = packages.extract_options!
264
+ depends = instance_variable_get("@#{kind}")
265
+ packages.each do |pack|
266
+ depends << [pack, opts]
267
+ end
268
+ depends.map {|a,b| a }
269
+ end
270
+
271
+ def tree_for_packages(packages, opts={}, &block)
272
+ depth = opts[:depth]
273
+ tree = []
274
+ packages.each do |dep, config|
275
+ package = PACKAGES[dep]
276
+ raise "Package definition not found for key: #{dep}" if not package and opts[:required]
277
+ next unless package # skip missing recommended packages as they're allowed to not exist
278
+ package = select_package(dep, package) if package.is_a? Array
279
+ package = package.instance(config)
280
+ block.call(self, package, depth) if block
281
+ tree << package.tree(depth + 1, &block)
282
+ end
283
+ tree
284
+ end
297
285
 
298
286
  def cloud_info(message)
299
287
  logger.info(message) if Sprinkle::OPTIONS[:cloud] or logger.debug?
@@ -1,3 +1,3 @@
1
1
  module Sprinkle
2
- Version = "0.5.1.1"
2
+ Version = "0.5.2"
3
3
  end
@@ -89,6 +89,30 @@ describe Sprinkle::Actors::Capistrano do
89
89
  end
90
90
 
91
91
  end
92
+
93
+ describe 'verifications' do
94
+
95
+ before do
96
+ @commands = %w( op1 op2 )
97
+ @roles = %w( app )
98
+ @package = stub(:name => "name")
99
+ @cap = create_cap
100
+ @verifier = stub(:package => @package, :commands => ["op1", "op2"])
101
+ end
102
+
103
+ it "should return true if successful" do
104
+ @cap.stub!(:run).and_return
105
+ res = @cap.verify(@verifier, @roles)
106
+ res.should == true
107
+ end
108
+
109
+ it "should return false if there was an error" do
110
+ @cap.stub!(:run).and_raise(::Capistrano::CommandError)
111
+ res = @cap.verify(@verifier, @roles)
112
+ res.should == false
113
+ end
114
+
115
+ end
92
116
 
93
117
  describe 'processing commands' do
94
118
 
@@ -102,6 +126,14 @@ describe Sprinkle::Actors::Capistrano do
102
126
 
103
127
  @testing_errors = false
104
128
  end
129
+
130
+ it "should strip excessive occurrences of sudo" do
131
+ # pretend the package or installer has also added sudo
132
+ @commands =["sudo op1"]
133
+ @cap.stub(:sudo_command).and_return("sudo")
134
+ @cap.unstub!(:run)
135
+ @cap.config.should_receive(:invoke_command).with('op1', :via => :sudo).ordered.and_return
136
+ end
105
137
 
106
138
  it 'should dynamically create a capistrano task containing the commands' do
107
139
  @cap.config.should_receive(:task).and_return
@@ -118,17 +150,6 @@ describe Sprinkle::Actors::Capistrano do
118
150
  lambda { @cap.process @name, @commands, @roles }.should raise_error(::Capistrano::CommandError)
119
151
  end
120
152
 
121
- it 'should not raise errors and instead return false when suppressing parameter is set' do
122
- @testing_errors = true
123
-
124
- @cap.should_receive(:run).and_raise(::Capistrano::CommandError)
125
-
126
- value = nil
127
- lambda { value = @cap.process(@name, @commands, @roles, true) }.should_not raise_error(::Capistrano::CommandError)
128
-
129
- value.should_not be
130
- end
131
-
132
153
  after do
133
154
  @cap.process @name, @commands, @roles unless @testing_errors
134
155
  end
@@ -141,6 +162,7 @@ describe Sprinkle::Actors::Capistrano do
141
162
  @source = 'source'
142
163
  @dest = 'dest'
143
164
  @roles = %w( app )
165
+ @commands = [:TRANSFER]
144
166
  @name = 'name'
145
167
 
146
168
  @cap = create_cap do; recipes 'deploy'; end
@@ -167,17 +189,6 @@ describe Sprinkle::Actors::Capistrano do
167
189
  lambda { @cap.process @name, @commands, @roles }.should raise_error(::Capistrano::CommandError)
168
190
  end
169
191
 
170
- it 'should not raise errors and instead return false when suppressing parameter is set' do
171
- @testing_errors = true
172
-
173
- @cap.should_receive(:run).and_raise(::Capistrano::CommandError)
174
-
175
- value = nil
176
- lambda { value = @cap.process(@name, @commands, @roles, true) }.should_not raise_error(::Capistrano::CommandError)
177
-
178
- value.should_not be
179
- end
180
-
181
192
  after do
182
193
  @cap.process @package.name, @installer.install_sequence, @roles
183
194
  end
@@ -191,12 +202,12 @@ describe Sprinkle::Actors::Capistrano do
191
202
  @name = 'name'
192
203
 
193
204
  @cap = create_cap do; recipes 'deploy'; end
194
- @cap.config.stub!(:fetch).and_return(:sudo)
195
205
  @cap.config.stub!(:invoke_command).and_return
196
206
  end
197
207
 
198
208
  it 'should use sudo to invoke commands when so configured' do
199
- @cap.config.should_receive(:fetch).with(:run_method, :sudo).and_return(:sudo)
209
+ @cap.config.should_receive(:invoke_command).with('op1', :via => :sudo).ordered.and_return
210
+ @cap.config.should_receive(:invoke_command).with('op2', :via => :sudo).ordered.and_return
200
211
  end
201
212
 
202
213
  it 'should run the supplied commands' do
@@ -33,13 +33,18 @@ describe Sprinkle::Actors::Local do
33
33
  @verifier.commands += ["test","test"]
34
34
  @roles = %w( app )
35
35
  @name = 'name'
36
-
37
- @local.stub!(:run_command).and_return(0)
36
+ end
37
+
38
+ it 'should return false when verification fails' do
39
+ @local.stub!(:run_command).and_return(1)
40
+ res = @local.verify @verifier, @roles
41
+ res.should == false
38
42
  end
39
43
 
40
44
  it 'should run the commands on the local system' do
41
- @local.should_receive(:run_command).twice.and_return
42
- @local.verify @verifier, @roles
45
+ @local.stub!(:run_command).and_return(0)
46
+ res = @local.verify @verifier, @roles
47
+ res.should == true
43
48
  end
44
49
 
45
50
  end
@@ -73,7 +73,8 @@ describe Sprinkle::Installers::Apt do
73
73
  end
74
74
 
75
75
  it 'should install a specific version if defined' do
76
- pending
76
+ @installer = create_apt 'ruby=2'
77
+ @installer.send(:install_sequence).should == [ %(env DEBCONF_TERSE='yes' DEBIAN_PRIORITY='critical' DEBIAN_FRONTEND=noninteractive apt-get --force-yes -qyu install ruby=2)]
77
78
  end
78
79
 
79
80
  end
@@ -47,8 +47,6 @@ describe Sprinkle::Installers::Brew do
47
47
  @installer.send(:install_sequence).should == [ 'op1', %(brew install ruby), 'op2' ]
48
48
  end
49
49
 
50
- it 'should install a specific version if defined'
51
-
52
50
  end
53
51
 
54
52
  # describe 'during dependencies only installation' do
@@ -7,7 +7,8 @@ describe Sprinkle::Installers::Installer do
7
7
  @package = mock(Sprinkle::Package, :name => 'package')
8
8
  @empty = Proc.new { }
9
9
  @sequence = ['op1', 'op2']
10
- @delivery = mock(Sprinkle::Deployment, :process => true, :install => true)
10
+ @delivery = mock(Sprinkle::Deployment, :process => true, :install => true,
11
+ :sudo_command => "sudo")
11
12
  @installer = create_installer
12
13
  @installer.delivery = @delivery
13
14
  @roles = []
@@ -102,6 +103,12 @@ describe Sprinkle::Installers::Installer do
102
103
  @installer.delivery = @delivery
103
104
  end
104
105
 
106
+ it "should use sudo command from actor" do
107
+ @installer.delivery = mock(Sprinkle::Deployment, :process => true, :install => true,
108
+ :sudo_command => "sudo -p blah")
109
+ @installer.sudo_cmd.should =~ /sudo -p blah /
110
+ end
111
+
105
112
  it "should know it uses sudo" do
106
113
  @installer.sudo?.should == true
107
114
  end
@@ -47,9 +47,6 @@ describe Sprinkle::Installers::Rpm do
47
47
  @installer.send(:install_sequence).should == [ 'op1', 'rpm -Uvh ruby', 'op2' ]
48
48
  end
49
49
 
50
- it 'should specify a non interactive mode to the apt installer' do
51
- pending
52
- end
53
50
  it 'should install a specific version if defined' do
54
51
  pending
55
52
  end
@@ -43,7 +43,8 @@ describe Sprinkle::Installers::Yum do
43
43
  end
44
44
 
45
45
  it 'should install a specific version if defined' do
46
- pending
46
+ @installer = create_rpm 'ruby-2.0'
47
+ @installer.send(:install_sequence).should == [ 'yum install ruby-2.0 -y' ]
47
48
  end
48
49
 
49
50
  end
@@ -1,4 +1,5 @@
1
1
  require "./lib/sprinkle/version"
2
+ require 'date'
2
3
 
3
4
  Gem::Specification.new do |s|
4
5
  s.name = "sprinkle"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sprinkle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1.1
4
+ version: 0.5.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-04-27 00:00:00.000000000 Z
13
+ date: 2013-05-15 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rspec
@@ -134,6 +134,7 @@ extra_rdoc_files:
134
134
  files:
135
135
  - .gitignore
136
136
  - .travis.yml
137
+ - CHANGELOG.md
137
138
  - CREDITS
138
139
  - Gemfile
139
140
  - Gemfile.lock