22
33require 'pathname'
44require 'json'
5+ require 'erb' # ensure ERB is available
6+ require 'puppet/pops/evaluator/deferred_resolver'
57require_relative '../../../puppet_x/puppetlabs/dsc_lite/powershell_hash_formatter'
68
79Puppet ::Type . type ( :base_dsc_lite ) . provide ( :powershell ) do
3133 Puppet (including 3.x), or to a Puppet version newer than 3.x.
3234 UPGRADE
3335
36+ # ---------- class helpers ----------
37+
3438 def self . upgrade_message
3539 Puppet . warning DSC_LITE_MODULE_PUPPET_UPGRADE_MSG unless @upgrade_warning_issued
3640 @upgrade_warning_issued = true
@@ -40,6 +44,25 @@ def self.vendored_modules_path
4044 File . expand_path ( Pathname . new ( __FILE__ ) . dirname + '../../../' + 'puppet_x/dsc_resources' )
4145 end
4246
47+ def self . template_path
48+ File . expand_path ( Pathname . new ( __FILE__ ) . dirname )
49+ end
50+
51+ def self . format_dsc_lite ( dsc_value )
52+ PuppetX ::PuppetLabs ::DscLite ::PowerShellHashFormatter . format ( dsc_value )
53+ end
54+
55+ def self . escape_quotes ( text )
56+ text . gsub ( "'" , "''" )
57+ end
58+
59+ def self . redact_content ( content )
60+ # Redact Sensitive unwraps that appear as "'secret' # PuppetSensitive"
61+ content . gsub ( %r{= '.+' # PuppetSensitive;?(\\ n)?$} , "= '[REDACTED]'" )
62+ end
63+
64+ # ---------- instance helpers ----------
65+
4366 def dsc_parameters
4467 resource . parameters_with_value . select do |p |
4568 p . name . to_s . include? 'dsc_'
@@ -52,10 +75,6 @@ def dsc_property_param
5275 end
5376 end
5477
55- def self . template_path
56- File . expand_path ( Pathname . new ( __FILE__ ) . dirname )
57- end
58-
5978 def set_timeout
6079 resource [ :dsc_timeout ] ? resource [ :dsc_timeout ] * 1000 : 1_200_000
6180 end
@@ -65,11 +84,42 @@ def ps_manager
6584 Pwsh ::Manager . instance ( command ( :powershell ) , Pwsh ::Manager . powershell_args , debug : debug_output )
6685 end
6786
87+ # Minimal provider-side formatter for ERB (keeps patched templates working)
88+ def format_for_ps ( value )
89+ self . class . format_dsc_lite ( value )
90+ end
91+
92+ # ---- Option 1: force-resolve all Deferreds in the catalog before rendering ----
93+ def force_resolve_catalog_deferred!
94+ cat = resource &.catalog
95+ return unless cat
96+
97+ facts = Puppet . lookup ( :facts ) { nil }
98+ env = if cat . respond_to? ( :environment_instance )
99+ cat . environment_instance
100+ else
101+ Puppet . lookup ( :current_environment ) { nil }
102+ end
103+
104+ begin
105+ Puppet ::Pops ::Evaluator ::DeferredResolver . resolve_and_replace ( facts , cat , env , true )
106+ Puppet . debug ( 'DSC_lite: DeferredResolver.resolve_and_replace() invoked on catalog' )
107+ rescue StandardError => e
108+ Puppet . debug ( "DSC_lite: resolve_and_replace failed: #{ e . class } : #{ e . message } " )
109+ end
110+ end
111+
112+ # ---------- provider operations ----------
113+
68114 def exists?
69115 timeout = set_timeout
70116 Puppet . debug "Dsc Timeout: #{ timeout } milliseconds"
71117 version = Facter . value ( :powershell_version )
72118 Puppet . debug "PowerShell Version: #{ version } "
119+
120+ # Ensure all Deferreds (including nested) are resolved prior to ERB rendering
121+ force_resolve_catalog_deferred!
122+
73123 script_content = ps_script_content ( 'test' )
74124 Puppet . debug "\n " + self . class . redact_content ( script_content )
75125
@@ -97,6 +147,10 @@ def exists?
97147 def create
98148 timeout = set_timeout
99149 Puppet . debug "Dsc Timeout: #{ timeout } milliseconds"
150+
151+ # Ensure all Deferreds (including nested) are resolved prior to ERB rendering
152+ force_resolve_catalog_deferred!
153+
100154 script_content = ps_script_content ( 'set' )
101155 Puppet . debug "\n " + self . class . redact_content ( script_content )
102156
@@ -138,24 +192,6 @@ def notify_reboot_pending
138192 end
139193 end
140194
141- def self . format_dsc_lite ( dsc_value )
142- PuppetX ::PuppetLabs ::DscLite ::PowerShellHashFormatter . format ( dsc_value )
143- end
144-
145- def self . escape_quotes ( text )
146- text . gsub ( "'" , "''" )
147- end
148-
149- def self . redact_content ( content )
150- # Note that here we match after an equals to ensure we redact the value being passed, but not the key.
151- # This means a redaction of a string not including '= ' before the string value will not redact.
152- # Every secret unwrapped in this module will unwrap as "'secret' # PuppetSensitive" and, currently,
153- # always inside a hash table to be passed along. This means we can (currently) expect the value to
154- # always come after an equals sign.
155- # Note that the line may include a semi-colon and/or a newline character after the sensitive unwrap.
156- content . gsub ( %r{= '.+' # PuppetSensitive;?(\\ n)?$} , "= '[REDACTED]'" )
157- end
158-
159195 def ps_script_content ( mode )
160196 self . class . ps_script_content ( mode , resource , self )
161197 end
@@ -165,6 +201,10 @@ def self.ps_script_content(mode, resource, provider)
165201 @param_hash = resource
166202 template_name = resource . generic_dsc ? '/invoke_generic_dsc_resource.ps1.erb' : '/invoke_dsc_resource.ps1.erb'
167203 file = File . new ( template_path + template_name , encoding : Encoding ::UTF_8 )
204+
205+ # Make vendored_modules_path available in ERB (used by invoke_dsc_resource.ps1.erb)
206+ vendored_modules_path = self . vendored_modules_path
207+
168208 template = ERB . new ( file . read , trim_mode : '-' )
169209 template . result ( binding )
170210 end
0 commit comments