chef 18.9.4 → 18.11.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +16 -12
  3. data/README.md +0 -1
  4. data/Rakefile +21 -5
  5. data/chef-universal-mingw-ucrt.gemspec +4 -2
  6. data/chef.gemspec +17 -14
  7. data/lib/chef/application/apply.rb +1 -1
  8. data/lib/chef/application/base.rb +4 -4
  9. data/lib/chef/application/exit_code.rb +1 -1
  10. data/lib/chef/chef_fs/command_line.rb +4 -4
  11. data/lib/chef/chef_fs/file_system/chef_server/cookbook_dir.rb +1 -1
  12. data/lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb +2 -3
  13. data/lib/chef/client.rb +2 -3
  14. data/lib/chef/compliance/fetcher/chef_server.rb +1 -1
  15. data/lib/chef/compliance/reporter/cli.rb +2 -2
  16. data/lib/chef/compliance/runner.rb +19 -1
  17. data/lib/chef/dsl/chef_vault.rb +1 -1
  18. data/lib/chef/dsl/rest_resource.rb +223 -16
  19. data/lib/chef/encrypted_data_bag_item/decryptor.rb +1 -5
  20. data/lib/chef/environment.rb +1 -1
  21. data/lib/chef/exceptions.rb +1 -1
  22. data/lib/chef/file_cache.rb +1 -5
  23. data/lib/chef/formatters/error_description.rb +8 -1
  24. data/lib/chef/formatters/error_inspectors/compile_error_inspector.rb +1 -1
  25. data/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb +1 -1
  26. data/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb +1 -1
  27. data/lib/chef/group.rb +1 -1
  28. data/lib/chef/handler/slow_report.rb +1 -1
  29. data/lib/chef/http/authenticator.rb +25 -12
  30. data/lib/chef/mixin/api_version_request_handling.rb +1 -1
  31. data/lib/chef/mixin/powershell_type_coercions.rb +2 -2
  32. data/lib/chef/mixin/securable.rb +2 -2
  33. data/lib/chef/mixin/template.rb +25 -3
  34. data/lib/chef/mixin/versioned_api.rb +1 -1
  35. data/lib/chef/monkey_patches/net-http.rb +8 -8
  36. data/lib/chef/monkey_patches/reline-windows.rb +116 -0
  37. data/lib/chef/property.rb +1 -1
  38. data/lib/chef/provider/cron.rb +1 -1
  39. data/lib/chef/provider/directory.rb +11 -0
  40. data/lib/chef/provider/dsc_resource.rb +2 -2
  41. data/lib/chef/provider/file/content.rb +3 -2
  42. data/lib/chef/provider/file.rb +1 -1
  43. data/lib/chef/provider/git.rb +1 -1
  44. data/lib/chef/provider/ifconfig/debian.rb +1 -1
  45. data/lib/chef/provider/ifconfig/redhat.rb +1 -1
  46. data/lib/chef/provider/mount/linux.rb +1 -1
  47. data/lib/chef/provider/mount/windows.rb +4 -4
  48. data/lib/chef/provider/package/chocolatey.rb +78 -19
  49. data/lib/chef/provider/package/dnf/dnf_helper.py +355 -65
  50. data/lib/chef/provider/package/dnf/python_helper.rb +7 -4
  51. data/lib/chef/provider/package/dnf.rb +25 -6
  52. data/lib/chef/provider/package/homebrew.rb +3 -3
  53. data/lib/chef/provider/package/rubygems.rb +25 -2
  54. data/lib/chef/provider/package/snap.rb +1 -1
  55. data/lib/chef/provider/package/windows/exe.rb +1 -1
  56. data/lib/chef/provider/package/windows/msi.rb +1 -1
  57. data/lib/chef/provider/package/yum/rpm_utils.rb +2 -2
  58. data/lib/chef/provider/package/yum.rb +1 -1
  59. data/lib/chef/provider/powershell_script.rb +84 -84
  60. data/lib/chef/provider/service/debian.rb +2 -4
  61. data/lib/chef/provider/user/dscl.rb +1 -1
  62. data/lib/chef/provider/user/mac.rb +1 -1
  63. data/lib/chef/provider/windows_script.rb +1 -1
  64. data/lib/chef/resource/_rest_resource.rb +9 -7
  65. data/lib/chef/resource/alternatives.rb +41 -41
  66. data/lib/chef/resource/apt_package.rb +31 -31
  67. data/lib/chef/resource/apt_preference.rb +22 -22
  68. data/lib/chef/resource/apt_repository.rb +2 -2
  69. data/lib/chef/resource/archive_file.rb +52 -18
  70. data/lib/chef/resource/bash.rb +129 -129
  71. data/lib/chef/resource/bff_package.rb +15 -15
  72. data/lib/chef/resource/breakpoint.rb +44 -44
  73. data/lib/chef/resource/cab_package.rb +22 -22
  74. data/lib/chef/resource/chef_client_config.rb +251 -89
  75. data/lib/chef/resource/chef_client_cron.rb +18 -18
  76. data/lib/chef/resource/chef_client_launchd.rb +1 -1
  77. data/lib/chef/resource/chef_client_scheduled_task.rb +31 -31
  78. data/lib/chef/resource/chef_client_systemd_timer.rb +16 -16
  79. data/lib/chef/resource/chef_client_trusted_certificate.rb +28 -28
  80. data/lib/chef/resource/chef_handler.rb +107 -107
  81. data/lib/chef/resource/chef_vault_secret.rb +1 -1
  82. data/lib/chef/resource/chocolatey_config.rb +14 -14
  83. data/lib/chef/resource/chocolatey_feature.rb +1 -1
  84. data/lib/chef/resource/chocolatey_installer.rb +60 -42
  85. data/lib/chef/resource/chocolatey_package.rb +1 -1
  86. data/lib/chef/resource/chocolatey_source.rb +15 -15
  87. data/lib/chef/resource/cron/_cron_shared.rb +0 -2
  88. data/lib/chef/resource/cron/cron.rb +70 -70
  89. data/lib/chef/resource/csh.rb +5 -5
  90. data/lib/chef/resource/directory.rb +6 -6
  91. data/lib/chef/resource/dpkg_package.rb +3 -3
  92. data/lib/chef/resource/group.rb +28 -28
  93. data/lib/chef/resource/habitat/habitat_package.rb +80 -80
  94. data/lib/chef/resource/habitat/habitat_sup.rb +93 -95
  95. data/lib/chef/resource/habitat/habitat_sup_systemd.rb +10 -10
  96. data/lib/chef/resource/habitat/habitat_sup_windows.rb +3 -3
  97. data/lib/chef/resource/habitat_config.rb +13 -13
  98. data/lib/chef/resource/habitat_install.rb +17 -17
  99. data/lib/chef/resource/habitat_service.rb +1 -1
  100. data/lib/chef/resource/habitat_user_toml.rb +12 -12
  101. data/lib/chef/resource/helpers/cron_validations.rb +2 -2
  102. data/lib/chef/resource/homebrew_cask.rb +1 -1
  103. data/lib/chef/resource/homebrew_package.rb +20 -20
  104. data/lib/chef/resource/homebrew_tap.rb +1 -1
  105. data/lib/chef/resource/hostname.rb +2 -2
  106. data/lib/chef/resource/http_request.rb +2 -2
  107. data/lib/chef/resource/ifconfig.rb +34 -34
  108. data/lib/chef/resource/inspec_input.rb +37 -37
  109. data/lib/chef/resource/inspec_waiver.rb +63 -63
  110. data/lib/chef/resource/inspec_waiver_file_entry.rb +25 -25
  111. data/lib/chef/resource/ksh.rb +5 -5
  112. data/lib/chef/resource/launchd.rb +2 -2
  113. data/lib/chef/resource/link.rb +8 -8
  114. data/lib/chef/resource/locale.rb +6 -6
  115. data/lib/chef/resource/log.rb +4 -4
  116. data/lib/chef/resource/mdadm.rb +36 -36
  117. data/lib/chef/resource/mount.rb +3 -3
  118. data/lib/chef/resource/msu_package.rb +1 -1
  119. data/lib/chef/resource/ohai.rb +32 -32
  120. data/lib/chef/resource/ohai_hint.rb +24 -24
  121. data/lib/chef/resource/openssl_ec_private_key.rb +2 -2
  122. data/lib/chef/resource/openssl_ec_public_key.rb +2 -2
  123. data/lib/chef/resource/openssl_rsa_private_key.rb +1 -1
  124. data/lib/chef/resource/openssl_rsa_public_key.rb +2 -2
  125. data/lib/chef/resource/openssl_x509_certificate.rb +1 -1
  126. data/lib/chef/resource/openssl_x509_crl.rb +15 -15
  127. data/lib/chef/resource/openssl_x509_request.rb +2 -2
  128. data/lib/chef/resource/osx_profile.rb +54 -54
  129. data/lib/chef/resource/package.rb +6 -6
  130. data/lib/chef/resource/perl.rb +5 -5
  131. data/lib/chef/resource/plist.rb +4 -4
  132. data/lib/chef/resource/powershell_package_source.rb +12 -12
  133. data/lib/chef/resource/python.rb +4 -4
  134. data/lib/chef/resource/reboot.rb +5 -5
  135. data/lib/chef/resource/registry_key.rb +81 -81
  136. data/lib/chef/resource/remote_file.rb +72 -72
  137. data/lib/chef/resource/resource_notification.rb +4 -4
  138. data/lib/chef/resource/rhsm_errata_level.rb +1 -1
  139. data/lib/chef/resource/rhsm_register.rb +2 -2
  140. data/lib/chef/resource/scm/git.rb +79 -79
  141. data/lib/chef/resource/scm/subversion.rb +9 -9
  142. data/lib/chef/resource/script.rb +3 -3
  143. data/lib/chef/resource/selinux_boolean.rb +12 -12
  144. data/lib/chef/resource/selinux_fcontext.rb +21 -21
  145. data/lib/chef/resource/selinux_install.rb +18 -18
  146. data/lib/chef/resource/selinux_login.rb +11 -11
  147. data/lib/chef/resource/selinux_module.rb +16 -16
  148. data/lib/chef/resource/selinux_permissive.rb +9 -9
  149. data/lib/chef/resource/selinux_port.rb +16 -16
  150. data/lib/chef/resource/selinux_state.rb +28 -28
  151. data/lib/chef/resource/selinux_user.rb +14 -14
  152. data/lib/chef/resource/service.rb +5 -5
  153. data/lib/chef/resource/snap_package.rb +16 -16
  154. data/lib/chef/resource/ssh_known_hosts_entry.rb +10 -10
  155. data/lib/chef/resource/sudo.rb +68 -68
  156. data/lib/chef/resource/swap_file.rb +12 -12
  157. data/lib/chef/resource/sysctl.rb +42 -42
  158. data/lib/chef/resource/systemd_unit.rb +38 -38
  159. data/lib/chef/resource/timezone.rb +16 -16
  160. data/lib/chef/resource/user/mac_user.rb +3 -3
  161. data/lib/chef/resource/user.rb +6 -6
  162. data/lib/chef/resource/user_ulimit.rb +22 -22
  163. data/lib/chef/resource/windows_ad_join.rb +25 -25
  164. data/lib/chef/resource/windows_audit_policy.rb +34 -34
  165. data/lib/chef/resource/windows_auto_run.rb +8 -8
  166. data/lib/chef/resource/windows_certificate.rb +20 -20
  167. data/lib/chef/resource/windows_defender.rb +22 -22
  168. data/lib/chef/resource/windows_defender_exclusion.rb +17 -17
  169. data/lib/chef/resource/windows_env.rb +6 -6
  170. data/lib/chef/resource/windows_feature.rb +42 -42
  171. data/lib/chef/resource/windows_feature_dism.rb +8 -8
  172. data/lib/chef/resource/windows_feature_powershell.rb +20 -20
  173. data/lib/chef/resource/windows_firewall_profile.rb +28 -28
  174. data/lib/chef/resource/windows_firewall_rule.rb +47 -47
  175. data/lib/chef/resource/windows_font.rb +7 -7
  176. data/lib/chef/resource/windows_package.rb +59 -59
  177. data/lib/chef/resource/windows_pagefile.rb +29 -29
  178. data/lib/chef/resource/windows_path.rb +12 -12
  179. data/lib/chef/resource/windows_printer.rb +28 -28
  180. data/lib/chef/resource/windows_printer_port.rb +21 -21
  181. data/lib/chef/resource/windows_security_policy.rb +23 -23
  182. data/lib/chef/resource/windows_service.rb +108 -108
  183. data/lib/chef/resource/windows_share.rb +19 -19
  184. data/lib/chef/resource/windows_shortcut.rb +7 -7
  185. data/lib/chef/resource/windows_task.rb +105 -105
  186. data/lib/chef/resource/windows_uac.rb +14 -14
  187. data/lib/chef/resource/windows_update_settings.rb +27 -27
  188. data/lib/chef/resource/windows_user_privilege.rb +59 -59
  189. data/lib/chef/resource/windows_workgroup.rb +12 -12
  190. data/lib/chef/resource/yum_package.rb +1 -1
  191. data/lib/chef/resource/yum_repository.rb +36 -36
  192. data/lib/chef/resource.rb +1 -1
  193. data/lib/chef/resource_collection/resource_set.rb +2 -3
  194. data/lib/chef/resource_inspector.rb +1 -2
  195. data/lib/chef/run_context.rb +2 -2
  196. data/lib/chef/runner.rb +1 -1
  197. data/lib/chef/secret_fetcher/akeyless_vault.rb +0 -1
  198. data/lib/chef/secret_fetcher/hashi_vault.rb +0 -1
  199. data/lib/chef/shell/ext.rb +20 -20
  200. data/lib/chef/shell/shell_session.rb +1 -1
  201. data/lib/chef/shell.rb +7 -0
  202. data/lib/chef/version.rb +1 -1
  203. data/lib/chef/version_constraint.rb +1 -1
  204. data/lib/chef/win32/file/version_info.rb +1 -1
  205. metadata +63 -38
@@ -20,56 +20,263 @@ require "chef/constants" unless defined?(NOT_PASSED)
20
20
  class Chef
21
21
  module DSL
22
22
  module RestResource
23
+ private
24
+
25
+ def inherited_accessor(name)
26
+ superclass.public_send(name) if superclass.respond_to?(name)
27
+ end
28
+
29
+ public
30
+
31
+ # Define property mapping between resource properties and JSON API fields
32
+ #
33
+ # Maps resource properties to their corresponding locations in the JSON
34
+ # API payload. Supports both simple 1:1 mappings and complex transformations.
35
+ #
36
+ # @param rest_property_map [Hash, Array, NOT_PASSED] The property mapping configuration
37
+ # - Hash: Keys are property symbols, values are JMESPath strings or symbol references
38
+ # - Array: Simple 1:1 mapping (property name matches JSON field name)
39
+ # - NOT_PASSED: Acts as getter, returns current mapping
40
+ #
41
+ # @return [Hash] The current property mapping
42
+ #
43
+ # @example Simple 1:1 mapping
44
+ # rest_property_map [:username, :email, :role]
45
+ # # Equivalent to: { username: 'username', email: 'email', role: 'role' }
46
+ #
47
+ # @example Nested JSON paths
48
+ # rest_property_map({
49
+ # username: 'user.name',
50
+ # email: 'user.contact.email',
51
+ # role: 'permissions.role'
52
+ # })
53
+ #
54
+ # @example Custom mapping functions
55
+ # rest_property_map({
56
+ # username: 'username',
57
+ # tags: :tags_mapping # Calls tags_from_json and tags_to_json methods
58
+ # })
59
+ #
60
+ # @see #json_to_property Method that uses this mapping to extract values
61
+ # @see #property_to_json Method that uses this mapping to create JSON
23
62
  def rest_property_map(rest_property_map = NOT_PASSED)
24
- if rest_property_map != NOT_PASSED
63
+ unless rest_property_map.equal?(NOT_PASSED)
25
64
  rest_property_map = rest_property_map.to_h { |k| [k.to_sym, k] } if rest_property_map.is_a? Array
26
65
 
27
66
  @rest_property_map = rest_property_map
28
67
  end
29
- @rest_property_map
68
+ @rest_property_map || inherited_accessor(:rest_property_map)
30
69
  end
31
70
 
32
- # URL to collection
71
+ # Define the REST API collection URL
72
+ #
73
+ # Sets the base URL for the collection of resources. This URL is used for:
74
+ # - GET requests to list all resources
75
+ # - POST requests to create new resources
76
+ #
77
+ # @param rest_api_collection [String, NOT_PASSED] The collection URL path
78
+ # - String: Must be an absolute path starting with '/'
79
+ # - NOT_PASSED: Acts as getter, returns current collection URL
80
+ #
81
+ # @return [String] The current collection URL
82
+ #
83
+ # @raise [ArgumentError] If the path doesn't start with '/'
84
+ #
85
+ # @example
86
+ # rest_api_collection '/api/v1/users'
87
+ # # GET /api/v1/users # List all users
88
+ # # POST /api/v1/users # Create new user
33
89
  def rest_api_collection(rest_api_collection = NOT_PASSED)
