beaker 2.7.1 → 2.8.0

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 (53) hide show
  1. checksums.yaml +8 -8
  2. data/HISTORY.md +121 -2
  3. data/lib/beaker/dsl.rb +2 -2
  4. data/lib/beaker/dsl/helpers.rb +13 -1429
  5. data/lib/beaker/dsl/helpers/facter_helpers.rb +48 -0
  6. data/lib/beaker/dsl/helpers/hiera_helpers.rb +71 -0
  7. data/lib/beaker/dsl/helpers/host_helpers.rb +506 -0
  8. data/lib/beaker/dsl/helpers/puppet_helpers.rb +698 -0
  9. data/lib/beaker/dsl/helpers/tk_helpers.rb +101 -0
  10. data/lib/beaker/dsl/helpers/web_helpers.rb +115 -0
  11. data/lib/beaker/dsl/install_utils.rb +8 -1570
  12. data/lib/beaker/dsl/install_utils/ezbake_utils.rb +256 -0
  13. data/lib/beaker/dsl/install_utils/module_utils.rb +237 -0
  14. data/lib/beaker/dsl/install_utils/pe_utils.rb +518 -0
  15. data/lib/beaker/dsl/install_utils/puppet_utils.rb +722 -0
  16. data/lib/beaker/dsl/outcomes.rb +0 -4
  17. data/lib/beaker/dsl/roles.rb +0 -3
  18. data/lib/beaker/dsl/structure.rb +127 -4
  19. data/lib/beaker/dsl/wrappers.rb +0 -4
  20. data/lib/beaker/host.rb +23 -0
  21. data/lib/beaker/host/unix/pkg.rb +4 -4
  22. data/lib/beaker/host_prebuilt_steps.rb +11 -5
  23. data/lib/beaker/hypervisor/vagrant.rb +1 -0
  24. data/lib/beaker/hypervisor/vmpooler.rb +38 -0
  25. data/lib/beaker/logger.rb +10 -4
  26. data/lib/beaker/network_manager.rb +5 -4
  27. data/lib/beaker/options/command_line_parser.rb +7 -0
  28. data/lib/beaker/shared.rb +2 -1
  29. data/lib/beaker/shared/semvar.rb +41 -0
  30. data/lib/beaker/test_suite.rb +20 -6
  31. data/lib/beaker/version.rb +1 -1
  32. data/spec/beaker/dsl/helpers/facter_helpers_spec.rb +59 -0
  33. data/spec/beaker/dsl/helpers/hiera_helpers_spec.rb +96 -0
  34. data/spec/beaker/dsl/helpers/host_helpers_spec.rb +413 -0
  35. data/spec/beaker/dsl/{helpers_spec.rb → helpers/puppet_helpers_spec.rb} +2 -611
  36. data/spec/beaker/dsl/helpers/tk_helpers_spec.rb +83 -0
  37. data/spec/beaker/dsl/helpers/web_helpers_spec.rb +60 -0
  38. data/spec/beaker/dsl/install_utils/module_utils_spec.rb +241 -0
  39. data/spec/beaker/dsl/install_utils/pe_utils_spec.rb +475 -0
  40. data/spec/beaker/dsl/install_utils/puppet_utils_spec.rb +523 -0
  41. data/spec/beaker/dsl/structure_spec.rb +108 -0
  42. data/spec/beaker/host_prebuilt_steps_spec.rb +44 -0
  43. data/spec/beaker/host_spec.rb +41 -0
  44. data/spec/beaker/hypervisor/vagrant_spec.rb +2 -1
  45. data/spec/beaker/logger_spec.rb +9 -2
  46. data/spec/beaker/network_manager_spec.rb +7 -1
  47. data/spec/beaker/options/command_line_parser_spec.rb +3 -2
  48. data/spec/beaker/shared/semvar_spec.rb +36 -0
  49. data/spec/beaker/test_suite_spec.rb +48 -0
  50. data/spec/mocks.rb +10 -0
  51. metadata +23 -5
  52. data/lib/beaker/dsl/ezbake_utils.rb +0 -259
  53. data/spec/beaker/dsl/install_utils_spec.rb +0 -1242
@@ -0,0 +1,48 @@
1
+ module Beaker
2
+ module DSL
3
+ module Helpers
4
+ # Methods that help you interact with your facter installation, facter must be installed
5
+ # for these methods to execute correctly
6
+ #
7
+ module FacterHelpers
8
+
9
+ # @!macro common_opts
10
+ # @param [Hash{Symbol=>String}] opts Options to alter execution.
11
+ # @option opts [Boolean] :silent (false) Do not produce log output
12
+ # @option opts [Array<Fixnum>] :acceptable_exit_codes ([0]) An array
13
+ # (or range) of integer exit codes that should be considered
14
+ # acceptable. An error will be thrown if the exit code does not
15
+ # match one of the values in this list.
16
+ # @option opts [Hash{String=>String}] :environment ({}) These will be
17
+ # treated as extra environment variables that should be set before
18
+ # running the command.
19
+ #
20
+
21
+ # Get a facter fact from a provided host
22
+ #
23
+ # @param [Host, Array<Host>, String, Symbol] host One or more hosts to act upon,
24
+ # or a role (String or Symbol) that identifies one or more hosts.
25
+ # @param [String] name The name of the fact to query for
26
+ # @!macro common_opts
27
+ #
28
+ # @return String The value of the fact 'name' on the provided host
29
+ # @raise [FailTest] Raises an exception if call to facter fails
30
+ def fact_on(host, name, opts = {})
31
+ result = on host, facter(name, opts)
32
+ if result.kind_of?(Array)
33
+ result.map { |res| res.stdout.chomp }
34
+ else
35
+ result.stdout.chomp
36
+ end
37
+ end
38
+
39
+ # Get a facter fact from the default host
40
+ # @see #fact_on
41
+ def fact(name, opts = {})
42
+ fact_on(default, name, opts)
43
+ end
44
+
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,71 @@
1
+ module Beaker
2
+ module DSL
3
+ module Helpers
4
+ # Methods that help you interact with your hiera installation, hiera must be installed
5
+ # for these methods to execute correctly
6
+ module HieraHelpers
7
+
8
+ # @!macro common_opts
9
+ # @param [Hash{Symbol=>String}] opts Options to alter execution.
10
+ # @option opts [Boolean] :silent (false) Do not produce log output
11
+ # @option opts [Array<Fixnum>] :acceptable_exit_codes ([0]) An array
12
+ # (or range) of integer exit codes that should be considered
13
+ # acceptable. An error will be thrown if the exit code does not
14
+ # match one of the values in this list.
15
+ # @option opts [Hash{String=>String}] :environment ({}) These will be
16
+ # treated as extra environment variables that should be set before
17
+ # running the command.
18
+
19
+ # Write hiera config file on one or more provided hosts
20
+ #
21
+ # @param[Host, Array<Host>, String, Symbol] host One or more hosts to act upon,
22
+ # or a role (String or Symbol) that identifies one or more hosts.
23
+ # @param[Array] One or more hierarchy paths
24
+ def write_hiera_config_on(host, hierarchy)
25
+
26
+ block_on host do |host|
27
+ hiera_config=Hash.new
28
+ hiera_config[:backends] = 'yaml'
29
+ hiera_config[:yaml] = {}
30
+ hiera_config[:yaml][:datadir] = hiera_datadir(host)
31
+ hiera_config[:hierarchy] = hierarchy
32
+ hiera_config[:logger] = 'console'
33
+ create_remote_file host, host.puppet['hiera_config'], hiera_config.to_yaml
34
+ end
35
+ end
36
+
37
+ # Write hiera config file for the default host
38
+ # @see #write_hiera_config_on
39
+ def write_hiera_config(hierarchy)
40
+ write_hiera_config_on(default, hierarchy)
41
+ end
42
+
43
+ # Copy hiera data files to one or more provided hosts
44
+ #
45
+ # @param[Host, Array<Host>, String, Symbol] host One or more hosts to act upon,
46
+ # or a role (String or Symbol) that identifies one or more hosts.
47
+ # @param[String] Directory containing the hiera data files.
48
+ def copy_hiera_data_to(host, source)
49
+ scp_to host, File.expand_path(source), hiera_datadir(host)
50
+ end
51
+
52
+ # Copy hiera data files to the default host
53
+ # @see #copy_hiera_data_to
54
+ def copy_hiera_data(source)
55
+ copy_hiera_data_to(default, source)
56
+ end
57
+
58
+ # Get file path to the hieradatadir for a given host.
59
+ # Handles whether or not a host is AIO-based & backwards compatibility
60
+ #
61
+ # @param[Host] host Host you want to use the hieradatadir from
62
+ #
63
+ # @return [String] Path to the hiera data directory
64
+ def hiera_datadir(host)
65
+ host[:type] =~ /aio/ ? File.join(host.puppet['codedir'], 'hieradata') : host[:hieradatadir]
66
+ end
67
+
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,506 @@
1
+ module Beaker
2
+ module DSL
3
+ module Helpers
4
+ # Methods that help you interact and manage the state of your Beaker SUTs, these
5
+ # methods do not require puppet to be installed to execute correctly
6
+ module HostHelpers
7
+
8
+ # @!macro common_opts
9
+ # @param [Hash{Symbol=>String}] opts Options to alter execution.
10
+ # @option opts [Boolean] :silent (false) Do not produce log output
11
+ # @option opts [Array<Fixnum>] :acceptable_exit_codes ([0]) An array
12
+ # (or range) of integer exit codes that should be considered
13
+ # acceptable. An error will be thrown if the exit code does not
14
+ # match one of the values in this list.
15
+ # @option opts [Hash{String=>String}] :environment ({}) These will be
16
+ # treated as extra environment variables that should be set before
17
+ # running the command.
18
+ #
19
+
20
+ # The primary method for executing commands *on* some set of hosts.
21
+ #
22
+ # @param [Host, Array<Host>, String, Symbol] host One or more hosts to act upon,
23
+ # or a role (String or Symbol) that identifies one or more hosts.
24
+ # @param [String, Command] command The command to execute on *host*.
25
+ # @param [Proc] block Additional actions or assertions.
26
+ # @!macro common_opts
27
+ #
28
+ # @example Most basic usage
29
+ # on hosts, 'ls /tmp'
30
+ #
31
+ # @example Allowing additional exit codes to pass
32
+ # on agents, 'puppet agent -t', :acceptable_exit_codes => [0,2]
33
+ #
34
+ # @example Using the returned result for any kind of checking
35
+ # if on(host, 'ls -la ~').stdout =~ /\.bin/
36
+ # ...do some action...
37
+ # end
38
+ #
39
+ # @example Using TestCase helpers from within a test.
40
+ # agents.each do |agent|
41
+ # on agent, 'cat /etc/puppet/puppet.conf' do
42
+ # assert_match stdout, /server = #{master}/, 'WTF Mate'
43
+ # end
44
+ # end
45
+ #
46
+ # @example Using a role (defined in a String) to identify the host
47
+ # on "master", "echo hello"
48
+ #
49
+ # @example Using a role (defined in a Symbol) to identify the host
50
+ # on :dashboard, "echo hello"
51
+ # @return [Result] An object representing the outcome of *command*.
52
+ # @raise [FailTest] Raises an exception if *command* obviously fails.
53
+ def on(host, command, opts = {}, &block)
54
+ block_on host do | host |
55
+ cur_command = command
56
+ if command.is_a? Command
57
+ cur_command = command.cmd_line(host)
58
+ end
59
+ cmd_opts = {}
60
+ #add any additional environment variables to the command
61
+ if opts[:environment]
62
+ cmd_opts['ENV'] = opts[:environment]
63
+ end
64
+ @result = host.exec(Command.new(cur_command.to_s, [], cmd_opts), opts)
65
+
66
+ # Also, let additional checking be performed by the caller.
67
+ if block_given?
68
+ case block.arity
69
+ #block with arity of 0, just hand back yourself
70
+ when 0
71
+ yield self
72
+ #block with arity of 1 or greater, hand back the result object
73
+ else
74
+ yield @result
75
+ end
76
+ end
77
+ @result
78
+ end
79
+ end
80
+
81
+ # The method for executing commands on the default host
82
+ #
83
+ # @param [String, Command] command The command to execute on *host*.
84
+ # @param [Proc] block Additional actions or assertions.
85
+ # @!macro common_opts
86
+ #
87
+ # @example Most basic usage
88
+ # shell 'ls /tmp'
89
+ #
90
+ # @example Allowing additional exit codes to pass
91
+ # shell 'puppet agent -t', :acceptable_exit_codes => [0,2]
92
+ #
93
+ # @example Using the returned result for any kind of checking
94
+ # if shell('ls -la ~').stdout =~ /\.bin/
95
+ # ...do some action...
96
+ # end
97
+ #
98
+ # @example Using TestCase helpers from within a test.
99
+ # agents.each do |agent|
100
+ # shell('cat /etc/puppet/puppet.conf') do |result|
101
+ # assert_match result.stdout, /server = #{master}/, 'WTF Mate'
102
+ # end
103
+ # end
104
+ #
105
+ # @return [Result] An object representing the outcome of *command*.
106
+ # @raise [FailTest] Raises an exception if *command* obviously fails.
107
+ def shell(command, opts = {}, &block)
108
+ on(default, command, opts, &block)
109
+ end
110
+
111
+ # @deprecated
112
+ # An proxy for the last {Beaker::Result#stdout} returned by
113
+ # a method that makes remote calls. Use the {Beaker::Result}
114
+ # object returned by the method directly instead. For Usage see
115
+ # {Beaker::Result}.
116
+ def stdout
117
+ return nil if @result.nil?
118
+ @result.stdout
119
+ end
120
+
121
+ # @deprecated
122
+ # An proxy for the last {Beaker::Result#stderr} returned by
123
+ # a method that makes remote calls. Use the {Beaker::Result}
124
+ # object returned by the method directly instead. For Usage see
125
+ # {Beaker::Result}.
126
+ def stderr
127
+ return nil if @result.nil?
128
+ @result.stderr
129
+ end
130
+
131
+ # @deprecated
132
+ # An proxy for the last {Beaker::Result#exit_code} returned by
133
+ # a method that makes remote calls. Use the {Beaker::Result}
134
+ # object returned by the method directly instead. For Usage see
135
+ # {Beaker::Result}.
136
+ def exit_code
137
+ return nil if @result.nil?
138
+ @result.exit_code
139
+ end
140
+
141
+ # Move a file from a remote to a local path
142
+ # @note If using {Beaker::Host} for the hosts *scp* is not
143
+ # required on the system as it uses Ruby's net/scp library. The
144
+ # net-scp gem however is required (and specified in the gemspec).
145
+ #
146
+ # @param [Host, #do_scp_from] host One or more hosts (or some object
147
+ # that responds like
148
+ # {Beaker::Host#do_scp_from}.
149
+ # @param [String] from_path A remote path to a file.
150
+ # @param [String] to_path A local path to copy *from_path* to.
151
+ # @!macro common_opts
152
+ #
153
+ # @return [Result] Returns the result of the SCP operation
154
+ def scp_from host, from_path, to_path, opts = {}
155
+ block_on host do | host |
156
+ @result = host.do_scp_from(from_path, to_path, opts)
157
+ @result.log logger
158
+ @result
159
+ end
160
+ end
161
+
162
+ # Move a local file to a remote host using scp
163
+ # @note If using {Beaker::Host} for the hosts *scp* is not
164
+ # required on the system as it uses Ruby's net/scp library. The
165
+ # net-scp gem however is required (and specified in the gemspec.
166
+ # When using SCP with Windows it will now auto expand path when
167
+ # using `cygpath instead of failing or requiring full path
168
+ #
169
+ # @param [Host, #do_scp_to] host One or more hosts (or some object
170
+ # that responds like
171
+ # {Beaker::Host#do_scp_to}.
172
+ # @param [String] from_path A local path to a file.
173
+ # @param [String] to_path A remote path to copy *from_path* to.
174
+ # @!macro common_opts
175
+ #
176
+ # @return [Result] Returns the result of the SCP operation
177
+ def scp_to host, from_path, to_path, opts = {}
178
+ block_on host do | host |
179
+ if host['platform'] =~ /windows/ && to_path.match('`cygpath')
180
+ result = on host, "echo #{to_path}"
181
+ to_path = result.raw_output.chomp
182
+ end
183
+ @result = host.do_scp_to(from_path, to_path, opts)
184
+ @result.log logger
185
+ @result
186
+ end
187
+ end
188
+
189
+ # Move a local file or directory to a remote host using rsync
190
+ # @note rsync is required on the local host.
191
+ #
192
+ # @param [Host, #do_scp_to] host A host object that responds like
193
+ # {Beaker::Host}.
194
+ # @param [String] from_path A local path to a file or directory.
195
+ # @param [String] to_path A remote path to copy *from_path* to.
196
+ # @!macro common_opts
197
+ #
198
+ # @return [Result] Returns the result of the rsync operation
199
+ def rsync_to host, from_path, to_path, opts = {}
200
+ block_on host do | host |
201
+ if host['platform'] =~ /windows/ && to_path.match('`cygpath')
202
+ result = host.echo "#{to_path}"
203
+ to_path = result.raw_output.chomp
204
+ end
205
+ @result = host.do_rsync_to(from_path, to_path, opts)
206
+ @result
207
+ end
208
+ end
209
+
210
+ # Deploy packaging configurations generated by
211
+ # https://github.com/puppetlabs/packaging to a host.
212
+ #
213
+ # @note To ensure the repo configs are available for deployment,
214
+ # you should run `rake pl:jenkins:deb_repo_configs` and
215
+ # `rake pl:jenkins:rpm_repo_configs` on your project checkout
216
+ #
217
+ # @param [Host] host
218
+ # @param [String] path The path to the generated repository config
219
+ # files. ex: /myproject/pkg/repo_configs
220
+ # @param [String] name A human-readable name for the repository
221
+ # @param [String] version The version of the project, as used by the
222
+ # packaging tools. This can be determined with
223
+ # `rake pl:print_build_params` from the packaging
224
+ # repo.
225
+ def deploy_package_repo host, path, name, version
226
+ host.deploy_package_repo path, name, version
227
+ end
228
+
229
+ # Create a remote file out of a string
230
+ # @note This method uses Tempfile in Ruby's STDLIB as well as {#scp_to}.
231
+ #
232
+ # @param [Host, #do_scp_to] hosts One or more hosts (or some object
233
+ # that responds like
234
+ # {Beaker::Host#do_scp_from}.
235
+ # @param [String] file_path A remote path to place *file_content* at.
236
+ # @param [String] file_content The contents of the file to be placed.
237
+ # @!macro common_opts
238
+ # @option opts [String] :protocol Name of the underlying transfer method.
239
+ # Valid options are 'scp' or 'rsync'.
240
+ #
241
+ # @return [Result] Returns the result of the underlying SCP operation.
242
+ def create_remote_file(hosts, file_path, file_content, opts = {})
243
+ Tempfile.open 'beaker' do |tempfile|
244
+ File.open(tempfile.path, 'w') {|file| file.puts file_content }
245
+
246
+ opts[:protocol] ||= 'scp'
247
+ case opts[:protocol]
248
+ when 'scp'
249
+ scp_to hosts, tempfile.path, file_path, opts
250
+ when 'rsync'
251
+ rsync_to hosts, tempfile.path, file_path, opts
252
+ else
253
+ logger.debug "Unsupported transfer protocol, returning nil"
254
+ nil
255
+ end
256
+ end
257
+ end
258
+
259
+ # Move a local script to a remote host and execute it
260
+ # @note this relies on {#on} and {#scp_to}
261
+ #
262
+ # @param [Host, #do_scp_to] host One or more hosts (or some object
263
+ # that responds like
264
+ # {Beaker::Host#do_scp_from}.
265
+ # @param [String] script A local path to find an executable script at.
266
+ # @!macro common_opts
267
+ # @param [Proc] block Additional tests to run after script has executed
268
+ #
269
+ # @return [Result] Returns the result of the underlying SCP operation.
270
+ def run_script_on(host, script, opts = {}, &block)
271
+ # this is unsafe as it uses the File::SEPARATOR will be set to that
272
+ # of the coordinator node. This works for us because we use cygwin
273
+ # which will properly convert the paths. Otherwise this would not
274
+ # work for running tests on a windows machine when the coordinator
275
+ # that the harness is running on is *nix. We should use
276
+ # {Beaker::Host#temp_path} instead. TODO
277
+ remote_path = File.join("", "tmp", File.basename(script))
278
+
279
+ scp_to host, script, remote_path
280
+ on host, remote_path, opts, &block
281
+ end
282
+
283
+ # Move a local script to default host and execute it
284
+ # @see #run_script_on
285
+ def run_script(script, opts = {}, &block)
286
+ run_script_on(default, script, opts, &block)
287
+ end
288
+
289
+ # Install a package on a host
290
+ #
291
+ # @param [Host] host A host object
292
+ # @param [String] package_name Name of the package to install
293
+ #
294
+ # @return [Result] An object representing the outcome of *install command*.
295
+ def install_package host, package_name, package_version = nil
296
+ host.install_package package_name, '', package_version
297
+ end
298
+
299
+ # Check to see if a package is installed on a remote host
300
+ #
301
+ # @param [Host] host A host object
302
+ # @param [String] package_name Name of the package to check for.
303
+ #
304
+ # @return [Boolean] true/false if the package is found
305
+ def check_for_package host, package_name
306
+ host.check_for_package package_name
307
+ end
308
+
309
+ # Upgrade a package on a host. The package must already be installed
310
+ #
311
+ # @param [Host] host A host object
312
+ # @param [String] package_name Name of the package to install
313
+ #
314
+ # @return [Result] An object representing the outcome of *upgrade command*.
315
+ def upgrade_package host, package_name
316
+ host.upgrade_package package_name
317
+ end
318
+
319
+ # Configure a host entry on the give host
320
+ # @example: will add a host entry for forge.puppetlabs.com
321
+ # add_system32_hosts_entry(host, { :ip => '23.251.154.122', :name => 'forge.puppetlabs.com' })
322
+ #
323
+ # @return nil
324
+ def add_system32_hosts_entry(host, opts = {})
325
+ if host['platform'] =~ /windows/
326
+ hosts_file = "C:\\Windows\\System32\\Drivers\\etc\\hosts"
327
+ host_entry = "#{opts['ip']}`t`t#{opts['name']}"
328
+ on host, powershell("\$text = \\\"#{host_entry}\\\"; Add-Content -path '#{hosts_file}' -value \$text")
329
+ else
330
+ raise "nothing to do for #{host.name} on #{host['platform']}"
331
+ end
332
+ end
333
+
334
+ # Back up the given file in the current_dir to the new_dir
335
+ #
336
+ # @!visibility private
337
+ #
338
+ # @param host [Beaker::Host] The target host
339
+ # @param current_dir [String] The directory containing the file to back up
340
+ # @param new_dir [String] The directory to copy the file to
341
+ # @param filename [String] The file to back up. Defaults to 'puppet.conf'
342
+ #
343
+ # @return [String, nil] The path to the file if the file exists, nil if it
344
+ # doesn't exist.
345
+ def backup_the_file host, current_dir, new_dir, filename = 'puppet.conf'
346
+
347
+ old_location = current_dir + '/' + filename
348
+ new_location = new_dir + '/' + filename + '.bak'
349
+
350
+ if host.file_exist? old_location
351
+ host.exec( Command.new( "cp #{old_location} #{new_location}" ) )
352
+ return new_location
353
+ else
354
+ logger.warn "Could not backup file '#{old_location}': no such file"
355
+ nil
356
+ end
357
+ end
358
+
359
+ #Run a curl command on the provided host(s)
360
+ #
361
+ # @param [Host, Array<Host>, String, Symbol] host One or more hosts to act upon,
362
+ # or a role (String or Symbol) that identifies one or more hosts.
363
+ # @param [String, Command] cmd The curl command to execute on *host*.
364
+ # @param [Proc] block Additional actions or assertions.
365
+ # @!macro common_opts
366
+ #
367
+ def curl_on(host, cmd, opts = {}, &block)
368
+ if options.is_pe? #check global options hash
369
+ on host, "curl --tlsv1 %s" % cmd, opts, &block
370
+ else
371
+ on host, "curl %s" % cmd, opts, &block
372
+ end
373
+ end
374
+
375
+
376
+ def curl_with_retries(desc, host, url, desired_exit_codes, max_retries = 60, retry_interval = 1)
377
+ opts = {
378
+ :desired_exit_codes => desired_exit_codes,
379
+ :max_retries => max_retries,
380
+ :retry_interval => retry_interval
381
+ }
382
+ retry_on(host, "curl -m 1 #{url}", opts)
383
+ end
384
+
385
+ # This command will execute repeatedly until success or it runs out with an error
386
+ #
387
+ # @param [Host, Array<Host>, String, Symbol] host One or more hosts to act upon,
388
+ # or a role (String or Symbol) that identifies one or more hosts.
389
+ # @param [String, Command] command The command to execute on *host*.
390
+ # @param [Hash{Symbol=>String}] opts Options to alter execution.
391
+ # @param [Proc] block Additional actions or assertions.
392
+ #
393
+ # @option opts [Array<Fixnum>, Fixnum] :desired_exit_codes (0) An array
394
+ # or integer exit code(s) that should be considered
395
+ # acceptable. An error will be thrown if the exit code never
396
+ # matches one of the values in this list.
397
+ # @option opts [Fixnum] :max_retries (60) number of times the
398
+ # command will be tried before failing
399
+ # @option opts [Float] :retry_interval (1) number of seconds
400
+ # that we'll wait between tries
401
+ # @option opts [Boolean] :verbose (false)
402
+ def retry_on(host, command, opts = {}, &block)
403
+ option_exit_codes = opts[:desired_exit_codes]
404
+ option_max_retries = opts[:max_retries].to_i
405
+ option_retry_interval = opts[:retry_interval].to_f
406
+ desired_exit_codes = option_exit_codes ? [option_exit_codes].flatten : [0]
407
+ desired_exit_codes = [0] if desired_exit_codes.empty?
408
+ max_retries = option_max_retries == 0 ? 60 : option_max_retries # nil & "" both return 0
409
+ retry_interval = option_retry_interval == 0 ? 1 : option_retry_interval
410
+ verbose = true.to_s == opts[:verbose]
411
+
412
+ log_prefix = host.log_prefix
413
+ logger.debug "\n#{log_prefix} #{Time.new.strftime('%H:%M:%S')}$ #{command}"
414
+ logger.debug " Trying command #{max_retries} times."
415
+ logger.debug ".", add_newline=false
416
+
417
+ result = on host, command, {:acceptable_exit_codes => (0...127), :silent => !verbose}, &block
418
+ num_retries = 0
419
+ until desired_exit_codes.include?(result.exit_code)
420
+ sleep retry_interval
421
+ result = on host, command, {:acceptable_exit_codes => (0...127), :silent => !verbose}, &block
422
+ num_retries += 1
423
+ logger.debug ".", add_newline=false
424
+ if (num_retries > max_retries)
425
+ logger.debug " Command \`#{command}\` failed."
426
+ fail("Command \`#{command}\` failed.")
427
+ end
428
+ end
429
+ logger.debug "\n#{log_prefix} #{Time.new.strftime('%H:%M:%S')}$ #{command} ostensibly successful."
430
+ result
431
+ end
432
+
433
+ # FIX: this should be moved into host/platform
434
+ # @visibility private
435
+ def run_cron_on(host, action, user, entry="", &block)
436
+ block_on host do | host |
437
+ platform = host['platform']
438
+ if platform.include?('solaris') || platform.include?('aix') then
439
+ case action
440
+ when :list then args = '-l'
441
+ when :remove then args = '-r'
442
+ when :add
443
+ on( host,
444
+ "echo '#{entry}' > /var/spool/cron/crontabs/#{user}",
445
+ &block )
446
+ end
447
+
448
+ else # default for GNU/Linux platforms
449
+ case action
450
+ when :list then args = '-l -u'
451
+ when :remove then args = '-r -u'
452
+ when :add
453
+ on( host,
454
+ "echo '#{entry}' > /tmp/#{user}.cron && " +
455
+ "crontab -u #{user} /tmp/#{user}.cron",
456
+ &block )
457
+ end
458
+ end
459
+
460
+ if args
461
+ case action
462
+ when :list, :remove then on(host, "crontab #{args} #{user}", &block)
463
+ end
464
+ end
465
+ end
466
+ end
467
+
468
+ # Create a temp directory on remote host owned by specified user.
469
+ #
470
+ # @param [Host, Array<Host>, String, Symbol] host One or more hosts to act upon,
471
+ # or a role (String or Symbol) that identifies one or more hosts.
472
+ # @param [String] path_prefix A remote path prefix for the new temp
473
+ # directory.
474
+ # @param [String] user The name of user that should own the temp
475
+ # directory. If no username is specified defaults to the currently logged in user
476
+ # per host
477
+ #
478
+ # @return [String, Array<String>] Returns the name of the newly-created dir, or an array
479
+ # of names of newly-created dirs per-host
480
+ def create_tmpdir_on(host, path_prefix = '', user=nil)
481
+
482
+ block_on host do | host |
483
+ # use default user logged into this host
484
+ if not user
485
+ user = host['user']
486
+ end
487
+
488
+ if not on(host, "getent passwd #{user}").exit_code == 0
489
+ raise "User #{user} does not exist on #{host}."
490
+ end
491
+
492
+ if defined? host.tmpdir
493
+ dir = host.tmpdir(path_prefix)
494
+ on host, "chown #{user}:#{user} #{dir}"
495
+ dir
496
+ else
497
+ raise "Host platform not supported by `create_tmpdir_on`."
498
+ end
499
+ end
500
+ end
501
+
502
+
503
+ end
504
+ end
505
+ end
506
+ end