chef 14.7.17 → 14.8.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/chef/mixin/which.rb +3 -1
- data/lib/chef/provider/apt_repository.rb +1 -1
- data/lib/chef/provider/execute.rb +1 -1
- data/lib/chef/provider/osx_profile.rb +1 -1
- data/lib/chef/provider/package/apt.rb +15 -3
- data/lib/chef/provider/package/cab.rb +6 -2
- data/lib/chef/provider/package/chocolatey.rb +1 -1
- data/lib/chef/provider/package/dnf/python_helper.rb +2 -1
- data/lib/chef/provider/package/openbsd.rb +1 -1
- data/lib/chef/provider/package/rubygems.rb +38 -16
- data/lib/chef/resource/apt_package.rb +1 -1
- data/lib/chef/resource/apt_preference.rb +1 -1
- data/lib/chef/resource/apt_repository.rb +1 -1
- data/lib/chef/resource/chocolatey_config.rb +1 -1
- data/lib/chef/resource/chocolatey_source.rb +1 -1
- data/lib/chef/resource/cron.rb +27 -8
- data/lib/chef/resource/cron_access.rb +1 -1
- data/lib/chef/resource/cron_d.rb +8 -8
- data/lib/chef/resource/directory.rb +1 -1
- data/lib/chef/resource/dmg_package.rb +9 -9
- data/lib/chef/resource/execute.rb +2 -2
- data/lib/chef/resource/homebrew_cask.rb +1 -1
- data/lib/chef/resource/homebrew_tap.rb +1 -1
- data/lib/chef/resource/hostname.rb +2 -2
- data/lib/chef/resource/kernel_module.rb +1 -1
- data/lib/chef/resource/link.rb +1 -1
- data/lib/chef/resource/ohai_hint.rb +1 -1
- data/lib/chef/resource/package.rb +1 -1
- data/lib/chef/resource/scm.rb +1 -1
- data/lib/chef/resource/swap_file.rb +1 -1
- data/lib/chef/resource/sysctl.rb +1 -1
- data/lib/chef/resource/systemd_unit.rb +1 -1
- data/lib/chef/resource/template.rb +11 -3
- data/lib/chef/resource/windows_ad_join.rb +1 -1
- data/lib/chef/resource/windows_auto_run.rb +1 -1
- data/lib/chef/resource/windows_certificate.rb +2 -1
- data/lib/chef/resource/windows_feature.rb +6 -6
- data/lib/chef/resource/windows_feature_dism.rb +13 -17
- data/lib/chef/resource/windows_feature_powershell.rb +1 -1
- data/lib/chef/resource/windows_share.rb +9 -3
- data/lib/chef/resource/windows_task.rb +230 -231
- data/lib/chef/resource/windows_workgroup.rb +32 -23
- data/lib/chef/resource/yum_repository.rb +1 -1
- data/lib/chef/version.rb +1 -1
- data/spec/functional/resource/msu_package_spec.rb +14 -0
- data/spec/integration/knife/raw_spec.rb +11 -7
- data/spec/integration/knife/redirection_spec.rb +6 -4
- data/spec/support/shared/integration/app_server_support.rb +2 -2
- data/spec/unit/provider/apt_repository_spec.rb +3 -3
- data/spec/unit/provider/package/apt_spec.rb +81 -4
- data/spec/unit/provider/package/rubygems_spec.rb +47 -9
- data/spec/unit/resource/windows_certificate.rb +30 -0
- data/spec/unit/resource/windows_feature.rb +4 -0
- data/spec/unit/resource/windows_workgroup_spec.rb +27 -0
- metadata +4 -4
@@ -65,7 +65,7 @@ class Chef
|
|
65
65
|
description: "The current working directory from which the command will be run."
|
66
66
|
|
67
67
|
property :environment, Hash,
|
68
|
-
description: "
|
68
|
+
description: "A Hash of environment variables in the form of ({'ENV_VARIABLE' => 'VALUE'})."
|
69
69
|
|
70
70
|
alias :env :environment
|
71
71
|
|
@@ -87,7 +87,7 @@ class Chef
|
|
87
87
|
description: "The amount of time (in seconds) a command is to wait before timing out."
|
88
88
|
|
89
89
|
property :user, [ String, Integer ],
|
90
|
-
description: "The user name of the user identity with which to launch the new process. The user name may optionally be specifed with a domain, i.e. domainuser or user@my.dns.domain.com via Universal Principal Name (UPN)format. It can also be specified without a domain simply as user if the domain is instead specified using the domain
|
90
|
+
description: "The user name of the user identity with which to launch the new process. The user name may optionally be specifed with a domain, i.e. domainuser or user@my.dns.domain.com via Universal Principal Name (UPN)format. It can also be specified without a domain simply as user if the domain is instead specified using the domain property. On Windows only, if this property is specified, the password property must be specified."
|
91
91
|
|
92
92
|
property :domain, String,
|
93
93
|
introduced: "12.21",
|
@@ -32,7 +32,7 @@ class Chef
|
|
32
32
|
include Chef::Mixin::HomebrewUser
|
33
33
|
|
34
34
|
property :cask_name, String,
|
35
|
-
description: "
|
35
|
+
description: "An optional property to set the cask name if it differs from the resource block's name.",
|
36
36
|
regex: %r{^[\w/-]+$},
|
37
37
|
validation_message: "The provided Homebrew cask name is not valid. Cask names can contain alphanumeric characters, _, -, or / only!",
|
38
38
|
name_property: true
|
@@ -32,7 +32,7 @@ class Chef
|
|
32
32
|
include Chef::Mixin::HomebrewUser
|
33
33
|
|
34
34
|
property :tap_name, String,
|
35
|
-
description: "
|
35
|
+
description: "An optional property to set the tap name if it differs from the resource block's name.",
|
36
36
|
validation_message: "Homebrew tap names must be in the form REPO/TAP format!",
|
37
37
|
regex: %r{^[\w-]+(?:\/[\w-]+)+$},
|
38
38
|
name_property: true
|
@@ -27,7 +27,7 @@ class Chef
|
|
27
27
|
introduced "14.0"
|
28
28
|
|
29
29
|
property :hostname, String,
|
30
|
-
description: "
|
30
|
+
description: "An optional property to set the hostname if it differs from the resource block's name.",
|
31
31
|
name_property: true
|
32
32
|
|
33
33
|
property :compile_time, [ TrueClass, FalseClass ],
|
@@ -36,7 +36,7 @@ class Chef
|
|
36
36
|
|
37
37
|
property :ipaddress, String,
|
38
38
|
description: "The IP address to use when configuring the hosts file.",
|
39
|
-
default: lazy { node["ipaddress"] }
|
39
|
+
default: lazy { node["ipaddress"] }, default_description: "The node's IP address as determined by Ohai."
|
40
40
|
|
41
41
|
property :aliases, [ Array, nil ],
|
42
42
|
description: "An array of hostname aliases to use when configuring the hosts file.",
|
@@ -18,7 +18,7 @@ class Chef
|
|
18
18
|
introduced "14.3"
|
19
19
|
|
20
20
|
property :modname, String,
|
21
|
-
description: "
|
21
|
+
description: "An optional property to set the kernel module name if it differs from the resource block's name.",
|
22
22
|
name_property: true, identity: true
|
23
23
|
|
24
24
|
property :load_dir, String,
|
data/lib/chef/resource/link.rb
CHANGED
@@ -48,7 +48,7 @@ class Chef
|
|
48
48
|
end
|
49
49
|
|
50
50
|
property :target_file, String,
|
51
|
-
description: "
|
51
|
+
description: "An optional property to set the target file if it differs from the resource block's name.",
|
52
52
|
name_property: true, identity: true
|
53
53
|
|
54
54
|
property :to, String,
|
@@ -27,7 +27,7 @@ class Chef
|
|
27
27
|
introduced "14.0"
|
28
28
|
|
29
29
|
property :hint_name, String,
|
30
|
-
description: "
|
30
|
+
description: "An optional property to set the hint name if it differs from the resource block's name.",
|
31
31
|
name_property: true
|
32
32
|
|
33
33
|
property :content, Hash,
|
@@ -42,7 +42,7 @@ class Chef
|
|
42
42
|
end
|
43
43
|
|
44
44
|
property :package_name, [ String, Array ],
|
45
|
-
description: "
|
45
|
+
description: "An optional property to set the package name if it differs from the resource block's name.",
|
46
46
|
identity: true
|
47
47
|
|
48
48
|
property :version, [ String, Array ],
|
data/lib/chef/resource/scm.rb
CHANGED
@@ -66,7 +66,7 @@ class Chef
|
|
66
66
|
default: "deploy"
|
67
67
|
|
68
68
|
property :environment, [Hash, nil],
|
69
|
-
description: "A Hash of environment variables in the form of ({'ENV_VARIABLE' => 'VALUE'}).
|
69
|
+
description: "A Hash of environment variables in the form of ({'ENV_VARIABLE' => 'VALUE'}).",
|
70
70
|
default: nil
|
71
71
|
|
72
72
|
alias :env :environment
|
@@ -27,7 +27,7 @@ class Chef
|
|
27
27
|
introduced "14.0"
|
28
28
|
|
29
29
|
property :path, String,
|
30
|
-
description: "The path where the swap file will be created on the system, if it differs from the resource block name.",
|
30
|
+
description: "The path where the swap file will be created on the system, if it differs from the resource block's name.",
|
31
31
|
name_property: true
|
32
32
|
|
33
33
|
property :size, Integer,
|
data/lib/chef/resource/sysctl.rb
CHANGED
@@ -33,7 +33,7 @@ class Chef
|
|
33
33
|
introduced "14.0"
|
34
34
|
|
35
35
|
property :key, String,
|
36
|
-
description: "The kernel parameter key in dotted format, if it differs from the resource block name.",
|
36
|
+
description: "The kernel parameter key in dotted format, if it differs from the resource block's name.",
|
37
37
|
name_property: true
|
38
38
|
|
39
39
|
property :ignore_error, [TrueClass, FalseClass],
|
@@ -60,7 +60,7 @@ class Chef
|
|
60
60
|
|
61
61
|
property :unit_name, String, desired_state: false,
|
62
62
|
identity: true, name_property: true,
|
63
|
-
description: "The name of the unit file if it differs from the resource block name.",
|
63
|
+
description: "The name of the unit file if it differs from the resource block's name.",
|
64
64
|
introduced: "13.7"
|
65
65
|
|
66
66
|
def to_ini
|
@@ -57,9 +57,17 @@ class Chef
|
|
57
57
|
)
|
58
58
|
end
|
59
59
|
|
60
|
-
property :variables, Hash,
|
61
|
-
|
62
|
-
|
60
|
+
property :variables, Hash,
|
61
|
+
description: "The variables property of the template resource can be used to reference a partial template file by using a Hash.",
|
62
|
+
default: lazy { Hash.new }
|
63
|
+
|
64
|
+
property :cookbook, String,
|
65
|
+
description: "The cookbook in which a file is located (if it is not located in the current cookbook). The default value is the current cookbook.",
|
66
|
+
desired_state: false
|
67
|
+
|
68
|
+
property :local, [ TrueClass, FalseClass ],
|
69
|
+
default: false, desired_state: false,
|
70
|
+
description: "Load a template from a local path. By default, the chef-client loads templates from a cookbook’s /templates directory. When this property is set to true, use the source property to specify the path to a template on the local node."
|
63
71
|
|
64
72
|
# Declares a helper method to be defined in the template context when
|
65
73
|
# rendering.
|
@@ -65,7 +65,7 @@ class Chef
|
|
65
65
|
|
66
66
|
unless on_domain?
|
67
67
|
cmd = "$pswd = ConvertTo-SecureString \'#{new_resource.domain_password}\' -AsPlainText -Force;"
|
68
|
-
cmd << "$credential = New-Object System.Management.Automation.PSCredential (\"#{new_resource.domain_user}\",$pswd);"
|
68
|
+
cmd << "$credential = New-Object System.Management.Automation.PSCredential (\"#{new_resource.domain_user}@#{new_resource.domain_name}\",$pswd);"
|
69
69
|
cmd << "Add-Computer -DomainName #{new_resource.domain_name} -Credential $credential"
|
70
70
|
cmd << " -OUPath \"#{new_resource.ou_path}\"" if new_resource.ou_path
|
71
71
|
cmd << " -NewName \"#{new_resource.new_hostname}\"" if new_resource.new_hostname
|
@@ -28,7 +28,7 @@ class Chef
|
|
28
28
|
introduced "14.0"
|
29
29
|
|
30
30
|
property :program_name, String,
|
31
|
-
description: "The name of the program to run at login, if it differs from the resource block name.",
|
31
|
+
description: "The name of the program to run at login, if it differs from the resource block's name.",
|
32
32
|
name_property: true
|
33
33
|
|
34
34
|
property :path, String,
|
@@ -17,6 +17,7 @@
|
|
17
17
|
# limitations under the License.
|
18
18
|
#
|
19
19
|
|
20
|
+
require "chef/util/path_helper"
|
20
21
|
require "chef/resource"
|
21
22
|
require "win32-certstore" if Chef::Platform.windows?
|
22
23
|
require "openssl"
|
@@ -180,7 +181,7 @@ class Chef
|
|
180
181
|
|
181
182
|
def cert_script(persist)
|
182
183
|
cert_script = "$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2"
|
183
|
-
file =
|
184
|
+
file = Chef::Util::PathHelper.cleanpath(new_resource.source)
|
184
185
|
cert_script << " \"#{file}\""
|
185
186
|
if ::File.extname(file.downcase) == ".pfx"
|
186
187
|
cert_script << ", \"#{new_resource.pfx_password}\""
|
@@ -28,7 +28,7 @@ class Chef
|
|
28
28
|
introduced "14.0"
|
29
29
|
|
30
30
|
property :feature_name, [Array, String],
|
31
|
-
description: "The name of the feature(s) or role(s) to install, if it differs from the resource block name. The same feature may have different names depending on the underlying installation method being used (ie DHCPServer vs DHCP; DNS-Server-Full-Role vs DNS).",
|
31
|
+
description: "The name of the feature(s) or role(s) to install, if it differs from the resource block's name. The same feature may have different names depending on the underlying installation method being used (ie DHCPServer vs DHCP; DNS-Server-Full-Role vs DNS).",
|
32
32
|
name_property: true
|
33
33
|
|
34
34
|
property :source, String,
|
@@ -44,7 +44,8 @@ class Chef
|
|
44
44
|
|
45
45
|
property :install_method, Symbol,
|
46
46
|
description: "The underlying installation method to use for feature installation. Specify ':windows_feature_dism' for DISM or ':windows_feature_powershell' for PowerShell.",
|
47
|
-
equal_to: [:windows_feature_dism, :windows_feature_powershell, :windows_feature_servermanagercmd]
|
47
|
+
equal_to: [:windows_feature_dism, :windows_feature_powershell, :windows_feature_servermanagercmd],
|
48
|
+
default: :windows_feature_dism
|
48
49
|
|
49
50
|
property :timeout, Integer,
|
50
51
|
description: "Specifies a timeout (in seconds) for the feature installation.",
|
@@ -72,16 +73,15 @@ class Chef
|
|
72
73
|
# call the appropriate windows_feature resource based on the specified subresource
|
73
74
|
# @return [void]
|
74
75
|
def run_default_subresource(desired_action)
|
75
|
-
raise "Support for Windows feature installation via servermanagercmd.exe has been removed as this support is no longer needed in Windows 2008 R2 and above. You will need to update your
|
76
|
+
raise "Support for Windows feature installation via servermanagercmd.exe has been removed as this support is no longer needed in Windows 2008 R2 and above. You will need to update your recipe to install either via dism or powershell (preferred)." if new_resource.install_method == :windows_feature_servermanagercmd
|
76
77
|
|
77
|
-
|
78
|
-
declare_resource(subresource, new_resource.name) do
|
78
|
+
declare_resource(new_resource.install_method, new_resource.name) do
|
79
79
|
action desired_action
|
80
80
|
feature_name new_resource.feature_name
|
81
81
|
source new_resource.source if new_resource.source
|
82
82
|
all new_resource.all
|
83
83
|
timeout new_resource.timeout
|
84
|
-
management_tools new_resource.management_tools if
|
84
|
+
management_tools new_resource.management_tools if new_resource.install_method == :windows_feature_powershell
|
85
85
|
end
|
86
86
|
end
|
87
87
|
end
|
@@ -57,7 +57,6 @@ class Chef
|
|
57
57
|
|
58
58
|
reload_cached_dism_data unless node["dism_features_cache"]
|
59
59
|
fail_if_unavailable # fail if the features don't exist
|
60
|
-
fail_if_removed # fail if the features are in removed state
|
61
60
|
|
62
61
|
logger.trace("Windows features needing installation: #{features_to_install.empty? ? 'none' : features_to_install.join(',')}")
|
63
62
|
unless features_to_install.empty?
|
@@ -66,8 +65,12 @@ class Chef
|
|
66
65
|
install_command = "dism.exe /online /enable-feature #{features_to_install.map { |f| "/featurename:#{f}" }.join(' ')} /norestart"
|
67
66
|
install_command << " /LimitAccess /Source:\"#{new_resource.source}\"" if new_resource.source
|
68
67
|
install_command << " /All" if new_resource.all
|
69
|
-
|
70
|
-
|
68
|
+
begin
|
69
|
+
shell_out!(install_command, returns: [0, 42, 127, 3010], timeout: new_resource.timeout)
|
70
|
+
rescue Mixlib::ShellOut::ShellCommandFailed => e
|
71
|
+
raise "Error 50 returned by DISM related to parent features, try setting the 'all' property to 'true' on the 'windows_feature_dism' resource." if required_parent_feature?(e.inspect)
|
72
|
+
raise e.message
|
73
|
+
end
|
71
74
|
|
72
75
|
reload_cached_dism_data # Reload cached dism feature state
|
73
76
|
end
|
@@ -118,10 +121,10 @@ class Chef
|
|
118
121
|
# disabled features are always available to install
|
119
122
|
available_for_install = node["dism_features_cache"]["disabled"].dup
|
120
123
|
|
121
|
-
#
|
122
|
-
available_for_install.concat(node["dism_features_cache"]["removed"])
|
124
|
+
# removed features are also available for installation
|
125
|
+
available_for_install.concat(node["dism_features_cache"]["removed"])
|
123
126
|
|
124
|
-
# the intersection of the features to install & disabled/removed
|
127
|
+
# the intersection of the features to install & disabled/removed features are what needs installing
|
125
128
|
new_resource.feature_name & available_for_install
|
126
129
|
end
|
127
130
|
end
|
@@ -204,22 +207,15 @@ class Chef
|
|
204
207
|
node.override["dism_features_cache"][feature_type] << feature_details
|
205
208
|
end
|
206
209
|
|
207
|
-
# Fail if any of the packages are in a removed state
|
208
|
-
# @return [void]
|
209
|
-
def fail_if_removed
|
210
|
-
return if new_resource.source # if someone provides a source then all is well
|
211
|
-
if node["platform_version"].to_f > 6.2 # 2012R2 or later
|
212
|
-
return if registry_key_exists?('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Servicing') && registry_value_exists?('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Servicing', name: "LocalSourcePath") # if source is defined in the registry, still fine
|
213
|
-
end
|
214
|
-
removed = new_resource.feature_name & node["dism_features_cache"]["removed"]
|
215
|
-
raise "The Windows feature#{'s' if removed.count > 1} #{removed.join(',')} #{removed.count > 1 ? 'are' : 'is'} removed from the host and cannot be installed." unless removed.empty?
|
216
|
-
end
|
217
|
-
|
218
210
|
# Fail unless we're on windows 8+ / 2012+ where deleting a feature is supported
|
219
211
|
# @return [void]
|
220
212
|
def raise_if_delete_unsupported
|
221
213
|
raise Chef::Exceptions::UnsupportedAction, "#{self} :delete action not supported on Windows releases before Windows 8/2012. Cannot continue!" if older_than_win_2012_or_8?
|
222
214
|
end
|
215
|
+
|
216
|
+
def required_parent_feature?(error_message)
|
217
|
+
error_message.include?("Error: 50") && error_message.include?("required parent feature")
|
218
|
+
end
|
223
219
|
end
|
224
220
|
end
|
225
221
|
end
|
@@ -31,7 +31,7 @@ class Chef
|
|
31
31
|
introduced "14.0"
|
32
32
|
|
33
33
|
property :feature_name, [Array, String],
|
34
|
-
description: "The name of the feature(s) or role(s) to install, if it differs from the resource block name.",
|
34
|
+
description: "The name of the feature(s) or role(s) to install, if it differs from the resource block's name.",
|
35
35
|
coerce: proc { |x| to_formatted_array(x) },
|
36
36
|
name_property: true
|
37
37
|
|
@@ -102,7 +102,7 @@ class Chef
|
|
102
102
|
# this command selects individual objects because EncryptData & CachingMode have underlying
|
103
103
|
# types that get converted to their Integer values by ConvertTo-Json & we need to make sure
|
104
104
|
# those get written out as strings
|
105
|
-
share_state_cmd = "Get-SmbShare -Name '#{desired.share_name}' | Select-Object Name,Path, Description, Temporary, CATimeout, ContinuouslyAvailable, ConcurrentUserLimit, EncryptData | ConvertTo-Json"
|
105
|
+
share_state_cmd = "Get-SmbShare -Name '#{desired.share_name}' | Select-Object Name,Path, Description, Temporary, CATimeout, ContinuouslyAvailable, ConcurrentUserLimit, EncryptData | ConvertTo-Json -Compress"
|
106
106
|
|
107
107
|
Chef::Log.debug("Running '#{share_state_cmd}' to determine share state'")
|
108
108
|
ps_results = powershell_out(share_state_cmd)
|
@@ -126,7 +126,7 @@ class Chef
|
|
126
126
|
encrypt_data results["EncryptData"]
|
127
127
|
# folder_enumeration_mode results['FolderEnumerationMode']
|
128
128
|
|
129
|
-
perm_state_cmd = %{Get-SmbShareAccess -Name "#{desired.share_name}" | Select-Object AccountName,AccessControlType,AccessRight | ConvertTo-Json}
|
129
|
+
perm_state_cmd = %{Get-SmbShareAccess -Name "#{desired.share_name}" | Select-Object AccountName,AccessControlType,AccessRight | ConvertTo-Json -Compress}
|
130
130
|
|
131
131
|
Chef::Log.debug("Running '#{perm_state_cmd}' to determine share permissions state'")
|
132
132
|
ps_perm_results = powershell_out(perm_state_cmd)
|
@@ -242,6 +242,10 @@ class Chef
|
|
242
242
|
|
243
243
|
Chef::Log.debug("Running '#{share_cmd}' to create the share")
|
244
244
|
powershell_out!(share_cmd)
|
245
|
+
|
246
|
+
# New-SmbShare adds the "Everyone" user with read access no matter what so we need to remove it
|
247
|
+
# before we add our permissions
|
248
|
+
revoke_user_permissions(["Everyone"])
|
245
249
|
end
|
246
250
|
|
247
251
|
# determine what users in the current state don't exist in the desired state
|
@@ -297,8 +301,10 @@ class Chef
|
|
297
301
|
false
|
298
302
|
end
|
299
303
|
|
304
|
+
# revoke user permissions from a share
|
305
|
+
# @param [Array] users
|
300
306
|
def revoke_user_permissions(users)
|
301
|
-
revoke_command = "Revoke-SmbShareAccess -Name '#{new_resource.share_name}' -AccountName \"#{users.join(',')}\" -Force"
|
307
|
+
revoke_command = "Revoke-SmbShareAccess -Name '#{new_resource.share_name}' -AccountName \"#{users.join('","')}\" -Force"
|
302
308
|
Chef::Log.debug("Running '#{revoke_command}' to revoke share permissions")
|
303
309
|
powershell_out!(revoke_command)
|
304
310
|
end
|
@@ -22,319 +22,318 @@ require "chef/win32/security" if Chef::Platform.windows?
|
|
22
22
|
class Chef
|
23
23
|
class Resource
|
24
24
|
class WindowsTask < Chef::Resource
|
25
|
-
|
26
|
-
|
27
|
-
provides(:windows_task) { true }
|
25
|
+
resource_name :windows_task
|
26
|
+
provides(:windows_task) { true }
|
28
27
|
|
29
|
-
|
30
|
-
|
28
|
+
description "Use the windows_task resource to create, delete or run a Windows scheduled task. Requires Windows Server 2008 or later due to API usage."
|
29
|
+
introduced "13.0"
|
31
30
|
|
32
|
-
|
33
|
-
|
31
|
+
allowed_actions :create, :delete, :run, :end, :enable, :disable, :change
|
32
|
+
default_action :create
|
34
33
|
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
property :task_name, String, regex: [/\A[^\/\:\*\?\<\>\|]+\z/],
|
35
|
+
description: "The task name, such as 'Task Name' or '/Task Name'",
|
36
|
+
name_property: true
|
38
37
|
|
39
|
-
|
40
|
-
|
38
|
+
property :command, String,
|
39
|
+
description: "The command to be executed by the windows scheduled task."
|
41
40
|
|
42
|
-
|
43
|
-
|
41
|
+
property :cwd, String,
|
42
|
+
description: "The directory the task will be run from."
|
44
43
|
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
property :user, String,
|
45
|
+
description: "The user to run the task as.",
|
46
|
+
default: lazy { Chef::ReservedNames::Win32::Security::SID.LocalSystem.account_simple_name if Chef::Platform.windows? },
|
47
|
+
default_description: "The localized SYSTEM user for the node."
|
48
48
|
|
49
|
-
|
50
|
-
|
49
|
+
property :password, String,
|
50
|
+
description: "The user’s password. The user property must be set if using this property."
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
52
|
+
property :run_level, Symbol, equal_to: [:highest, :limited],
|
53
|
+
description: "Run with ':limited' or ':highest' privileges.",
|
54
|
+
default: :limited
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
|
56
|
+
property :force, [TrueClass, FalseClass],
|
57
|
+
description: "When used with create, will update the task.",
|
58
|
+
default: false
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
|
60
|
+
property :interactive_enabled, [TrueClass, FalseClass],
|
61
|
+
description: "Allow task to run interactively or non-interactively. Requires user and password to also be set.",
|
62
|
+
default: false
|
63
63
|
|
64
|
-
|
65
|
-
|
64
|
+
property :frequency_modifier, [Integer, String],
|
65
|
+
default: 1
|
66
66
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
67
|
+
property :frequency, Symbol, equal_to: [:minute,
|
68
|
+
:hourly,
|
69
|
+
:daily,
|
70
|
+
:weekly,
|
71
|
+
:monthly,
|
72
|
+
:once,
|
73
|
+
:on_logon,
|
74
|
+
:onstart,
|
75
|
+
:on_idle,
|
76
|
+
:none],
|
77
|
+
description: "The frequency with which to run the task."
|
78
78
|
|
79
|
-
|
80
|
-
|
79
|
+
property :start_day, String,
|
80
|
+
description: "Specifies the first date on which the task runs in MM/DD/YYYY format."
|
81
81
|
|
82
|
-
|
83
|
-
|
82
|
+
property :start_time, String,
|
83
|
+
description: "Specifies the start time to run the task, in HH:mm format."
|
84
84
|
|
85
|
-
|
86
|
-
|
85
|
+
property :day, [String, Integer],
|
86
|
+
description: "The day(s) on which the task runs."
|
87
87
|
|
88
|
-
|
89
|
-
|
88
|
+
property :months, String,
|
89
|
+
description: "The Months of the year on which the task runs, such as: 'JAN, FEB' or '\*'. Multiple months should be comma delimited. e.g. 'Jan, Feb, Mar, Dec'."
|
90
90
|
|
91
|
-
|
92
|
-
|
91
|
+
property :idle_time, Integer,
|
92
|
+
description: "For :on_idle frequency, the time (in minutes) without user activity that must pass to trigger the task, from 1 - 999."
|
93
93
|
|
94
|
-
|
95
|
-
|
94
|
+
property :random_delay, [String, Integer],
|
95
|
+
description: "Delays the task up to a given time (in seconds)."
|
96
96
|
|
97
|
-
|
98
|
-
|
99
|
-
|
97
|
+
property :execution_time_limit, [String, Integer],
|
98
|
+
description: "The maximum time (in seconds) the task will run.",
|
99
|
+
default: "PT72H" # 72 hours in ISO8601 duration format
|
100
100
|
|
101
|
-
|
102
|
-
|
101
|
+
property :minutes_duration, [String, Integer],
|
102
|
+
description: ""
|
103
103
|
|
104
|
-
|
105
|
-
|
104
|
+
property :minutes_interval, [String, Integer],
|
105
|
+
description: ""
|
106
106
|
|
107
|
-
|
108
|
-
|
109
|
-
|
107
|
+
property :priority, Integer,
|
108
|
+
description: "Use to set Priority Levels range from 0 to 10.",
|
109
|
+
default: 7, callbacks: { "should be in range of 0 to 10" => proc { |v| v >= 0 && v <= 10 } }
|
110
110
|
|
111
|
-
|
112
|
-
|
113
|
-
|
111
|
+
property :disallow_start_if_on_batteries, [TrueClass, FalseClass],
|
112
|
+
introduced: "14.4", default: false,
|
113
|
+
description: "Disallow start of the task if the system is running on battery power."
|
114
114
|
|
115
|
-
|
116
|
-
|
117
|
-
|
115
|
+
property :stop_if_going_on_batteries, [TrueClass, FalseClass],
|
116
|
+
introduced: "14.4", default: false,
|
117
|
+
description: "Scheduled task option when system is switching on battery."
|
118
118
|
|
119
|
-
|
120
|
-
|
121
|
-
|
119
|
+
property :description, String,
|
120
|
+
introduced: "14.7",
|
121
|
+
description: "The task description."
|
122
122
|
|
123
|
-
|
123
|
+
attr_accessor :exists, :task, :command_arguments
|
124
124
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
125
|
+
VALID_WEEK_DAYS = %w{ mon tue wed thu fri sat sun * }.freeze
|
126
|
+
VALID_DAYS_OF_MONTH = ("1".."31").to_a << "last" << "lastday"
|
127
|
+
VALID_MONTHS = %w{JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC *}.freeze
|
128
|
+
VALID_WEEKS = %w{FIRST SECOND THIRD FOURTH LAST LASTDAY}.freeze
|
129
129
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
if execution_time_limit
|
137
|
-
execution_time_limit(259200) if execution_time_limit == "PT72H"
|
138
|
-
raise ArgumentError, "Invalid value passed for `execution_time_limit`. Please pass seconds as an Integer (e.g. 60) or a String with numeric values only (e.g. '60')." unless numeric_value_in_string?(execution_time_limit)
|
139
|
-
execution_time_limit(sec_to_min(execution_time_limit))
|
140
|
-
end
|
130
|
+
def after_created
|
131
|
+
if random_delay
|
132
|
+
validate_random_delay(random_delay, frequency)
|
133
|
+
random_delay(sec_to_min(random_delay))
|
134
|
+
end
|
141
135
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
validate_interactive_setting(interactive_enabled, password)
|
147
|
-
validate_create_frequency_modifier(frequency, frequency_modifier) if frequency_modifier
|
148
|
-
validate_create_day(day, frequency, frequency_modifier) if day
|
149
|
-
validate_create_months(months, frequency) if months
|
150
|
-
validate_frequency_monthly(frequency_modifier, months, day) if frequency == :monthly
|
151
|
-
validate_idle_time(idle_time, frequency)
|
152
|
-
idempotency_warning_for_frequency_weekly(day, start_day) if frequency == :weekly
|
136
|
+
if execution_time_limit
|
137
|
+
execution_time_limit(259200) if execution_time_limit == "PT72H"
|
138
|
+
raise ArgumentError, "Invalid value passed for `execution_time_limit`. Please pass seconds as an Integer (e.g. 60) or a String with numeric values only (e.g. '60')." unless numeric_value_in_string?(execution_time_limit)
|
139
|
+
execution_time_limit(sec_to_min(execution_time_limit))
|
153
140
|
end
|
154
141
|
|
155
|
-
|
142
|
+
validate_frequency(frequency) if action.include?(:create) || action.include?(:change)
|
143
|
+
validate_start_time(start_time, frequency)
|
144
|
+
validate_start_day(start_day, frequency) if start_day
|
145
|
+
validate_user_and_password(user, password)
|
146
|
+
validate_interactive_setting(interactive_enabled, password)
|
147
|
+
validate_create_frequency_modifier(frequency, frequency_modifier) if frequency_modifier
|
148
|
+
validate_create_day(day, frequency, frequency_modifier) if day
|
149
|
+
validate_create_months(months, frequency) if months
|
150
|
+
validate_frequency_monthly(frequency_modifier, months, day) if frequency == :monthly
|
151
|
+
validate_idle_time(idle_time, frequency)
|
152
|
+
idempotency_warning_for_frequency_weekly(day, start_day) if frequency == :weekly
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
156
|
|
157
157
|
## Resource is not idempotent when day, start_day is not provided with frequency :weekly
|
158
158
|
## we set start_day when not given by user as current date based on which we set the day property for current current date day is monday ..
|
159
159
|
## we set the monday as the day so at next run when new_resource.day is nil and current_resource day is monday due to which udpate gets called
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
end
|
160
|
+
def idempotency_warning_for_frequency_weekly(day, start_day)
|
161
|
+
if start_day.nil? && day.nil?
|
162
|
+
logger.warn "To maintain idempotency for frequency :weekly provide start_day, start_time and day."
|
164
163
|
end
|
164
|
+
end
|
165
165
|
|
166
166
|
# Validate the passed value is numeric values only if it is a string
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
167
|
+
def numeric_value_in_string?(val)
|
168
|
+
return true if Integer(val)
|
169
|
+
rescue ArgumentError
|
170
|
+
false
|
171
|
+
end
|
172
172
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
end
|
173
|
+
def validate_frequency(frequency)
|
174
|
+
if frequency.nil? || !([:minute, :hourly, :daily, :weekly, :monthly, :once, :on_logon, :onstart, :on_idle, :none].include?(frequency))
|
175
|
+
raise ArgumentError, "Frequency needs to be provided. Valid frequencies are :minute, :hourly, :daily, :weekly, :monthly, :once, :on_logon, :onstart, :on_idle, :none."
|
177
176
|
end
|
177
|
+
end
|
178
178
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
end
|
184
|
-
|
185
|
-
# frequency_modifer 2-12 is used to set every (n) months, so using :months propety with frequency_modifer is not valid since they both used to set months.
|
186
|
-
# Not checking value 1 here for frequecy_modifier since we are setting that as default value it won't break anything since preference is given to months property
|
187
|
-
if (frequency_modifier.to_i.between?(2, 12)) && !(months.nil?)
|
188
|
-
raise ArgumentError, "For frequency :monthly either use property months or frequency_modifier to set months."
|
189
|
-
end
|
179
|
+
def validate_frequency_monthly(frequency_modifier, months, day)
|
180
|
+
# validates the frequency :monthly and raises error if frequency_modifier is first, second, thrid etc and day is not provided
|
181
|
+
if (frequency_modifier != 1) && (frequency_modifier_includes_days_of_weeks?(frequency_modifier)) && !(day)
|
182
|
+
raise ArgumentError, "Please select day on which you want to run the task e.g. 'Mon, Tue'. Multiple values must be seprated by comma."
|
190
183
|
end
|
191
184
|
|
192
|
-
#
|
193
|
-
|
194
|
-
|
195
|
-
frequency_modifier
|
196
|
-
(frequency_modifier - VALID_WEEKS).empty?
|
185
|
+
# frequency_modifer 2-12 is used to set every (n) months, so using :months propety with frequency_modifer is not valid since they both used to set months.
|
186
|
+
# Not checking value 1 here for frequecy_modifier since we are setting that as default value it won't break anything since preference is given to months property
|
187
|
+
if (frequency_modifier.to_i.between?(2, 12)) && !(months.nil?)
|
188
|
+
raise ArgumentError, "For frequency :monthly either use property months or frequency_modifier to set months."
|
197
189
|
end
|
190
|
+
end
|
198
191
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
192
|
+
# returns true if frequency_modifer has values First, second, third, fourth, last, lastday
|
193
|
+
def frequency_modifier_includes_days_of_weeks?(frequency_modifier)
|
194
|
+
frequency_modifier = frequency_modifier.to_s.split(",")
|
195
|
+
frequency_modifier.map! { |value| value.strip.upcase }
|
196
|
+
(frequency_modifier - VALID_WEEKS).empty?
|
197
|
+
end
|
203
198
|
|
204
|
-
|
199
|
+
def validate_random_delay(random_delay, frequency)
|
200
|
+
if [:on_logon, :onstart, :on_idle, :none].include? frequency
|
201
|
+
raise ArgumentError, "`random_delay` property is supported only for frequency :once, :minute, :hourly, :daily, :weekly and :monthly"
|
205
202
|
end
|
206
203
|
|
204
|
+
raise ArgumentError, "Invalid value passed for `random_delay`. Please pass seconds as an Integer (e.g. 60) or a String with numeric values only (e.g. '60')." unless numeric_value_in_string?(random_delay)
|
205
|
+
end
|
206
|
+
|
207
207
|
# @todo when we drop ruby 2.3 support this should be converted to .match?() instead of =~f
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
208
|
+
def validate_start_day(start_day, frequency)
|
209
|
+
if start_day && frequency == :none
|
210
|
+
raise ArgumentError, "`start_day` property is not supported with frequency: #{frequency}"
|
211
|
+
end
|
212
212
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
end
|
213
|
+
# make sure the start_day is in MM/DD/YYYY format: http://rubular.com/r/cgjHemtWl5
|
214
|
+
if start_day
|
215
|
+
raise ArgumentError, "`start_day` property must be in the MM/DD/YYYY format." unless /^(0[1-9]|1[012])[- \/.](0[1-9]|[12][0-9]|3[01])[- \/.](19|20)\d\d$/ =~ start_day
|
217
216
|
end
|
217
|
+
end
|
218
218
|
|
219
219
|
# @todo when we drop ruby 2.3 support this should be converted to .match?() instead of =~
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
end
|
220
|
+
def validate_start_time(start_time, frequency)
|
221
|
+
if start_time
|
222
|
+
raise ArgumentError, "`start_time` property is not supported with `frequency :none`" if frequency == :none
|
223
|
+
raise ArgumentError, "`start_time` property must be in the HH:mm format (e.g. 6:20pm -> 18:20)." unless /^[0-2][0-9]:[0-5][0-9]$/ =~ start_time
|
224
|
+
else
|
225
|
+
raise ArgumentError, "`start_time` needs to be provided with `frequency :once`" if frequency == :once
|
227
226
|
end
|
227
|
+
end
|
228
228
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
end
|
229
|
+
def validate_user_and_password(user, password)
|
230
|
+
if password_required?(user) && password.nil?
|
231
|
+
raise ArgumentError, "Cannot specify a user other than the system users without specifying a password!. Valid passwordless users: '#{Chef::ReservedNames::Win32::Security::SID::SYSTEM_USER.join("', '")}'"
|
233
232
|
end
|
233
|
+
end
|
234
234
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
235
|
+
def password_required?(user)
|
236
|
+
return false if user.nil?
|
237
|
+
@password_required ||= !Chef::ReservedNames::Win32::Security::SID.system_user?(user)
|
238
|
+
end
|
239
|
+
|
240
|
+
def validate_interactive_setting(interactive_enabled, password)
|
241
|
+
raise ArgumentError, "Please provide the password when attempting to set interactive/non-interactive." if interactive_enabled && password.nil?
|
242
|
+
end
|
239
243
|
|
240
|
-
|
241
|
-
|
244
|
+
def validate_create_frequency_modifier(frequency, frequency_modifier)
|
245
|
+
if ([:on_logon, :onstart, :on_idle, :none].include?(frequency)) && ( frequency_modifier != 1)
|
246
|
+
raise ArgumentError, "frequency_modifier property not supported with frequency :#{frequency}"
|
242
247
|
end
|
243
248
|
|
244
|
-
|
245
|
-
|
246
|
-
raise ArgumentError, "frequency_modifier
|
249
|
+
if frequency == :monthly
|
250
|
+
unless (1..12).cover?(frequency_modifier.to_i) || frequency_modifier_includes_days_of_weeks?(frequency_modifier)
|
251
|
+
raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST'."
|
247
252
|
end
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
else
|
267
|
-
min
|
268
|
-
end
|
269
|
-
unless frequency_modifier.between?(min, max)
|
270
|
-
raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :#{frequency} frequency are #{min} - #{max}."
|
271
|
-
end
|
253
|
+
else
|
254
|
+
unless frequency.nil? || frequency_modifier.nil?
|
255
|
+
frequency_modifier = frequency_modifier.to_i
|
256
|
+
min = 1
|
257
|
+
max = case frequency
|
258
|
+
when :minute
|
259
|
+
1439
|
260
|
+
when :hourly
|
261
|
+
23
|
262
|
+
when :daily
|
263
|
+
365
|
264
|
+
when :weekly
|
265
|
+
52
|
266
|
+
else
|
267
|
+
min
|
268
|
+
end
|
269
|
+
unless frequency_modifier.between?(min, max)
|
270
|
+
raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :#{frequency} frequency are #{min} - #{max}."
|
272
271
|
end
|
273
272
|
end
|
274
273
|
end
|
274
|
+
end
|
275
275
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
end
|
276
|
+
def validate_create_day(day, frequency, frequency_modifier)
|
277
|
+
raise ArgumentError, "day property is only valid for tasks that run monthly or weekly" unless [:weekly, :monthly].include?(frequency)
|
278
|
+
|
279
|
+
# This has been verified with schtask.exe https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/schtasks#d-dayday--
|
280
|
+
# verified with earlier code if day "*" is given with frequency it raised exception Invalid value for /D option
|
281
|
+
raise ArgumentError, "day wild card (*) is only valid with frequency :weekly" if frequency == :monthly && day == "*"
|
282
|
+
|
283
|
+
if day.is_a?(String) && day.to_i.to_s != day
|
284
|
+
days = day.split(",")
|
285
|
+
if days_includes_days_of_months?(days)
|
286
|
+
# Following error will be raise if day is set as 1-31 and frequency is selected as :weekly since those values are valid with only frequency :monthly
|
287
|
+
raise ArgumentError, "day values 1-31 or last is only valid with frequency :monthly" if frequency == :weekly
|
288
|
+
else
|
289
|
+
days.map! { |day| day.to_s.strip.downcase }
|
290
|
+
unless (days - VALID_WEEK_DAYS).empty?
|
291
|
+
raise ArgumentError, "day property invalid. Only valid values are: #{VALID_WEEK_DAYS.map(&:upcase).join(', ')}. Multiple values must be separated by a comma."
|
293
292
|
end
|
294
293
|
end
|
295
294
|
end
|
295
|
+
end
|
296
296
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
end
|
297
|
+
def validate_create_months(months, frequency)
|
298
|
+
raise ArgumentError, "months property is only valid for tasks that run monthly" if frequency != :monthly
|
299
|
+
if months.is_a?(String)
|
300
|
+
months = months.split(",")
|
301
|
+
months.map! { |month| month.strip.upcase }
|
302
|
+
unless (months - VALID_MONTHS).empty?
|
303
|
+
raise ArgumentError, "months property invalid. Only valid values are: #{VALID_MONTHS.join(', ')}. Multiple values must be separated by a comma."
|
305
304
|
end
|
306
305
|
end
|
306
|
+
end
|
307
307
|
|
308
308
|
# This method returns true if day has values from 1-31 which is a days of moths and used with frequency :monthly
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
309
|
+
def days_includes_days_of_months?(days)
|
310
|
+
days.map! { |day| day.to_s.strip.downcase }
|
311
|
+
(days - VALID_DAYS_OF_MONTH).empty?
|
312
|
+
end
|
313
313
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
end
|
321
|
-
unless idle_time.nil? || idle_time > 0 && idle_time <= 999
|
322
|
-
raise ArgumentError, "idle_time value #{idle_time} is invalid. Valid values for :on_idle frequency are 1 - 999."
|
323
|
-
end
|
314
|
+
def validate_idle_time(idle_time, frequency)
|
315
|
+
if !idle_time.nil? && frequency != :on_idle
|
316
|
+
raise ArgumentError, "idle_time property is only valid for tasks that run on_idle"
|
317
|
+
end
|
318
|
+
if idle_time.nil? && frequency == :on_idle
|
319
|
+
raise ArgumentError, "idle_time value should be set for :on_idle frequency."
|
324
320
|
end
|
321
|
+
unless idle_time.nil? || idle_time > 0 && idle_time <= 999
|
322
|
+
raise ArgumentError, "idle_time value #{idle_time} is invalid. Valid values for :on_idle frequency are 1 - 999."
|
323
|
+
end
|
324
|
+
end
|
325
325
|
|
326
326
|
# Converts the number of seconds to an ISO8601 duration format and returns it.
|
327
327
|
# Ref : https://github.com/arnau/ISO8601/blob/master/lib/iso8601/duration.rb#L18-L23
|
328
328
|
# e.g.
|
329
329
|
# ISO8601::Duration.new(65707200).to_s
|
330
330
|
# returns 'PT65707200S'
|
331
|
-
|
332
|
-
|
333
|
-
|
331
|
+
def sec_to_dur(seconds)
|
332
|
+
ISO8601::Duration.new(seconds.to_i).to_s
|
333
|
+
end
|
334
334
|
|
335
|
-
|
336
|
-
|
337
|
-
end
|
335
|
+
def sec_to_min(seconds)
|
336
|
+
seconds.to_i / 60
|
338
337
|
end
|
339
338
|
end
|
340
339
|
end
|