Skip to content

Commit 04a41d4

Browse files
imaqsoodclaude
andcommitted
(MODULES-11829) Add synced moduleroot/CLAUDE.md.erb
Add a module-shaped CLAUDE.md template that syncs into every CAT-supported module on pdk update, giving Claude an explicit conventions file before it touches code. Consolidates the proven pattern from the 5 modules that already hand-maintain a CLAUDE.md (firewall, lvm, package, postgresql, service). - ERB with OS-conditional content driven by the module's metadata.json via @configs['module_metadata']['operatingsystem_support'] (same convention as moduleroot_init/*.erb), with rescue fallbacks. Renders a "Linux specifics" and/or "Windows specifics" section; the Windows section is a stub expanded by MODULES-11830. Empty metadata defaults to Linux, and `has_linux ||= !has_windows` ensures AIX/Solaris/Darwin-only metadata still renders at least one section. - Common content covers project layout, rake commands, rspec-puppet patterns, coverage, litmus/beaker, Ruby code style, Gemfile gem sourcing + version-pin env vars, type/provider DSL gotchas, task-module conventions, metadata.json conventions, CI/nightly log pointers, outcome-based review policy, and the canonical project rules. - moduleroot/ is the community-wide default template, so the file lands in public consumer repos: no internal Jenkins/runbook pointers, no internal Jira stub, and CI guidance does not assume cat-github-actions shared workflows. - Pure ASCII: PDK's diff engine raises invalid-byte-sequence on non-ASCII under a non-UTF-8 locale, so the template avoids unicode entirely. Modules with their own CLAUDE.md opt out via .sync.yml `CLAUDE.md: unmanaged: true`. Validated locally with PDK 3.4.0: pdk convert renders the Linux-only section for lvm and both sections for concat across Linux-only, Linux+Windows, Windows-only, AIX-only, and empty-metadata cases; output is pure ASCII with no leftover ERB tags and is MD022 clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 6c994fc commit 04a41d4

1 file changed

Lines changed: 268 additions & 0 deletions

File tree

moduleroot/CLAUDE.md.erb

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
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

Comments
 (0)