chef 14.6.47 → 14.7.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/chef-universal-mingw32.gemspec +1 -0
  3. data/lib/chef/application/apply.rb +6 -4
  4. data/lib/chef/knife/config_list_profiles.rb +1 -1
  5. data/lib/chef/knife/list.rb +1 -1
  6. data/lib/chef/knife/raw.rb +1 -1
  7. data/lib/chef/knife/role_env_run_list_clear.rb +1 -1
  8. data/lib/chef/knife/role_env_run_list_remove.rb +1 -1
  9. data/lib/chef/knife/role_env_run_list_replace.rb +1 -1
  10. data/lib/chef/knife/role_env_run_list_set.rb +1 -1
  11. data/lib/chef/knife/role_run_list_clear.rb +1 -1
  12. data/lib/chef/knife/role_run_list_remove.rb +1 -1
  13. data/lib/chef/knife/role_run_list_replace.rb +1 -1
  14. data/lib/chef/knife/role_run_list_set.rb +1 -1
  15. data/lib/chef/knife/show.rb +1 -1
  16. data/lib/chef/knife/upload.rb +1 -1
  17. data/lib/chef/knife/xargs.rb +1 -1
  18. data/lib/chef/node/attribute.rb +189 -185
  19. data/lib/chef/platform/rebooter.rb +1 -0
  20. data/lib/chef/property.rb +25 -1
  21. data/lib/chef/provider/windows_task.rb +19 -6
  22. data/lib/chef/resource.rb +17 -2
  23. data/lib/chef/resource/apt_preference.rb +2 -1
  24. data/lib/chef/resource/apt_repository.rb +1 -1
  25. data/lib/chef/resource/apt_update.rb +1 -0
  26. data/lib/chef/resource/build_essential.rb +1 -1
  27. data/lib/chef/resource/cab_package.rb +1 -1
  28. data/lib/chef/resource/chef_gem.rb +1 -1
  29. data/lib/chef/resource/cookbook_file.rb +2 -1
  30. data/lib/chef/resource/cron_d.rb +1 -1
  31. data/lib/chef/resource/dmg_package.rb +52 -40
  32. data/lib/chef/resource/execute.rb +2 -2
  33. data/lib/chef/resource/file.rb +1 -1
  34. data/lib/chef/resource/group.rb +2 -2
  35. data/lib/chef/resource/homebrew_cask.rb +3 -2
  36. data/lib/chef/resource/homebrew_tap.rb +2 -2
  37. data/lib/chef/resource/hostname.rb +2 -2
  38. data/lib/chef/resource/kernel_module.rb +19 -13
  39. data/lib/chef/resource/locale.rb +1 -0
  40. data/lib/chef/resource/macos_userdefaults.rb +7 -6
  41. data/lib/chef/resource/mdadm.rb +3 -2
  42. data/lib/chef/resource/mount.rb +3 -3
  43. data/lib/chef/resource/openssl_dhparam.rb +1 -1
  44. data/lib/chef/resource/openssl_ec_private_key.rb +1 -1
  45. data/lib/chef/resource/openssl_ec_public_key.rb +3 -3
  46. data/lib/chef/resource/openssl_rsa_private_key.rb +2 -2
  47. data/lib/chef/resource/openssl_rsa_public_key.rb +3 -3
  48. data/lib/chef/resource/openssl_x509_certificate.rb +1 -1
  49. data/lib/chef/resource/openssl_x509_crl.rb +1 -1
  50. data/lib/chef/resource/openssl_x509_request.rb +1 -1
  51. data/lib/chef/resource/package.rb +1 -1
  52. data/lib/chef/resource/powershell_package.rb +1 -1
  53. data/lib/chef/resource/remote_directory.rb +2 -2
  54. data/lib/chef/resource/rhsm_errata.rb +1 -1
  55. data/lib/chef/resource/rhsm_errata_level.rb +1 -1
  56. data/lib/chef/resource/rhsm_register.rb +7 -7
  57. data/lib/chef/resource/rhsm_repo.rb +1 -1
  58. data/lib/chef/resource/rhsm_subscription.rb +1 -1
  59. data/lib/chef/resource/sudo.rb +8 -8
  60. data/lib/chef/resource/swap_file.rb +1 -1
  61. data/lib/chef/resource/sysctl.rb +2 -2
  62. data/lib/chef/resource/systemd_unit.rb +19 -11
  63. data/lib/chef/resource/timezone.rb +60 -32
  64. data/lib/chef/resource/windows_ad_join.rb +1 -1
  65. data/lib/chef/resource/windows_auto_run.rb +1 -1
  66. data/lib/chef/resource/windows_certificate.rb +269 -0
  67. data/lib/chef/resource/windows_env.rb +12 -3
  68. data/lib/chef/resource/windows_feature.rb +6 -11
  69. data/lib/chef/resource/windows_feature_dism.rb +2 -2
  70. data/lib/chef/resource/windows_feature_powershell.rb +5 -9
  71. data/lib/chef/resource/windows_firewall_rule.rb +205 -0
  72. data/lib/chef/resource/windows_path.rb +3 -1
  73. data/lib/chef/resource/windows_printer.rb +2 -2
  74. data/lib/chef/resource/windows_printer_port.rb +3 -3
  75. data/lib/chef/resource/windows_service.rb +1 -0
  76. data/lib/chef/resource/windows_share.rb +315 -0
  77. data/lib/chef/resource/windows_task.rb +4 -0
  78. data/lib/chef/resource/windows_workgroup.rb +6 -4
  79. data/lib/chef/resource/yum_repository.rb +13 -7
  80. data/lib/chef/resource/zypper_package.rb +2 -2
  81. data/lib/chef/resource/zypper_repository.rb +2 -1
  82. data/lib/chef/resource_inspector.rb +4 -4
  83. data/lib/chef/resources.rb +3 -0
  84. data/lib/chef/version.rb +1 -1
  85. data/spec/functional/resource/windows_task_spec.rb +39 -0
  86. data/spec/support/platform_helpers.rb +2 -1
  87. data/spec/unit/resource/windows_certificate.rb +46 -0
  88. data/spec/unit/resource/windows_firewall_rule_spec.rb +401 -0
  89. data/spec/unit/resource/windows_share.rb +39 -0
  90. metadata +10 -4
