Skip to content

Commit 74447de

Browse files
committed
patch2
1 parent 2db2e70 commit 74447de

File tree

1 file changed

+94
-10
lines changed

1 file changed

+94
-10
lines changed

lib/puppet/provider/base_dsc_lite/powershell.rb

Lines changed: 94 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
require 'pathname'
44
require 'json'
55
require 'erb' # ensure ERB is available
6+
require 'puppet'
7+
require 'puppet/node'
8+
require 'puppet/parser/script_compiler'
69
require 'puppet/pops/evaluator/deferred_resolver'
710
require_relative '../../../puppet_x/puppetlabs/dsc_lite/powershell_hash_formatter'
811

@@ -84,22 +87,56 @@ def ps_manager
8487
Pwsh::Manager.instance(command(:powershell), Pwsh::Manager.powershell_args, debug: debug_output)
8588
end
8689

87-
# Minimal provider-side formatter for ERB (keeps patched templates working)
90+
# Minimal provider-side formatter used by ERB
8891
def format_for_ps(value)
8992
self.class.format_dsc_lite(value)
9093
end
9194

92-
# ---- Option 1: force-resolve all Deferreds in the catalog before rendering ----
95+
# --------- Two-stage resolver ---------
96+
97+
# Build an environment instance suitable for compilation on the agent
98+
def current_environment_instance
99+
# Prefer the catalog's environment_instance if present
100+
env = resource&.catalog&.respond_to?(:environment_instance) ? resource.catalog.environment_instance : nil
101+
return env if env
102+
103+
# Fallback to the current environment from the context
104+
begin
105+
Puppet.lookup(:current_environment) { nil }
106+
rescue StandardError
107+
nil
108+
end
109+
end
110+
111+
# Build Facts for the resolver. It accepts Puppet::Node::Facts or nil.
112+
def build_facts_for_resolver(env)
113+
# Best effort: use the looked-up facts if present
114+
facts = begin
115+
Puppet.lookup(:facts) { nil }
116+
rescue
117+
nil
118+
end
119+
return facts if facts
120+
121+
# Fallback: construct from Facter.to_hash for this node
122+
begin
123+
node_name = Puppet[:node_name_value]
124+
fact_hash = Facter.to_hash
125+
Puppet::Node::Facts.new(node_name, fact_hash, environment: env&.name)
126+
rescue StandardError => e
127+
Puppet.debug("DSC_lite: unable to construct facts for resolver: #{e.class}: #{e.message}")
128+
nil
129+
end
130+
end
131+
132+
# Stage 1: Resolve all deferreds in the entire catalog (direct and nested)
133+
# using Puppet's public API (used by Bolt/pxp-agent). See docs. [1](https://www.rubydoc.info/gems/puppet/8.4.0/Puppet/Pops/Evaluator/DeferredResolver)
93134
def force_resolve_catalog_deferred!
94135
cat = resource&.catalog
95136
return unless cat
96137

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
138+
env = current_environment_instance
139+
facts = build_facts_for_resolver(env)
103140

104141
begin
105142
Puppet::Pops::Evaluator::DeferredResolver.resolve_and_replace(facts, cat, env, true)
@@ -109,6 +146,51 @@ def force_resolve_catalog_deferred!
109146
end
110147
end
111148

149+
# Stage 2: If :properties still contains a Deferred (nested), resolve that
150+
# hash locally by spinning up a temporary ScriptCompiler and replace it back.
151+
# Uses DeferredResolver.resolve(value, compiler). [1](https://www.rubydoc.info/gems/puppet/8.4.0/Puppet/Pops/Evaluator/DeferredResolver)
152+
def resolve_properties_locally_if_needed!
153+
return unless resource.parameters.key?(:properties)
154+
155+
props = resource[:properties]
156+
return unless contains_deferred?(props)
157+
158+
begin
159+
env = current_environment_instance || Puppet::Node::Environment.create('production', [])
160+
node_name = Puppet[:node_name_value]
161+
162+
node = Puppet::Node.new(node_name, environment: env)
163+
164+
# Attach facts if available (improves function behavior during resolution)
165+
begin
166+
facts = build_facts_for_resolver(env)
167+
node.add_facts(facts) if facts
168+
rescue StandardError => e
169+
Puppet.debug("DSC_lite: failed to attach facts to node: #{e.class}: #{e.message}")
170+
end
171+
172+
compiler = Puppet::Parser::ScriptCompiler.new(node)
173+
174+
resolved = Puppet::Pops::Evaluator::DeferredResolver.resolve(props, compiler)
175+
resource[:properties] = resolved # write resolved value back into the resource
176+
Puppet.debug('DSC_lite: locally resolved :properties via ScriptCompiler')
177+
rescue StandardError => e
178+
Puppet.debug("DSC_lite: local properties resolution failed: #{e.class}: #{e.message}")
179+
end
180+
end
181+
182+
# Lightweight check for nested Deferred presence
183+
def contains_deferred?(obj)
184+
case obj
185+
when Hash
186+
obj.any? { |k, v| contains_deferred?(k) || contains_deferred?(v) }
187+
when Array
188+
obj.any? { |v| contains_deferred?(v) }
189+
else
190+
obj && obj.class && obj.class.name.to_s.include?('Deferred')
191+
end
192+
end
193+
112194
# ---------- provider operations ----------
113195

114196
def exists?
@@ -117,8 +199,9 @@ def exists?
117199
version = Facter.value(:powershell_version)
118200
Puppet.debug "PowerShell Version: #{version}"
119201

120-
# Ensure all Deferreds (including nested) are resolved prior to ERB rendering
202+
# Two-stage resolution before building the PowerShell script
121203
force_resolve_catalog_deferred!
204+
resolve_properties_locally_if_needed!
122205

123206
script_content = ps_script_content('test')
124207
Puppet.debug "\n" + self.class.redact_content(script_content)
@@ -148,8 +231,9 @@ def create
148231
timeout = set_timeout
149232
Puppet.debug "Dsc Timeout: #{timeout} milliseconds"
150233

151-
# Ensure all Deferreds (including nested) are resolved prior to ERB rendering
234+
# Two-stage resolution before building the PowerShell script
152235
force_resolve_catalog_deferred!
236+
resolve_properties_locally_if_needed!
153237

154238
script_content = ps_script_content('set')
155239
Puppet.debug "\n" + self.class.redact_content(script_content)

0 commit comments

Comments
 (0)