From cf828df9da15443b50714b48365f069983054d0b Mon Sep 17 00:00:00 2001 From: Lukas Audzevicius Date: Thu, 27 Aug 2026 11:33:49 +0100 Subject: [PATCH 01/15] (CAT-2589) Add Ruby 4.0 / Puppet 9 lane, source gems from puppetcore Adds a ruby_version: '4.0' / puppet_version: '~> 9.0' matrix entry to the spec and acceptance jobs in ci.yml and nightly.yml, alongside the existing 3.2 / puppet 8 lane. Updates the Gemfile to source the puppet gem from rubygems-puppetcore.puppet.com when PUPPET_FORGE_TOKEN is set, and bumps puppetlabs_spec_helper to ~> 9.0 to pick up the CVE-fixed puppetlabs-syntax fork. Mirrors puppetlabs_spec_helper@3bfa7d21. --- .github/workflows/ci.yml | 6 ++++++ .github/workflows/nightly.yml | 6 ++++++ Gemfile | 17 ++++++++++++----- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 317ca465..c969cef5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,9 +19,12 @@ jobs: matrix: ruby_version: - '3.2' + - '4.0' include: - ruby_version: '3.2' puppet_version: '~> 8.0' + - ruby_version: '4.0' + puppet_version: '~> 9.0' name: "spec (ruby ${{ matrix.ruby_version }} | puppet ${{ matrix.puppet_version }})" uses: "puppetlabs/cat-github-actions/.github/workflows/gem_ci.yml@main" secrets: "inherit" @@ -36,9 +39,12 @@ jobs: matrix: ruby_version: - "3.2" + - "4.0" include: - ruby_version: '3.2' puppet_version: '~> 8.0' + - ruby_version: '4.0' + puppet_version: '~> 9.0' runs_on: - "windows-latest" name: "acceptance (${{ matrix.runs_on}} ruby ${{ matrix.ruby_version }} | puppet ${{ matrix.puppet_version }})" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 24c49312..e9653194 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -12,9 +12,12 @@ jobs: matrix: ruby_version: - '3.2' + - '4.0' include: - ruby_version: '3.2' puppet_version: '~> 8.0' + - ruby_version: '4.0' + puppet_version: '~> 9.0' name: "spec (ruby ${{ matrix.ruby_version }} | puppet ${{ matrix.puppet_version }})" uses: "puppetlabs/cat-github-actions/.github/workflows/gem_ci.yml@main" secrets: "inherit" @@ -28,9 +31,12 @@ jobs: matrix: ruby_version: - "3.2" + - "4.0" include: - ruby_version: '3.2' puppet_version: '~> 8.0' + - ruby_version: '4.0' + puppet_version: '~> 9.0' runs_on: - "windows-latest" name: "acceptance (${{ matrix.runs_on}} ruby ${{ matrix.ruby_version }} | puppet ${{ matrix.puppet_version }})" diff --git a/Gemfile b/Gemfile index c82e8924..054bc6b1 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,13 @@ -source ENV['GEM_SOURCE'] || 'https://rubygems.org' +# For puppetcore, set GEM_SOURCE_PUPPETCORE = 'https://rubygems-puppetcore.puppet.com' +gemsource_default = ENV['GEM_SOURCE'] || 'https://rubygems.org' +gemsource_puppetcore = if ENV['PUPPET_FORGE_TOKEN'] + 'https://rubygems-puppetcore.puppet.com' + else + ENV['GEM_SOURCE_PUPPETCORE'] || gemsource_default + end +source gemsource_default -def location_for(place_or_version, fake_version = nil) +def location_for(place_or_version, fake_version = nil, opts = {}) git_url_regex = %r{\A(?(https?|git)[:@][^#]*)(#(?.*))?} file_url_regex = %r{\Afile:\/\/(?.*)} @@ -9,7 +16,7 @@ def location_for(place_or_version, fake_version = nil) elsif place_or_version && (file_url = place_or_version.match(file_url_regex)) ['>= 0', { path: File.expand_path(file_url[:path]), require: false }] else - [place_or_version, { require: false }] + [place_or_version, { require: false }.merge(opts)] end end @@ -33,7 +40,7 @@ group :development do end group :development, :release_prep do gem "puppet-strings", '~> 4.0', require: false - gem "puppetlabs_spec_helper", '~> 8.0', require: false + gem "puppetlabs_spec_helper", '~> 9.0', require: false end group :system_tests do gem "puppet_litmus", '~> 1.0', require: false, platforms: [:ruby, :x64_mingw] @@ -47,7 +54,7 @@ hiera_version = ENV['HIERA_GEM_VERSION'] gems = {} -gems['puppet'] = location_for(puppet_version) +gems['puppet'] = location_for(puppet_version, nil, { source: gemsource_puppetcore }) # If facter or hiera versions have been specified via the environment # variables From 3c3c885a44f04a21287a10126081c6ef7ecf321f Mon Sep 17 00:00:00 2001 From: Lukas Audzevicius Date: Thu, 27 Aug 2026 11:55:01 +0100 Subject: [PATCH 02/15] Treat empty PUPPET_FORGE_TOKEN as unset The reusable gem_ci.yml workflow injects PUPPET_FORGE_TOKEN as an empty string (not omitted) when the secret isn't configured. Ruby treats "" as truthy, so the bare `if ENV['PUPPET_FORGE_TOKEN']` check routed puppet resolution to the private puppetcore gem source with no actual credentials, failing bundle install with "Bad username or password" on every CI lane. --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 054bc6b1..25934644 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ # For puppetcore, set GEM_SOURCE_PUPPETCORE = 'https://rubygems-puppetcore.puppet.com' gemsource_default = ENV['GEM_SOURCE'] || 'https://rubygems.org' -gemsource_puppetcore = if ENV['PUPPET_FORGE_TOKEN'] +gemsource_puppetcore = if ENV['PUPPET_FORGE_TOKEN'] && !ENV['PUPPET_FORGE_TOKEN'].empty? 'https://rubygems-puppetcore.puppet.com' else ENV['GEM_SOURCE_PUPPETCORE'] || gemsource_default From 1b633048b44566f1a3dc00b23c1482c1ae53c193 Mon Sep 17 00:00:00 2001 From: Lukas Audzevicius Date: Thu, 27 Aug 2026 11:58:03 +0100 Subject: [PATCH 03/15] Bump voxpupuli-puppet-lint-plugins to ~> 7.0 puppetlabs_spec_helper >= 9.0.0 depends on puppet-lint ~> 5.0, which conflicts with the previously pinned voxpupuli-puppet-lint-plugins ~> 5.0 (depends on puppet-lint ~> 4.0). voxpupuli-puppet-lint-plugins 7.0.0 depends on puppet-lint ~> 5.1, which resolves cleanly. Verified locally: `bundle lock` now succeeds for the existing ruby 3.2 / puppet ~> 8.0 lane. --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 25934644..0cc01f1d 100644 --- a/Gemfile +++ b/Gemfile @@ -24,7 +24,7 @@ group :development do gem "json", '= 2.6.1', require: false if Gem::Requirement.create(['>= 3.1.0', '< 3.1.3']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) gem "json", '= 2.6.3', require: false if Gem::Requirement.create(['>= 3.2.0', '< 4.0.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) gem "deep_merge", '~> 1.0', require: false - gem "voxpupuli-puppet-lint-plugins", '~> 5.0', require: false + gem "voxpupuli-puppet-lint-plugins", '~> 7.0', require: false gem "facterdb", '~> 1.18', require: false gem "metadata-json-lint", '~> 4.0', require: false gem "rspec-puppet-facts", '~> 3.0', require: false From 8275b9a8907a9301463789ee9ee8db19c4585f12 Mon Sep 17 00:00:00 2001 From: Lukas Audzevicius Date: Thu, 27 Aug 2026 12:53:25 +0100 Subject: [PATCH 04/15] Bump rspec-puppet-facts to ~> 6.0, facterdb to ~> 4.0 rspec-puppet-facts < 5.4.0 depends on puppet >= 7, < 9, which conflicts with the new Gemfile's puppet ~> 9.0 branch. 6.x dropped the direct puppet dependency entirely (now depends on facterdb >= 3.1, < 5.0 and openfact ~> 5.0 instead), so bumping clears the conflict. facterdb bumped to ~> 4.0 to satisfy 6.x's floor; not referenced directly anywhere in ruby-pwsh's own spec code. Verified locally: `bundle lock` resolves cleanly for the ruby 3.2 / puppet ~> 8.0 lane with these versions. --- Gemfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 0cc01f1d..501ed857 100644 --- a/Gemfile +++ b/Gemfile @@ -25,9 +25,9 @@ group :development do gem "json", '= 2.6.3', require: false if Gem::Requirement.create(['>= 3.2.0', '< 4.0.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) gem "deep_merge", '~> 1.0', require: false gem "voxpupuli-puppet-lint-plugins", '~> 7.0', require: false - gem "facterdb", '~> 1.18', require: false + gem "facterdb", '~> 4.0', require: false gem "metadata-json-lint", '~> 4.0', require: false - gem "rspec-puppet-facts", '~> 3.0', require: false + gem "rspec-puppet-facts", '~> 6.0', require: false gem "dependency_checker", '~> 1.0.0', require: false gem "parallel_tests", '3.13.0', require: false gem "pry", '~> 0.10', require: false From 8887c799b05beab334a0f397d2efc1f1f97b054d Mon Sep 17 00:00:00 2001 From: Lukas Audzevicius Date: Thu, 27 Aug 2026 13:49:25 +0100 Subject: [PATCH 05/15] Bump puppet_litmus to ~> 2.0 puppet_litmus ~> 1.0 pulled in an old bolt (4.0.0 via orchestrator_client 0.7.2 -> faraday 1.9.0 -> faraday-patron -> patron), whose native C extension (patron, a libcurl wrapper) fails to build on Ruby 4.0. puppet_litmus is not referenced anywhere in this repo's own spec code or Rakefile (dead weight in the system_tests group), so bumping is low-risk. 2.8.0 resolves via a modern orchestrator_client/faraday 2.x stack with no native-extension dependency. Verified locally: `bundle lock` resolves cleanly for the ruby 3.2 / puppet ~> 8.0 lane, and the puppet ~> 9.0 lane no longer conflicts on anything other than requiring the private puppetcore gem source. --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 501ed857..932daba6 100644 --- a/Gemfile +++ b/Gemfile @@ -43,7 +43,7 @@ group :development, :release_prep do gem "puppetlabs_spec_helper", '~> 9.0', require: false end group :system_tests do - gem "puppet_litmus", '~> 1.0', require: false, platforms: [:ruby, :x64_mingw] + gem "puppet_litmus", '~> 2.0', require: false, platforms: [:ruby, :x64_mingw] gem "CFPropertyList", '< 3.0.7', require: false, platforms: [:mswin, :mingw, :x64_mingw] gem "serverspec", '~> 2.41', require: false end From ec94c6b75c006a555f23679743c248154aa19219 Mon Sep 17 00:00:00 2001 From: Lukas Audzevicius Date: Thu, 27 Aug 2026 13:52:48 +0100 Subject: [PATCH 06/15] Pin faraday to ~> 2.0 orchestrator_client (pulled in via bolt <- puppet_litmus) allows faraday >= 1.4, < 3.0. When resolving alongside a real puppet ~> 9.0 candidate from puppetcore, Bundler's resolver backtracks to faraday 1.9.0 instead of 2.x. Faraday 1.x's gemspec bundles faraday-patron (a native libcurl-wrapping extension) as a hard runtime dependency, which fails to build on Ruby 4.0. Pinning faraday directly forces the resolver onto the 2.x branch regardless of solver ordering; faraday 2.x's adapters are fully optional, so faraday-patron/patron never enters the graph. Local `bundle lock` can't fully validate this against real puppet 9.x metadata (only available via the private puppetcore source), so this is verified against the ruby 3.2 / puppet ~> 8.0 lane locally and relies on CI for confirmation on the ruby 4.0 / puppet ~> 9.0 lane. --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index 932daba6..09c84326 100644 --- a/Gemfile +++ b/Gemfile @@ -46,6 +46,7 @@ group :system_tests do gem "puppet_litmus", '~> 2.0', require: false, platforms: [:ruby, :x64_mingw] gem "CFPropertyList", '< 3.0.7', require: false, platforms: [:mswin, :mingw, :x64_mingw] gem "serverspec", '~> 2.41', require: false + gem "faraday", '~> 2.0', require: false end puppet_version = ENV['PUPPET_GEM_VERSION'] From 183d18601b40a627358ae998d758e66bf3bc986a Mon Sep 17 00:00:00 2001 From: Lukas Audzevicius Date: Thu, 27 Aug 2026 13:55:08 +0100 Subject: [PATCH 07/15] Remove unused puppet_litmus dependency puppet_litmus is not referenced anywhere in this repo's code, specs, or Rakefile -- the actual acceptance task (spec/acceptance/dsc/*.rb, namespace :acceptance in Rakefile) uses plain RSpec with a hand-rolled DSC-module vendoring step, not litmus/bolt provisioning. puppet_litmus -> bolt -> r10k -> puppet_forge forms an intractable dependency conflict on Ruby 4.0: bolt's r10k/puppet_forge chain forces faraday < 2.0 unless a newer puppet_forge (>= 4.0.0) is used, but that newer puppet_forge hard-pins faraday-follow_redirects ~> 0.3.0, and versions of faraday-follow_redirects below 0.5.0 refuse to run on Ruby 4.0 at all. No combination of version pins satisfies this. Since the gem is unused, removing it (and the now-unnecessary faraday pin from the previous commit) is simpler and more correct than trying to force a resolution through an unused dependency's broken chain. Verified locally: `bundle lock` resolves cleanly for the ruby 3.2 / puppet ~> 8.0 lane, with bolt/r10k/patron/puppet_litmus fully absent from the graph. --- Gemfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Gemfile b/Gemfile index 09c84326..b14d079a 100644 --- a/Gemfile +++ b/Gemfile @@ -43,10 +43,8 @@ group :development, :release_prep do gem "puppetlabs_spec_helper", '~> 9.0', require: false end group :system_tests do - gem "puppet_litmus", '~> 2.0', require: false, platforms: [:ruby, :x64_mingw] gem "CFPropertyList", '< 3.0.7', require: false, platforms: [:mswin, :mingw, :x64_mingw] gem "serverspec", '~> 2.41', require: false - gem "faraday", '~> 2.0', require: false end puppet_version = ENV['PUPPET_GEM_VERSION'] From 39d3d24d1673db9aeb25edc16489742d46cd17a6 Mon Sep 17 00:00:00 2001 From: Lukas Audzevicius Date: Thu, 27 Aug 2026 13:58:43 +0100 Subject: [PATCH 08/15] Add explicit rexml dependency lib/pwsh.rb requires 'rexml/document' directly, but rexml was never declared as a dependency anywhere -- it was only ever satisfied as an accidental transitive dependency pulled in via puppet_litmus's bolt/ r10k chain. Removing puppet_litmus in the previous commit surfaced this: `cannot load such file -- rexml/document` on every spec run, since rexml is a Ruby "default gem" that Bundler's isolation won't fall back to unless explicitly declared. Added to both: - ruby-pwsh.gemspec (spec.add_dependency), so real consumers of the published gem get it resolved correctly - Gemfile directly, since this Gemfile doesn't call `gemspec` and so never pulls in the gemspec's runtime dependencies on its own Verified locally: `bundle exec rspec spec/unit/pwsh_spec.rb` no longer raises LoadError on rexml/document (fails locally only on "No pwsh discovered!", because this machine has no local PowerShell install -- unrelated to this change, and not something CI hits). --- Gemfile | 2 ++ ruby-pwsh.gemspec | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Gemfile b/Gemfile index b14d079a..0dc3acc4 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,8 @@ gemsource_puppetcore = if ENV['PUPPET_FORGE_TOKEN'] && !ENV['PUPPET_FORGE_TOKEN' end source gemsource_default +gem "rexml", require: false + def location_for(place_or_version, fake_version = nil, opts = {}) git_url_regex = %r{\A(?(https?|git)[:@][^#]*)(#(?.*))?} file_url_regex = %r{\Afile:\/\/(?.*)} diff --git a/ruby-pwsh.gemspec b/ruby-pwsh.gemspec index 187b37c4..eac436d1 100644 --- a/ruby-pwsh.gemspec +++ b/ruby-pwsh.gemspec @@ -28,6 +28,8 @@ Gem::Specification.new do |spec| 'spec/**/*', ] + spec.add_dependency 'rexml' + spec.bindir = 'exe' spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] From 212eaa22875ffaf6f5fc6c353339d7f7ff55dec9 Mon Sep 17 00:00:00 2001 From: Lukas Audzevicius Date: Thu, 27 Aug 2026 14:05:42 +0100 Subject: [PATCH 09/15] Fix PUPPET_GEM_VERSION not being exported in custom_acceptance.yml The "export environment" step had a misplaced closing quote, putting ">> \$GITHUB_ENV" inside the echoed string instead of using it as shell redirection: echo "PUPPET_GEM_VERSION=${{ inputs.puppet_version }} >> $GITHUB_ENV" PUPPET_GEM_VERSION was therefore never actually set for the acceptance job, on any lane. This was silently harmless when there was only one ruby/puppet pairing to test (unconstrained puppet resolution happened to work fine on Ruby 3.x), but surfaces as a real Bundler version- solving failure on the new Ruby 4.0 lane: with no PUPPET_GEM_VERSION, the Gemfile can't scope puppet to ~> 9.0, so Bundler falls back to puppetlabs_spec_helper's own broad puppet >= 8, < 10 constraint (via puppetlabs-syntax) -- which pulls in a facter version that caps Ruby at < 4.0, conflicting with the actual Ruby 4.0.6 runtime. --- .github/workflows/custom_acceptance.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/custom_acceptance.yml b/.github/workflows/custom_acceptance.yml index a061e54b..ce53c351 100644 --- a/.github/workflows/custom_acceptance.yml +++ b/.github/workflows/custom_acceptance.yml @@ -43,7 +43,7 @@ jobs: - name: "export environment" run: | - echo "PUPPET_GEM_VERSION=${{ inputs.puppet_version }} >> $GITHUB_ENV" + echo "PUPPET_GEM_VERSION=${{ inputs.puppet_version }}" >> $GITHUB_ENV - name: "setup ruby" uses: "ruby/setup-ruby@v1" From fe619fed0a804d6f64951d2e62f31fd4e12c546b Mon Sep 17 00:00:00 2001 From: Lukas Audzevicius Date: Thu, 27 Aug 2026 14:11:10 +0100 Subject: [PATCH 10/15] Add fail-fast: false to acceptance job matrix The acceptance job's matrix strategy had no fail-fast setting (unlike spec, which already sets fail-fast: false), so it defaulted to true: whenever one lane (e.g. ruby 4.0 | puppet ~> 9.0) failed, GitHub Actions auto-cancelled the sibling lane (ruby 3.2 | puppet ~> 8.0) mid-run, masking its true pass/fail status as "canceled" instead. --- .github/workflows/ci.yml | 1 + .github/workflows/nightly.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c969cef5..e815b4d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,7 @@ jobs: acceptance: needs: "spec" strategy: + fail-fast: false matrix: ruby_version: - "3.2" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index e9653194..38b3e346 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -28,6 +28,7 @@ jobs: acceptance: needs: "spec" strategy: + fail-fast: false matrix: ruby_version: - "3.2" From b03e64b0a574ec2502a28a41af5b028993e4e8c3 Mon Sep 17 00:00:00 2001 From: Lukas Audzevicius Date: Thu, 27 Aug 2026 14:43:44 +0100 Subject: [PATCH 11/15] DEBUG: dump puppet apply stdout/stderr on first acceptance test --- spec/acceptance/dsc/basic.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/acceptance/dsc/basic.rb b/spec/acceptance/dsc/basic.rb index 47669175..7230b01f 100644 --- a/spec/acceptance/dsc/basic.rb +++ b/spec/acceptance/dsc/basic.rb @@ -56,6 +56,9 @@ def execute_reset_command(reset_command) it 'applies idempotently' do first_run_result = powershell.execute(command) + warn "DEBUG exitcode=#{first_run_result[:exitcode]}" + warn "DEBUG stdout=#{first_run_result[:native_stdout]}" + warn "DEBUG stderr=#{first_run_result[:native_stderr]}" expect(first_run_result[:exitcode]).to be(2) expect(first_run_result[:native_stdout]).to match(/dsc_installationpolicy changed 'Untrusted' to 'Trusted'/) expect(first_run_result[:native_stdout]).to match(/Updating: Finished/) From b97fd62c8cda66a031be5cfb2a96d5f8fc3bb0fd Mon Sep 17 00:00:00 2001 From: Lukas Audzevicius Date: Thu, 27 Aug 2026 14:55:48 +0100 Subject: [PATCH 12/15] DEBUG: probe trivial pwsh execute and bundle exec puppet --version --- spec/acceptance/dsc/basic.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/acceptance/dsc/basic.rb b/spec/acceptance/dsc/basic.rb index 7230b01f..4843b547 100644 --- a/spec/acceptance/dsc/basic.rb +++ b/spec/acceptance/dsc/basic.rb @@ -55,6 +55,15 @@ def execute_reset_command(reset_command) end it 'applies idempotently' do + trivial_result = powershell.execute('Write-Output "hello from pwsh"') + warn "DEBUG trivial exitcode=#{trivial_result[:exitcode]}" + warn "DEBUG trivial stdout=#{trivial_result[:native_stdout]}" + warn "DEBUG trivial stderr=#{trivial_result[:native_stderr]}" + bundle_check = powershell.execute('bundle exec puppet --version') + warn "DEBUG bundle_check exitcode=#{bundle_check[:exitcode]}" + warn "DEBUG bundle_check stdout=#{bundle_check[:native_stdout]}" + warn "DEBUG bundle_check stderr=#{bundle_check[:native_stderr]}" + warn "DEBUG command=#{command}" first_run_result = powershell.execute(command) warn "DEBUG exitcode=#{first_run_result[:exitcode]}" warn "DEBUG stdout=#{first_run_result[:native_stdout]}" From 8fa08c7c9966b7d1a8e413f6182d2380c72b9036 Mon Sep 17 00:00:00 2001 From: Lukas Audzevicius Date: Thu, 27 Aug 2026 15:52:32 +0100 Subject: [PATCH 13/15] Route facter through gemsource_puppetcore facter's latest public RubyGems release (4.10.0) declares required_ruby_version '>= 2.5', '< 4.0' -- no version on public RubyGems supports Ruby 4.0 at all, which blocked bundle install entirely for the ruby 4.0 / puppet ~> 9.0 lane (needed transitively via puppet's own facter >= 4.3.0, < 5 dependency). Mirrors puppetlabs-motd's Gemfile: route facter through gemsource_puppetcore unconditionally, the same way puppet already is, so it resolves against the private puppetcore build (which supports Ruby 4.0) instead of the public gem when PUPPET_FORGE_TOKEN is set. --- Gemfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 0dc3acc4..6abd05cf 100644 --- a/Gemfile +++ b/Gemfile @@ -56,11 +56,10 @@ hiera_version = ENV['HIERA_GEM_VERSION'] gems = {} gems['puppet'] = location_for(puppet_version, nil, { source: gemsource_puppetcore }) +gems['facter'] = location_for(facter_version, nil, { source: gemsource_puppetcore }) -# If facter or hiera versions have been specified via the environment -# variables +# If a hiera version has been specified via the environment variable -gems['facter'] = location_for(facter_version) if facter_version gems['hiera'] = location_for(hiera_version) if hiera_version gems.each do |gem_name, gem_params| From 8f7f46940f1e4602af8684a551bdadc838bee16c Mon Sep 17 00:00:00 2001 From: Lukas Audzevicius Date: Thu, 27 Aug 2026 15:57:24 +0100 Subject: [PATCH 14/15] Export PUPPET_FORGE_TOKEN in custom_acceptance.yml custom_acceptance.yml never exposed PUPPET_FORGE_TOKEN or the puppetcore bundler source credential as environment variables, unlike cat-github-actions' gem_ci.yml (used by the spec job). This meant bundle install in the acceptance job only ever consulted public rubygems.org, silently ignoring gemsource_puppetcore entirely -- confirmed by the log only showing "Fetching gem metadata from https://rubygems.org/", never rubygems-puppetcore.puppet.com. Mirrors gem_ci.yml's env block, including the PUPPET_FORGE_TOKEN_PUBLIC fallback since ruby-pwsh is a public repo. --- .github/workflows/custom_acceptance.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/custom_acceptance.yml b/.github/workflows/custom_acceptance.yml index ce53c351..605c868c 100644 --- a/.github/workflows/custom_acceptance.yml +++ b/.github/workflows/custom_acceptance.yml @@ -31,6 +31,10 @@ on: default: "ubuntu-latest" type: "string" +env: + PUPPET_FORGE_TOKEN: ${{ secrets.PUPPET_FORGE_TOKEN || secrets.PUPPET_FORGE_TOKEN_PUBLIC }} + BUNDLE_RUBYGEMS___PUPPETCORE__PUPPET__COM: "forge-key:${{ secrets.PUPPET_FORGE_TOKEN || secrets.PUPPET_FORGE_TOKEN_PUBLIC }}" + jobs: acceptance: name: "acceptance" From 54020c5260b19486634f2451ee99589ae448c11f Mon Sep 17 00:00:00 2001 From: Lukas Audzevicius Date: Thu, 27 Aug 2026 16:15:48 +0100 Subject: [PATCH 15/15] Make DSC acceptance regexes agnostic to Ruby's Hash#inspect format Ruby 3.4 changed Hash#inspect's default output from {:key=>"value"} to {key: "value"}. These four assertions hardcoded the pre-3.4 format, so they failed under the new Ruby 4.0 lane even though the actual DSC resource application succeeded correctly (confirmed via the debug probes added in a prior commit -- the real puppet apply output was present, just in the new hash format the regex didn't expect). Since the 3.2 lane still runs pre-3.4 Ruby and would produce the old format, made the regexes match either style rather than picking one. --- spec/acceptance/dsc/cim_instances.rb | 2 +- spec/acceptance/dsc/complex.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/acceptance/dsc/cim_instances.rb b/spec/acceptance/dsc/cim_instances.rb index b181898e..99f656dc 100644 --- a/spec/acceptance/dsc/cim_instances.rb +++ b/spec/acceptance/dsc/cim_instances.rb @@ -67,7 +67,7 @@ def execute_reset_command(reset_command) expect(first_run_result[:exitcode]).to be(2) # Access Control Set expect(first_run_result[:native_stdout]).to match(/dsc_accesscontrollist: dsc_accesscontrollist changed/) - expect(first_run_result[:native_stdout]).to match(%r{dsc_ntfsaccessentry\[{:name=>"Test", :dsc_path=>".+/spec/fixtures/access_control"}\]: Updating: Finished}) + expect(first_run_result[:native_stdout]).to match(%r{dsc_ntfsaccessentry\[\{:?name(?:=>|: )"Test", :?dsc_path(?:=>|: )".+/spec/fixtures/access_control"\}\]: Updating: Finished}) expect(first_run_result[:stderr]).not_to match(/Error/) expect(first_run_result[:stderr]).not_to match(/Warning: Provider returned data that does not match the Type Schema/) expect(first_run_result[:stderr]).not_to match(/Value type mismatch/) diff --git a/spec/acceptance/dsc/complex.rb b/spec/acceptance/dsc/complex.rb index 079294d6..81614556 100644 --- a/spec/acceptance/dsc/complex.rb +++ b/spec/acceptance/dsc/complex.rb @@ -114,10 +114,10 @@ def execute_reset_command(reset_command) expect(first_run_result[:exitcode]).to be(2) # The Default Site is stopped expect(first_run_result[:native_stdout]).to match(%r{Dsc_xwebsite\[DefaultSite\]/dsc_state: dsc_state changed 'Started' to 'Stopped'}) - expect(first_run_result[:native_stdout]).to match(/dsc_xwebsite\[{:name=>"DefaultSite", :dsc_name=>"Default Web Site"}\]: Updating: Finished/) + expect(first_run_result[:native_stdout]).to match(/dsc_xwebsite\[\{:?name(?:=>|: )"DefaultSite", :?dsc_name(?:=>|: )"Default Web Site"\}\]: Updating: Finished/) # AspNet45 is installed expect(first_run_result[:native_stdout]).to match(%r{Dsc_xwindowsfeature\[AspNet45\]/dsc_ensure: dsc_ensure changed 'Absent' to 'Present'}) - expect(first_run_result[:native_stdout]).to match(/dsc_xwindowsfeature\[{:name=>"AspNet45", :dsc_name=>"Web-Asp-Net45"}\]: Creating: Finished/) + expect(first_run_result[:native_stdout]).to match(/dsc_xwindowsfeature\[\{:?name(?:=>|: )"AspNet45", :?dsc_name(?:=>|: )"Web-Asp-Net45"\}\]: Creating: Finished/) # Web content folder created expect(first_run_result[:native_stdout]).to match(%r{File\[WebContentFolder\]/ensure: created}) # Web content index created @@ -128,7 +128,7 @@ def execute_reset_command(reset_command) expect(first_run_result[:native_stdout]).to match(%r{Dsc_xwebsite\[NewWebsite\]/dsc_physicalpath: dsc_physicalpath changed.*to '.+fixtures/website'}) expect(first_run_result[:native_stdout]).to match(%r{Dsc_xwebsite\[NewWebsite\]/dsc_state: dsc_state changed.*to 'Started'}) expect(first_run_result[:native_stdout]).to match(%r{Dsc_xwebsite\[NewWebsite\]/dsc_serverautostart: dsc_serverautostart changed.*to 'true'}) - expect(first_run_result[:native_stdout]).to match(/dsc_xwebsite\[{:name=>"NewWebsite", :dsc_name=>"Puppet DSC Site"}\]: Creating: Finished/) + expect(first_run_result[:native_stdout]).to match(/dsc_xwebsite\[\{:?name(?:=>|: )"NewWebsite", :?dsc_name(?:=>|: )"Puppet DSC Site"\}\]: Creating: Finished/) # Run finished expect(first_run_result[:native_stdout]).to match(/Applied catalog/) # Second run is idempotent