@@ -31,9 +31,18 @@ class Chef
31
31
  default_action :create
32
32
  allowed_actions :create, :delete, :modify
33
33
 
34
- property :key_name, String, identity: true, name_property: true
35
- property :value, String, required: true
36
- property :delim, [ String, nil, false ], desired_state: false
34
+ property :key_name, String,
35
+ description: "The name of the key that is to be created, deleted, or modified.",
36
+ identity: true, name_property: true
37
+
38
+ property :value, String,
39
+ description: "The value of the environmental variable to set.",
40
+ required: true
41
+
42
+ property :delim, [ String, nil, false ],
43
+ description: "The delimiter that is used to separate multiple values for a single key.",
44
+ desired_state: false
45
+
37
46
  property :user, String, default: "<System>"
38
47
  end
39
48
  end
@@ -24,35 +24,30 @@ class Chef
24
24
  resource_name :windows_feature
25
25
  provides(:windows_feature) { true }
26
26
 
27
- description "Use the windows_feature resource to add, remove or delete Windows features and roles. This resource calls"\
28
- " the 'windows_feature_dism' or 'windows_feature_powershell' resources depending on the specified installation"\
29
- " method and defaults to dism, which is available on both Workstation and Server editions of Windows."
27
+ description "Use the windows_feature resource to add, remove or entirely delete Windows features and roles. This resource calls the 'windows_feature_dism' or 'windows_feature_powershell' resources depending on the specified installation method and defaults to dism, which is available on both Workstation and Server editions of Windows."
30
28
  introduced "14.0"
31
29
 
32
30
  property :feature_name, [Array, String],
33
- description: "The name of the feature/role(s) to install. The same feature may have different"\
34
- " names depending on the underlying resource being used (ie DHCPServer vs DHCP;"\
35
- " 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 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).",
36
32
  name_property: true
37
33
 
38
34
  property :source, String,
39
- description: "Use a local repository for the feature install."
35
+ description: "Specify a local repository for the feature install."
40
36
 
41
37
  property :all, [TrueClass, FalseClass],
42
38
  description: "Install all sub features.",
43
39
  default: false
44
40
 
45
41
  property :management_tools, [TrueClass, FalseClass],
46
- description: "Install all applicable management tools of the roles, role services, or features (PowerShell only).",
42
+ description: "Install all applicable management tools for the roles, role services, or features (PowerShell-only).",
47
43
  default: false
48
44
 
49
45
  property :install_method, Symbol,
50
- description: "If DISM or PowerShell should be used for the installation. Note feature names differ"\
51
- " between the two installation methods.",
46
+ description: "The underlying installation method to use for feature installation. Specify ':windows_feature_dism' for DISM or ':windows_feature_powershell' for PowerShell.",
52
47
  equal_to: [:windows_feature_dism, :windows_feature_powershell, :windows_feature_servermanagercmd]
53
48
 
54
49
  property :timeout, Integer,
55
- description: "Specifies a timeout (in seconds) for feature install.",
50
+ description: "Specifies a timeout (in seconds) for the feature installation.",
56
51
  default: 600
57
52
 
58
53
  action :install do
@@ -25,7 +25,7 @@ class Chef
25
25
  resource_name :windows_feature_dism
26
26
  provides(:windows_feature_dism) { true }
27
27
 
28
- description "Use the windows_feature_dism resource to add, remove or delete Windows features and roles using DISM"
28
+ description "Use the windows_feature_dism resource to add, remove, or entirely delete Windows features and roles using DISM."
29
29
  introduced "14.0"
30
30
 
31
31
  property :feature_name, [Array, String],
