Skip to content

Commit d6daa55

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 owned/expanded by MODULES-11830. Empty metadata defaults to Linux. - 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. - 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 Linux-only section for lvm and both sections for concat; output is ASCII with no leftover ERB tags. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 1514103 commit d6daa55

1 file changed

Lines changed: 265 additions & 0 deletions

File tree

moduleroot/CLAUDE.md.erb

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

0 commit comments

Comments
 (0)