34
- if rest_api_collection != NOT_PASSED
90
+ unless rest_api_collection.equal?(NOT_PASSED)
35
91
  raise ArgumentError, "You must pass an absolute path to rest_api_collection" unless rest_api_collection.start_with? "/"
36
92
 
37
93
  @rest_api_collection = rest_api_collection
38
94
  end
39
95
 
40
- @rest_api_collection
96
+ @rest_api_collection || inherited_accessor(:rest_api_collection)
41
97
  end
42
98
 
43
- # RFC6570-Templated URL to document
99
+ # Define the REST API document URL with RFC 6570 template support
100
+ #
101
+ # Sets the URL pattern for individual resource documents. The URL can include
102
+ # RFC 6570 URI templates that will be expanded using property values. This URL
103
+ # is used for:
104
+ # - GET requests to retrieve a specific resource
105
+ # - PATCH/PUT requests to update a resource
106
+ # - DELETE requests to remove a resource
107
+ #
108
+ # @param rest_api_document [String, NOT_PASSED] The document URL pattern
109
+ # - String: Must be an absolute path starting with '/'
110
+ # - Can include RFC 6570 templates like {property_name}
111
+ # - NOT_PASSED: Acts as getter, returns current document URL
112
+ #
113
+ # @param first_element_only [Boolean] If true and API returns array, extract first element only
114
+ #
115
+ # @return [String] The current document URL pattern
116
+ #
117
+ # @raise [ArgumentError] If the path doesn't start with '/'
118
+ #
119
+ # @example Path-based URL
120
+ # rest_api_document '/api/v1/users/{username}'
121
+ # # With username='john':
122
+ # # GET /api/v1/users/john
123
+ # # PATCH /api/v1/users/john
124
+ # # DELETE /api/v1/users/john
125
+ #
126
+ # @example Query-based URL
127
+ # rest_api_document '/api/v1/users?name={username}&email={email}'
128
+ # # With username='john', email='john@example.com':
129
+ # # GET /api/v1/users?name=john&email=john@example.com
130
+ #
131
+ # @example With first_element_only
132
+ # rest_api_document '/api/v1/users?name={username}', first_element_only: true
133
+ # # API returns: [{"name": "john", ...}]
134
+ # # Resource sees: {"name": "john", ...}
135
+ #
136
+ # @see https://tools.ietf.org/html/rfc6570 RFC 6570 URI Template specification
44
137
  def rest_api_document(rest_api_document = NOT_PASSED, first_element_only: false)
45
- if rest_api_document != NOT_PASSED
138
+ unless rest_api_document.equal?(NOT_PASSED)
46
139
  raise ArgumentError, "You must pass an absolute path to rest_api_document" unless rest_api_document.start_with? "/"
47
140
 
48
141
  @rest_api_document = rest_api_document
49
142
  @rest_api_document_first_element_only = first_element_only
50
143
  end
51
- @rest_api_document
144
+ @rest_api_document ||
145
+ inherited_accessor(:rest_api_document) ||
146
+ (rest_api_collection && rest_identity_property ? "#{rest_api_collection}/{#{rest_identity_property}}" : nil)
52
147
  end
53
148
 