@@ -41,7 +41,7 @@ class Chef
41
41
  default: false
42
42
 
43
43
  property :timeout, Integer,
44
- description: "Specifies a timeout (in seconds) for feature install.",
44
+ description: "Specifies a timeout (in seconds) for the feature installation.",
45
45
  default: 600
46
46
 
47
47
  # @return [Array] lowercase the array unless we're on < Windows 2012
@@ -27,27 +27,23 @@ class Chef
27
27
  resource_name :windows_feature_powershell
28
28
  provides(:windows_feature_powershell) { true }
29
29
 
30
- description "Use the windows_feature_powershell resource to add, remove or"\
31
- " delete Windows features and roles using PowerShell. This resource"\
32
- " offers significant speed benefits over the windows_feature_dism resource,"\
33
- " but requires installing the Remote Server Administration Tools on"\
34
- " non-server releases of Windows"
30
+ description "Use the windows_feature_powershell resource to add, remove, or entirely delete Windows features and roles using PowerShell. This resource offers significant speed benefits over the windows_feature_dism resource, but requires installing the Remote Server Administration Tools on non-server releases of Windows."
35
31
  introduced "14.0"
36
32
 
37
33
  property :feature_name, [Array, String],
38
- description: "The name of the feature/role(s) to install if it differs from the resource name.",
34
+ description: "The name of the feature(s) or role(s) to install, if it differs from the resource block name.",
39
35
  coerce: proc { |x| to_formatted_array(x) },
40
36
  name_property: true
41
37
 
42
38
  property :source, String,
43
- description: "Use a local repository for the feature install."
39
+ description: "Specify a local repository for the feature install."
44
40
 
45
41
  property :all, [TrueClass, FalseClass],
46
- description: "Install all sub features. This is equivalent to using the -InstallAllSubFeatures switch with Add-WindowsFeature.",
42
+ description: "Install all subfeatures. When set to 'true', this is the equivalent of specifying the '-InstallAllSubFeatures' switch with 'Add-WindowsFeature'.",
47
43
  default: false
48
44
 
49
45
  property :timeout, Integer,
50
- description: "Specifies a timeout (in seconds) for feature install.",
46
+ description: "Specifies a timeout (in seconds) for the feature installation.",
51
47
  default: 600
52
48
 
53
49
  property :management_tools, [TrueClass, FalseClass],
