chef 12.4.0.rc.2 → 12.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/distro/powershell/chef/chef.psm1 +327 -0
  3. data/lib/chef/chef_class.rb +4 -4
  4. data/lib/chef/client.rb +12 -6
  5. data/lib/chef/node_map.rb +63 -38
  6. data/lib/chef/platform/priority_map.rb +54 -0
  7. data/lib/chef/platform/provider_mapping.rb +2 -2
  8. data/lib/chef/platform/provider_priority_map.rb +3 -21
  9. data/lib/chef/platform/resource_priority_map.rb +5 -22
  10. data/lib/chef/provider.rb +1 -1
  11. data/lib/chef/provider/package/rpm.rb +2 -2
  12. data/lib/chef/provider/service/debian.rb +0 -2
  13. data/lib/chef/provider/service/insserv.rb +0 -2
  14. data/lib/chef/provider/service/invokercd.rb +0 -2
  15. data/lib/chef/provider/service/redhat.rb +0 -2
  16. data/lib/chef/provider/service/upstart.rb +0 -2
  17. data/lib/chef/provider/user.rb +0 -2
  18. data/lib/chef/resource.rb +23 -24
  19. data/lib/chef/resource/lwrp_base.rb +2 -1
  20. data/lib/chef/resource/macports_package.rb +2 -1
  21. data/lib/chef/resource/package.rb +0 -5
  22. data/lib/chef/resource_resolver.rb +1 -0
  23. data/lib/chef/version.rb +1 -1
  24. data/spec/integration/recipes/lwrp_spec.rb +2 -6
  25. data/spec/integration/recipes/recipe_dsl_spec.rb +254 -39
  26. data/spec/support/shared/shared_examples.rb +1 -1
  27. data/spec/unit/api_client_spec.rb +1 -1
  28. data/spec/unit/client_spec.rb +35 -19
  29. data/spec/unit/cookbook_version_spec.rb +1 -1
  30. data/spec/unit/data_bag_item_spec.rb +1 -1
  31. data/spec/unit/data_bag_spec.rb +1 -1
  32. data/spec/unit/environment_spec.rb +1 -1
  33. data/spec/unit/exceptions_spec.rb +1 -1
  34. data/spec/unit/json_compat_spec.rb +1 -1
  35. data/spec/unit/lwrp_spec.rb +43 -4
  36. data/spec/unit/node_spec.rb +1 -1
  37. data/spec/unit/osc_user_spec.rb +1 -1
  38. data/spec/unit/provider/package/rpm_spec.rb +335 -124
  39. data/spec/unit/provider_resolver_spec.rb +0 -1
  40. data/spec/unit/recipe_spec.rb +12 -8
  41. data/spec/unit/resource_collection_spec.rb +1 -1
  42. data/spec/unit/resource_resolver_spec.rb +49 -0
  43. data/spec/unit/resource_spec.rb +19 -4
  44. data/spec/unit/role_spec.rb +1 -1
  45. data/spec/unit/run_list_spec.rb +1 -1
  46. data/spec/unit/runner_spec.rb +2 -2
  47. data/spec/unit/user_spec.rb +1 -1
  48. metadata +10 -8
  49. data/spec/support/pedant/Gemfile.lock +0 -67
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6cc65df98615f048747a52dcd91ef0f181538081
4
- data.tar.gz: 3ab69cb36f0c3b4c47463047dbd251ae3f7fe8c7
3
+ metadata.gz: 7c24dee2aa13014a1cbc3ab4577f5a4460412f9f
4
+ data.tar.gz: 7ee73735afc1e3effecd6b8eaff0d0016587462b
5
5
  SHA512:
