Skip to content

Commit 66ba172

Browse files
committed
enhance diagnostics
1 parent 6d4cbce commit 66ba172

File tree

1 file changed

+58
-24
lines changed

1 file changed

+58
-24
lines changed

lib/puppet/provider/base_dsc_lite/powershell.rb

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
require 'pathname'
44
require 'json'
5-
require 'erb' # ensure ERB is available
5+
require 'erb' # ensure ERB is available for template rendering
66
require_relative '../../../puppet_x/puppetlabs/dsc_lite/powershell_hash_formatter'
77

88
Puppet::Type.type(:base_dsc_lite).provide(:powershell) do
@@ -96,8 +96,7 @@ def ps_manager
9696
Pwsh::Manager.instance(command(:powershell), Pwsh::Manager.powershell_args, debug: debug_output)
9797
end
9898

99-
# --- NEW: deep scan for Deferred ---
100-
# Recursively detect any object whose class name suggests it's a Puppet Deferred
99+
# --- Helper: recursively detect any Puppet Deferred ---
101100
def deep_contains_deferred?(obj)
102101
case obj
103102
when Hash
@@ -109,12 +108,30 @@ def deep_contains_deferred?(obj)
109108
end
110109
end
111110

112-
# --- NEW: best-effort resolution hook ---
113-
# Attempt to resolve any remaining Deferreds before rendering ERB.
114-
# This is intentionally defensive: it tries available agent-side options
115-
# and degrades to no-op if none are present.
111+
# --- Helper: enumerate nested paths to Deferreds (e.g., "properties.Content", "properties.Credential.password") ---
112+
def deferred_paths(obj, prefix = '')
113+
paths = []
114+
if obj.is_a?(Hash)
115+
obj.each do |k, v|
116+
key = k.is_a?(Symbol) ? k.to_s : k.to_s
117+
child_prefix = prefix.empty? ? key : "#{prefix}.#{key}"
118+
paths.concat(deferred_paths(v, child_prefix))
119+
end
120+
elsif obj.is_a?(Array)
121+
obj.each_with_index do |v, i|
122+
paths.concat(deferred_paths(v, "#{prefix}[#{i}]"))
123+
end
124+
else
125+
if obj && obj.class && obj.class.name.to_s.include?('Deferred')
126+
paths << (prefix.empty? ? '(root)' : prefix)
127+
end
128+
end
129+
paths
130+
end
131+
132+
# --- Best-effort, agent-safe "last chance" resolution ---
116133
def ensure_deferreds_resolved!
117-
# 1) Catalog-wide resolve (if the catalog supports it)
134+
# 1) Catalog-wide resolve if supported
118135
begin
119136
cat = resource&.catalog
120137
if cat && cat.respond_to?(:resolve_and_replace)
@@ -125,37 +142,32 @@ def ensure_deferreds_resolved!
125142
Puppet.debug("DSC_lite: resolve_and_replace raised #{e.class}: #{e.message}")
126143
end
127144

128-
# 2) Compiler lookup (often nil in agent/provider context)
145+
# 2) Try compiler lookup (often nil in agent/provider context)
129146
begin
130147
compiler = Puppet.respond_to?(:lookup) ? Puppet.lookup(:compiler) { nil } : nil
131148
Puppet.debug('DSC_lite: compiler available for resolution') if compiler
132149
rescue StandardError => e
133150
Puppet.debug("DSC_lite: compiler lookup failed: #{e.class}: #{e.message}")
134151
end
135152

136-
# 3) Nothing else to do safely here—actual evaluation requires scope,
137-
# which agent providers typically don't have. We rely on (1), and if
138-
# it still leaks we'll detect/log just before formatting.
153+
# Nothing else safe to do here—actual evaluation requires scope (not available here).
139154
nil
140155
end
141156