@@ -0,0 +1,205 @@
1
+ # Author:: Matt Clifton (spartacus003@hotmail.com)
2
+ # Author:: Matt Stratton (matt.stratton@gmail.com)
3
+ # Author:: Tor Magnus Rakvåg (tor.magnus@outlook.com)
4
+ # Author:: Tim Smith (tsmith@chef.io)
5
+ # Copyright:: 2013-2015 Matt Clifton
6
+ # Copyright:: 2018, Chef Software, Inc.
7
+ # Copyright:: 2018, Intility AS
8
+ #
9
+ # Licensed under the Apache License, Version 2.0 (the "License");
10
+ # you may not use this file except in compliance with the License.
11
+ # You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing, software
16
+ # distributed under the License is distributed on an "AS IS" BASIS,
17
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
+ # See the License for the specific language governing permissions and
19
+ # limitations under the License.
20
+ #
21
+
22
+ require "chef/json_compat"
23
+
24
+ class Chef
25
+ class Resource
26
+ class WindowsFirewallRule < Chef::Resource
27
+ preview_resource true
28
+ resource_name :windows_firewall_rule
29
+
30
+ description "Use the windows_firewall_rule resource to create, change or remove windows firewall rules."
31
+ introduced "14.7"
32
+
33
+ property :rule_name, String,
34
+ name_property: true,
35
+ description: "The name to assign to the firewall rule."
36
+
37
+ property :description, String,
38
+ default: "Firewall rule",
39
+ description: "The description to assign to the firewall rule."
40
+
41
+ property :local_address, String,
42
+ description: "The local address the firewall rule applies to."
43
+
44
+ property :local_port, [String, Integer, Array],
45
+ # split various formats of comma separated lists and provide a sorted array of strings to match PS output
46
+ coerce: proc { |d| d.is_a?(String) ? d.split(/\s*,\s*/).sort : Array(d).sort.map { |x| x.to_s } },
47
+ description: "The local port the firewall rule applies to."
48
+
49
+ property :remote_address, String,
50
+ description: "The remote address the firewall rule applies to."
51
+
52
+ property :remote_port, [String, Integer, Array],
53
+ # split various formats of comma separated lists and provide a sorted array of strings to match PS output
54
+ coerce: proc { |d| d.is_a?(String) ? d.split(/\s*,\s*/).sort : Array(d).sort.map { |x| x.to_s } },
55
+ description: "The remote port the firewall rule applies to."
56
+
57
+ property :direction, [Symbol, String],
58
+ default: :inbound, equal_to: [:inbound, :outbound],
59
+ description: "The direction of the firewall rule. Direction means either inbound or outbound traffic.",
60
+ coerce: proc { |d| d.is_a?(String) ? d.downcase.to_sym : d }
61
+
62
+ property :protocol, String,
63
+ default: "TCP",
64
+ description: "The protocol the firewall rule applies to."
65
+
66
+ property :firewall_action, [Symbol, String],
67
+ default: :allow, equal_to: [:allow, :block, :notconfigured],
68
+ description: "The action of the firewall rule.",
69
+ coerce: proc { |f| f.is_a?(String) ? f.downcase.to_sym : f }
70
+
71
+ property :profile, [Symbol, String],
72
+ default: :any, equal_to: [:public, :private, :domain, :any, :notapplicable],
73
+ description: "The profile the firewall rule applies to.",
74
+ coerce: proc { |p| p.is_a?(String) ? p.downcase.to_sym : p }
75
+
76
+ property :program, String,
77
+ description: "The program the firewall rule applies to."
78
+
79
+ property :service, String,
80
+ description: "The service the firewall rule applies to."
81
+
82
+ property :interface_type, [Symbol, String],
83
+ default: :any, equal_to: [:any, :wireless, :wired, :remoteaccess],
84
+ description: "The interface type the firewall rule applies to.",
85
+ coerce: proc { |i| i.is_a?(String) ? i.downcase.to_sym : i }
86
+
87
+ property :enabled, [TrueClass, FalseClass],
88
+ default: true,
89
+ description: "Whether or not to enable the firewall rule."
90
+
91
+ alias_method :localip, :local_address
92
+ alias_method :remoteip, :remote_address
93
+ alias_method :localport, :local_port
94
+ alias_method :remoteport, :remote_port
95
+ alias_method :interfacetype, :interface_type
96
+
97
+ load_current_value do
98
+ load_state_cmd = load_firewall_state(rule_name)
99
+ output = powershell_out(load_state_cmd)
100
+ if output.stdout.empty?
101
+ current_value_does_not_exist!
102
+ else
103
+ state = Chef::JSONCompat.from_json(output.stdout)
104
+ end
105
+ local_address state["local_address"]
106
+ local_port Array(state["local_port"]).sort
107
+ remote_address state["remote_address"]
108
+ remote_port Array(state["remote_port"]).sort
109
+ direction state["direction"]
110
+ protocol state["protocol"]
111
+ firewall_action state["firewall_action"]
112
+ profile state["profile"]
113
+ program state["program"]
114
+ service state["service"]
115
+ interface_type state["interface_type"]
116
+ enabled state["enabled"]
117
+ end
118
+
119
+ action :create do
120
+ description "Create a Windows firewall entry."
121
+
122
+ if current_resource
123
+ converge_if_changed :rule_name, :local_address, :local_port, :remote_address, :remote_port, :direction,
124
+ :protocol, :firewall_action, :profile, :program, :service, :interface_type, :enabled do
125
+ cmd = firewall_command("Set")
126
+ powershell_out!(cmd)
127
+ end
128
+ else
129
+ converge_by("create firewall rule #{new_resource.rule_name}") do
130
+ cmd = firewall_command("New")
131
+ powershell_out!(cmd)
132
+ end
133
+ end
134
+ end
135
+
136
+ action :delete do
137
+ description "Delete an existing Windows firewall entry."
138
+
139
+ if current_resource
140
+ converge_by("delete firewall rule #{new_resource.rule_name}") do
141
+ powershell_out!("Remove-NetFirewallRule -Name '#{new_resource.rule_name}'")
142
+ end
143
+ else
144
+ Chef::Log.info("Firewall rule \"#{new_resource.rule_name}\" doesn't exist. Skipping.")
145
+ end
146
+ end
147
+
148
+ action_class do
149
+ # build the command to create a firewall rule based on new_resource values
150
+ # @return [String] firewall create command
151
+ def firewall_command(cmdlet_type)
152
+ cmd = "#{cmdlet_type}-NetFirewallRule -Name '#{new_resource.rule_name}'"
153
+ cmd << " -DisplayName '#{new_resource.rule_name}'" if cmdlet_type == "New"
154
+ cmd << " -Description '#{new_resource.description}'" if new_resource.description
155
+ cmd << " -LocalAddress '#{new_resource.local_address}'" if new_resource.local_address
156
+ cmd << " -LocalPort #{new_resource.local_port.join(',')}" if new_resource.local_port
157
+ cmd << " -RemoteAddress '#{new_resource.remote_address}'" if new_resource.remote_address
158
+ cmd << " -RemotePort #{new_resource.remote_port.join(',')}" if new_resource.remote_port
159
+ cmd << " -Direction '#{new_resource.direction}'" if new_resource.direction
160
+ cmd << " -Protocol '#{new_resource.protocol}'" if new_resource.protocol
161
+ cmd << " -Action '#{new_resource.firewall_action}'" if new_resource.firewall_action
162
+ cmd << " -Profile '#{new_resource.profile}'" if new_resource.profile
163
+ cmd << " -Program '#{new_resource.program}'" if new_resource.program
164
+ cmd << " -Service '#{new_resource.service}'" if new_resource.service
165
+ cmd << " -InterfaceType '#{new_resource.interface_type}'" if new_resource.interface_type
166
+ cmd << " -Enabled '#{new_resource.enabled}'"
167
+
168
+ cmd
169
+ end
170
+ end
171
+
172
+ private
173
+
174
+ # build the command to load the current resource
175
+ # # @return [String] current firewall state
176
+ def load_firewall_state(rule_name)
177
+ <<-EOH
178
+ Remove-TypeData System.Array # workaround for PS bug here: https://bit.ly/2SRMQ8M
179
+ $rule = Get-NetFirewallRule -Name '#{rule_name}'
180
+ $addressFilter = $rule | Get-NetFirewallAddressFilter
181
+ $portFilter = $rule | Get-NetFirewallPortFilter
182
+ $applicationFilter = $rule | Get-NetFirewallApplicationFilter
183
+ $serviceFilter = $rule | Get-NetFirewallServiceFilter
184
+ $interfaceTypeFilter = $rule | Get-NetFirewallInterfaceTypeFilter
185
+ ([PSCustomObject]@{
186
+ rule_name = $rule.Name
187
+ description = $rule.Description
188
+ local_address = $addressFilter.LocalAddress
189
+ local_port = $portFilter.LocalPort
190
+ remote_address = $addressFilter.RemoteAddress
191
+ remote_port = $portFilter.RemotePort
192
+ direction = $rule.Direction.ToString()
193
+ protocol = $portFilter.Protocol
194
+ firewall_action = $rule.Action.ToString()
195
+ profile = $rule.Profile.ToString()
196
+ program = $applicationFilter.Program
197
+ service = $serviceFilter.Service
198
+ interface_type = $interfaceTypeFilter.InterfaceType.ToString()
199
+ enabled = [bool]::Parse($rule.Enabled.ToString())
200
+ }) | ConvertTo-Json
201
+ EOH
202
+ end
203
+ end
204
+ end
205
+ end
@@ -30,7 +30,9 @@ class Chef
30
30
  allowed_actions :add, :remove
