chef 15.6.10-universal-mingw32 → 15.7.30-universal-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/README.md +2 -2
  4. data/lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb +22 -5
  5. data/lib/chef/cookbook/cookbook_version_loader.rb +12 -6
  6. data/lib/chef/cookbook_loader.rb +55 -2
  7. data/lib/chef/cookbook_uploader.rb +2 -0
  8. data/lib/chef/cookbook_version.rb +17 -0
  9. data/lib/chef/dist.rb +3 -3
  10. data/lib/chef/event_loggers/windows_eventlog.rb +1 -1
  11. data/lib/chef/knife/bootstrap.rb +5 -0
  12. data/lib/chef/knife/bootstrap/templates/chef-full.erb +11 -11
  13. data/lib/chef/knife/cookbook_upload.rb +75 -45
  14. data/lib/chef/knife/core/bootstrap_context.rb +5 -5
  15. data/lib/chef/knife/core/windows_bootstrap_context.rb +1 -1
  16. data/lib/chef/knife/supermarket_install.rb +1 -1
  17. data/lib/chef/log/winevt.rb +1 -1
  18. data/lib/chef/mixin/openssl_helper.rb +21 -0
  19. data/lib/chef/monkey_patches/net_http.rb +0 -38
  20. data/lib/chef/provider/cron.rb +54 -9
  21. data/lib/chef/provider/cron/aix.rb +9 -2
  22. data/lib/chef/provider/launchd.rb +1 -1
  23. data/lib/chef/provider/user/aix.rb +1 -1
  24. data/lib/chef/provider/user/mac.rb +17 -22
  25. data/lib/chef/provider/windows_task.rb +2 -2
  26. data/lib/chef/resource/archive_file.rb +5 -2
  27. data/lib/chef/resource/cron.rb +29 -0
  28. data/lib/chef/resource/cron_d.rb +29 -0
  29. data/lib/chef/resource/openssl_x509_certificate.rb +32 -21
  30. data/lib/chef/resource/sudo.rb +13 -4
  31. data/lib/chef/version.rb +2 -2
  32. data/lib/chef/version_string.rb +3 -126
  33. data/spec/data/cookbooks/apache2/metadata.json +33 -0
  34. data/spec/data/cookbooks/java/metadata.json +33 -0
  35. data/spec/functional/event_loggers/windows_eventlog_spec.rb +5 -5
  36. data/spec/integration/knife/chef_fs_data_store_spec.rb +7 -2
  37. data/spec/integration/knife/cookbook_upload_spec.rb +10 -0
  38. data/spec/integration/knife/deps_spec.rb +11 -0
  39. data/spec/integration/knife/upload_spec.rb +115 -14
  40. data/spec/unit/cookbook/cookbook_version_loader_spec.rb +7 -4
  41. data/spec/unit/knife/cookbook_upload_spec.rb +79 -18
  42. data/spec/unit/mixin/openssl_helper_spec.rb +42 -0
  43. data/spec/unit/provider/cron_spec.rb +127 -0
  44. data/spec/unit/provider/user/aix_spec.rb +2 -2
  45. metadata +8 -6
@@ -158,11 +158,11 @@ class Chef
158
158
  end
159
159
 
160
160
  if encrypted_data_bag_secret
161
- client_rb << %Q{encrypted_data_bag_secret "#{Chef::Dist::CONF_DIR}/encrypted_data_bag_secret"\n}
161
+ client_rb << %Q{encrypted_data_bag_secret "/etc/chef/encrypted_data_bag_secret"\n}
162
162
  end
163
163
 
164
164
  unless trusted_certs.empty?
165
- client_rb << %Q{trusted_certs_dir "#{Chef::Dist::CONF_DIR}/trusted_certs"\n}
165
+ client_rb << %Q{trusted_certs_dir "/etc/chef/trusted_certs"\n}
166
166
  end
167
167
 
168
168
  if Chef::Config[:fips]
@@ -175,7 +175,7 @@ class Chef
175
175
  def start_chef
176
176
  # If the user doesn't have a client path configure, let bash use the PATH for what it was designed for
177
177
  client_path = @chef_config[:chef_client_path] || "#{Chef::Dist::CLIENT}"
178
- s = "#{client_path} -j #{Chef::Dist::CONF_DIR}/first-boot.json"
178
+ s = "#{client_path} -j /etc/chef/first-boot.json"
179
179
  if @config[:verbosity] && @config[:verbosity] >= 3
180
180
  s << " -l trace"
181
181
  elsif @config[:verbosity] && @config[:verbosity] >= 2
@@ -226,7 +226,7 @@ class Chef
226
226
  content = ""
227
227
  if @chef_config[:trusted_certs_dir]
228
228
  Dir.glob(File.join(Chef::Util::PathHelper.escape_glob_dir(@chef_config[:trusted_certs_dir]), "*.{crt,pem}")).each do |cert|
229
- content << "cat > #{Chef::Dist::CONF_DIR}/trusted_certs/#{File.basename(cert)} <<'EOP'\n" +
229
+ content << "cat > /etc/chef/trusted_certs/#{File.basename(cert)} <<'EOP'\n" +
230
230
  IO.read(File.expand_path(cert)) + "\nEOP\n"
231
231
  end
232
232
  end
@@ -240,7 +240,7 @@ class Chef
240
240
  root.find do |f|
241
241
  relative = f.relative_path_from(root)
242
242
  if f != root
243
- file_on_node = "#{Chef::Dist::CONF_DIR}/client.d/#{relative}"
243
+ file_on_node = "/etc/chef/client.d/#{relative}"
244
244
  if f.directory?
245
245
  content << "mkdir #{file_on_node}\n"
246
246
  else
@@ -158,7 +158,7 @@ class Chef
158
158
 
159
159
  def start_chef
160
160
  bootstrap_environment_option = bootstrap_environment.nil? ? "" : " -E #{bootstrap_environment}"
161
- start_chef = "SET \"PATH=%SystemRoot%\\system32;%SystemRoot%;%SystemRoot%\\System32\\Wbem;%SYSTEMROOT%\\System32\\WindowsPowerShell\\v1.0\\;C:\\ruby\\bin;C:\\opscode\\chef\\bin;C:\\opscode\\chef\\embedded\\bin\"\n"
161
+ start_chef = "SET \"PATH=%SystemRoot%\\system32;%SystemRoot%;%SystemRoot%\\System32\\Wbem;%SYSTEMROOT%\\System32\\WindowsPowerShell\\v1.0\\;C:\\ruby\\bin;C:\\opscode\\chef\\bin;C:\\opscode\\chef\\embedded\\bin\;%PATH%\"\n"
162
162
  start_chef << "chef-client -c c:/chef/client.rb -j c:/chef/first-boot.json#{bootstrap_environment_option}\n"
163
163
  end
164
164
 
@@ -137,7 +137,7 @@ class Chef
137
137
  end
138
138
 
139
139
  def download_cookbook_to(download_path)
140
- downloader = Chef::Knife::CookbookSiteDownload.new
140
+ downloader = Chef::Knife::SupermarketDownload.new
141
141
  downloader.config[:file] = download_path
142
142
  downloader.config[:supermarket_site] = config[:supermarket_site]
143
143
  downloader.name_args = name_args
@@ -37,7 +37,7 @@ class Chef
37
37
  FATAL_EVENT_ID = 10104
38
38
 
39
39
  # Since we must install the event logger, this is not really configurable
40
- SOURCE = Chef::Dist::PRODUCT.freeze
40
+ SOURCE = Chef::Dist::SHORT.freeze
41
41
 
42
42
  include Chef::Mixin::Unformatter
43
43
 
