Skip to content

(CAT-2589) Add Ruby 4.0 / Puppet 9 lane, source gems from puppetcore - #385

Open
LukasAud wants to merge 15 commits into
mainfrom
CAT-2589-ruby4-puppet9
Open

(CAT-2589) Add Ruby 4.0 / Puppet 9 lane, source gems from puppetcore#385
LukasAud wants to merge 15 commits into
mainfrom
CAT-2589-ruby4-puppet9

Conversation

@LukasAud

Copy link
Copy Markdown
Contributor

Summary

Adds Ruby 4.0 / Puppet 9 CI coverage to ruby-pwsh, mirroring the same update already carried out on other repos (e.g. puppetlabs_spec_helper@3bfa7d21, bolt-private#120):

  • Add 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 / ~> 8.0 lane.
  • Update the Gemfile to source the puppet gem from rubygems-puppetcore.puppet.com when PUPPET_FORGE_TOKEN is set (falls back to GEM_SOURCE_PUPPETCORE or the default gem source otherwise).
  • Bump puppetlabs_spec_helper from ~> 8.0 to ~> 9.0 to pick up the CVE-fixed puppetlabs-syntax fork.

ruby-pwsh doesn't carry bolt's Gemfile Ruby-version-conditional/canary-spec workaround for the Puppet 8.x FrozenError-on-Ruby-4.0 bug, since its CI already explicitly pins puppet_version per matrix lane rather than resolving puppet dynamically from the runtime Ruby version — so that part of the bolt-private pattern doesn't apply here.

Test plan

  • CI passes on both the existing ruby 3.2 | puppet ~> 8.0 lane and the new ruby 4.0 | puppet ~> 9.0 lane, for both spec and acceptance jobs.

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.
@LukasAud
LukasAud requested a review from a team as a code owner August 27, 2026 10:34
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.
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.
@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.98%. Comparing base (d7cee48) to head (54020c5).
⚠️ Report is 11 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #385      +/-   ##
==========================================
- Coverage   90.99%   88.98%   -2.02%     
==========================================
  Files           6        6              
  Lines         733      853     +120     
==========================================
+ Hits          667      759      +92     
- Misses         66       94      +28     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant