beaker 2.7.1 → 2.8.0

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