@@ -401,6 +401,27 @@ class Chef
401
401
  crl.sign(ca_private_key, ::OpenSSL::Digest::SHA256.new)
402
402
  crl
403
403
  end
404
+
405
+ # Return true if a certificate need to be renewed (or doesn't exist) according to the number
406
+ # of days before expiration given
407
+ # @param [string] cert_file path of the cert file or cert content
408
+ # @param [integer] renew_before_expiry number of days before expiration
409
+ # @return [true, false]
410
+ def cert_need_renewall?(cert_file, renew_before_expiry)
411
+ resp = true
412
+ cert_content = ::File.exist?(cert_file) ? File.read(cert_file) : cert_file
413
+ begin
414
+ cert = OpenSSL::X509::Certificate.new cert_content
415
+ rescue ::OpenSSL::X509::CertificateError
416
+ return resp
417
+ end
418
+
419
+ unless cert.not_after <= Time.now + 3600 * 24 * renew_before_expiry
420
+ resp = false
421
+ end
422
+
423
+ resp
424
+ end
404
425
  end
405
426
  end
406
427
  end
@@ -24,41 +24,3 @@ module Net
24
24
  include ChefNetHTTPExceptionExtensions
25
25
  end
26
26
  end
27
-
28
- if Net::HTTP.instance_methods.map(&:to_s).include?("proxy_uri")
29
- begin
30
- # Ruby 2.0 changes the way proxy support is implemented in Net::HTTP.
31
- # The implementation does not work correctly with IPv6 literals because it
32
- # concatenates the address into a URI without adding square brackets for
33
- # IPv6 addresses.
34
- #
35
- # If the bug is present, a call to Net::HTTP#proxy_uri when the host is an
36
- # IPv6 address will fail by creating an invalid URI, like so:
37
- #
38
- # ruby -r'net/http' -e 'Net::HTTP.new("::1", 80).proxy_uri'
39
- # /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/generic.rb:214:in `initialize': the scheme http does not accept registry part: ::1:80 (or bad hostname?) (URI::InvalidURIError)
40
- # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/http.rb:84:in `initialize'
41
- # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/common.rb:214:in `new'
42
- # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/common.rb:214:in `parse'
43
- # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/common.rb:747:in `parse'
44
- # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/uri/common.rb:994:in `URI'
45
- # from /Users/ddeleo/.rbenv/versions/2.0.0-p247/lib/ruby/2.0.0/net/http.rb:1027:in `proxy_uri'
46
- # from -e:1:in `<main>'
47
- #
48
- # https://bugs.ruby-lang.org/issues/9129
49
- #
50
- # NOTE: This should be fixed in Ruby 2.2.0, and backported to Ruby 2.0 and
51
- # 2.1 (not yet released so the version/patchlevel required isn't known
52
- # yet).
53
- Net::HTTP.new("::1", 80).proxy_uri
54
- rescue URI::InvalidURIError
55
- class Net::HTTP
56
-
57
- def proxy_uri # :nodoc:
58
- ipv6_safe_addr = address.to_s.include?(":") ? "[#{address}]" : address
59
- @proxy_uri ||= URI("http://#{ipv6_safe_addr}:#{port}").find_proxy
60
- end
61
-
62
- end
63
- end
64
- end
@@ -216,11 +216,13 @@ class Chef
216
216
  raise Chef::Exceptions::Cron, "Error updating state of #{new_resource.name}, error: #{e}"
217
217
  end
218
218
 
219
- def get_crontab_entry
220
- newcron = ""
221
- newcron << "# Chef Name: #{new_resource.name}\n"
219
+ #
220
+ # @return [String] The string of Env Variables containing line breaks.
221
+ #
222
+ def env_var_str
223
+ str = []
222
224
  %i{mailto path shell home}.each do |v|
223
- newcron << "#{v.to_s.upcase}=\"#{new_resource.send(v)}\"\n" if new_resource.send(v)
225
+ str << "#{v.to_s.upcase}=\"#{new_resource.send(v)}\"" if new_resource.send(v)
224
226
  end
225
227
  new_resource.environment.each do |name, value|
226
228
  if ENVIRONMENT_PROPERTIES.include?(name)
@@ -228,20 +230,63 @@ class Chef
228
230
  logger.warn("#{new_resource.name}: the environment property contains the '#{name}' variable, which should be set separately as a property.")
229
231
  new_resource.send(name.downcase.to_sym, value.gsub(/^"|"$/, ""))
230
232
  new_resource.environment.delete(name)
231
- newcron << "#{name.to_s.upcase}=\"#{value}\"\n"
233
+ str << "#{name.to_s.upcase}=\"#{value}\""
232
234
  else
233
235
  raise Chef::Exceptions::Cron, "#{new_resource.name}: the '#{name}' property is set and environment property also contains the '#{name}' variable. Remove the variable from the environment property."
234
236
  end
235
237
  else
236
- newcron << "#{name}=#{value}\n"
238
+ str << "#{name}=#{value}"
237
239
  end
238
240
  end
241
+ str.join("\n")
242
+ end
243
+
244
+ #
245
+ # @return [String] The Cron time string consisting five fields that Cron converts into a time interval.
246
+ #
247
+ def duration_str
239
248
  if new_resource.time
240
- newcron << "@#{new_resource.time} #{new_resource.command}\n"
249
+ "@#{new_resource.time}"
241
250
  else
242
- newcron << "#{new_resource.minute} #{new_resource.hour} #{new_resource.day} #{new_resource.month} #{new_resource.weekday} #{new_resource.command}\n"
251
+ "#{new_resource.minute} #{new_resource.hour} #{new_resource.day} #{new_resource.month} #{new_resource.weekday}"
243
252
  end
244
- newcron
253
+ end
254
+
255
+ #
256
+ # @return [String] The timeout command string formed as per time_out property.
257
+ #
258
+ def time_out_str
259
+ return "" if new_resource.time_out.empty?
260
+
261
+ str = " timeout"
262
+ str << " --preserve-status" if new_resource.time_out["preserve-status"].to_s.downcase == "true"
263
+ str << " --foreground" if new_resource.time_out["foreground"].to_s.downcase == "true"
264
+ str << " --kill-after #{new_resource.time_out["kill-after"]}" if new_resource.time_out["kill-after"]
265
+ str << " --signal #{new_resource.time_out["signal"]}" if new_resource.time_out["signal"]
266
+ str << " #{new_resource.time_out["duration"]};"
267
+ str
268
+ end
269
+
270
+ #
271
+ # @return [String] The command to be executed. The new line at the end has been added purposefully.
272
+ #
273
+ def cmd_str
274
+ " #{new_resource.command}\n"
275
+ end
276
+
277
+ # Concatenates various information and formulates a complete string that
278
+ # could be written in the crontab
279
+ #
280
+ # @return [String] A crontab string formed as per the user inputs.
281
+ #
282
+ def get_crontab_entry
283
+ # Initialize
284
+ newcron = []
285
+ newcron << "# Chef Name: #{new_resource.name}"
286
+ newcron << env_var_str unless env_var_str.empty?
287
+ newcron << duration_str + time_out_str + cmd_str
288
+
289
+ newcron.join("\n")
245
290
  end
246
291
 
247
292
  def weekday_in_crontab
@@ -33,8 +33,11 @@ class Chef
33
33
  raise Chef::Exceptions::Cron, "Aix cron entry does not support environment variables. Please set them in script and use script in cron."
34
34
  end
35
35
 
36
- newcron = ""
37
- newcron << "# Chef Name: #{new_resource.name}\n"
36
+ if time_out_set?
37
+ raise Chef::Exceptions::Cron, "Aix cron entry does not support timeout."
38
+ end
39
+
40
+ newcron = "# Chef Name: #{new_resource.name}\n"
38
41
  newcron << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday}"
