|
| 1 | +<%- |
| 2 | + # OS detection from the module's metadata.json (exposed by PDK as |
| 3 | + # @configs['module_metadata']). Falls back safely when metadata is absent |
| 4 | + # (e.g. `pdk new module` before the interview) -- default to Linux content. |
| 5 | + md = (@configs['module_metadata'] rescue {}) || {} |
| 6 | + os_support = (md['operatingsystem_support'] rescue []) || [] |
| 7 | + os_names = os_support.map { |o| ((o['operatingsystem'] || '').to_s.downcase rescue '') } |
| 8 | + linux_names = %w[redhat centos oraclelinux scientific sles suse opensuse debian ubuntu rocky almalinux amazon fedora virtuozzolinux gentoo archlinux] |
| 9 | + has_windows = os_names.include?('windows') |
| 10 | + has_linux = os_names.empty? || os_names.any? { |n| linux_names.include?(n) } |
| 11 | + # Guarantee at least one OS section: any non-Windows module (incl. AIX/Solaris/Darwin-only) |
| 12 | + # defaults to the Linux content; Windows-only modules stay Windows-only. |
| 13 | + has_linux ||= !has_windows |
| 14 | +-%> |
| 15 | +# CLAUDE.md |
| 16 | + |
| 17 | +This file provides guidance to Claude Code (claude.ai/code) and human contributors when |
| 18 | +working with code in this repository. |
| 19 | + |
| 20 | +> **This file is synced from [puppetlabs/pdk-templates](https://github.com/puppetlabs/pdk-templates) |
| 21 | +> (`moduleroot/CLAUDE.md.erb`).** Do not edit it directly in a module repo -- changes are |
| 22 | +> overwritten on the next `pdk update`. To customise per module, add module-specific notes via a |
| 23 | +> `## Module-specific notes` section in a file PDK does not manage, or opt this file out entirely |
| 24 | +> with `CLAUDE.md: unmanaged: true` in the module's `.sync.yml` and maintain a hand-written copy. |
| 25 | + |
| 26 | +For generic Puppet/PDK workflow that *is* derivable from the files themselves (the full PDK command |
| 27 | +reference, CI matrix mechanics, README badges), see the PDK docs and the README. The notes below are |
| 28 | +the conventions Claude should treat as binding before touching code, plus the things that are not |
| 29 | +obvious from a single file. |
| 30 | + |
| 31 | +--- |
| 32 | + |
| 33 | +## Project layout |
| 34 | + |
| 35 | +A CAT-supported Puppet module uses some subset of this structure -- read what actually exists before |
| 36 | +assuming. Modules come in a few shapes: **manifest modules** (classes/defined types), **type/provider |
| 37 | +modules** (custom resources in `lib/puppet/`), and **task modules** (Bolt tasks, often no manifests). |
| 38 | + |
| 39 | +``` |
| 40 | +manifests/ # Puppet DSL: classes and defined types (.pp) |
| 41 | +lib/puppet/type/ # Custom resource type definitions (Ruby) |
| 42 | +lib/puppet/provider/<type>/ # Providers implementing each type per OS/tool |
| 43 | +lib/puppet/functions/ # Modern Puppet 4.x function API (Ruby) |
| 44 | +lib/facter/ # Custom facts |
| 45 | +lib/puppet_x/ # Shared helper libraries (parsing, utilities) |
| 46 | +functions/ # Puppet-language functions (.pp) |
| 47 | +tasks/ # Bolt tasks (<name>.{rb,sh,ps1} + <name>.json schema) |
| 48 | +plans/ # Bolt plans (.pp) |
| 49 | +files/ # Static files / shell helpers shipped with the module |
| 50 | +data/ + hiera.yaml # Module Hiera data (per-OS defaults) |
| 51 | +templates/ # ERB/EPP templates |
| 52 | +spec/ # Tests (see Testing below) |
| 53 | +metadata.json # Module metadata, dependencies, supported OS matrix |
| 54 | +REFERENCE.md # Generated by `puppet strings` -- do not hand-edit |
| 55 | +``` |
| 56 | + |
| 57 | +- Manifests orchestrate; for type/provider modules the real work lives in `lib/puppet/`. |
| 58 | +- Prefer per-OS defaults in `data/` (Hiera, keyed by `os.family`/`os.name`) over hard-coding in `params.pp`. |
| 59 | +- Regenerate reference docs: `bundle exec puppet strings generate --format markdown --out REFERENCE.md`. |
| 60 | + |
| 61 | +--- |
| 62 | + |
| 63 | +## Common commands |
| 64 | + |
| 65 | +```bash |
| 66 | +bundle install # Install dependencies |
| 67 | +bundle exec rake spec_prep # Install fixture modules from .fixtures.yml (before specs) |
| 68 | +bundle exec rake spec # Run all unit tests |
| 69 | +bundle exec rake parallel_spec # Run unit tests in parallel (faster on large suites) |
| 70 | +bundle exec rake lint # puppet-lint |
| 71 | +bundle exec rake lint_fix # Auto-fix lint offences |
| 72 | +bundle exec rake rubocop # Ruby style checks |
| 73 | +bundle exec rake validate # Syntax-check Ruby, Puppet manifests, and metadata |
| 74 | +bundle exec rake syntax # Puppet manifest + Hiera syntax check |
| 75 | +bundle exec rake metadata_lint # Validate metadata.json |
| 76 | +bundle exec rake release_checks # Full pre-release gate (validate + lint + spec) |
| 77 | + |
| 78 | +# Run a single spec file / example |
| 79 | +bundle exec rspec spec/unit/puppet/provider/<type>/<provider>_spec.rb |
| 80 | +bundle exec rspec spec/unit/puppet/type/<type>_spec.rb:42 # by line number |
| 81 | +bundle exec rspec spec/.../foo_spec.rb -e "creates the resource" # by description |
| 82 | +``` |
| 83 | + |
| 84 | +Acceptance tests (Litmus; require Docker or provisioned targets): |
| 85 | + |
| 86 | +```bash |
| 87 | +bundle exec rake litmus:provision_list[default] |
| 88 | +bundle exec rake litmus:install_module |
| 89 | +bundle exec rake litmus:acceptance:parallel |
| 90 | +``` |
| 91 | + |
| 92 | +--- |
| 93 | + |
| 94 | +## Testing conventions |
| 95 | + |
| 96 | +### rspec-puppet (unit, for manifests) |
| 97 | + |
| 98 | +- Every class/defined type should have `it { is_expected.to compile.with_all_deps }` -- a clean |
| 99 | + compile across the supported OS matrix is the baseline gate. |
| 100 | +- Drive OS variants with `on_supported_os` (reads `metadata.json`) rather than hand-listing facts. |
| 101 | +- Stub facts via the `:facts` hash or `spec/default_facts.yml`; do not rely on the host's real facts. |
| 102 | +- Provide Hiera test data through `spec/fixtures/hiera/` and a test `hiera.yaml` when behaviour is |
| 103 | + data-driven. Use `let(:params)` / `let(:pre_condition)` for class params and dependencies. |
| 104 | +- Fixture modules are declared in `.fixtures.yml` and installed by `rake spec_prep`. |
| 105 | + |
| 106 | +### Type/provider unit specs |
| 107 | + |
| 108 | +- `spec/unit/puppet/type/` -- attribute validation, `munge`/`validate`, `autorequire` wiring. |
| 109 | +- `spec/unit/puppet/provider/<type>/` -- CRUD behaviour. Stub the external CLI/syscalls; never shell |
| 110 | + out to the real tool. Assert the *commands generated* and the *state parsed*. |
| 111 | +- **Mocking framework varies per module** -- check `spec/spec_helper.rb`'s `mock_with`. Some modules |
| 112 | + use Mocha (`stub_everything`, `stubs`, `expects`); others use rspec-mocks (`allow`, `expect`, |
| 113 | + `receive`). Match the module's existing convention. |
| 114 | + |
| 115 | +### Coverage |
| 116 | + |
| 117 | +```bash |
| 118 | +COVERAGE=yes bundle exec rspec # SimpleCov report |
| 119 | +``` |
| 120 | + |
| 121 | +- `RSpec::Puppet::Coverage.report!` runs from the PDK-managed spec helper; the minimum percentage is |
| 122 | + set in `.sync.yml` / `config_defaults.yml`. |
| 123 | +- SimpleCov `track_files` and any extra coverage config belong in `spec/spec_helper_local.rb` |
| 124 | + (`lib/**/*.rb` for ruby modules, `tasks/**/*.rb` for task modules) -- not in `spec_helper.rb`. |
| 125 | +- Do not weaken the coverage gate to make a build pass. |
| 126 | + |
| 127 | +### Litmus / beaker (acceptance) |
| 128 | + |
| 129 | +- Acceptance specs live in `spec/acceptance/` and run against Litmus-provisioned targets. They must be |
| 130 | + idempotent: apply the manifest, then assert a second apply is a no-op |
| 131 | + (`apply_manifest(pp, catch_changes: true)`). |
| 132 | +- Keep acceptance coverage on real behaviour (package installed, service running, file present) using |
| 133 | + `serverspec` matchers -- not a re-test of unit-level logic. |
| 134 | + |
| 135 | +### Spec helpers |
| 136 | + |
| 137 | +- `spec/spec_helper.rb` is **PDK-managed -- do not edit**; it is overwritten on `pdk update`. |
| 138 | +- Put project-local setup in `spec/spec_helper_local.rb` (safe to edit). |
| 139 | + |
| 140 | +--- |
| 141 | + |
| 142 | +## Ruby code style |
| 143 | + |
| 144 | +- Style is enforced by `.rubocop.yml` (deployed from pdk-templates, `strict` profile). Run |
| 145 | + `bundle exec rake rubocop` / `rubocop -a` before pushing; do not hand-tune cops in the synced file. |
| 146 | +- Conventions the profile enforces: `Layout/LineLength` max 200, `snake_case` naming, `%r{}` for regex |
| 147 | + literals, trailing commas on multiline arrays/args, and the `rubocop-performance` / `rubocop-rspec` |
| 148 | + extensions. Target Ruby version follows `.rubocop.yml`'s `TargetRubyVersion`. |
| 149 | + |
| 150 | +--- |
| 151 | + |
| 152 | +## Gemfile, gem sources and version pinning |
| 153 | + |
| 154 | +- The `Gemfile` is PDK-managed. It resolves `puppet`, `facter`, `hiera` (and `bolt`) gems through a |
| 155 | + configurable source -- do not hard-code gem versions in it. |
| 156 | +- Environment knobs: `PUPPET_GEM_VERSION`, `FACTER_GEM_VERSION`, `HIERA_GEM_VERSION`, `BOLT_GEM_VERSION` |
| 157 | + pin versions; `GEM_SOURCE` overrides the default RubyGems source; `GEM_SOURCE_PUPPETCORE` (set |
| 158 | + automatically when `PUPPET_FORGE_TOKEN` is present) selects the authenticated PuppetCore registry. |
| 159 | +- The `:system_tests` group holds Litmus/serverspec; `:development` holds rspec, lint, and coverage gems. |
| 160 | +- In code and specs, depend on the public Puppet API (`Puppet::ResourceApi`, `Puppet::Type`, `Facter`), |
| 161 | + never on a specific gem name, so the module works whichever runtime gem is resolved. |
| 162 | + |
| 163 | +--- |
| 164 | + |
| 165 | +## Type & provider DSL gotchas |
| 166 | + |
| 167 | +Applies to modules that ship custom resources under `lib/puppet/`: |
| 168 | + |
| 169 | +- **`autorequire`** -- declare implicit ordering (e.g. a package before the service that uses it) in |
| 170 | + the type, rather than forcing users to write `require =>` everywhere. |
| 171 | +- **`prefetch`** -- providers listing existing resources implement `self.instances` and |
| 172 | + `self.prefetch(resources)`. `prefetch` must match discovered instances back to catalog resources by |
| 173 | + name/namevar so Puppet edits instead of recreating. Missing this causes duplicate-resource churn. |
| 174 | +- **`self.instances`** -- return only fully populated instances; partial instances break resource |
| 175 | + purging (`resources { 'x': purge => true }`). |
| 176 | +- **Idempotency** -- getters read live state, setters change it. A second run with no manifest change |
| 177 | + must report zero changes. This is the single most common review rejection. |
| 178 | +- **Resource API vs legacy** -- prefer `Puppet::ResourceApi.register_type` for new types. If a module |
| 179 | + has already migrated (check the type files), do not reintroduce the legacy `Puppet::Type.newtype` pattern. |
| 180 | +- **Boolean munging** -- Puppet booleans arrive as `:true/:false`, `true/false`, or `'true'/'false'`. |
| 181 | + Normalise explicitly before passing to a shell command. |
| 182 | +- **`commands` / `optional_commands`** -- declare required binaries with `commands` (Puppet fails at |
| 183 | + apply time if missing); use `optional_commands` for tools that may be absent. |
| 184 | + |
| 185 | +--- |
| 186 | + |
| 187 | +## Task module conventions |
| 188 | + |
| 189 | +Applies to modules whose primary interface is Bolt tasks (`tasks/`): |
| 190 | + |
| 191 | +- Each task is `<name>.{rb,sh,ps1}` plus a `<name>.json` metadata/parameter schema. |
| 192 | +- Ruby tasks use the Puppet ruby shebang `#!/opt/puppetlabs/puppet/bin/ruby` and read parameters as |
| 193 | + JSON from `$stdin`. When unit-testing, stub `$stdin`/`JSON.parse` before `require`-ing the file so |
| 194 | + the script-level dispatch block does not run on load. |
| 195 | +- Cross-platform tasks delegate to per-OS implementations (e.g. shell helpers in `files/`). |
| 196 | + |
| 197 | +--- |
| 198 | + |
| 199 | +## metadata.json conventions |
| 200 | + |
| 201 | +- `metadata.json` is the source of truth for the supported OS matrix; `on_supported_os` in specs and |
| 202 | + the CI platform list both derive from it. Update it when adding/removing platform support. |
| 203 | +- Bump `version` per [SemVer](https://semver.org): breaking change -> major, feature -> minor, fix -> patch. |
| 204 | +- Keep `dependencies` ranges current (e.g. `stdlib >= 9.0.0 < 10.0.0`); validate with `rake metadata_lint`. |
| 205 | +- Do not hand-edit machine-managed fields (`pdk-version`, `template-url`, `template-ref`). |
| 206 | +- `requirements` pins the supported Puppet range (typically `>= 8.0.0 < 9.0.0`); keep code and specs in step. |
| 207 | + |
| 208 | +--- |
| 209 | + |
| 210 | +## CI & nightly logs |
| 211 | + |
| 212 | +- CI lives in `.github/workflows/`. Many Puppetlabs modules use the shared workflows from |
| 213 | + [`puppetlabs/cat-github-actions`](https://github.com/puppetlabs/cat-github-actions) -- typically |
| 214 | + `ci.yml` (spec + acceptance on PRs to `main`), `nightly.yml` (scheduled runs against nightly |
| 215 | + builds), and `release.yml` / `release_prep.yml` -- but check what the module actually defines. |
| 216 | +- **Nightly failures:** check the module's **Actions -> "nightly"** runs on GitHub for per-job logs |
| 217 | + first. Nightlies often fail on infrastructure/provisioning rather than module code, so confirm the |
| 218 | + failure is code-related before changing anything. |
| 219 | + |
| 220 | +--- |
| 221 | + |
| 222 | +<% if has_linux -%> |
| 223 | +## Linux specifics |
| 224 | + |
| 225 | +- **Package providers**: behaviour differs by family -- `apt`/`dpkg` (Debian/Ubuntu), `yum`/`dnf`/`rpm` |
| 226 | + (RedHat/CentOS/Rocky/AlmaLinux/Oracle/Amazon), `zypper` (SLES/openSUSE). Don't assume one; gate on |
| 227 | + `$facts['os']['family']` and test each supported family. |
| 228 | +- **Service management**: modern targets are `systemd`; older ones use SysV `init`/`upstart`. Use the |
| 229 | + service provider rather than calling the init system directly. |
| 230 | +- **Paths**: Puppet ships under `/opt/puppetlabs/puppet` (ruby at `/opt/puppetlabs/puppet/bin/ruby`); |
| 231 | + config under `/etc`. Be SELinux-aware on RedHat-family targets (file contexts, `restorecon`). |
| 232 | +- **Acceptance**: Litmus provisions Linux targets as Docker containers; keep test manifests |
| 233 | + container-friendly (avoid hard reboots / kernel-level changes where possible). |
| 234 | + |
| 235 | +<% end -%> |
| 236 | +<% if has_windows -%> |
| 237 | +## Windows specifics |
| 238 | + |
| 239 | +- **Package management**: Chocolatey / MSI / Windows installer providers; not apt/yum. |
| 240 | +- **Services**: managed via the Windows SCM (`sc.exe` / the service provider), no systemd. |
| 241 | +- **Tasks**: PowerShell tasks are `<name>.ps1`; mind execution policy and quoting. |
| 242 | +- **Paths**: Puppet installs under `C:\Program Files\Puppet Labs\Puppet`; use backslash paths and |
| 243 | + `windows`-appropriate file resources. Line endings and case-insensitive paths matter in specs. |
| 244 | + |
| 245 | +<% end -%> |
| 246 | +## Review policy (outcome-based) |
| 247 | + |
| 248 | +PRs are judged on outcomes, not line counts: |
| 249 | + |
| 250 | +- **Idempotency and a clean compile across the supported OS matrix are mandatory.** A change that |
| 251 | + passes unit specs but isn't idempotent in acceptance is not done. |
| 252 | +- New behaviour ships with the test that proves it (unit for logic, acceptance for real-system effect). |
| 253 | +- Don't reduce coverage or weaken the coverage gate to make a build pass. |
| 254 | +- Keep changes scoped to the ticket; flag unrelated cleanup separately. |
| 255 | +- Reference the driving `MODULES-*` (or relevant) ticket in the PR description. |
| 256 | + |
| 257 | +--- |
| 258 | + |
| 259 | +## Project rules |
| 260 | + |
| 261 | +- At the start of a session, review the repository structure and relevant README/REFERENCE before changing anything. |
| 262 | +- Always read the files relevant to the task before suggesting or making a change. |
| 263 | +- Never merge a pull request. |
| 264 | +- Never work directly on `main` or `master`. |
| 265 | +- Never push a branch without explicit instruction. |
| 266 | +- Never delete a file without permission -- this applies even after a blanket "yes to all". |
| 267 | +- Never output, log, save, or hardcode security-sensitive values -- passwords, tokens, API keys, |
| 268 | + private keys, secrets, or credentials of any kind. Do not write them to files, commit messages, or responses. |
0 commit comments