Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .ci/fixtures/new_provider_sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ Gemfile:
spec/spec_helper.rb:
mock_with: ':rspec'
.rubocop.yml:
default_configs:
cop_overrides:
Performance/CaseWhenSplat:
Enabled: false
4 changes: 2 additions & 2 deletions .ci/test_script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ pdk new module new_module --template-url="file://$TEMPLATE_PR_DIR" --template-re
pushd new_module
grep template < metadata.json
cp "$TEMPLATE_PR_DIR/.ci/fixtures/new_provider_sync.yml" ./.sync.yml
grep -A 1 "Performance/CaseWhenSplat" ./.rubocop.yml | grep -q "true" # Ensure that the template is applied
grep -A 5 "AllCops" ./.rubocop.yml | grep -q "NewCops" # Ensure the slim template is applied (NewCops governs cops, not per-cop enumeration)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-update CI assertion is weaker than it looks.

grep -A 5 "AllCops" ./.rubocop.yml | grep -q "NewCops" would pass even if the profile deep-merge were completely broken, because NewCops is hardcoded in the defaults hash unconditionally — it appears in the rendered file regardless of whether profile['configs'] contributed anything.

The old assertion (grep -A 1 "Performance/CaseWhenSplat" | grep -q "true") at least verified that the profile merge contributed a specific cop setting. Consider adding an assertion that checks a tuning from default_configs actually appears — e.g. checking that Style/TrailingCommaInHashLiteral or RSpec/DescribeClass appears in the rendered file with the expected value.

