chef 18.10.17 → 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.
- checksums.yaml +4 -4
- data/Gemfile +15 -7
- data/README.md +0 -1
- data/chef-universal-mingw-ucrt.gemspec +4 -2
- data/chef.gemspec +15 -12
- data/lib/chef/compliance/runner.rb +19 -1
- data/lib/chef/dsl/rest_resource.rb +223 -16
- data/lib/chef/formatters/error_description.rb +8 -1
- data/lib/chef/http/authenticator.rb +17 -4
- data/lib/chef/mixin/template.rb +24 -2
- data/lib/chef/monkey_patches/reline-windows.rb +116 -0
- data/lib/chef/provider/file/content.rb +3 -2
- data/lib/chef/provider/file.rb +1 -1
- data/lib/chef/provider/ifconfig/redhat.rb +1 -1
- data/lib/chef/provider/package/dnf/dnf_helper.py +355 -65
- data/lib/chef/provider/package/dnf/python_helper.rb +7 -4
- data/lib/chef/provider/package/dnf.rb +25 -6
- data/lib/chef/provider/package/yum.rb +1 -1
- data/lib/chef/resource/_rest_resource.rb +4 -2
- data/lib/chef/resource/archive_file.rb +52 -18
- data/lib/chef/resource/chef_client_config.rb +185 -19
- data/lib/chef/resource/chef_vault_secret.rb +1 -1
- data/lib/chef/resource/chocolatey_installer.rb +31 -13
- data/lib/chef/resource/chocolatey_package.rb +1 -1
- data/lib/chef/resource/habitat/habitat_package.rb +1 -1
- data/lib/chef/resource/habitat/habitat_sup.rb +2 -2
- data/lib/chef/resource/habitat_config.rb +1 -1
- data/lib/chef/resource/habitat_service.rb +1 -1
- data/lib/chef/resource/openssl_ec_private_key.rb +1 -1
- data/lib/chef/resource/openssl_ec_public_key.rb +2 -2
- data/lib/chef/resource/openssl_rsa_private_key.rb +1 -1
- data/lib/chef/resource/openssl_rsa_public_key.rb +2 -2
- data/lib/chef/resource/openssl_x509_certificate.rb +1 -1
- data/lib/chef/resource/openssl_x509_request.rb +1 -1
- data/lib/chef/resource/powershell_package_source.rb +1 -1
- data/lib/chef/resource/rhsm_register.rb +1 -1
- data/lib/chef/resource/windows_ad_join.rb +1 -1
- data/lib/chef/resource/windows_certificate.rb +1 -1
- data/lib/chef/resource/windows_service.rb +1 -1
- data/lib/chef/resource/windows_task.rb +1 -1
- data/lib/chef/resource/yum_package.rb +1 -1
- data/lib/chef/resource/yum_repository.rb +2 -2
- data/lib/chef/shell.rb +7 -0
- data/lib/chef/version.rb +1 -1
- metadata +59 -34
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bf0beb126b5eeb735670c67b48e79aae54a6ba25760f99babf2468fe6ff78437
|
|
4
|
+
data.tar.gz: 99033f783a17e8870ccfc0f20ef3489cfed936f2882d7030beaf0d78971e8a85
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c3e593233b5da46f034602581455a9ee6d32eaf77a1bbcedc91fb17d5826081c139480192887fdb3804cda29862383fe46fa74bae56af7947096d0adc42493e8
|
|
7
|
+
data.tar.gz: d80743280c5469a1c869fe02cce993d0649fdfecf6a3ca8a66892465b5b3239aba64378579c659800122aeb408745efd11712ff6dddb46b75d92ac000a36dc30
|
data/Gemfile
CHANGED
|
@@ -7,7 +7,7 @@ gem "ohai", git: "https://github.com/chef/ohai.git", branch: "18-stable"
|
|
|
7
7
|
# Nwed to file a bug with rest-client. In the meantime, we can use this until they accept the update.
|
|
8
8
|
gem "rest-client", git: "https://github.com/chef/rest-client", branch: "jfm/ucrt_update1"
|
|
9
9
|
|
|
10
|
-
gem "ffi", ">= 1.15.5", "<= 1.
|
|
10
|
+
gem "ffi", ">= 1.15.5", "<= 1.18.0"
|
|
11
11
|
gem "chef-utils", path: File.expand_path("chef-utils", __dir__) if File.exist?(File.expand_path("chef-utils", __dir__))
|
|
12
12
|
gem "chef-config", path: File.expand_path("chef-config", __dir__) if File.exist?(File.expand_path("chef-config", __dir__))
|
|
13
13
|
|
|
@@ -31,15 +31,19 @@ group(:omnibus_package) do
|
|
|
31
31
|
gem "rb-readline"
|
|
32
32
|
gem "chef-vault"
|
|
33
33
|
|
|
34
|
-
gem "inspec-core-bin", "
|
|
34
|
+
gem "inspec-core-bin", "~> 5.24", "< 6"
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
group(:omnibus_package, :pry) do
|
|
38
38
|
# Locked because pry-byebug is broken with 13+.
|
|
39
39
|
# some work is ongoing? https://github.com/deivid-rodriguez/pry-byebug/issues/343
|
|
40
|
-
gem "pry", "
|
|
41
|
-
# byebug does not install on freebsd on ruby 3.0
|
|
42
|
-
|
|
40
|
+
gem "pry", "0.15.2"
|
|
41
|
+
# byebug does not install on freebsd on ruby 3.0; byebug >= 12 requires ruby >= 3.1
|
|
42
|
+
# which is incompatible with the AIX omnibus toolchain (ruby 3.0.3)
|
|
43
|
+
unless RUBY_PLATFORM.match?(/freebsd|aix/i)
|
|
44
|
+
gem "byebug", "< 13"
|
|
45
|
+
gem "pry-byebug", "< 3.12"
|
|
46
|
+
end
|
|
43
47
|
gem "pry-stack_explorer"
|
|
44
48
|
end
|
|
45
49
|
|
|
@@ -60,7 +64,7 @@ group(:development, :test) do
|
|
|
60
64
|
gem "rake", ">= 12.3.3"
|
|
61
65
|
gem "rspec"
|
|
62
66
|
gem "webmock"
|
|
63
|
-
gem "crack", "< 0.
|
|
67
|
+
gem "crack", "< 1.0.2" # due to https://github.com/jnunemaker/crack/pull/75
|
|
64
68
|
gem "fauxhai-ng" # for chef-utils gem
|
|
65
69
|
end
|
|
66
70
|
|
|
@@ -70,4 +74,8 @@ instance_eval(ENV["GEMFILE_MOD"]) if ENV["GEMFILE_MOD"]
|
|
|
70
74
|
|
|
71
75
|
# If you want to load debugging tools into the bundle exec sandbox,
|
|
72
76
|
# add these additional dependencies into Gemfile.local
|
|
73
|
-
|
|
77
|
+
#
|
|
78
|
+
# But doing eval_gemfile("./Gemfile.local") breaks dependabot, so a
|
|
79
|
+
# bit of indirection here
|
|
80
|
+
local_gemfile = File.join(__dir__, "Gemfile.local")
|
|
81
|
+
eval(File.read(local_gemfile)) if File.exist?(local_gemfile)
|
data/README.md
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# Chef Infra
|
|
2
|
-
[](https://codeclimate.com/github/chef/chef)
|
|
3
2
|
[](https://buildkite.com/chef-oss/chef-chef-main-verify)
|
|
4
3
|
[](https://badge.fury.io/rb/chef)
|
|
5
4
|
[](https://github.com/chef/chef/blob/main/docs/dev/design_documents/client_release_cadence.md)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
# rubocop:disable Chef/Ruby/GemspecLicense
|
|
2
|
+
# License is in the included gemspec.
|
|
3
|
+
gemspec = Gem::Specification.load(File.expand_path("chef.gemspec", __dir__))
|
|
2
4
|
|
|
3
5
|
gemspec.platform = Gem::Platform.new(%w{universal mingw-ucrt})
|
|
4
6
|
|
|
@@ -6,7 +8,7 @@ gemspec.add_dependency "win32-api", "~> 1.10.0"
|
|
|
6
8
|
gemspec.add_dependency "win32-event", "~> 0.6.1"
|
|
7
9
|
# TODO: Relax this pin and make the necessary updaets. The issue originally
|
|
8
10
|
# leading to this pin has been fixed in 0.6.5.
|
|
9
|
-
gemspec.add_dependency "win32-eventlog", "0.6.
|
|
11
|
+
gemspec.add_dependency "win32-eventlog", "0.6.7"
|
|
10
12
|
gemspec.add_dependency "win32-mmap", "~> 0.4.1"
|
|
11
13
|
gemspec.add_dependency "win32-mutex", "~> 0.4.2"
|
|
12
14
|
gemspec.add_dependency "win32-process", "~> 0.9"
|
data/chef.gemspec
CHANGED
|
@@ -25,10 +25,11 @@ Gem::Specification.new do |s|
|
|
|
25
25
|
s.email = "adam@chef.io"
|
|
26
26
|
s.homepage = "https://www.chef.io"
|
|
27
27
|
|
|
28
|
+
# help dependabot by specifying a default. If the default is in
|
|
29
|
+
# the 'else', dependabot misses it and falls back to a very old ruby
|
|
30
|
+
s.required_ruby_version = ">= 3.1.0"
|
|
28
31
|
if RUBY_PLATFORM =~ /aix/
|
|
29
32
|
s.required_ruby_version = ">= 3.0.3"
|
|
30
|
-
else
|
|
31
|
-
s.required_ruby_version = ">= 3.1.0"
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
s.add_dependency "chef-config", "= #{Chef::VERSION}"
|
|
@@ -47,35 +48,37 @@ Gem::Specification.new do |s|
|
|
|
47
48
|
|
|
48
49
|
# AIX Gemfile.aix.lock uses this, but currently a manual process
|
|
49
50
|
if RUBY_PLATFORM.include?("aix") || ENV["GENERATE_AIX"] == "true"
|
|
50
|
-
s.add_dependency "inspec-core", ">= 5", "
|
|
51
|
+
s.add_dependency "inspec-core", ">= 5", "< 8"
|
|
51
52
|
else
|
|
52
|
-
s.add_dependency "inspec-core", ">= 5", "<
|
|
53
|
+
s.add_dependency "inspec-core", ">= 5", "< 8"
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
s.add_dependency "ffi", ">= 1.15.5", "<= 1.16.3"
|
|
56
|
-
s.add_dependency "ffi-yajl", "
|
|
57
|
+
s.add_dependency "ffi-yajl", ">= 2.2", "< 4.0"
|
|
57
58
|
s.add_dependency "net-sftp", ">= 2.1.2", "< 5.0" # remote_file resource
|
|
58
59
|
s.add_dependency "net-ftp" # remote_file resource
|
|
59
60
|
s.add_dependency "erubis", "~> 2.7" # template resource / cookbook syntax check
|
|
60
|
-
s.add_dependency "diff-lcs", ">= 1.2.4", "!= 1.4.0", "< 1.
|
|
61
|
+
s.add_dependency "diff-lcs", ">= 1.2.4", "!= 1.4.0", "< 2.1.0" # 1.4 breaks output. Used in lib/chef/util/diff
|
|
61
62
|
s.add_dependency "ffi-libarchive", "~> 1.0", ">= 1.0.3" # archive_file resource
|
|
62
|
-
s.add_dependency "chef-zero", "
|
|
63
|
+
s.add_dependency "chef-zero", "~> 15.1.11"
|
|
63
64
|
s.add_dependency "chef-vault" # chef-vault resources and helpers
|
|
64
65
|
|
|
65
66
|
s.add_dependency "plist", "~> 3.2" # launchd, dscl/mac user, macos_userdefaults, osx_profile and plist resources
|
|
66
67
|
s.add_dependency "iniparse", "~> 1.4" # systemd_unit resource
|
|
67
68
|
s.add_dependency "addressable"
|
|
68
69
|
s.add_dependency "syslog-logger", "~> 1.6"
|
|
69
|
-
s.add_dependency "uuidtools", ">= 2.1.5", "<
|
|
70
|
-
s.add_dependency "unf_ext", "
|
|
71
|
-
s.add_dependency "uri", "
|
|
70
|
+
s.add_dependency "uuidtools", ">= 2.1.5", "< 4.0" # osx_profile resource
|
|
71
|
+
s.add_dependency "unf_ext", ">= 0.0.8.2", "< 0.0.10.0" # older platforms
|
|
72
|
+
s.add_dependency "uri", ">= 1.0.4", "< 1.2.0" # This is used by net-http. We are forcing a version to get past a CVE in 1.0.2
|
|
72
73
|
s.add_dependency "corefoundation", "~> 0.3.4" # macos_userdefaults resource
|
|
73
74
|
|
|
74
75
|
s.add_dependency "proxifier2", "~> 1.1"
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
# 1.219 depends (transitively) on bigdecimal 4.1.2 which dodesn't compile
|
|
78
|
+
# on CentOS7
|
|
79
|
+
s.add_dependency "aws-sdk-s3", ">= 1.218.0", "< 1.219.0"
|
|
77
80
|
s.add_dependency "aws-sdk-secretsmanager", "~> 1.46"
|
|
78
|
-
s.add_dependency "vault", "
|
|
81
|
+
s.add_dependency "vault", ">= 0.18.2", "< 0.21.0" # hashi vault official client gem
|
|
79
82
|
|
|
80
83
|
s.bindir = "bin"
|
|
81
84
|
s.executables = %w{ }
|
|
@@ -72,14 +72,32 @@ class Chef
|
|
|
72
72
|
logger.debug("#{self.class}##{__method__}: enabling Compliance Phase")
|
|
73
73
|
|
|
74
74
|
report_with_interval
|
|
75
|
+
@compliance_phase_completed = true
|
|
75
76
|
end
|
|
76
77
|
|
|
77
|
-
def run_failed(
|
|
78
|
+
def run_failed(exception, _run_status)
|
|
78
79
|
# If the run has failed because our own validation of compliance
|
|
79
80
|
# phase configuration has failed, we don't want to submit a report
|
|
80
81
|
# because we're still not configured correctly.
|
|
81
82
|
return unless enabled? && @validation_passed
|
|
82
83
|
|
|
84
|
+
# A reboot exception is not a real failure — it is an expected outcome
|
|
85
|
+
# when a reboot resource fires :reboot_now. In that case run_completed
|
|
86
|
+
# has already executed the Compliance Phase, so running it again here
|
|
87
|
+
# would produce a duplicate scan (and the second scan may be killed by
|
|
88
|
+
# the pending reboot).
|
|
89
|
+
if exception.is_a?(Chef::Exceptions::Reboot)
|
|
90
|
+
logger.debug("#{self.class}##{__method__}: skipping Compliance Phase because a reboot was requested")
|
|
91
|
+
return
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Guard against any other code path that might trigger both run_completed
|
|
95
|
+
# and run_failed in the same client run.
|
|
96
|
+
if @compliance_phase_completed
|
|
97
|
+
logger.debug("#{self.class}##{__method__}: skipping Compliance Phase because it has already run in this client run")
|
|
98
|
+
return
|
|
99
|
+
end
|
|
100
|
+
|
|
83
101
|
logger.debug("#{self.class}##{__method__}: enabling Compliance Phase")
|
|
84
102
|
|
|
85
103
|
report_with_interval
|
|
@@ -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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|
|
@@ -150,8 +150,9 @@ class Chef
|
|
|
150
150
|
|
|
151
151
|
if !!results
|
|
152
152
|
@raw_key = results
|
|
153
|
-
elsif key_file
|
|
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
|
-
|
|
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))
|
|
@@ -338,7 +351,7 @@ class Chef
|
|
|
338
351
|
Try {
|
|
339
352
|
$my_pwd = ConvertTo-SecureString -String "#{password}" -Force -AsPlainText;
|
|
340
353
|
$cert = Get-ChildItem -path cert:\\#{store}\\My -Recurse | Where-Object { $_.Subject -match "chef-#{client_name}$" } -ErrorAction Stop;
|
|
341
|
-
$tempfile = [System.IO.Path]::GetTempPath() + "
|
|
354
|
+
$tempfile = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName() + ".pfx");
|
|
342
355
|
Export-PfxCertificate -Cert $cert -Password $my_pwd -FilePath $tempfile;
|
|
343
356
|
}
|
|
344
357
|
Catch {
|
data/lib/chef/mixin/template.rb
CHANGED
|
@@ -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
|
|
@@ -258,7 +271,16 @@ class Chef
|
|
|
258
271
|
end
|
|
259
272
|
|
|
260
273
|
def to_s
|
|
261
|
-
|
|
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
|