142-
# --- NEW: provider helper used by ERB ---
143-
# Route every formatted value through here so we can:
144-
# - perform a last-chance Deferred resolution
145-
# - then delegate to the pure formatter
157+
# --- ERB-facing formatter (deferred-safe) ---
158+
# Route EVERY formatted value through this method from ERB.
146159
def format_for_ps(value)
147160
if deep_contains_deferred?(value)
148161
Puppet.debug('DSC_lite: Deferred detected in ERB-bound value; attempting last-chance resolution')
149162
ensure_deferreds_resolved!
150-
if deep_contains_deferred?(value)
151-
Puppet.debug('DSC_lite: value still contains Deferred after last-chance resolution')
152-
# Optional: uncomment to fail fast with a clearer message instead of formatter type error
153-
# raise Puppet::Error, 'DSC_lite: Deferred value reached ERB formatting; '\
154-
# 'agent invoked provider before deferrals completed (Puppet 8 timing).'
155-
end
163+
Puppet.debug('DSC_lite: value still contains Deferred after last-chance resolution') if deep_contains_deferred?(value)
164+
# Optional fail-fast (clear message) instead of later formatter error:
165+
# raise Puppet::Error, 'DSC_lite: Deferred value reached ERB formatting; '\
166+
# 'agent invoked provider before deferrals completed (Puppet 8 timing).'
156167
end
157168
self.class.format_dsc_lite(value)
158169
end
170+
159171
# ---------------------------
160172
# Provider operations
161173
# ---------------------------
@@ -166,13 +178,23 @@ def exists?
166178
version = Facter.value(:powershell_version)
167179
Puppet.debug "PowerShell Version: #{version}"
168180

169-
# Last-chance resolve + pinpoint diagnostic BEFORE rendering
181+
# Last-chance resolve + enhanced diagnostics BEFORE rendering
170182
ensure_deferreds_resolved!
171183
leaks = resource.parameters_with_value
172184
.select { |p| deep_contains_deferred?(p.value) }
173185
.map { |p| "#{p.name}=#{p.value.class}" }
174186
Puppet.debug("DSC_lite: still deferred after resolution? #{leaks.join(', ')}")
175187

188+
props_param = resource.parameters_with_value.find { |p| p.name == :properties }
189+
if props_param
190+
nested = deferred_paths(props_param.value)
191+
Puppet.debug("DSC_lite: nested Deferred paths inside properties: #{nested.join(', ')}") unless nested.empty?
192+
# Optional clear fail-fast (uncomment if you want CI to stop with a crisp message)
193+
# unless nested.empty?
194+
# raise Puppet::Error, "DSC_lite: Deferred values remain under properties at: #{nested.join(', ')}. "\
195+
# "Agent invoked provider before all deferrals resolved (Puppet 8 timing)."
196+
# end
197+
end
176198
script_content = ps_script_content('test')
177199
Puppet.debug "\n" + self.class.redact_content(script_content)
178200

@@ -201,13 +223,24 @@ def create
201223
timeout = set_timeout
202224
Puppet.debug "Dsc Timeout: #{timeout} milliseconds"
203225

204-
# Last-chance resolve + pinpoint diagnostic BEFORE rendering
226+
# Last-chance resolve + enhanced diagnostics BEFORE rendering
205227
ensure_deferreds_resolved!
206228
leaks = resource.parameters_with_value
207229
.select { |p| deep_contains_deferred?(p.value) }
208230
.map { |p| "#{p.name}=#{p.value.class}" }
209231
Puppet.debug("DSC_lite: still deferred after resolution? #{leaks.join(', ')}")
210232

233+
props_param = resource.parameters_with_value.find { |p| p.name == :properties }
234+
if props_param
235+
nested = deferred_paths(props_param.value)
236+
Puppet.debug("DSC_lite: nested Deferred paths inside properties: #{nested.join(', ')}") unless nested.empty?
237+
# Optional clear fail-fast (uncomment if you want CI to stop with a crisp message)
238+
# unless nested.empty?
239+
# raise Puppet::Error, "DSC_lite: Deferred values remain under properties at: #{nested.join(', ')}. "\
240+
# "Agent invoked provider before all deferrals resolved (Puppet 8 timing)."
241+
# end
242+
end
243+
211244
script_content = ps_script_content('set')
212245
Puppet.debug "\n" + self.class.redact_content(script_content)
213246

@@ -227,6 +260,7 @@ def create
227260
raise(data['errormessage']) unless data['errormessage'].empty?
228261

229262
notify_reboot_pending if data['rebootrequired'] == true
263+
230264
data
231265
end
232266

0 commit comments

Comments
 (0)