22
33require 'pathname'
44require 'json'
5- require 'erb' # ensure ERB is available
5+ require 'erb' # ensure ERB is available for template rendering
66require_relative '../../../puppet_x/puppetlabs/dsc_lite/powershell_hash_formatter'
77
88Puppet ::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