31
31
  default_action :add
32
32
 
33
- property :path, String, name_property: true
33
+ property :path, String,
34
+ description: "The name of the value to add to the system path",
35
+ name_property: true
34
36
  end
35
37
  end
36
38
  end
@@ -42,7 +42,7 @@ class Chef
42
42
  default: false
43
43
 
44
44
  property :driver_name, String,
45
- description: "Exact name of printer driver. Note that the printer driver must already be installed on the node.",
45
+ description: "The exact name of printer driver installed on the system.",
46
46
  required: true
47
47
 
48
48
  property :location, String,
@@ -61,7 +61,7 @@ class Chef
61
61
  regex: Resolv::IPv4::Regex
62
62
 
63
63
  property :exists, [TrueClass, FalseClass],
64
- desired_state: true
64
+ skip_docs: true
65
65
 
66
66
  PRINTERS_REG_KEY = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Printers\\'.freeze unless defined?(PRINTERS_REG_KEY)
67
67
 
@@ -34,7 +34,7 @@ class Chef
34
34
  name_property: true,
35
35
  regex: Resolv::IPv4::Regex,
36
36
  validation_message: "The ipv4_address property must be in the format of WWW.XXX.YYY.ZZZ!",
37
- description: "The IPv4 address of the printer port."
37
+ description: "An optional property for the IPv4 address of the printer if it differs from the resource block's name."
38
38
 
39
39
  property :port_name, String,
40
40
  description: "The port name."
@@ -51,12 +51,12 @@ class Chef
51
51
  default: false
52
52
 
53
53
  property :port_protocol, Integer,
54
- description: "The printer port protocol, 1 (RAW), or 2 (LPR).",
54
+ description: "The printer port protocol: 1 (RAW) or 2 (LPR).",
55
55
  validation_message: "port_protocol must be either 1 for RAW or 2 for LPR!",
56
56
  default: 1, equal_to: [1, 2]
57
57
 
58
58
  property :exists, [TrueClass, FalseClass],
59
- desired_state: true
59
+ skip_docs: true
60
60
 
61
61
  PORTS_REG_KEY = 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Monitors\Standard TCP/IP Port\Ports\\'.freeze unless defined?(PORTS_REG_KEY)
62
62
 
@@ -50,6 +50,7 @@ class Chef
50
50
  # The display name to be used by user interface programs to identify the
51
51
  # service. This string has a maximum length of 256 characters.
52
52
  property :display_name, String, regex: /^.{1,256}$/,
53
+ validation_message: "The display_name can only be a maximum of 256 characters!",
53
54
  introduced: "14.0"
54
55
 
55
56
  # https://github.com/djberg96/win32-service/blob/ffi/lib/win32/windows/constants.rb#L19-L29