39
42
 
40
43
  newcron << " #{@new_resource.command}\n"
@@ -44,6 +47,10 @@ class Chef
44
47
  def env_vars_are_set?
45
48
  @new_resource.environment.length > 0 || !@new_resource.mailto.nil? || !@new_resource.path.nil? || !@new_resource.shell.nil? || !@new_resource.home.nil?
46
49
  end
50
+
51
+ def time_out_set?
52
+ !@new_resource.time_out.empty?
53
+ end
47
54
  end
48
55
  end
49
56
  end
@@ -194,7 +194,7 @@ class Chef
194
194
  "environment_variables" => "EnvironmentVariables",
195
195
  "exit_timeout" => "ExitTimeout",
196
196
  "ld_group" => "GroupName",
197
- "hard_resource_limits" => "HardreSourceLimits",
197
+ "hard_resource_limits" => "HardResourceLimits",
198
198
  "inetd_compatibility" => "inetdCompatibility",
199
199
  "init_groups" => "InitGroups",
200
200
  "keep_alive" => "KeepAlive",
@@ -110,7 +110,7 @@ class Chef
110
110
  return unless current_resource.password != new_resource.password && new_resource.password
111
111
 
112
112
  logger.trace("#{new_resource.username} setting password to #{new_resource.password}")
113
- command = "echo '#{new_resource.username}:#{new_resource.password}' | chpasswd -e"
113
+ command = "echo '#{new_resource.username}:#{new_resource.password}' | chpasswd -c -e"
114
114
  shell_out!(command)
115
115
  end
116
116
 
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # Author:: Ryan Cragun (<ryan@chef.io>)
3
- # Copyright:: Copyright (c) 2019, Chef Software Inc.
3
+ # Copyright:: Copyright (c) 2019-2019, Chef Software Inc.
4
4
  # License:: Apache License, Version 2.0
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -92,6 +92,8 @@ class Chef
92
92
 
93
93
  @user_plist = Plist.new(::Plist.parse_xml(user_xml))
94
94
 
95
+ return unless user_plist[:shadow_hash]
96
+
95
97
  shadow_hash_hex = user_plist[:shadow_hash][0]
96
98
  return unless shadow_hash_hex && shadow_hash_hex != ""
97
99
 
@@ -148,14 +150,12 @@ class Chef
148
150
  cmd += ["-adminPassword", new_resource.admin_password]
149
151
  end
150
152
 
151
- converge_by "create user" do
152
- # sysadminctl doesn't exit with a non-zero exit code if it encounters
153
- # a problem. We'll check stderr and make sure we see that it finished
154
- # correctly.
155
- res = run_sysadminctl(cmd)
156
- unless res.downcase =~ /creating user/
157
- raise Chef::Exceptions::User, "error when creating user: #{res}"
158
- end
153
+ # sysadminctl doesn't exit with a non-zero exit code if it encounters
154
+ # a problem. We'll check stderr and make sure we see that it finished
155
+ # correctly.
156
+ res = run_sysadminctl(cmd)
157
+ unless res.downcase =~ /creating user/
158
+ raise Chef::Exceptions::User, "error when creating user: #{res}"
159
159
  end
160
160
 
161
161
  # Wait for the user to show up in the ds cache
@@ -289,11 +289,9 @@ class Chef
289
289
 
290
290
  # sysadminctl doesn't exit with a non-zero exit code if it encounters
291
291
  # a problem. We'll check stderr and make sure we see that it finished
292
- converge_by "remove user" do
293
- res = run_sysadminctl(cmd)
294
- unless res.downcase =~ /deleting record|not found/
295
- raise Chef::Exceptions::User, "error deleting user: #{res}"
296
- end
292
+ res = run_sysadminctl(cmd)
293
+ unless res.downcase =~ /deleting record|not found/
294
+ raise Chef::Exceptions::User, "error deleting user: #{res}"
297
295
  end