pdk update --force
grep -A 1 "Performance/CaseWhenSplat" ./.rubocop.yml | grep -q "false" # Ensure that the update command changes the template
grep -A 1 "Performance/CaseWhenSplat" ./.rubocop.yml | grep -q "false" # Ensure the cop_overrides override is rendered after update
pdk new class new_module
pdk new defined_type test_type
pdk new fact test_fact || true # not available in pdk 1.18 yet
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@
# Rubocop profile builder temporary files
/rubocop/.rubocop.yml
/rubocop/.rubocop_todo.yml
# GSD planning docs (kept local, not tracked)
/.planning/
19 changes: 7 additions & 12 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ This repository contains the default templates used by [Puppet Development Kit (
- `moduleroot/`: Full module templates deployed on `pdk new module`, `pdk convert`, and `pdk update`. Enforces boilerplate for central files.
- `moduleroot_init/`: Skeleton templates deployed only when the target module does not yet exist (required by `pdk convert`; do not remove).
- `object_templates/`: Templates for generated objects (classes, defined types, tasks, plans, functions, transports, providers).
- `rubocop/`: RuboCop profiles (`cleanup_cops.yml`, `defaults-1.50.0.yml`) and the `profile_builder.rb` tool for regenerating them.
- `config_defaults.yml`: The canonical source of default template configuration consumed by PDK.
- `.ci/`: CI scripts (`install_pdk.sh`, `test_script.sh`) and fixtures.

Expand Down Expand Up @@ -39,13 +38,6 @@ pdk test unit
popd
```

Regenerate RuboCop defaults after a RuboCop version bump (run from `rubocop/`):

```bash
cd rubocop
bundle exec ruby profile_builder.rb
```

## Architecture: How Templates Are Rendered

PDK merges two YAML files to produce a configuration hash before rendering any template:
Expand All @@ -72,11 +64,14 @@ The `unmanaged: true` key tells PDK to leave a file untouched; `delete: true` te

## RuboCop Profile System

`rubocop/` contains the tooling for the `.rubocop.yml` template that PDK deploys into modules:
`moduleroot/.rubocop.yml.erb` uses `deep_merge` to layer configuration in the order `defaults -> profile['configs'] -> cop_overrides` before rendering the final `.rubocop.yml`:

- `default_configs`: a YAML anchor in `config_defaults.yml` holding the base EnforcedStyle tunings that the `'on'` profile reuses as its `configs` (resolved when `config_defaults.yml` is assembled, not read directly at render time). The anchor also tunes several PDK / rspec-puppet-convention cops (e.g. trailing-comma style, the rspec-puppet describe/context/double idioms) so that freshly-scaffolded modules validate clean under `NewCops: enable`. Each such tuning carries an inline `Description:` rationale explaining why it exists; do NOT remove those `Description:` lines, as they prevent future drift and accidental re-enabling.
- `profiles`: defined in `config_defaults.yml` under `.rubocop.yml.profiles`. Two profiles exist: `'on'` (canonical; `NewCops: enable`) and `'off'` (`DisabledByDefault: true`, `NewCops: disable`). The names `cleanups_only`, `strict`, and `hardcore` are backward-compatible aliases that resolve to `'on'`.
- `cop_overrides`: the authoritative per-module override surface, merged last. Use `CopName: { Enabled: false }` to disable a cop; use the knockout prefix `---` to remove an override. The same `---` prefix applies to both PDK's `.sync.yml` overlay (the outer PDK merge) and this template's internal `defaults -> profile -> cop_overrides` deep-merge, so there is one knockout prefix everywhere.
- `selected_profile`: chooses which profile is active (ships as `strict`, a backward-compatible alias that resolves to `'on'`). Set to `'off'` to disable all cops.

- `defaults-1.50.0.yml`: Auto-generated list of all enabled/disabled cops for RuboCop 1.50.0. Regenerate with `profile_builder.rb` after a version update.
- `cleanup_cops.yml`: Curated set of cleanup cops included in the `strict` and `hardcore` profiles.
- Profiles (`cleanups_only`, `strict`, `hardcore`, `off`) are defined in `config_defaults.yml` under `.rubocop.yml.profiles` and selected via `selected_profile`.
**Future improvements:** The convention tunings above cover the object types that `.ci/test_script.sh` actually scaffolds and validates. Object types NOT currently exercised by CI -- notably `plan` and the legacy V1 `function` -- were intentionally left untouched: there is no scaffold coverage to validate a change against, so fixing them speculatively would be unverifiable. They are candidates for future template-validation coverage once CI scaffolds them.

## Packaging and Release Context

Expand Down
32 changes: 25 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,32 @@ Gemfile:

### Manage Rubocop rules

To disable or enable certain Rubocop rules, use the following structure:
Use `cop_overrides` in `.sync.yml` to override individual cops. Entries in `cop_overrides` are merged last (after the built-in defaults and the selected profile's configs), making it the authoritative per-module override surface.

To disable a cop:

```yaml
.rubocop.yml:
default_configs:
cop_overrides:
Style/Documentation:
Enabled: false
```

To remove a cop override entirely (knockout):

```yaml
.rubocop.yml:
cop_overrides:
Style/Documentation: '---'
```

To select a profile:

```yaml
.rubocop.yml:
selected_profile: 'off'
```

## PDK default config values

The following is a description and explanation of each of the keys within `.config_defaults.yml`. This will help clarify the default settings that are applied by PDK.
Expand Down Expand Up @@ -246,14 +263,15 @@ In this example the automated release prep workflow is triggered every Saturday
| Key | Description |
| :------------- |:--------------|
|include\_todos|Allows you to use rubocop's "TODOs" to temporarily skip checks by setting this to `true`. See rubocop's `--auto-gen-config` option for details. Defaults to `false`.|
|selected\_profile|Allows you to define which profile is used by default, which is set to `strict`, which is fully defined within the `profiles` section.|
|default\_configs |Allows you to define the default configuration of which cops will run. Includes the full name of the cop followed by a description of it and an enforced style. Can also make use of the key `excludes` to exclude any files from that specific cop.|
|cleanup\_cops |Defines a set of cleanup cops to then be included within a rubocop profile. Cops are defined by their full name, and further configuration can be done by specifying secondary keys. By default we specify a large amount of cleanup cops using their default configuration.|
|profiles |Defines the profiles that can be enabled and used within rubocop through the `selected_profile` option. By default we have set up three profiles: cleanups\_only, strict, hardcore and off.|
|selected\_profile|Selects the active RuboCop profile. The canonical value is `'on'`, which enables all cops via `NewCops: enable`. The names `cleanups_only`, `strict`, and `hardcore` are backward-compatible aliases that resolve to `'on'`. Set to `'off'` to disable all cops (`DisabledByDefault: true`).|
|default\_configs|A YAML anchor in `config_defaults.yml` holding the base EnforcedStyle tunings that the `'on'` profile reuses as its `configs`. It is resolved when `config_defaults.yml` is assembled, not read directly at render time, so setting it in `.sync.yml` has no effect on the generated `.rubocop.yml`. To adjust cop settings per module, use `cop_overrides`.|
|cop\_overrides|Authoritative per-module override surface. Merged last (precedence: `defaults -> profile configs -> cop_overrides`). Use `CopName: { Enabled: false }` to disable a cop; use the knockout prefix `---` (e.g. `CopName: '---'`) to remove an override entirely.|
|profiles|Defines the profile configurations. Two profiles are provided: `'on'` (canonical; sets `NewCops: enable`, with its `configs` sourced from the `default_configs` anchor) and `'off'` (`DisabledByDefault: true`, `NewCops: disable`). The named aliases `cleanups_only`, `strict`, and `hardcore` resolve to `'on'`.|
|TargetRubyVersion|Overrides the `AllCops.TargetRubyVersion` in the rendered `.rubocop.yml`. Defaults to `3.1`.|

#### Puppet 9 subprocess-creation detection

The `strict` profile enables two security cops that flag the pipe-based subprocess creation removed in Ruby 4.0 (which ships with Puppet 9). See [Ruby #19630](https://bugs.ruby-lang.org/issues/19630).
These `Security/*` cops are enabled implicitly via `AllCops.NewCops: enable` for any non-`'off'` profile (i.e. `'on'` and all its aliases: `cleanups_only`, `strict`, `hardcore`) and are disabled when `selected_profile: 'off'`. They can be pinned or overridden per module via `cop_overrides` regardless of profile. See [Ruby #19630](https://bugs.ruby-lang.org/issues/19630).

| Cop | Flags | Recommended replacement |
| :-- | :---- | :---------------------- |
Expand Down
Loading
Loading