@@ -0,0 +1,315 @@
1
+ #
2
+ # Author:: Sölvi Páll Ásgeirsson (<solvip@gmail.com>)
3
+ # Author:: Richard Lavey (richard.lavey@calastone.com)
4
+ # Author:: Tim Smith (tsmith@chef.io)
5
+ #
6
+ # Copyright:: 2014-2017, Sölvi Páll Ásgeirsson.
7
+ # Copyright:: 2018, Chef Software, Inc.
8
+ #
9
+ # Licensed under the Apache License, Version 2.0 (the "License");
10
+ # you may not use this file except in compliance with the License.
11
+ # You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing, software
16
+ # distributed under the License is distributed on an "AS IS" BASIS,
17
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
+ # See the License for the specific language governing permissions and
19
+ # limitations under the License.
20
+ #
21
+
22
+ require "chef/resource"
23
+ require "chef/json_compat"
24
+
25
+ class Chef
26
+ class Resource
27
+ class WindowsShare < Chef::Resource
28
+ preview_resource true
29
+ resource_name :windows_share
30
+
31
+ description "Use the windows_share resource to create, modify and remove Windows shares."
32
+ introduced "14.7"
33
+
34
+ # Specifies a name for the SMB share. The name may be composed of any valid file name characters, but must be less than 80 characters long. The names pipe and mailslot are reserved for use by the computer.
35
+ property :share_name, String,
36
+ description: "The name to assign to the share.",
37
+ name_property: true
38
+
39
+ # Specifies the path of the location of the folder to share. The path must be fully qualified. Relative paths or paths that contain wildcard characters are not permitted.
40
+ property :path, String,
41
+ description: "The path of the folder to share. Required when creating. If the share already exists on a different path then it is deleted and re-created."
42
+
43
+ # Specifies an optional description of the SMB share. A description of the share is displayed by running the Get-SmbShare cmdlet. The description may not contain more than 256 characters.
44
+ property :description, String,
45
+ description: "The description to be applied to the share.",
46
+ default: ""
47
+
48
+ # Specifies which accounts are granted full permission to access the share. Use a comma-separated list to specify multiple accounts. An account may not be specified more than once in the FullAccess, ChangeAccess, or ReadAccess parameter lists, but may be specified once in the FullAccess, ChangeAccess, or ReadAccess parameter list and once in the NoAccess parameter list.
49
+ property :full_users, Array,
50
+ description: "The users that should have 'Full control' permissions on the share in domain\\username format.",
51
+ default: lazy { [] }, coerce: proc { |u| u.sort }
52
+
53
+ # Specifies which users are granted modify permission to access the share
54
+ property :change_users, Array,
55
+ description: "The users that should have 'modify' permission on the share in domain\\username format.",
56
+ default: lazy { [] }, coerce: proc { |u| u.sort }
57
+
58
+ # Specifies which users are granted read permission to access the share. Multiple users can be specified by supplying a comma-separated list.
59
+ property :read_users, Array,
60
+ description: "The users that should have 'read' permission on the share in domain\\username format.",
61
+ default: lazy { [] }, coerce: proc { |u| u.sort }
62
+
63
+ # Specifies the lifetime of the new SMB share. A temporary share does not persist beyond the next restart of the computer. By default, new SMB shares are persistent, and non-temporary.
64
+ property :temporary, [TrueClass, FalseClass],
65
+ description: "The lifetime of the new SMB share. A temporary share does not persist beyond the next restart of the computer.",
66
+ default: false
67
+
68
+ # Specifies the scope name of the share.
69
+ property :scope_name, String,
70
+ description: "The scope name of the share.",
71
+ default: "*"
72
+
73
+ # Specifies the continuous availability time-out for the share.
74
+ property :ca_timeout, Integer,
75
+ description: "The continuous availability time-out for the share.",
76
+ default: 0
77
+
78
+ # Indicates that the share is continuously available.
79
+ property :continuously_available, [TrueClass, FalseClass],
80
+ description: "Indicates that the share is continuously available.",
81
+ default: false
82
+
83
+ # Specifies the caching mode of the offline files for the SMB share.
84
+ # property :caching_mode, String, equal_to: %w(None Manual Documents Programs BranchCache)
85
+
86
+ # Specifies the maximum number of concurrently connected users that the new SMB share may accommodate. If this parameter is set to zero (0), then the number of users is unlimited.
87
+ property :concurrent_user_limit, Integer,
88
+ description: "The maximum number of concurrently connected users the share can accommodate.",
89
+ default: 0
90
+
91
+ # Indicates that the share is encrypted.
92
+ property :encrypt_data, [TrueClass, FalseClass],
93
+ description: "Indicates that the share is encrypted.",
94
+ default: false
95
+
96
+ # Specifies which files and folders in the SMB share are visible to users. AccessBased: SMB does not the display the files and folders for a share to a user unless that user has rights to access the files and folders. By default, access-based enumeration is disabled for new SMB shares. Unrestricted: SMB displays files and folders to a user even when the user does not have permission to access the items.
97
+ # property :folder_enumeration_mode, String, equal_to: %(AccessBased Unrestricted)
98
+
99
+ include Chef::Mixin::PowershellOut
100
+
101
+ load_current_value do |desired|
102
+ # this command selects individual objects because EncryptData & CachingMode have underlying
103
+ # types that get converted to their Integer values by ConvertTo-Json & we need to make sure
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"
106
+
107
+ Chef::Log.debug("Running '#{share_state_cmd}' to determine share state'")
108
+ ps_results = powershell_out(share_state_cmd)
109
+
110
+ # detect a failure without raising and then set current_resource to nil
111
+ if ps_results.error?
112
+ Chef::Log.debug("Error fetching share state: #{ps_results.stderr}")
113
+ current_value_does_not_exist!
114
+ end
115
+
116
+ Chef::Log.debug("The Get-SmbShare results were #{ps_results.stdout}")
117
+ results = Chef::JSONCompat.from_json(ps_results.stdout)
118
+
119
+ path results["Path"]
120
+ description results["Description"]
121
+ temporary results["Temporary"]
122
+ ca_timeout results["CATimeout"]
123
+ continuously_available results["ContinuouslyAvailable"]
124
+ # caching_mode results['CachingMode']
125
+ concurrent_user_limit results["ConcurrentUserLimit"]
126
+ encrypt_data results["EncryptData"]
127
+ # folder_enumeration_mode results['FolderEnumerationMode']
128
+
129
+ perm_state_cmd = %{Get-SmbShareAccess -Name "#{desired.share_name}" | Select-Object AccountName,AccessControlType,AccessRight | ConvertTo-Json}
130
+
131
+ Chef::Log.debug("Running '#{perm_state_cmd}' to determine share permissions state'")
132
+ ps_perm_results = powershell_out(perm_state_cmd)
133
+
134
+ # we raise here instead of warning like above because we'd only get here if the above Get-SmbShare
135
+ # command was successful and that continuing would leave us with 1/2 known state
136
+ raise "Could not determine #{desired.share_name} share permissions by running '#{perm_state_cmd}'" if ps_perm_results.error?
137
+
138
+ Chef::Log.debug("The Get-SmbShareAccess results were #{ps_perm_results.stdout}")
139
+
140
+ f_users, c_users, r_users = parse_permissions(ps_perm_results.stdout)
141
+
142
+ full_users f_users
143
+ change_users c_users
144
+ read_users r_users
145
+ end
146
+
147
+ def after_created
148
+ raise "The windows_share resource relies on PowerShell cmdlets not present in Windows releases prior to 8/2012. Cannot continue!" if node["platform_version"].to_f < 6.3
149
+ end
150
+
151
+ # given the string output of Get-SmbShareAccess parse out
152
+ # arrays of full access users, change users, and read only users
153
+ def parse_permissions(results_string)
154
+ json_results = Chef::JSONCompat.from_json(results_string)
155
+ json_results = [json_results] unless json_results.is_a?(Array) # single result is not an array
156
+
157
+ f_users = []
158
+ c_users = []
159
+ r_users = []
160
+
161
+ json_results.each do |perm|
162
+ next unless perm["AccessControlType"] == 0 # allow
163
+ case perm["AccessRight"]
164
+ when 0 then f_users << stripped_account(perm["AccountName"]) # 0 full control
165
+ when 1 then c_users << stripped_account(perm["AccountName"]) # 1 == change
166
+ when 2 then r_users << stripped_account(perm["AccountName"]) # 2 == read
167
+ end
168
+ end
169
+ [f_users, c_users, r_users]
170
+ end
171
+
172
+ # local names are returned from Get-SmbShareAccess in the full format MACHINE\\NAME
173
+ # but users of this resource would simply say NAME so we need to strip the values for comparison
174
+ def stripped_account(name)
175
+ name.slice!("#{node['hostname']}\\")
176
+ name
177
+ end
178
+
179
+ action :create do
180
+ description "Create and modify Windows shares."
181
+
182
+ # we do this here instead of requiring the property because :delete doesn't need path set
183
+ raise "No path property set" unless new_resource.path
184
+
185
+ converge_if_changed do
186
+ # you can't actually change the path so you have to delete the old share first
187
+ delete_share if different_path?
188
+
189
+ # powershell cmdlet for create is different than updates
190
+ if current_resource.nil?
191
+ Chef::Log.debug("The current resource is nil so we will create a new share")
192
+ create_share
193
+ else
194
+ Chef::Log.debug("The current resource was not nil so we will update an existing share")
195
+ update_share
196
+ end
197
+
198
+ # creating the share does not set permissions so we need to update
199
+ update_permissions
200
+ end
201
+ end
202
+
203
+ action :delete do
204
+ description "Delete an existing Windows share."
205
+
206
+ if current_resource.nil?
207
+ Chef::Log.debug("#{new_resource.share_name} does not exist - nothing to do")
208
+ else
209
+ converge_by("delete #{new_resource.share_name}") do
210
+ delete_share
211
+ end
212
+ end
213
+ end
214
+
215
+ action_class do
216
+ def different_path?
217
+ return false if current_resource.nil? # going from nil to something isn't different for our concerns
218
+ return false if current_resource.path == new_resource.path
219
+ true
220
+ end
221
+
222
+ def delete_share
223
+ delete_command = "Remove-SmbShare -Name '#{new_resource.share_name}' -Force"
224
+
225
+ Chef::Log.debug("Running '#{delete_command}' to remove the share")
226
+ powershell_out!(delete_command)
227
+ end
228
+
229
+ def update_share
230
+ update_command = "Set-SmbShare -Name '#{new_resource.share_name}' -Description '#{new_resource.description}' -Force"
231
+
232
+ Chef::Log.debug("Running '#{update_command}' to update the share")
233
+ powershell_out!(update_command)
234
+ end
235
+
236
+ def create_share
237
+ raise "#{new_resource.path} is missing or not a directory. Shares cannot be created if the path doesn't first exist." unless ::File.directory? new_resource.path
238
+
239
+ share_cmd = "New-SmbShare -Name '#{new_resource.share_name}' -Path '#{new_resource.path}' -Description '#{new_resource.description}' -ConcurrentUserLimit #{new_resource.concurrent_user_limit} -CATimeout #{new_resource.ca_timeout} -EncryptData:#{bool_string(new_resource.encrypt_data)} -ContinuouslyAvailable:#{bool_string(new_resource.continuously_available)}"
240
+ share_cmd << " -ScopeName #{new_resource.scope_name}" unless new_resource.scope_name == "*" # passing * causes the command to fail
241
+ share_cmd << " -Temporary:#{bool_string(new_resource.temporary)}" if new_resource.temporary # only set true
242
+
243
+ Chef::Log.debug("Running '#{share_cmd}' to create the share")
244
+ powershell_out!(share_cmd)
245
+ end
246
+
247
+ # determine what users in the current state don't exist in the desired state
248
+ # users/groups will have their permissions updated with the same command that
249
+ # sets it, but removes must be performed with Revoke-SmbShareAccess
250
+ def users_to_revoke
251
+ @users_to_revoke ||= begin
252
+ # if the resource doesn't exist then nothing needs to be revoked
253
+ if current_resource.nil?
254
+ []
255
+ else # if it exists then calculate the current to new resource diffs
256
+ (current_resource.full_users + current_resource.change_users + current_resource.read_users) - (new_resource.full_users + new_resource.change_users + new_resource.read_users)
257
+ end
258
+ end
259
+ end
260
+
261
+ # update existing permissions on a share
262
+ def update_permissions
263
+ # revoke any users that had something, but now has nothing
264
+ revoke_user_permissions(users_to_revoke) unless users_to_revoke.empty?
265
+
266
+ # set permissions for each of the permission types
267
+ %w{full read change}.each do |perm_type|
268
+ # set permissions for a brand new share OR
269
+ # update permissions if the current state and desired state differ
270
+ next unless permissions_need_update?(perm_type)
271
+ grant_command = "Grant-SmbShareAccess -Name '#{new_resource.share_name}' -AccountName \"#{new_resource.send("#{perm_type}_users").join('","')}\" -Force -AccessRight #{perm_type}"
272
+
273
+ Chef::Log.debug("Running '#{grant_command}' to update the share permissions")
274
+ powershell_out!(grant_command)
275
+ end
276
+ end
277
+
278
+ # determine if permissions need to be updated.
279
+ # Brand new share with no permissions defined: no
280
+ # Brand new share with permissions defined: yes
281
+ # Existing share with differing permissions: yes
282
+ #
283
+ # @param [String] type the permissions type (Full, Read, or Change)
284
+ def permissions_need_update?(type)
285
+ property_name = "#{type}_users"
286
+
287
+ # brand new share, but nothing to set
288
+ return false if current_resource.nil? && new_resource.send(property_name).empty?
289
+
290
+ # brand new share with new permissions to set
291
+ return true if current_resource.nil? && !new_resource.send(property_name).empty?
292
+
293
+ # there's a difference between the current and desired state
294
+ return true unless (new_resource.send(property_name) - current_resource.send(property_name)).empty?
295
+
296
+ # anything else
297
+ false
298
+ end
299
+
300
+ def revoke_user_permissions(users)
301
+ revoke_command = "Revoke-SmbShareAccess -Name '#{new_resource.share_name}' -AccountName \"#{users.join(',')}\" -Force"
302
+ Chef::Log.debug("Running '#{revoke_command}' to revoke share permissions")
303
+ powershell_out!(revoke_command)
304
+ end
305
+
306
+ # convert True/False into "$True" & "$False"
307
+ def bool_string(bool)
308
+ # bool ? 1 : 0
309
+ bool ? "$true" : "$false"
310
+ end
311
+ end
312
+
313
+ end
314
+ end
315
+ end