298
296
 
299
297
  reload_user_plist
@@ -301,18 +299,15 @@ class Chef
301
299
  end
302
300
 
303
301
  def lock_user
304
- converge_by "lock user" do
305
- run_dscl("append", "/Users/#{new_resource.username}", "AuthenticationAuthority", ";DisabledUser;")
306
- end
302
+ run_dscl("append", "/Users/#{new_resource.username}", "AuthenticationAuthority", ";DisabledUser;")
307
303
 
308
304
  reload_user_plist
309
305
  end
310
306
 
311
307
  def unlock_user
312
308
  auth_string = user_plist[:auth_authority].reject! { |tag| tag == ";DisabledUser;" }.join.strip
313
- converge_by "unlock user" do
314
- run_dscl("create", "/Users/#{new_resource.username}", "AuthenticationAuthority", auth_string)
315
- end
309
+
310
+ run_dscl("create", "/Users/#{new_resource.username}", "AuthenticationAuthority", auth_string)
316
311
 
317
312
  reload_user_plist
318
313
  end
@@ -483,7 +478,7 @@ class Chef
483
478
  )
484
479
  end
485
480
 
486
- shadow_hash = user_plist[:shadow_hash][0]
481
+ shadow_hash = user_plist[:shadow_hash] ? user_plist[:shadow_hash][0] : {}
487
482
  shadow_hash["SALTED-SHA512-PBKDF2"] = {
488
483
  "entropy" => entropy,
489
484
  "salt" => salt,
@@ -533,7 +528,7 @@ class Chef
533
528
  run_dsimport(import_file, "/Local/Default", "M")
534
529
  run_dscl("create", "/Users/#{new_resource.username}", "Password", "********")
535
530
  ensure
536
- ::File.delete(import_file) if defined?(import_file) && ::File.exist?(import_file)
531
+ ::File.delete(import_file) if import_file && ::File.exist?(import_file)
537
532
  end
538
533
 
539
534
  def wait_for_user
@@ -328,7 +328,7 @@ class Chef
328
328
  def task_needs_update?(task)
329
329
  flag = false
330
330
  if new_resource.frequency == :none
331
- flag = (task.account_information != new_resource.user ||
331
+ flag = (task.author != new_resource.user ||
332
332
  task.application_name != new_resource.command ||
333
333
  description_needs_update?(task) ||
334
334
  task.parameters != new_resource.command_arguments.to_s ||
@@ -352,7 +352,7 @@ class Chef
352
352
  current_task_trigger[:type] != new_task_trigger[:type] ||
353
353
  current_task_trigger[:random_minutes_interval].to_i != new_task_trigger[:random_minutes_interval].to_i ||
354
354
  current_task_trigger[:minutes_interval].to_i != new_task_trigger[:minutes_interval].to_i ||
355
- task.account_information.to_s.casecmp(new_resource.user.to_s) != 0 ||
355
+ task.author.to_s.casecmp(new_resource.user.to_s) != 0 ||
356
356
  task.application_name != new_resource.command ||
357
357
  description_needs_update?(task) ||
358
358
  task.parameters != new_resource.command_arguments.to_s ||
@@ -102,8 +102,11 @@ class Chef
102
102
  end
103
103
 
104
104
  if new_resource.owner || new_resource.group
105
- converge_by("set owner of #{new_resource.destination} to #{new_resource.owner}:#{new_resource.group}") do
106
- FileUtils.chown_R(new_resource.owner, new_resource.group, new_resource.destination)
105
+ converge_by("set owner of files extracted in #{new_resource.destination} to #{new_resource.owner}:#{new_resource.group}") do
106
+ archive = Archive::Reader.open_filename(new_resource.path)
107
+ archive.each_entry do |e|
108
+ FileUtils.chown(new_resource.owner, new_resource.group, "#{new_resource.destination}/#{e.pathname}")
109
+ end
107
110
  end
108
111
  end
109
112
  end
@@ -162,6 +162,35 @@ class Chef
162
162
  description: "A Hash of environment variables in the form of ({'ENV_VARIABLE' => 'VALUE'}).",
163
163
  default: lazy { {} }
164
164
 
165
+ TIMEOUT_OPTS = %w{duration preserve-status foreground kill-after signal}.freeze
166
+ TIMEOUT_REGEX = /\A\S+/.freeze
167
+
168
+ property :time_out, Hash,
169
+ description: "A Hash of timeouts in the form of ({'OPTION' => 'VALUE'}).
170
+ Accepted valid options are:
171
+ preserve-status (BOOL, default: 'false'),
172
+ foreground (BOOL, default: 'false'),
173
+ kill-after (in seconds),
174
+ signal (a name like 'HUP' or a number)",
175
+ default: lazy { {} },
176
+ introduced: "15.7",
177
+ coerce: proc { |h|
178
+ if h.is_a?(Hash)
179
+ invalid_keys = h.keys - TIMEOUT_OPTS
180
+ unless invalid_keys.empty?
181
+ error_msg = "Key of option time_out must be equal to one of: \"#{TIMEOUT_OPTS.join('", "')}\"! You passed \"#{invalid_keys.join(", ")}\"."
182
+ raise Chef::Exceptions::ValidationFailed, error_msg
183
+ end
184
+ unless h.values.all? { |x| x =~ TIMEOUT_REGEX }
185
+ error_msg = "Values of option time_out should be non-empty string without any leading whitespaces."
186
+ raise Chef::Exceptions::ValidationFailed, error_msg
187
+ end
188
+ h
189
+ elsif h.is_a?(Integer) || h.is_a?(String)
190
+ { "duration" => h }
191
+ end
192
+ }
193
+
165
194
  private
166
195
 
167
196
  def integerize(integerish)
@@ -206,6 +206,35 @@ class Chef
206
206
  description: "A Hash containing additional arbitrary environment variables under which the cron job will be run in the form of ``({'ENV_VARIABLE' => 'VALUE'})``.",
207
207
  default: lazy { {} }
208
208
 
209
+ TIMEOUT_OPTS = %w{duration preserve-status foreground kill-after signal}.freeze
210
+ TIMEOUT_REGEX = /\A\S+/.freeze
211
+
212
+ property :time_out, Hash,
213
+ description: "A Hash of timeouts in the form of ({'OPTION' => 'VALUE'}).
214
+ Accepted valid options are:
215
+ preserve-status (BOOL, default: 'false'),
216
+ foreground (BOOL, default: 'false'),
217
+ kill-after (in seconds),
218
+ signal (a name like 'HUP' or a number)",
219
+ default: lazy { {} },
220
+ introduced: "15.7",
221
+ coerce: proc { |h|
222
+ if h.is_a?(Hash)
223
+ invalid_keys = h.keys - TIMEOUT_OPTS
224
+ unless invalid_keys.empty?
225
+ error_msg = "Key of option time_out must be equal to one of: \"#{TIMEOUT_OPTS.join('", "')}\"! You passed \"#{invalid_keys.join(", ")}\"."
226
+ raise Chef::Exceptions::ValidationFailed, error_msg
227
+ end
228
+ unless h.values.all? { |x| x =~ TIMEOUT_REGEX }
229
+ error_msg = "Values of option time_out should be non-empty string without any leading whitespaces."
230
+ raise Chef::Exceptions::ValidationFailed, error_msg
231
+ end
232
+ h
233
+ elsif h.is_a?(Integer) || h.is_a?(String)
234
+ { "duration" => h }
235
+ end
236
+ }
237
+
209
238
  property :mode, [String, Integer],
210
239
  description: "The octal mode of the generated crontab file.",
211
240
  default: "0600"