6
- metadata.gz: c967f541e668e6c8ee195d61b00e026fb16d8f4f0016862abe3a764ad48db1d59b4b5068fec2075228cb82ac85a01965391762e6016bb18845f3a8a77a706b6b
7
- data.tar.gz: 9b43407d79c34ef867fc69da68b597dfb23c41e059d2e603633de7943e9c5aef18b8f243d2c58e5222733bc14fa294742b8b55c34aa744820e3c40a1763eec4a
6
+ metadata.gz: f46d9c7c2748126570d681f2a75b4916e8e808a89556419d4b337bf8c290769f51468e44d3751f8747866271666c646bcdd5011cf8f453ff86b6a103024665d5
7
+ data.tar.gz: 13564560eb4ede717eb2a8a23d3d15020fec529921b471639289b3b6f55d5505df85f0add82ad4852b3a8387aa6339fdedbffd347d56255d91de7a7f46894d5d
@@ -0,0 +1,327 @@
1
+ 
2
+ function Load-Win32Bindings {
3
+ Add-Type -TypeDefinition @"
4
+ using System;
5
+ using System.Diagnostics;
6
+ using System.Runtime.InteropServices;
7
+
8
+ namespace Chef
9
+ {
10
+
11
+ [StructLayout(LayoutKind.Sequential)]
12
+ public struct PROCESS_INFORMATION
13
+ {
14
+ public IntPtr hProcess;
15
+ public IntPtr hThread;
16
+ public uint dwProcessId;
17
+ public uint dwThreadId;
18
+ }
19
+
20
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
21
+ public struct STARTUPINFO
22
+ {
23
+ public uint cb;
24
+ public string lpReserved;
25
+ public string lpDesktop;
26
+ public string lpTitle;
27
+ public uint dwX;
28
+ public uint dwY;
29
+ public uint dwXSize;
30
+ public uint dwYSize;
31
+ public uint dwXCountChars;
32
+ public uint dwYCountChars;
33
+ public uint dwFillAttribute;
34
+ public STARTF dwFlags;
35
+ public ShowWindow wShowWindow;
36
+ public short cbReserved2;
37
+ public IntPtr lpReserved2;
38
+ public IntPtr hStdInput;
39
+ public IntPtr hStdOutput;
40
+ public IntPtr hStdError;
41
+ }
42
+
43
+ [StructLayout(LayoutKind.Sequential)]
44
+ public struct SECURITY_ATTRIBUTES
45
+ {
46
+ public int length;
47
+ public IntPtr lpSecurityDescriptor;
48
+ public bool bInheritHandle;
49
+ }
50
+
51
+ [Flags]
52
+ public enum CreationFlags : int
53
+ {
54
+ NONE = 0,
55
+ DEBUG_PROCESS = 0x00000001,
56
+ DEBUG_ONLY_THIS_PROCESS = 0x00000002,
57
+ CREATE_SUSPENDED = 0x00000004,
58
+ DETACHED_PROCESS = 0x00000008,
59
+ CREATE_NEW_CONSOLE = 0x00000010,
60
+ CREATE_NEW_PROCESS_GROUP = 0x00000200,
61
+ CREATE_UNICODE_ENVIRONMENT = 0x00000400,
62
+ CREATE_SEPARATE_WOW_VDM = 0x00000800,
63
+ CREATE_SHARED_WOW_VDM = 0x00001000,
64
+ CREATE_PROTECTED_PROCESS = 0x00040000,
65
+ EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
66
+ CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
67
+ CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
68
+ CREATE_DEFAULT_ERROR_MODE = 0x04000000,
69
+ CREATE_NO_WINDOW = 0x08000000,
70
+ }
71
+
72
+ [Flags]
73
+ public enum STARTF : uint
74
+ {
75
+ STARTF_USESHOWWINDOW = 0x00000001,
76
+ STARTF_USESIZE = 0x00000002,
77
+ STARTF_USEPOSITION = 0x00000004,
78
+ STARTF_USECOUNTCHARS = 0x00000008,
79
+ STARTF_USEFILLATTRIBUTE = 0x00000010,
80
+ STARTF_RUNFULLSCREEN = 0x00000020, // ignored for non-x86 platforms
81
+ STARTF_FORCEONFEEDBACK = 0x00000040,
82
+ STARTF_FORCEOFFFEEDBACK = 0x00000080,
83
+ STARTF_USESTDHANDLES = 0x00000100,
84
+ }
85
+
86
+ public enum ShowWindow : short
87
+ {
88
+ SW_HIDE = 0,
89
+ SW_SHOWNORMAL = 1,
90
+ SW_NORMAL = 1,
91
+ SW_SHOWMINIMIZED = 2,
92
+ SW_SHOWMAXIMIZED = 3,
93
+ SW_MAXIMIZE = 3,
94
+ SW_SHOWNOACTIVATE = 4,
95
+ SW_SHOW = 5,
96
+ SW_MINIMIZE = 6,
97
+ SW_SHOWMINNOACTIVE = 7,
98
+ SW_SHOWNA = 8,
99
+ SW_RESTORE = 9,
100
+ SW_SHOWDEFAULT = 10,
101
+ SW_FORCEMINIMIZE = 11,
102
+ SW_MAX = 11
103
+ }
104
+
105
+ public enum StandardHandle : int
106
+ {
107
+ Input = -10,
108
+ Output = -11,
109
+ Error = -12
110
+ }
111
+
112
+ public static class Kernel32
113
+ {
114
+ [DllImport("kernel32.dll", SetLastError=true)]
115
+ [return: MarshalAs(UnmanagedType.Bool)]
116
+ public static extern bool CreateProcess(
117
+ string lpApplicationName,
118
+ string lpCommandLine,
119
+ ref SECURITY_ATTRIBUTES lpProcessAttributes,
120
+ ref SECURITY_ATTRIBUTES lpThreadAttributes,
121
+ [MarshalAs(UnmanagedType.Bool)] bool bInheritHandles,
122
+ CreationFlags dwCreationFlags,
123
+ IntPtr lpEnvironment,
124
+ string lpCurrentDirectory,
125
+ ref STARTUPINFO lpStartupInfo,
126
+ out PROCESS_INFORMATION lpProcessInformation);
127
+
128
+ [DllImport("kernel32.dll", SetLastError=true)]
129
+ public static extern IntPtr GetStdHandle(
130
+ StandardHandle nStdHandle);
131
+
132
+ [DllImport("kernel32", SetLastError=true)]
133
+ public static extern int WaitForSingleObject(
134
+ IntPtr hHandle,
135
+ int dwMilliseconds);
136
+
137
+ [DllImport("kernel32", SetLastError=true)]
138
+ [return: MarshalAs(UnmanagedType.Bool)]
139
+ public static extern bool CloseHandle(
140
+ IntPtr hObject);
141
+
142
+ [DllImport("kernel32", SetLastError=true)]
143
+ [return: MarshalAs(UnmanagedType.Bool)]
144
+ public static extern bool GetExitCodeProcess(
145
+ IntPtr hProcess,
146
+ out int lpExitCode);
147
+ }
148
+ }
149
+ "@
150
+ }
151
+
152
+ function Run-ExecutableAndWait($AppPath, $ArgumentString) {
153
+ # Use the Win32 API to create a new process and wait for it to terminate.
154
+ $null = Load-Win32Bindings
155
+
156
+ $si = New-Object Chef.STARTUPINFO
157
+ $pi = New-Object Chef.PROCESS_INFORMATION
158
+
159
+ $si.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($si)
160
+ $si.wShowWindow = [Chef.ShowWindow]::SW_SHOW
161
+ $si.dwFlags = [Chef.STARTF]::STARTF_USESTDHANDLES
162
+ $si.hStdError = [Chef.Kernel32]::GetStdHandle([Chef.StandardHandle]::Error)
163
+ $si.hStdOutput = [Chef.Kernel32]::GetStdHandle([Chef.StandardHandle]::Output)
164
+ $si.hStdInput = [Chef.Kernel32]::GetStdHandle([Chef.StandardHandle]::Input)
165
+
166
+ $pSec = New-Object Chef.SECURITY_ATTRIBUTES
167
+ $pSec.Length = [System.Runtime.InteropServices.Marshal]::SizeOf($pSec)
168
+ $pSec.bInheritHandle = $true
169
+ $tSec = New-Object Chef.SECURITY_ATTRIBUTES
170
+ $tSec.Length = [System.Runtime.InteropServices.Marshal]::SizeOf($tSec)
171
+ $tSec.bInheritHandle = $true
172
+
173
+ $success = [Chef.Kernel32]::CreateProcess($AppPath, $ArgumentString, [ref] $pSec, [ref] $tSec, $true, [Chef.CreationFlags]::NONE, [IntPtr]::Zero, $pwd, [ref] $si, [ref] $pi)
174
+ if (-Not $success) {
175
+ $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
176
+ throw "Unable to create process [$ArgumentString]. Error code $reason."
177
+ }
178
+ $waitReason = [Chef.Kernel32]::WaitForSingleObject($pi.hProcess, -1)
179
+ if ($waitReason -ne 0) {
180
+ if ($waitReason -eq -1) {
181
+ $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
182
+ throw "Could not wait for process to terminate. Error code $reason."
183
+ } else {
184
+ throw "WaitForSingleObject failed with return code $waitReason - it's impossible!"
185
+ }
186
+ }
187
+ $success = [Chef.Kernel32]::GetExitCodeProcess($pi.hProcess, [ref] $global:LASTEXITCODE)
188
+ if (-Not $success) {
189
+ $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
190
+ throw "Process exit code unavailable. Error code $reason."
191
+ }
192
+ $success = [Chef.Kernel32]::CloseHandle($pi.hProcess)
193
+ if (-Not $success) {
194
+ $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
195
+ throw "Unable to release process handle. Error code $reason."
196
+ }
197
+ $success = [Chef.Kernel32]::CloseHandle($pi.hThread)
198
+ if (-Not $success) {
199
+ $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
200
+ throw "Unable to release thread handle. Error code $reason."
201
+ }
202
+ }
203
+
204
+ function Get-ScriptDirectory {
205
+ if (!$PSScriptRoot) {
206
+ $Invocation = (Get-Variable MyInvocation -Scope 1).Value
207
+ $PSScriptRoot = Split-Path $Invocation.MyCommand.Path
208
+ }
209
+ $PSScriptRoot
210
+ }
211
+
212
+ function Run-RubyCommand($command, $argList) {
213
+ # This method exists to take the given list of arguments and get it past ruby's command-line
214
+ # interpreter unscathed and untampered. See https://github.com/ruby/ruby/blob/trunk/win32/win32.c#L1582
215
+ # for a list of transformations that ruby attempts to perform with your command-line arguments
216
+ # before passing it onto a script. The most important task is to defeat the globbing
217
+ # and wild-card expansion that ruby performs. Note that ruby does not use MSVCRT's argc/argv
218
+ # and deliberately reparses the raw command-line instead.
219
+ #
220
+ # To stop ruby from interpreting command-line arguments as globs, they need to be enclosed in '
221
+ # Ruby doesn't allow any escape characters inside '. This unfortunately prevents us from sending
222
+ # any strings which themselves contain '. Ruby does allow multi-fragment arguments though.
223
+ # "foo bar"'baz qux'123"foo" is interpreted as 1 argument because there are no un-escaped
224
+ # whitespace there. The argument would be interpreted as the string "foo barbaz qux123foo".
225
+ # This lets us escape ' characters by exiting the ' quoted string, injecting a "'" fragment and
226
+ # then resuming the ' quoted string again.
227
+ #
228
+ # In the process of defeating ruby, one must also defeat the helpfulness of powershell.
229
+ # When arguments come into this method, the standard PS rules for interpreting cmdlet arguments
230
+ # apply. When using & (call operator) and providing an array of arguments, powershell (verified
231
+ # on PS 4.0 on Windows Server 2012R2) will not evaluate them but (contrary to documentation),
232
+ # it will still marginally interpret them. The behaviour of PS 5.0 seems to be different but
233
+ # ignore that for now. If any of the provided arguments has a space in it, powershell checks
234
+ # the first and last character to ensure that they are " characters (and that's all it checks).
235
+ # If they are not, it will blindly surround that argument with " characters. It won't do this
236
+ # operation if no space is present, even if other special characters are present. If it notices
237
+ # leading and trailing " characters, it won't actually check to see if there are other "
238
+ # characters in the string. Since PS 5.0 changes this behavior, we could consider using the --%
239
+ # "stop screwing up my arguments" operator, which is available since PS 3.0. When encountered
240
+ # --% indicates that the rest of line is to be sent literally... except if the parser encounters
241
+ # %FOO% cmd style environment variables. Because reasons. And there is no way to escape the
242
+ # % character in *any* waym shape or form.
243
+ # https://connect.microsoft.com/PowerShell/feedback/details/376207/executing-commands-which-require-quotes-and-variables-is-practically-impossible
244
+ #
245
+ # In case you think that you're either reading this incorrectly or that I'm full of shit, here
246
+ # are some examples. These use EchoArgs.exe from the PowerShell Community Extensions package.
247
+ # I have not included the argument parsing output from EchoArgs.exe to prevent confusing you with
248
+ # more details about MSVCRT's parsing algorithm.
249
+ #
250
+ # $x = "foo '' bar `"baz`""
251
+ # & EchoArgs @($x, $x)
252
+ # Command line:
253
+ # "C:\Program Files (x86)\PowerShell Community Extensions\Pscx3\Pscx\Apps\EchoArgs.exe" "foo '' bar "baz"" "foo '' bar "baz""
254
+ #
255
+ # $x = "abc'123'nospace`"lulz`"!!!"
256
+ # & EchoArgs @($x, $x)
257
+ # Command line:
258
+ # "C:\Program Files (x86)\PowerShell Community Extensions\Pscx3\Pscx\Apps\EchoArgs.exe" abc'123'nospace"lulz"!!! abc'123'nospace"lulz"!!!
259
+ #
260
+ # $x = "`"`"Look ma! Tonnes of spaces! 'foo' 'bar'`"`""
261
+ # & EchoArgs @($x, $x)
262
+ # Command line:
263
+ # "C:\Program Files (x86)\PowerShell Community Extensions\Pscx3\Pscx\Apps\EchoArgs.exe" ""Look ma! Tonnes of spaces! 'foo' 'bar'"" ""Look ma! Tonnes of spaces! 'foo' 'bar'""
264
+ #
265
+ # Given all this, we can now device a strategy to work around all these immensely helpful, well
266
+ # documented and useful tools by looking at each incoming argument, escaping any ' characters
267
+ # with a '"'"' sequence, surrounding each argument with ' & joining them with a space separating
268
+ # them.
269
+ # There is another bug (https://bugs.ruby-lang.org/issues/11142) that causes ruby to mangle any
270
+ # "" two-character double quote sequence but since we always emit our strings inside ' except for
271
+ # ' characters, this should be ok. Just remember that an argument '' should get translated to
272
+ # ''"'"''"'"'' on the command line. If those intervening empty ''s are not present, the presence
273
+ # of "" will cause ruby to mangle that argument.
274
+ $transformedList = $argList | foreach { "'" + ( $_ -replace "'","'`"'`"'" ) + "'" }
275
+ $fortifiedArgString = $transformedList -join ' '
276
+
277
+ # Use the correct embedded ruby path. We'll be deployed at a path that looks like
278
+ # [C:\opscode or some other prefix]\chef\modules\chef
279
+ $ruby = Join-Path (Get-ScriptDirectory) "..\..\embedded\bin\ruby.exe"
280
+ $commandPath = Join-Path (Get-ScriptDirectory) "..\..\bin\$command"
281
+
282
+ Run-ExecutableAndWait $ruby """$ruby"" '$commandPath' $fortifiedArgString"
283
+ }
284
+
285
+
286
+ function chef-apply {
287
+ Run-RubyCommand 'chef-apply' $args
288
+ }
289
+
290
+ function chef-client {
291
+ Run-RubyCommand 'chef-client' $args
292
+ }
293
+
294
+ function chef-service-manager {
295
+ Run-RubyCommand 'chef-service-manager' $args
296
+ }
297
+
298
+ function chef-shell {
299
+ Run-RubyCommand 'chef-shell' $args
300
+ }
301
+
302
+ function chef-solo {
303
+ Run-RubyCommand 'chef-solo' $args
304
+ }
305
+
306
+ function chef-windows-service {
307
+ Run-RubyCommand 'chef-windows-service' $args
308
+ }
309
+
310
+ function knife {
311
+ Run-RubyCommand 'knife' $args
312
+ }
313
+
314
+ Export-ModuleMember -function chef-apply
315
+ Export-ModuleMember -function chef-client
316
+ Export-ModuleMember -function chef-service-manager
317
+ Export-ModuleMember -function chef-shell
318
+ Export-ModuleMember -function chef-solo
319
+ Export-ModuleMember -function chef-windows-service
320
+ Export-ModuleMember -function knife
321
+
322
+ # To debug this module, uncomment the line below and then run the following.
323
+ # Export-ModuleMember -function Run-RubyCommand
324
+ # Remove-Module chef
325
+ # Import-Module chef
326
+ # "puts ARGV" | Out-File C:\opscode\chef\bin\puts_args
327
+ # Run-RubyCommand puts_args 'Here' "are" some '"very interesting"' 'arguments[to]' "`"try out`""
@@ -58,7 +58,7 @@ class Chef
58
58
  # @return [Array<Class>] Priority Array of Provider Classes to use for the resource_name on the node
59
59
  #
60
60
  def get_provider_priority_array(resource_name)
61
- result = provider_priority_map.get_priority_array(node, resource_name)
61
+ result = provider_priority_map.get_priority_array(node, resource_name.to_sym)
62
62
  result = result.dup if result
63
63
  result
64
64
  end
@@ -71,7 +71,7 @@ class Chef
71
71
  # @return [Array<Class>] Priority Array of Resource Classes to use for the resource_name on the node
72
72
  #
73
73
  def get_resource_priority_array(resource_name)
74
- result = resource_priority_map.get_priority_array(node, resource_name)
74
+ result = resource_priority_map.get_priority_array(node, resource_name.to_sym)
75
75
  result = result.dup if result
76
76
  result
77
77
  end
@@ -86,7 +86,7 @@ class Chef
86
86
  # @return [Array<Class>] Modified Priority Array of Provider Classes to use for the resource_name on the node
87
87
  #
88
88
  def set_provider_priority_array(resource_name, priority_array, *filter, &block)
89
- result = provider_priority_map.set_priority_array(resource_name, priority_array, *filter, &block)
89
+ result = provider_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block)
90
90
  result = result.dup if result
91
91
  result
92
92
  end
@@ -101,7 +101,7 @@ class Chef
101
101
  # @return [Array<Class>] Modified Priority Array of Resource Classes to use for the resource_name on the node
102
102
  #
103
103
  def set_resource_priority_array(resource_name, priority_array, *filter, &block)
104
- result = resource_priority_map.set_priority_array(resource_name, priority_array, *filter, &block)
104
+ result = resource_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block)
105
105
  result = result.dup if result
106
106
  result
107
107
  end
@@ -309,12 +309,18 @@ class Chef
309
309
  # with the proper exit status code and everything gets raised
310
310
  # as a RunFailedWrappingError
311
311
  if run_error || converge_error || audit_error
312
- error = if run_error == converge_error
313
- Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
314
- else
315
- Chef::Exceptions::RunFailedWrappingError.new(run_error, converge_error, audit_error)
316
- end
317
- error.fill_backtrace
312
+ error = if Chef::Config[:audit_mode] == :disabled
313
+ run_error || converge_error
314
+ else
315
+ e = if run_error == converge_error
316
+ Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
317
+ else
318
+ Chef::Exceptions::RunFailedWrappingError.new(run_error, converge_error, audit_error)
319
+ end
320
+ e.fill_backtrace
321
+ e
322
+ end
323
+
318
324
  Chef::Application.debug_stacktrace(error)
319
325
  raise error
320
326
  end
@@ -38,30 +38,35 @@ class Chef
38
38
  #
39
39
  # @return [NodeMap] Returns self for possible chaining
40
40
  #
41
- def set(key, value, platform: nil, platform_version: nil, platform_family: nil, os: nil, on_platform: nil, on_platforms: nil, canonical: nil, &block)
41
+ def set(key, value, platform: nil, platform_version: nil, platform_family: nil, os: nil, on_platform: nil, on_platforms: nil, canonical: nil, override: nil, &block)
42
42
  Chef::Log.deprecation "The on_platform option to node_map has been deprecated" if on_platform
43
43
  Chef::Log.deprecation "The on_platforms option to node_map has been deprecated" if on_platforms
44
44
  platform ||= on_platform || on_platforms
45
- filters = { platform: platform, platform_version: platform_version, platform_family: platform_family, os: os }
46
- new_matcher = { filters: filters, block: block, value: value, canonical: canonical }
47
- @map[key] ||= []
48
- # Decide where to insert the matcher; the new value is preferred over
49
- # anything more specific (see `priority_of`) and is preferred over older
50
- # values of the same specificity. (So all other things being equal,
51
- # newest wins.)
45
+ filters = {}
46
+ filters[:platform] = platform if platform
47
+ filters[:platform_version] = platform_version if platform_version
48
+ filters[:platform_family] = platform_family if platform_family
49
+ filters[:os] = os if os
50
+ new_matcher = { value: value, filters: filters }
51
+ new_matcher[:block] = block if block
52
+ new_matcher[:canonical] = canonical if canonical
53
+ new_matcher[:override] = override if override
54
+
55
+ # The map is sorted in order of preference already; we just need to find
56
+ # our place in it (just before the first value with the same preference level).
52
57
  insert_at = nil
53
- @map[key].each_with_index do |matcher, index|
54
- if specificity(new_matcher) >= specificity(matcher)
55
- insert_at = index
56
- break
57
- end
58
+ @map[key] ||= []
59
+ @map[key].each_with_index do |matcher,index|
60
+ cmp = compare_matchers(key, new_matcher, matcher)
61
+ insert_at ||= index if cmp && cmp <= 0
58
62
  end
59
63
  if insert_at
60
64
  @map[key].insert(insert_at, new_matcher)
61
65
  else
62
66
  @map[key] << new_matcher
63
67
  end
64
- self
68
+ insert_at ||= @map[key].size - 1
69
+ @map
65
70
  end
66
71
 
67
72
  #
@@ -116,30 +121,7 @@ class Chef
116
121
  remaining
117
122
  end
118
123
 
119
- private
120
-
121
- #
122
- # Gives a value for "how specific" the matcher is.
123
- # Things which specify more specific filters get a higher number
124
- # (platform_version > platform > platform_family > os); things
125
- # with a block have higher specificity than similar things without
126
- # a block.
127
- #
128
- def specificity(matcher)
129
- if matcher[:filters][:platform_version]
130
- specificity = 8
131
- elsif matcher[:filters][:platform]
132
- specificity = 6
133
- elsif matcher[:filters][:platform_family]
134
- specificity = 4
135
- elsif matcher[:filters][:os]
136
- specificity = 2
137
- else
138
- specificity = 0
139
- end
140
- specificity += 1 if matcher[:block]
141
- specificity
142
- end
124
+ protected
143
125
 
144
126
  #
145
127
  # Succeeds if:
@@ -197,5 +179,48 @@ class Chef
197
179
  return true if canonical.nil?
198
180
  !!canonical == !!matcher[:canonical]
199
181
  end
182
+
183
+ def compare_matchers(key, new_matcher, matcher)
184
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:block] }
185
+ return cmp if cmp != 0
186
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_version] }
187
+ return cmp if cmp != 0
188
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform] }
189
+ return cmp if cmp != 0
190
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_family] }
191
+ return cmp if cmp != 0
192
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:os] }
193
+ return cmp if cmp != 0
194
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:override] }
195
+ return cmp if cmp != 0
196
+ # If all things are identical, return 0
197
+ 0
198
+ end
199
+
200
+ def compare_matcher_properties(new_matcher, matcher)
201
+ a = yield(new_matcher)
202
+ b = yield(matcher)
203
+
204
+ # Check for blcacklists ('!windows'). Those always come *after* positive
205
+ # whitelists.
206
+ a_negated = Array(a).any? { |f| f.is_a?(String) && f.start_with?('!') }
207
+ b_negated = Array(b).any? { |f| f.is_a?(String) && f.start_with?('!') }
208
+ if a_negated != b_negated
209
+ return 1 if a_negated
210
+ return -1 if b_negated
211
+ end
212
+
213
+ # We treat false / true and nil / not-nil with the same comparison
214
+ a = nil if a == false
215
+ b = nil if b == false
216
+ cmp = a <=> b
217
+ # This is the case where one is non-nil, and one is nil. The one that is
218
+ # nil is "greater" (i.e. it should come last).
219
+ if cmp.nil?
220
+ return 1 if a.nil?
221
+ return -1 if b.nil?
222
+ end
223
+ cmp
224
+ end
200
225
  end
201
226
  end