Skip to content

Renovate-resilient templates: split file management into bootstrap and in-place merge stages #50

Description

@silug

Problem

Renovate now manages version numbers inside files that puppetsync also manages (Gemfile gem pins, uses: actions/checkout@vN refs in GHA workflows, metadata.json dependencies). The profiles enforce those files with file { content => file(...)/epp(...) }, which overwrites unconditionally — so every puppetsync run clobbers Renovate's updates back to whatever version the template was written with.

Proposed design: split template management into two stages

Stage 1 — bootstrap (lay down complete file, only if missing)

Puppet's file resource already supports this via replace => false. Classify each managed file:

  • Fully-managed (no version content — .gitignore, .rspec, _puppet-lint.rc, …): keep overwriting as today.
  • Bootstrap + merge (Gemfile, GHA workflows, metadata.json, …): add replace => false so the template only seeds new repos.

Express the classification as a profile parameter (or a small profile::managed_file define) so Hiera can flip a file between modes per project type. No new pipeline stage needed — bootstrap stays inside apply_puppet_role.

Stage 2 — merge (update existing files in place, preserving Renovate-managed values and comments)

New merge_managed_files pipeline stage in plans/init.pp after apply_puppet_role, backed by one task per file family:

  • merge_yaml.rb — load the existing file with psych-pure (comment/format-preserving YAML), deep-merge the template's structure into it with a declared list of preserved paths (e.g. jobs.*.steps.*.uses), write back. Add psych-pure to gem.deps.rb and the install_gems stage.
  • merge_gemfile.rb — generalize the line-regexp editing already proven in tasks/configure_renovate.rb: ensure required gem lines/blocks exist, but never touch an existing version constraint.
  • JSON (metadata.json, renovate.json) needs no comment preservation; JSON.parse + pretty_generate as today.

Drive the merge specs from Hiera, keyed off the existing project_types hierarchy — e.g. data/project_types/pupmod.yaml gains a puppetsync::merge_rules hash mapping file path → {type: yaml, preserve: [...]}. The "what" stays in data, the "how" in tasks.

Prior art in this repo

  • profile::pupmod::gitlab_ci already implemented read-existing-file → extract repo-specific content → re-merge into template.
  • tasks/configure_renovate.rb already does line-oriented Gemfile edits and JSON merging.

Risks

  • psych-pure is young: before trusting it in the pipeline, add a round-trip test that parses and re-emits every workflow template under modules/profile/files/ and diffs the result. It must also install/run under Bolt's bundled Ruby.
  • Workflow restructuring is the hard semantic: when a template changes a job's steps, "preserve existing uses: refs" gets ambiguous. Proposed resolution: keep the existing ref when the same step still exists; take the template's ref for genuinely new steps.

Migration

Existing repos already have all the files, so bootstrap no-ops everywhere and the merge stage becomes the workhorse immediately.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions