(CAT-2600) Make bolt opt-in in generated module Gemfile#641
(CAT-2600) Make bolt opt-in in generated module Gemfile#641gavindidrichsen wants to merge 1 commit into
Conversation
Test evidence — fix verified, and the customer failure reproducedI tested this against PDK 3.6.1 (the version from CAT-2600), driving the real 1. The customer failure is real (reproduced)
PDK pins (The published puppetcore bolt expresses the floor as 2. The fix verified through
|
Scenario diagrams — what fails, and why the fix worksA subtlety worth making explicit: Before (unfixed) — the customer failureThe template adds a direct, unpinned flowchart TD
G["Generated module Gemfile (UNFIXED)"]
G --> P["puppet pinned = 8.10.0<br/>(PDK PUPPET_GEM_VERSION)"]
G --> LIT["puppet_litmus ~> 2.5<br/>→ bolt >= 4.0, < 6.0 (public source)"]
G --> DIR["template line 135: gem 'bolt'<br/>UNPINNED, source: puppetcore"]
DIR --> B5["puppetcore serves newest bolt = 5.x"]
B5 --> FLOOR["bolt 5.x requires puppet >= 8.11"]
P --> R{"one puppet version<br/>for everything?"}
FLOOR --> R
R -->|"8.10.0 is not >= 8.11"| FAIL["version solving has failed"]
style FAIL fill:#f8d7da,stroke:#dc3545
style DIR fill:#fff3cd,stroke:#ffc107
Verified against real puppetcore ( After (this PR) — resolvesWith the direct puppetcore flowchart TD
G["Generated module Gemfile (FIXED: bolt is opt-in)"]
G --> P["puppet pinned = 8.10.0"]
G --> LIT["puppet_litmus ~> 2.5<br/>→ bolt >= 4.0, < 6.0 (public source)"]
LIT --> B4["Bundler free to pick bolt 4.0.0 (public)"]
B4 --> OK["bolt 4.0.0 works with puppet 8.10.0"]
P --> R{"one puppet version<br/>for everything?"}
OK --> R
R --> PASS["resolves: puppet 8.10.0 + bolt 4.0.0"]
style PASS fill:#d1e7dd,stroke:#198754
Verified (same command, fixed generated Gemfile): Opt-in path is preservedA module that genuinely wants bolt sets flowchart LR
U["BOLT_GEM_VERSION unset<br/>(normal validate)"] --> N["no bolt line → no 8.11 floor imposed"]
O["BOLT_GEM_VERSION set<br/>(opt-in)"] --> Y["gem 'bolt' emitted from puppetcore<br/>(needs puppet >= 8.11 to resolve)"]
style N fill:#d1e7dd,stroke:#198754
style Y fill:#cfe2ff,stroke:#0d6efd
|
0badb23 to
d40fcf6
Compare
CAT-2357 added an unconditional, unpinned `bolt` to the generated module Gemfile. Because the bolt gem is pulled from puppetcore, Bundler resolves it to bolt 5.x, whose gemspec hard-requires `puppet >= 8.11` (and `facter ~> 4.11`) — both puppetcore-only. That collides with the puppet version PDK pins for a run (e.g. 8.10.0), so `pdk validate` fails at dependency resolution with "version solving has failed" before any validator runs. Every module inherited bolt's puppet floor, even modules that never use bolt. Guard the bolt line with `if bolt_version`, mirroring the existing `hiera` line, so bolt only enters the Gemfile when the user explicitly opts in via BOLT_GEM_VERSION. Bolt specs are already gated behind ENV['GEM_BOLT'], so a normal validate run never needs bolt present. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Gavin Didrichsen <gavin.didrichsen@gmail.com>
d40fcf6 to
6185b96
Compare
Problem
CAT-2600:
pdk validatefails withversion solving has failedon a repo validating against Puppet 8.10.0.CAT-2357 added an unconditional, unpinned
boltto the generated moduleGemfile(moduleroot/Gemfile.erb). The bolt line is sourced from puppetcore, so Bundler resolves it to bolt 5.x, whose gemspec hard-requirespuppet >= 8.11andfacter ~> 4.11— both puppetcore-only. PDK pins puppet to the version it ships (8.10.0), so resolution dies before any validator runs:Every module inherited bolt's Puppet floor — even modules that never touch bolt.
Fix
Guard the
boltline withif bolt_version, exactly mirroring the existinghieraline, soboltonly enters the Gemfile when the user explicitly opts in viaBOLT_GEM_VERSION:This is safe: bolt specs are already gated behind
ENV['GEM_BOLT'](spec/spec_helper.rb.erb), so a normalpdk validaterun never needs bolt present. Users who do want bolt setBOLT_GEM_VERSIONand take responsibility for a compatible puppet/bolt pair.Verification
Using
bundle lock --print(non-destructive) with the bolt 5.x constraint stood up via a localbolt-privatepath gem:gem 'puppet', '8.10.0'+ unconditionalgem 'bolt'→version solving has failed.BOLT_GEM_VERSIONunset) — Gemfile carries noboltline → resolves topuppet (8.10.0); validation proceeds.BOLT_GEM_VERSIONunset → bolt line skipped; set → bolt line emitted.🤖 Generated with Claude Code
How it works (before vs after)
boltis in the resolution either way, because the default Gemfile pullspuppet_litmus (~> 2.5)→bolt (>= 4.0, < 6.0). The fix only changes whichboltBundler may pick.Before — the template's direct, unpinned puppetcore
boltforces 5.x, which needspuppet >= 8.11:flowchart TD P["puppet pinned = 8.10.0"] D["template: gem 'bolt' UNPINNED, source: puppetcore"] --> B5["puppetcore newest bolt = 5.x → needs puppet >= 8.11"] P --> R{"one puppet for all?"} B5 --> R R -->|"8.10.0 is not >= 8.11"| F["version solving has failed"] style F fill:#f8d7da,stroke:#dc3545After — bolt is only constrained by
puppet_litmus(public), so Bundler picks 4.0.0, happy with 8.10.0:flowchart TD P["puppet pinned = 8.10.0"] L["puppet_litmus → bolt >= 4.0, < 6.0 (public)"] --> B4["Bundler picks bolt 4.0.0 (public)"] P --> R{"one puppet for all?"} B4 --> R R --> OK["resolves: puppet 8.10.0 + bolt 4.0.0"] style OK fill:#d1e7dd,stroke:#198754Opt-in is preserved: set
BOLT_GEM_VERSIONto pullboltfrom puppetcore again (requires an all-puppetcore stack, puppet >= 8.11).