54
- # Explicit REST document identity mapping
149
+ # Define explicit identity mapping for resource identification
150
+ #
151
+ # Explicitly specifies which JSON fields and resource properties uniquely
152
+ # identify a resource. Use this when automatic identity inference from the
153
+ # document URL is insufficient or when dealing with composite keys.
154
+ #
155
+ # If not specified, the identity is automatically inferred from RFC 6570
156
+ # templates in the document URL.
157
+ #
158
+ # @param rest_identity_map [Hash, NOT_PASSED] Mapping of JSON paths to properties
159
+ # - Hash: Keys are JMESPath-like strings, values are property symbols
160
+ # - NOT_PASSED: Acts as getter, returns current identity mapping
161
+ #
162
+ # @return [Hash, nil] The current identity mapping or nil if using auto-inference
163
+ #
164
+ # @example Simple identity
165
+ # property :username, String, identity: true
166
+ # rest_identity_map({ 'username' => :username })
167
+ #
168
+ # @example Composite identity
169
+ # property :name, String
170
+ # property :namespace, String
171
+ #
172
+ # rest_identity_map({
173
+ # 'name' => :name,
174
+ # 'namespace' => :namespace
175
+ # })
176
+ #
177
+ # @example Nested identity fields
178
+ # property :user_id, String
179
+ # property :org_id, String
180
+ #
181
+ # rest_identity_map({
182
+ # 'user.id' => :user_id,
183
+ # 'organization.id' => :org_id
184
+ # })
55
185
  def rest_identity_map(rest_identity_map = NOT_PASSED)
56
- @rest_identity_map = rest_identity_map if rest_identity_map != NOT_PASSED
57
- @rest_identity_map
186
+ @rest_identity_map = rest_identity_map unless rest_identity_map.equal?(NOT_PASSED)
187
+ @rest_identity_map || inherited_accessor(:rest_identity_map)
58
188
  end
59
189
 
60
- # Mark up properties for POST only, not PATCH/PUT
190
+ # Declare properties that should only be sent during resource creation
191
+ #
192
+ # Specifies which properties should only be included in POST (create) requests
193
+ # and excluded from PATCH/PUT (update) requests. This is useful for properties
194
+ # that can only be set during initial creation or would cause errors if
195
+ # included in updates.
196
+ #
197
+ # @param rest_post_only_properties [Symbol, Array<Symbol>, NOT_PASSED] Properties to mark
198
+ # - Symbol: Single property name
199
+ # - Array<Symbol>: Multiple property names
200
+ # - NOT_PASSED: Acts as getter, returns current post-only properties
201
+ #
202
+ # @return [Array<Symbol>] Current list of post-only properties (empty array if none)
203
+ #
204
+ # @example Single property
205
+ # property :password, String, sensitive: true
206
+ # property :username, String
207
+ #
208
+ # rest_post_only_properties :password
209
+ # # Password only sent when creating user, not when updating
210
+ #
211
+ # @example Multiple properties
212
+ # property :password, String, sensitive: true
213
+ # property :initial_role, String
214
+ # property :username, String
215
+ #
216
+ # rest_post_only_properties [:password, :initial_role]
217
+ #
218
+ # @example Common use cases
219
+ # # Passwords that can't be updated via API
220
+ # rest_post_only_properties :admin_password
221
+ #
222
+ # # Resource size that can't be changed after creation
223
+ # rest_post_only_properties :disk_size_gb
224
+ #
225
+ # # Initialization parameters
226
+ # rest_post_only_properties [:template_id, :source_snapshot]
61
227
  def rest_post_only_properties(rest_post_only_properties = NOT_PASSED)
62
- if rest_post_only_properties != NOT_PASSED
228
+ unless rest_post_only_properties.equal?(NOT_PASSED)
63
229
  @rest_post_only_properties = Array(rest_post_only_properties).map(&:to_sym)
64
230
  end
65
- @rest_post_only_properties || []
231
+ @rest_post_only_properties || inherited_accessor(:rest_post_only_properties) || []
66
232
  end
67
233
 
68
234
  def rest_api_document_first_element_only(rest_api_document_first_element_only = NOT_PASSED)
69
- if rest_api_document_first_element_only != NOT_PASSED
235
+ unless rest_api_document_first_element_only.equal?(NOT_PASSED)
70
236
  @rest_api_document_first_element_only = rest_api_document_first_element_only
71
237
  end
72
- @rest_api_document_first_element_only
238
+ @rest_api_document_first_element_only || inherited_accessor(:rest_api_document_first_element_only)
239
+ end
240
+
241
+ # Define the base URL for the REST API
242
+ #
243
+ # Sets the base endpoint URL that is prepended to all collection and document
244
+ # URLs. This allows resource definitions to be self-contained without requiring
245
+ # the Train transport endpoint to be pre-configured.
246
+ #
247
+ # @param rest_api_endpoint [String, NOT_PASSED] The base URL of the REST API
248
+ # - NOT_PASSED: Acts as getter, returns current endpoint URL
249
+ #
250
+ # @return [String, nil] The current endpoint URL
251
+ #
252
+ # @example
253
+ # rest_api_endpoint "https://api.example.com"
254
+ # rest_api_collection "/api/v1/users"
255
+ # # GET https://api.example.com/api/v1/users
256
+ def rest_api_endpoint(rest_api_endpoint = NOT_PASSED)
257
+ @rest_api_endpoint = rest_api_endpoint unless rest_api_endpoint.equal?(NOT_PASSED)
258
+ @rest_api_endpoint || inherited_accessor(:rest_api_endpoint)
259
+ end
260
+
261
+ # Declare the property that uniquely identifies a resource in the REST API
262
+ #
263
+ # Sets the identity property for the resource and auto-generates the document
264
+ # URL as "#{rest_api_collection}/{property}" when no explicit rest_api_document
265
+ # is provided. This is a convenience alternative to setting rest_api_document
266
+ # manually.
267
+ #
268
+ # @param property [Symbol, NOT_PASSED] The property name used as the resource identifier
269
+ # - NOT_PASSED: Acts as getter, returns current identity property
270
+ #
271
+ # @return [Symbol, nil] The current identity property name
272
+ #
273
+ # @example
274
+ # rest_api_collection "/api/v1/users"
275
+ # rest_identity_property :username
276
+ # # Auto-generates rest_api_document as "/api/v1/users/{username}"
277
+ def rest_identity_property(property = NOT_PASSED)
278
+ @rest_identity_property = property unless property.equal?(NOT_PASSED)
279
+ @rest_identity_property || inherited_accessor(:rest_identity_property)
73
280
  end
74
281
 
75
282
  end
@@ -177,11 +177,7 @@ class Chef::EncryptedDataBagItem
177
177
  digest = OpenSSL::Digest.new("sha256")
178
178
  raw_hmac = OpenSSL::HMAC.digest(digest, key, @encrypted_data["encrypted_data"])
179
179
 
180
- if candidate_hmac_matches?(raw_hmac)
181
- true
182
- else
183
- raise DecryptionFailure, "Error decrypting data bag value: invalid hmac. Most likely the provided key is incorrect"
184
- end
180
+ candidate_hmac_matches?(raw_hmac) || raise(DecryptionFailure, "Error decrypting data bag value: invalid hmac. Most likely the provided key is incorrect")
185
181
  end
186
182
 
187
183
  private
@@ -35,7 +35,7 @@ class Chef
35
35
  include Chef::Mixin::ParamsValidate
36
36
  include Chef::Mixin::FromFile
37
37
 
38
- COMBINED_COOKBOOK_CONSTRAINT = /(.+)(?:\s+)((?:#{Chef::VersionConstraint::OPS.join('|')})(?:\s+).+)$/.freeze
38
+ COMBINED_COOKBOOK_CONSTRAINT = /(.+)(?:\s+)((?:#{Chef::VersionConstraint::OPS.join("|")})(?:\s+).+)$/.freeze
39
39
 
40
40
  def initialize(chef_server_rest: nil)
41
41
  @name = ""
@@ -532,7 +532,7 @@ class Chef
532
532
  @resources_found = resources_found
533
533
  matches_info = @resources_found.each do |r|
534
534
  if r["Module"].nil?
535
- "Resource #{r["Name"]} was found in #{r["Module"]["Name"]}"
535
+ "Resource #{r["Name"]} was found in #{r["Module"]["Name"]}" # rubocop:disable Lint/Void
536
536
  else
537
537
  "Resource #{r["Name"]} is a binary resource"
538
538
  end
@@ -201,11 +201,7 @@ class Chef
201
201
  }
202
202
  )
203
203
  full_path = create_cache_path(path, false)
204
- if File.exist?(full_path)
205
- true
206
- else
207
- false
208
- end
204
+ File.exist?(full_path) || false
209
205
  end
210
206
 
211
207
  alias_method :has_key?, :key?
@@ -59,10 +59,17 @@ class Chef
59
59
 
60
60
  private
61
61
 
62
+ MAX_DISPLAY_TEXT_LENGTH = 10_000
63
+
62
64
  def display_section(heading, text, out)
63
65
  out.puts heading
64
66
  out.puts "-" * heading.size
65
- out.puts text
67
+ if text.is_a?(String) && text.length > MAX_DISPLAY_TEXT_LENGTH
68
+ out.puts text[0, MAX_DISPLAY_TEXT_LENGTH]
69
+ out.puts "\n... [truncated #{text.length - MAX_DISPLAY_TEXT_LENGTH} characters of output]"
70
+ else
71
+ out.puts text
72
+ end
66
73
  out.puts "\n"
67
74
  end
68
75
 
@@ -126,7 +126,7 @@ class Chef
126
126
  end
127
127
 
128
128
  def filtered_bt
129
- backtrace_lines_in_cookbooks.count > 0 ? backtrace_lines_in_cookbooks : exception.backtrace
129
+ backtrace_lines_in_cookbooks.any? ? backtrace_lines_in_cookbooks : exception.backtrace
130
130
  end
131
131
 
132
132
  def found_error_in_cookbooks?
@@ -69,7 +69,7 @@ class Chef
69
69
  describe_412_error(error_description)
70
70
  when Net::HTTPBadRequest
71
71
  describe_400_error(error_description)
72
- when Net::HTTPNotFound
72
+ when Net::HTTPNotFound # rubocop:disable Lint/EmptyWhen
73
73
  when Net::HTTPInternalServerError
74
74
  describe_500_error(error_description)
75
75
  when Net::HTTPBadGateway, Net::HTTPServiceUnavailable
@@ -64,7 +64,7 @@ class Chef
64
64
  describe_401_error(error_description)
65
65
  when Net::HTTPBadRequest
66
66
  describe_400_error(error_description)
67
- when Net::HTTPNotFound
67
+ when Net::HTTPNotFound # rubocop:disable Lint/EmptyWhen
68
68
  when Net::HTTPInternalServerError
69
69
  describe_500_error(error_description)
70
70
  when Net::HTTPBadGateway, Net::HTTPServiceUnavailable, Net::HTTPGatewayTimeOut
data/lib/chef/group.rb CHANGED
@@ -35,7 +35,7 @@ class Chef
35
35
  body_hash = {
36
36
  groupname: "#{groupname}",
37
37
  actors: {
38
- "users" => group["actors"].concat([username]),
38
+ "users" => group["actors"].push(username),
39
39
  "groups" => group["groups"],
40
40
  },
41
41
  }
@@ -54,7 +54,7 @@ class Chef
54
54
 
55
55
  def count
56
56
  num = all_resources.count
57
- num > amount ? amount : num
57
+ [num, amount].min
58
58
  end
59
59
 
60
60
  def stripped_source_line(resource)
@@ -150,8 +150,9 @@ class Chef
150
150
 
151
151
  if !!results
152
152
  @raw_key = results
153
- elsif key_file == nil? && raw_key == nil?
153
+ elsif key_file.nil? && raw_key.nil?
154
154
  puts "\nNo key detected\n"
155
+ return
155
156
  elsif !!key_file
156
157
  @raw_key = IO.read(key_file).strip
157
158
  elsif !!raw_key
@@ -305,14 +306,26 @@ class Chef
305
306
 
306
307
  if !!check_certstore_for_key(client_name)
307
308
  ps_blob = powershell_exec!(get_the_key_ps(client_name, password)).result
309
+ # ps_blob can be false when the PowerShell Catch block fires (e.g. cert export
310
+ # race condition or temporary store lock). Guard before calling Hash#[] to
311
+ # avoid NoMethodError: undefined method `[]' for false:FalseClass.
312
+ return false unless ps_blob.is_a?(Hash) && ps_blob["PSPath"]
313
+
308
314
  file_path = ps_blob["PSPath"].split("::")[1]
309
- pkcs = OpenSSL::PKCS12.new(File.binread(file_path), password)
315
+
316
+ # Use begin/ensure to guarantee the unique temp file is deleted even if
317
+ # File.binread or OpenSSL::PKCS12.new raises an exception, preventing
318
+ # orphaned PFX files on disk.
319
+ begin
320
+ pkcs = OpenSSL::PKCS12.new(File.binread(file_path), password)
321
+ ensure
322
+ File.delete(file_path) if File.exist?(file_path)
323
+ end
310
324
 
311
325
  # We check the pfx we just extracted the private key from
312
326
  # if that cert is expiring in 7 days or less we generate a new pfx/p12 object
313
327
  # then we post the new public key from that to the client endpoint on
314
328
  # chef server.
315
- File.delete(file_path)
316
329
  key_expiring = is_certificate_expiring?(pkcs)
317
330
  if key_expiring
318
331
  powershell_exec!(delete_old_key_ps(client_name))
@@ -335,15 +348,15 @@ class Chef
335
348
  def self.get_the_key_ps(client_name, password)
336
349
  store = get_cert_user
337
350
  powershell_code = <<~CODE
338
- Try {
339
- $my_pwd = ConvertTo-SecureString -String "#{password}" -Force -AsPlainText;
340
- $cert = Get-ChildItem -path cert:\\#{store}\\My -Recurse | Where-Object { $_.Subject -match "chef-#{client_name}$" } -ErrorAction Stop;
341
- $tempfile = [System.IO.Path]::GetTempPath() + "export_pfx.pfx";
342
- Export-PfxCertificate -Cert $cert -Password $my_pwd -FilePath $tempfile;
343
- }
344
- Catch {
345
- return $false
346
- }
351
+ Try {
352
+ $my_pwd = ConvertTo-SecureString -String "#{password}" -Force -AsPlainText;
353
+ $cert = Get-ChildItem -path cert:\\#{store}\\My -Recurse | Where-Object { $_.Subject -match "chef-#{client_name}$" } -ErrorAction Stop;
354
+ $tempfile = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName() + ".pfx");
355
+ Export-PfxCertificate -Cert $cert -Password $my_pwd -FilePath $tempfile;
356
+ }
357
+ Catch {
358
+ return $false
359
+ }
347
360
  CODE
348
361
  end
349
362
 
@@ -42,7 +42,7 @@ class Chef
42
42
  max_server_version = Integer(header["max_version"])
43
43
 
44
44
  supported_client_versions.each do |version|
45
- if version >= min_server_version && version <= max_server_version
45
+ if version.between?(min_server_version, max_server_version)
46
46
  server_supported_client_versions.push(version)
47
47
  end
48
48
  end
@@ -72,8 +72,8 @@ class Chef
72
72
  # do we need to worry about binary data?
73
73
  if unsafe?(s)
74
74
  encoded_str = Base64.strict_encode64(s.encode("UTF-8"))
75
- "([System.Text.Encoding]::UTF8.GetString("\
76
- "[System.Convert]::FromBase64String('#{encoded_str}')"\
75
+ "([System.Text.Encoding]::UTF8.GetString(" \
76
+ "[System.Convert]::FromBase64String('#{encoded_str}')" \
77
77
  "))"
78
78
  else
79
79
  "'#{s}'"
@@ -50,9 +50,9 @@ class Chef
50
50
 
51
51
  # Windows does not support the sticky or setuid bits
52
52
  if ChefUtils.windows?
53
- Integer(m) <= 0777 && Integer(m) >= 0
53
+ Integer(m).between?(0, 0777)
54
54
  else
55
- Integer(m) <= 07777 && Integer(m) >= 0
55
+ Integer(m).between?(0, 07777)
56
56
  end
57
57
  end,
58
58
  }
@@ -93,6 +93,12 @@ class Chef
93
93
  @_extension_modules = []
94
94
  end
95
95
 
96
+ # Provide a compact inspect so exception messages do not dump the full
97
+ # template context, including node attributes and secrets.
98
+ def inspect
99
+ "#<#{self.class} template_name=#{@template_name.inspect} cookbook_name=#{@cookbook_name.inspect}>"
100
+ end
101
+
96
102
  ###
97
103
  # USER FACING API
98
104
  ###
@@ -220,8 +226,15 @@ class Chef
220
226
  @original_exception, @template, @context, @options = original_exception, template, context, options
221
227
  end
222
228
 
229
+ MAX_MESSAGE_LENGTH = 10_000
230
+
223
231
  def message
224
- @original_exception.message
232
+ msg = @original_exception.message
233
+ if msg.length > MAX_MESSAGE_LENGTH
234
+ msg[0, MAX_MESSAGE_LENGTH] + "\n... [truncated #{msg.length - MAX_MESSAGE_LENGTH} characters]"
235
+ else
236
+ msg
237
+ end
225
238
  end
226
239
 
227
240
  def line_number
@@ -238,7 +251,7 @@ class Chef
238
251
 
239
252
  def source_listing
240
253
  @source_listing ||= begin
241
- lines = @template.split(/\n/)
254
+ lines = @template.split("\n")
242
255
  if line_number
243
256
  line_index = line_number - 1
244
257
  beginning_line = line_index <= SOURCE_CONTEXT_WINDOW ? 0 : line_index - SOURCE_CONTEXT_WINDOW
@@ -258,7 +271,16 @@ class Chef
258
271
  end
259
272
 
260
273
  def to_s
261
- "\n\n#{self.class} (#{message}) #{source_location}:\n\n" +
274
+ location_parts = []
275
+ if @context.respond_to?(:cookbook_name) && @context.cookbook_name
276
+ location_parts << "cookbook: #{@context.cookbook_name}"
277
+ end
278
+ if @context.respond_to?(:template_name) && @context.template_name
279
+ location_parts << "template: #{@context.template_name}"
280
+ end
281
+ location_hint = location_parts.empty? ? "" : " (#{location_parts.join(", ")})"
282
+
283
+ "\n\n#{self.class} (#{message}) #{source_location}#{location_hint}:\n\n" +
262
284
  "#{source_listing}\n\n #{original_exception.backtrace.join("\n ")}\n\n"
263
285
  end
264
286
  end
@@ -48,7 +48,7 @@ class Chef
48
48
  version = klass.send(:minimum_api_version)
49
49
  # min and max versions will be nil if we've not made a request to the server yet,
50
50
  # in which case we'll just start with the highest version and see what happens
51
- ServerAPIVersions.instance.min_server_version.nil? || (version >= ServerAPIVersions.instance.min_server_version && version <= ServerAPIVersions.instance.send(type))
51
+ ServerAPIVersions.instance.min_server_version.nil? || (version.between?(ServerAPIVersions.instance.min_server_version, ServerAPIVersions.instance.send(type)))
52
52
  end
53
53
  .max_by { |a| a.send(:minimum_api_version) }
54
54
  end
@@ -37,9 +37,9 @@ if RUBY_VERSION.split(".")[0..1].join(".") == "3.1"
37
37
  if use_ssl?
38
38
  if proxy?
39
39
  plain_sock = BufferedIO.new(s, read_timeout: @read_timeout,
40
- write_timeout: @write_timeout,
41
- continue_timeout: @continue_timeout,
42
- debug_output: @debug_output)
40
+ write_timeout: @write_timeout,
41
+ continue_timeout: @continue_timeout,
42
+ debug_output: @debug_output)
43
43
  buf = "CONNECT #{conn_address}:#{@port} HTTP/#{HTTPVersion}\r\n"
44
44
  buf << "Host: #{@address}:#{@port}\r\n"
45
45
  if proxy_user
@@ -65,8 +65,8 @@ if RUBY_VERSION.split(".")[0..1].join(".") == "3.1"
65
65
  @ssl_context.set_params(ssl_parameters)
66
66
  unless @ssl_context.session_cache_mode.nil? # a dummy method on JRuby
67
67
  @ssl_context.session_cache_mode =
68
- OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT |
69
- OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
68
+ OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT |
69
+ OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
70
70
  end
71
71
  if @ssl_context.respond_to?(:session_new_cb) # not implemented under JRuby
72
72
  @ssl_context.session_new_cb = proc { |sock, sess| @ssl_session = sess }
@@ -108,9 +108,9 @@ if RUBY_VERSION.split(".")[0..1].join(".") == "3.1"
108
108
  Chef::Log.debug("SSL established, protocol: #{s.ssl_version}, cipher: #{s.cipher[0]}")
109
109
  end
110
110
  @socket = BufferedIO.new(s, read_timeout: @read_timeout,
111
- write_timeout: @write_timeout,
112
- continue_timeout: @continue_timeout,
113
- debug_output: @debug_output)
111
+ write_timeout: @write_timeout,
112
+ continue_timeout: @continue_timeout,
113
+ debug_output: @debug_output)
114
114
  @last_communicated = nil
115
115
  on_connect
116
116
  rescue => exception