cem_acpt 0.11.0 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +8 -0
- data/.worktreeinclude +1 -0
- data/CLAUDE.md +64 -25
- data/Gemfile.lock +1 -1
- data/README.md +20 -7
- data/docs/ARCHITECTURE.md +1042 -0
- data/docs/rfcs/0000-template.md +54 -0
- data/docs/rfcs/0001-fix-bolt-missing-skip-path.md +105 -0
- data/docs/rfcs/0002-fix-default-character-substitutions.md +119 -0
- data/docs/rfcs/0003-windows-image-builder-template.md +110 -0
- data/docs/rfcs/0004-image-name-truncation-off-by-one.md +108 -0
- data/docs/rfcs/0005-os-dispatch-replace-windows-heuristic.md +117 -0
- data/docs/rfcs/0006-configurable-windows-bucket.md +96 -0
- data/docs/rfcs/0007-logging-quiet-and-typos.md +121 -0
- data/docs/rfcs/0008-namespace-platform-classes.md +110 -0
- data/docs/rfcs/0009-bolt-log-formatter-cleanup.md +111 -0
- data/docs/rfcs/0010-dead-code-cleanup.md +83 -0
- data/docs/rfcs/0011-provisioner-factory-consistency.md +89 -0
- data/docs/rfcs/README.md +34 -0
- data/lib/cem_acpt/cli.rb +10 -1
- data/lib/cem_acpt/config/cem_acpt.rb +4 -1
- data/lib/cem_acpt/image_builder/errors.rb +24 -0
- data/lib/cem_acpt/image_builder/provision_commands.rb +15 -3
- data/lib/cem_acpt/image_builder.rb +29 -2
- data/lib/cem_acpt/image_name_builder.rb +8 -1
- data/lib/cem_acpt/platform/gcp.rb +112 -106
- data/lib/cem_acpt/platform.rb +21 -19
- data/lib/cem_acpt/provision/terraform/linux.rb +1 -1
- data/lib/cem_acpt/provision/terraform/os_data.rb +23 -0
- data/lib/cem_acpt/provision/terraform/windows.rb +7 -1
- data/lib/cem_acpt/provision/terraform.rb +20 -16
- data/lib/cem_acpt/test_runner/log_formatter/bolt_summary_results_formatter.rb +2 -1
- data/lib/cem_acpt/test_runner/log_formatter.rb +0 -1
- data/lib/cem_acpt/test_runner.rb +21 -8
- data/lib/cem_acpt/utils/winrm_runner.rb +4 -3
- data/lib/cem_acpt/utils.rb +0 -12
- data/lib/cem_acpt/version.rb +1 -1
- data/lib/cem_acpt.rb +19 -7
- data/lib/terraform/gcp/linux/main.tf +6 -1
- data/lib/terraform/image/gcp/linux/main.tf +8 -1
- data/specifications/CEM-6713.md +165 -0
- data/specifications/CEM-6714.md +271 -0
- data/specifications/CEM-6715.md +133 -0
- data/specifications/CEM-6716.md +160 -0
- data/specifications/CEM-6717.md +239 -0
- data/specifications/CEM-6718.md +120 -0
- data/specifications/CEM-6719.md +173 -0
- metadata +26 -11
- data/.claude/settings.local.json +0 -7
- data/lib/cem_acpt/action_result.rb +0 -91
- data/lib/cem_acpt/puppet_helpers.rb +0 -38
- data/lib/cem_acpt/test_runner/log_formatter/bolt_error_formatter.rb +0 -65
- data/lib/cem_acpt/test_runner/log_formatter/bolt_output_formatter.rb +0 -54
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# CEM-6715 — Make the Windows GCS bucket configurable
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Replace the hard-coded `gs://win_cem_acpt` bucket in the Windows test
|
|
6
|
+
path with a configurable value (`platform.gcp.windows_bucket`),
|
|
7
|
+
defaulting to `win_cem_acpt` so internal puppetlabs users see no
|
|
8
|
+
behavior change. Surface a `--windows-bucket` CLI override.
|
|
9
|
+
|
|
10
|
+
Implements RFC 0006 (`docs/rfcs/0006-configurable-windows-bucket.md`).
|
|
11
|
+
|
|
12
|
+
## Functional Behavior
|
|
13
|
+
|
|
14
|
+
### Config schema
|
|
15
|
+
- New nested key `platform.gcp.windows_bucket`.
|
|
16
|
+
- Default: `'win_cem_acpt'` (preserves current behavior).
|
|
17
|
+
- Read via the existing `config.get('platform.gcp.windows_bucket')`
|
|
18
|
+
dot-notation API.
|
|
19
|
+
|
|
20
|
+
### CLI flag
|
|
21
|
+
- New `--windows-bucket BUCKET` long flag (no short alias) on the
|
|
22
|
+
`cem_acpt` command (not `cem_acpt_image` — image-build path does not
|
|
23
|
+
use the bucket).
|
|
24
|
+
- Sets `options[:platform][:gcp][:windows_bucket] = BUCKET`, which
|
|
25
|
+
merges into config above the user config / config-file layer.
|
|
26
|
+
|
|
27
|
+
### Runtime use in `test_runner.rb`
|
|
28
|
+
- New private helper `windows_bucket_uri` that returns
|
|
29
|
+
`"gs://#{config.get('platform.gcp.windows_bucket')}"` and raises a
|
|
30
|
+
clear error if the configured value is `nil` or empty.
|
|
31
|
+
- `upload_module_to_bucket` (line 386) uses `windows_bucket_uri` instead
|
|
32
|
+
of the literal `'gs://win_cem_acpt'`.
|
|
33
|
+
- `cleanup_bucket` continues to operate on `@run_data[:win_remote_module_path]`
|
|
34
|
+
(unchanged — already derived from upload).
|
|
35
|
+
- `WinNode` (in `lib/cem_acpt/utils/winrm_runner.rb`) is updated to
|
|
36
|
+
accept the bucket URI and use it on its own hardcoded line 83
|
|
37
|
+
(`gcloud storage cp gs://win_cem_acpt/#{@mod_name} ...`). Without this
|
|
38
|
+
fix, the RFC's stated goal is not actually achieved.
|
|
39
|
+
|
|
40
|
+
## Input/Output Contracts
|
|
41
|
+
|
|
42
|
+
### `CemAcpt::TestRunner::Runner#windows_bucket_uri` (private)
|
|
43
|
+
- Input: none (reads from `config`)
|
|
44
|
+
- Output: `String` of the form `"gs://<bucket>"`
|
|
45
|
+
- Raises: `RuntimeError` (message: `'platform.gcp.windows_bucket is not configured'`)
|
|
46
|
+
when the config value is nil or an empty string.
|
|
47
|
+
|
|
48
|
+
### `CemAcpt::Utils::WinRMRunner::WinNode.new`
|
|
49
|
+
- New signature: `initialize(login_info, mod_name, bucket_uri)`
|
|
50
|
+
- `bucket_uri` is the full URI string, e.g. `'gs://win_cem_acpt'`,
|
|
51
|
+
passed in from the runner. The class no longer hard-codes the bucket.
|
|
52
|
+
|
|
53
|
+
### CLI
|
|
54
|
+
- `--windows-bucket BUCKET` writes to nested options as
|
|
55
|
+
`{ platform: { gcp: { windows_bucket: BUCKET } } }`.
|
|
56
|
+
|
|
57
|
+
## Edge Cases
|
|
58
|
+
|
|
59
|
+
- **Empty / nil bucket value (e.g. user sets `windows_bucket: ''` in
|
|
60
|
+
YAML).** `windows_bucket_uri` raises with a clear message before any
|
|
61
|
+
`gcloud` call is attempted.
|
|
62
|
+
- **Bucket value with a `gs://` prefix** (e.g. user pastes a URL).
|
|
63
|
+
Out of scope. We expect a plain bucket name. Document the expectation
|
|
64
|
+
in the README and rely on `gcloud` to fail loudly if a malformed URI
|
|
65
|
+
is passed. (Avoiding silent normalization keeps the contract simple.)
|
|
66
|
+
- **Linux-only test runs.** Neither `upload_module_to_bucket` nor
|
|
67
|
+
`cleanup_bucket` runs (gated by `tests.first.include?('windows')` and
|
|
68
|
+
`@run_data[:win_remote_module_path]` being set). The new config key
|
|
69
|
+
is unused — no error is raised.
|
|
70
|
+
- **CLI flag interacting with `-O platform.gcp.windows_bucket=foo`.**
|
|
71
|
+
`-O` is parsed to nested options before `--windows-bucket`. If both
|
|
72
|
+
are supplied, the later (in argv order) wins because `OptionParser`
|
|
73
|
+
processes them sequentially and both write to the same key. Not a
|
|
74
|
+
new pattern; matches existing flag/`-O` interactions.
|
|
75
|
+
|
|
76
|
+
## Constraints / Invariants
|
|
77
|
+
|
|
78
|
+
- Default behavior unchanged for any existing user that does not set
|
|
79
|
+
`platform.gcp.windows_bucket`.
|
|
80
|
+
- `valid_keys` validation: `platform` is already in
|
|
81
|
+
`BASE_VALID_KEYS`, so nested `gcp.windows_bucket` is implicitly
|
|
82
|
+
accepted (validation is top-level only). No schema gate to update.
|
|
83
|
+
- Existing `Config::CemAcpt#defaults` already has `platform: { name: 'gcp' }`
|
|
84
|
+
— extend it to include `gcp: { windows_bucket: 'win_cem_acpt' }`.
|
|
85
|
+
|
|
86
|
+
## Error Handling
|
|
87
|
+
|
|
88
|
+
- Missing/empty bucket → `RuntimeError` from `windows_bucket_uri`,
|
|
89
|
+
raised before any `gcloud` invocation.
|
|
90
|
+
- `gcloud` upload/remove failures continue to be handled exactly as
|
|
91
|
+
today (`Shell.run_cmd` raises on upload; `raise_on_error: false` on
|
|
92
|
+
cleanup).
|
|
93
|
+
|
|
94
|
+
## Non-Goals
|
|
95
|
+
|
|
96
|
+
- Refactoring how the Windows path detects a Windows run
|
|
97
|
+
(`tests.first.include?('windows')`). Out of scope; covered by
|
|
98
|
+
separate RFCs.
|
|
99
|
+
- Generalizing the bucket idea to other platforms (no AWS S3, etc.).
|
|
100
|
+
- Validating bucket-name format (`gcloud` does that already).
|
|
101
|
+
- Reworking `WinNode` to take a richer config object — minimum
|
|
102
|
+
threading only.
|
|
103
|
+
|
|
104
|
+
## Acceptance Criteria
|
|
105
|
+
|
|
106
|
+
From the RFC:
|
|
107
|
+
|
|
108
|
+
- [x] Config schema (`defaults`) extended with
|
|
109
|
+
`platform.gcp.windows_bucket = 'win_cem_acpt'`.
|
|
110
|
+
- [x] CLI flag `--windows-bucket BUCKET` parsed in `Cli`.
|
|
111
|
+
- [x] All references to the literal `'gs://win_cem_acpt'` removed
|
|
112
|
+
from `test_runner.rb` **and** `utils/winrm_runner.rb`.
|
|
113
|
+
- [x] RSpec coverage for `windows_bucket_uri` (default, override,
|
|
114
|
+
empty/nil) and that `upload_module_to_bucket` /
|
|
115
|
+
`cleanup_bucket` use the configured bucket.
|
|
116
|
+
- [x] `docs/ARCHITECTURE.md` §13 updated to describe the new key
|
|
117
|
+
and the line-220 ASCII diagram updated.
|
|
118
|
+
- [x] README updated under "Testing with sce_windows" to mention
|
|
119
|
+
the new key/flag.
|
|
120
|
+
|
|
121
|
+
## Files Touched
|
|
122
|
+
|
|
123
|
+
- `lib/cem_acpt/config/cem_acpt.rb` — extend `platform` defaults.
|
|
124
|
+
- `lib/cem_acpt/cli.rb` — add `--windows-bucket` flag.
|
|
125
|
+
- `lib/cem_acpt/test_runner.rb` — add `windows_bucket_uri`, use it,
|
|
126
|
+
pass to `WinNode`.
|
|
127
|
+
- `lib/cem_acpt/utils/winrm_runner.rb` — accept bucket URI, drop
|
|
128
|
+
hardcoded literal.
|
|
129
|
+
- `spec/cem_acpt/test_runner_spec.rb` — coverage for the new helper
|
|
130
|
+
and call sites.
|
|
131
|
+
- `docs/ARCHITECTURE.md` — §13 prose + line-220 diagram.
|
|
132
|
+
- `README.md` — document new key/flag in the sce_windows section.
|
|
133
|
+
- `specifications/CEM-6715.md` — this file.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# CEM-6716 — Tighten `--quiet` semantics and fix logging typos
|
|
2
|
+
|
|
3
|
+
Implements RFC 0007 (`docs/rfcs/0007-logging-quiet-and-typos.md`).
|
|
4
|
+
|
|
5
|
+
## Summary
|
|
6
|
+
|
|
7
|
+
Two related logging defects:
|
|
8
|
+
|
|
9
|
+
1. `--quiet` is silently a no-op when used without `--log-file`. The
|
|
10
|
+
CI-mode override that re-adds `$stdout` is undocumented in
|
|
11
|
+
`--help`.
|
|
12
|
+
2. `lib/cem_acpt/test_runner.rb#upload_module_to_bucket` (line 401)
|
|
13
|
+
references `@run_data[:module_pakage_path]` — a typo for
|
|
14
|
+
`module_package_path` — which interpolates to an empty string in
|
|
15
|
+
the log line.
|
|
16
|
+
|
|
17
|
+
Fix both, document the CI-mode override, add coverage.
|
|
18
|
+
|
|
19
|
+
## Functional Behavior
|
|
20
|
+
|
|
21
|
+
### `lib/cem_acpt.rb#initialize_logger!`
|
|
22
|
+
|
|
23
|
+
Rewrite the body so that:
|
|
24
|
+
|
|
25
|
+
- `--quiet` always drops `$stdout` from `logdevs`.
|
|
26
|
+
- `--log-file FILE` always adds the file to `logdevs`.
|
|
27
|
+
- If `config.ci_mode?` is true and `$stdout` was dropped, re-add it
|
|
28
|
+
and emit a one-time `warn`-level message that `--quiet` was
|
|
29
|
+
overridden so `::group::` directives can reach Actions stdout.
|
|
30
|
+
- If `logdevs` is still empty after the above (i.e. `--quiet` outside
|
|
31
|
+
CI with no `--log-file`), raise a `RuntimeError` with the message
|
|
32
|
+
`--quiet without --log-file would silence all output; pass --log-file or drop --quiet`.
|
|
33
|
+
- Verbose / level / formatter handling is unchanged.
|
|
34
|
+
|
|
35
|
+
The warn is emitted via the freshly-built logger, which is safe
|
|
36
|
+
because the warning destination is exactly the destination the user
|
|
37
|
+
will end up with (`$stdout` and/or the log file). No new "warn-once"
|
|
38
|
+
helper is introduced; `initialize_logger!` runs once per process, so
|
|
39
|
+
"once" is implicit.
|
|
40
|
+
|
|
41
|
+
### `lib/cem_acpt/test_runner.rb#upload_module_to_bucket`
|
|
42
|
+
|
|
43
|
+
Rename `@run_data[:module_pakage_path]` → `@run_data[:module_package_path]`
|
|
44
|
+
in the `logger.info` call on line 401. The non-typoed key on the next
|
|
45
|
+
line already exists in `run_data` (set in `pre_provision_test_nodes`
|
|
46
|
+
at line 188).
|
|
47
|
+
|
|
48
|
+
### CLI help (`lib/cem_acpt/cli.rb`)
|
|
49
|
+
|
|
50
|
+
Update the `-q / --quiet` description from `'Do not log to stdout'`
|
|
51
|
+
to mention the CI override and the requirement to pair with
|
|
52
|
+
`--log-file`. Target text:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
'Drop stdout (requires --log-file outside CI; CI mode keeps stdout for ::group:: support)'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Documentation (`docs/ARCHITECTURE.md` §15)
|
|
59
|
+
|
|
60
|
+
Replace the "conditional / silently a no-op" prose with the new
|
|
61
|
+
behavior:
|
|
62
|
+
|
|
63
|
+
- `--quiet` *with* `--log-file FILE`: stdout dropped, logs go only to
|
|
64
|
+
`FILE`.
|
|
65
|
+
- `--quiet` *without* `--log-file`: `RuntimeError` at startup with a
|
|
66
|
+
clear message.
|
|
67
|
+
- CI mode (`GITHUB_ACTIONS`, `CI`, or `-I`): `--quiet` is overridden
|
|
68
|
+
so `$stdout` is re-added; a single warn line is emitted at startup
|
|
69
|
+
noting the override.
|
|
70
|
+
|
|
71
|
+
Also remove or update the §19 item 6 ("worth a behavioral sanity
|
|
72
|
+
check") — it has been resolved by this fix.
|
|
73
|
+
|
|
74
|
+
## Input/Output Contracts
|
|
75
|
+
|
|
76
|
+
### `CemAcpt#initialize_logger!` (private, instance method on the singleton)
|
|
77
|
+
|
|
78
|
+
- **Input:** none (reads `@config`).
|
|
79
|
+
- **Output:** `CemAcpt::Logging::MultiLogger` (unchanged return shape).
|
|
80
|
+
- **Raises:**
|
|
81
|
+
- `RuntimeError("Config must be loaded before logger can be initialized")` — unchanged.
|
|
82
|
+
- `RuntimeError("--quiet without --log-file would silence all output; pass --log-file or drop --quiet")` — new.
|
|
83
|
+
|
|
84
|
+
### `lib/cem_acpt/test_runner.rb#upload_module_to_bucket`
|
|
85
|
+
|
|
86
|
+
No signature change; only the interpolation key in the log line changes.
|
|
87
|
+
|
|
88
|
+
## Edge Cases
|
|
89
|
+
|
|
90
|
+
| Case | `--quiet` | `--log-file` | CI mode | Result |
|
|
91
|
+
| --- | --- | --- | --- | --- |
|
|
92
|
+
| 1 | unset | unset | no | logs to `$stdout` |
|
|
93
|
+
| 2 | unset | set | no | logs to `$stdout` and file |
|
|
94
|
+
| 3 | set | unset | no | **raises** at startup |
|
|
95
|
+
| 4 | set | set | no | logs only to file |
|
|
96
|
+
| 5 | unset | unset | yes | logs to `$stdout` (CI does not add a duplicate) |
|
|
97
|
+
| 6 | unset | set | yes | logs to `$stdout` and file |
|
|
98
|
+
| 7 | set | unset | yes | logs to `$stdout` (override warn emitted) |
|
|
99
|
+
| 8 | set | set | yes | logs to `$stdout` and file (override warn emitted) |
|
|
100
|
+
|
|
101
|
+
CI mode is detected by `Config::Base#ci_mode?` (sees env vars
|
|
102
|
+
`GITHUB_ACTIONS` / `CI` or the `-I` flag); the same source of truth
|
|
103
|
+
as today.
|
|
104
|
+
|
|
105
|
+
## Constraints / Invariants
|
|
106
|
+
|
|
107
|
+
- The logger is always the `MultiLogger` returned by `new_logger` —
|
|
108
|
+
no logger-shape changes leak to callers.
|
|
109
|
+
- The warn-once is emitted *after* the logger is built and *before*
|
|
110
|
+
`initialize_logger!` returns, so it lands in the same destinations
|
|
111
|
+
as the rest of the run's logs.
|
|
112
|
+
- Behavior outside `initialize_logger!` (level, verbose, signal
|
|
113
|
+
handlers, trap-context handling) is untouched.
|
|
114
|
+
|
|
115
|
+
## Error Handling
|
|
116
|
+
|
|
117
|
+
- The new `RuntimeError` is allowed to bubble out of `run_cem_acpt` /
|
|
118
|
+
`run_cem_acpt_image`. `OptionParser` already surfaces option errors
|
|
119
|
+
by writing to stderr and exiting non-zero; we want analogous
|
|
120
|
+
behavior here. We do **not** catch and re-raise.
|
|
121
|
+
|
|
122
|
+
## Non-Goals
|
|
123
|
+
|
|
124
|
+
- Promoting `--quiet` to fully silence logs in CI (would break
|
|
125
|
+
`::group::`).
|
|
126
|
+
- Adding a `--silent` / "really quiet" mode.
|
|
127
|
+
- Changing `--verbose`, `--debug`, or `-I` behavior.
|
|
128
|
+
- Refactoring `MultiLogger` or `Logger`.
|
|
129
|
+
- Making `logger_warn_once` a real helper — not needed because
|
|
130
|
+
`initialize_logger!` runs once per process.
|
|
131
|
+
|
|
132
|
+
## Acceptance Criteria
|
|
133
|
+
|
|
134
|
+
From the RFC:
|
|
135
|
+
|
|
136
|
+
- [ ] `lib/cem_acpt.rb#initialize_logger!` rewritten; comment cites
|
|
137
|
+
RFC 0007.
|
|
138
|
+
- [ ] `:module_pakage_path` typo fixed in `test_runner.rb`.
|
|
139
|
+
- [ ] RSpec covers the four logging combinations listed in RFC
|
|
140
|
+
(`-q`, `-q --log-file`, `--log-file` alone, `-q` in CI mode)
|
|
141
|
+
plus the new failure case (`-q` outside CI without
|
|
142
|
+
`--log-file`).
|
|
143
|
+
- [ ] RSpec smoke test for `upload_module_to_bucket` asserts the log
|
|
144
|
+
line contains the actual `module_package_path`.
|
|
145
|
+
- [ ] `docs/ARCHITECTURE.md` §15 updated; §19 item 6 removed (or
|
|
146
|
+
noted as resolved).
|
|
147
|
+
- [ ] `--help` output for `--quiet` mentions the CI override and the
|
|
148
|
+
`--log-file` requirement.
|
|
149
|
+
- [ ] `bundle exec rake spec` and `rubocop` are clean.
|
|
150
|
+
|
|
151
|
+
## Files Touched
|
|
152
|
+
|
|
153
|
+
- `lib/cem_acpt.rb` — rewrite `initialize_logger!`.
|
|
154
|
+
- `lib/cem_acpt/test_runner.rb` — fix typo in
|
|
155
|
+
`upload_module_to_bucket`.
|
|
156
|
+
- `lib/cem_acpt/cli.rb` — update `--quiet` description.
|
|
157
|
+
- `docs/ARCHITECTURE.md` — §15 prose, §19 item 6.
|
|
158
|
+
- `spec/cem_acpt_spec.rb` — `initialize_logger!` coverage.
|
|
159
|
+
- `spec/cem_acpt/test_runner_spec.rb` — log-line smoke test.
|
|
160
|
+
- `specifications/CEM-6716.md` — this file.
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# CEM-6717 — Namespace dynamically-created platform classes
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Move the dynamically-generated platform class (currently `::Gcp`) out
|
|
6
|
+
of the top-level constant table and into the `CemAcpt::Platform`
|
|
7
|
+
namespace. Also move the per-platform mixin out of top-level
|
|
8
|
+
(currently `::Platform`, defined in `lib/cem_acpt/platform/gcp.rb`)
|
|
9
|
+
into a sibling namespace `CemAcpt::Platform::Mixin::<Name>`. Use
|
|
10
|
+
`const_defined?(name, false)` for the cache check to avoid ancestor
|
|
11
|
+
lookup, and camel-case multi-word platform names correctly. Update
|
|
12
|
+
`docs/ARCHITECTURE.md` §6 and §19 to reflect the new behavior.
|
|
13
|
+
|
|
14
|
+
Implements RFC 0008 (`docs/rfcs/0008-namespace-platform-classes.md`),
|
|
15
|
+
plus the additional mixin relocation discussed during spec review.
|
|
16
|
+
|
|
17
|
+
## Functional Behavior
|
|
18
|
+
|
|
19
|
+
### `CemAcpt::Platform::Mixin` (new namespace)
|
|
20
|
+
|
|
21
|
+
Add a new namespace module `CemAcpt::Platform::Mixin` that holds the
|
|
22
|
+
per-platform mixin modules. The dynamic class will be loaded from the
|
|
23
|
+
sibling `CemAcpt::Platform::<Name>` constant; the mixin lives at
|
|
24
|
+
`CemAcpt::Platform::Mixin::<Name>`. Splitting the two avoids a
|
|
25
|
+
self-shadowing constant (we cannot have both
|
|
26
|
+
`CemAcpt::Platform::Gcp` the class and `CemAcpt::Platform::Gcp` the
|
|
27
|
+
mixin) and keeps both names purely descriptive.
|
|
28
|
+
|
|
29
|
+
The namespace is declared up front in `lib/cem_acpt/platform.rb`
|
|
30
|
+
alongside the existing `module CemAcpt::Platform`:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
module CemAcpt
|
|
34
|
+
module Platform
|
|
35
|
+
module Mixin; end
|
|
36
|
+
# ... existing content
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### `lib/cem_acpt/platform/gcp.rb`
|
|
42
|
+
|
|
43
|
+
Rename the module from top-level `module Platform` to
|
|
44
|
+
`module CemAcpt::Platform::Mixin::Gcp`. Method bodies, requires, and
|
|
45
|
+
the file path are unchanged.
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
# Before
|
|
49
|
+
module Platform
|
|
50
|
+
def platform_data
|
|
51
|
+
...
|
|
52
|
+
end
|
|
53
|
+
...
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# After
|
|
57
|
+
module CemAcpt
|
|
58
|
+
module Platform
|
|
59
|
+
module Mixin
|
|
60
|
+
module Gcp
|
|
61
|
+
def platform_data
|
|
62
|
+
...
|
|
63
|
+
end
|
|
64
|
+
...
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The full body of the existing top-level `Platform` module — every
|
|
72
|
+
public and private method — moves verbatim into
|
|
73
|
+
`CemAcpt::Platform::Mixin::Gcp`.
|
|
74
|
+
|
|
75
|
+
### `CemAcpt::Platform.platform_class(base_type, platform)`
|
|
76
|
+
|
|
77
|
+
Replace the body of `platform_class` so that:
|
|
78
|
+
|
|
79
|
+
1. `const_name` is built from the platform name with multi-word
|
|
80
|
+
support: `platform.split(/[_-]/).map(&:capitalize).join`
|
|
81
|
+
(e.g. `gcp` → `Gcp`, `aws_govcloud` → `AwsGovcloud`,
|
|
82
|
+
`foo-bar` → `FooBar`).
|
|
83
|
+
2. The cache check looks for the constant **directly on
|
|
84
|
+
`CemAcpt::Platform`**, with the second argument `false` so the
|
|
85
|
+
ancestor chain is not consulted:
|
|
86
|
+
`CemAcpt::Platform.const_defined?(const_name, false)`.
|
|
87
|
+
3. On cache hit, return `CemAcpt::Platform.const_get(const_name, false)`.
|
|
88
|
+
4. On miss:
|
|
89
|
+
a. `require_relative "platform/#{platform}"` to load the file.
|
|
90
|
+
b. Resolve the mixin via
|
|
91
|
+
`mixin = CemAcpt::Platform::Mixin.const_get(const_name, false)`.
|
|
92
|
+
A `NameError` here means the loaded file did not define
|
|
93
|
+
`CemAcpt::Platform::Mixin::<Name>` — let it propagate; the
|
|
94
|
+
message is already actionable.
|
|
95
|
+
c. Build the class with the resolved mixin:
|
|
96
|
+
`Class.new(baseklass) { include mixin }`.
|
|
97
|
+
d. Register via `CemAcpt::Platform.const_set(const_name, klass)`.
|
|
98
|
+
5. No top-level `Object.const_set` call. The `::<Platform>` constant
|
|
99
|
+
must not be created as a side effect.
|
|
100
|
+
|
|
101
|
+
The previous `include Platform` lexical-lookup hack is removed — the
|
|
102
|
+
mixin is now resolved by an explicit `const_get` against the new
|
|
103
|
+
namespace, which is what RFC 0008 was reaching for.
|
|
104
|
+
|
|
105
|
+
### Logging
|
|
106
|
+
|
|
107
|
+
`platform_class` already logs the cache hit / miss / final-class lines.
|
|
108
|
+
Keep them; they remain useful for `-d` runs. The wording can stay the
|
|
109
|
+
same — `class_name` becomes `const_name` in the log.
|
|
110
|
+
|
|
111
|
+
## Input/Output Contracts
|
|
112
|
+
|
|
113
|
+
### `CemAcpt::Platform.platform_class(base_type, platform)`
|
|
114
|
+
- Inputs unchanged: `base_type` is `:base` or `:test`; `platform` is a
|
|
115
|
+
string from the file basenames in `lib/cem_acpt/platform/`.
|
|
116
|
+
- Output unchanged: a `Class` object that subclasses
|
|
117
|
+
`CemAcpt::Platform::Base` (`:base`) or
|
|
118
|
+
`CemAcpt::Platform::TestBase` (`:test`).
|
|
119
|
+
- New invariant: the returned class is reachable via
|
|
120
|
+
`CemAcpt::Platform.const_get(const_name, false)` and is **not**
|
|
121
|
+
registered on `Object`.
|
|
122
|
+
- Existing error semantics preserved: invalid `base_type` raises
|
|
123
|
+
`CemAcpt::Platform::Error`.
|
|
124
|
+
|
|
125
|
+
### Public API (`use`, `get`)
|
|
126
|
+
- No signature changes. Callers continue to receive the same class
|
|
127
|
+
objects. Anything that holds a reference to the returned class keeps
|
|
128
|
+
working; there is no top-level `::Gcp` reference to break.
|
|
129
|
+
|
|
130
|
+
### Platform file contract (new)
|
|
131
|
+
- Each `lib/cem_acpt/platform/<name>.rb` MUST define
|
|
132
|
+
`CemAcpt::Platform::Mixin::<CamelCaseName>` and define
|
|
133
|
+
`#platform_data` and `#node_data` on it.
|
|
134
|
+
- Files are loaded by `platform_class` via `require_relative`; loading
|
|
135
|
+
happens at most once per platform per process.
|
|
136
|
+
|
|
137
|
+
## Edge Cases
|
|
138
|
+
|
|
139
|
+
- **Cache hit across calls.** Memoization still works because
|
|
140
|
+
`CemAcpt::Platform.const_defined?(const_name, false)` returns true
|
|
141
|
+
on the second call. `const_get(..., false)` returns the same object.
|
|
142
|
+
- **Multi-word platform names.** `aws_govcloud` → `AwsGovcloud`;
|
|
143
|
+
`azure-arm` → `AzureArm`. Both the dynamic class
|
|
144
|
+
(`CemAcpt::Platform::AwsGovcloud`) and its mixin
|
|
145
|
+
(`CemAcpt::Platform::Mixin::AwsGovcloud`) follow the same form.
|
|
146
|
+
- **Same name registered elsewhere.** A future `class CemAcpt::Foo` or
|
|
147
|
+
top-level `class ::Gcp` will not collide because:
|
|
148
|
+
- We register on `CemAcpt::Platform`, not `Object`.
|
|
149
|
+
- We use `const_defined?(name, false)`, which ignores ancestors.
|
|
150
|
+
- **Mixin missing or misnamed.** If a platform file does not define
|
|
151
|
+
`CemAcpt::Platform::Mixin::<Name>`, `const_get` raises `NameError`.
|
|
152
|
+
The error is allowed to propagate — its message names the missing
|
|
153
|
+
constant, which is enough for a contributor to fix the file.
|
|
154
|
+
- **Both `:base` and `:test` requested for the same platform.** Today
|
|
155
|
+
the second call returns the cached class regardless of `base_type`
|
|
156
|
+
— the first call's `base_type` wins. This is preserved (it is
|
|
157
|
+
out of scope for this ticket; the RFC does not call it out).
|
|
158
|
+
- **Re-loading.** Tests that exercise the dynamic path multiple times
|
|
159
|
+
may need to clear `CemAcpt::Platform.<Name>` between examples; the
|
|
160
|
+
new spec covers this with a `before`/`after` that removes the
|
|
161
|
+
constant if defined. They should not need to clear `Object`
|
|
162
|
+
constants any more.
|
|
163
|
+
|
|
164
|
+
## Constraints / Invariants
|
|
165
|
+
|
|
166
|
+
- `CemAcpt::Platform::Base` and `CemAcpt::Platform::TestBase` are
|
|
167
|
+
unchanged.
|
|
168
|
+
- `platforms` enumeration logic is unchanged.
|
|
169
|
+
- `const_defined?(name, false)` does not look up ancestors, so a
|
|
170
|
+
reopened `Object::Platform` (unlikely but possible) cannot leak in.
|
|
171
|
+
- The mixin contract is now explicit: each platform file defines a
|
|
172
|
+
module under `CemAcpt::Platform::Mixin`. There is exactly one
|
|
173
|
+
platform file in the repo today (`gcp.rb`), so the migration is
|
|
174
|
+
self-contained.
|
|
175
|
+
|
|
176
|
+
## Error Handling
|
|
177
|
+
|
|
178
|
+
- Unknown `base_type` still raises `CemAcpt::Platform::Error` with the
|
|
179
|
+
existing message `"Base type #{base_type} is not supported"`.
|
|
180
|
+
- Unknown `platform` is still rejected by `use` / `get` before reaching
|
|
181
|
+
`platform_class` (existing checks).
|
|
182
|
+
- Missing mixin in a loaded file raises `NameError` from `const_get`.
|
|
183
|
+
No new error classes; the stock `NameError` message identifies the
|
|
184
|
+
missing constant.
|
|
185
|
+
|
|
186
|
+
## Non-Goals
|
|
187
|
+
|
|
188
|
+
- Switching to a registration DSL (`Platform.register :gcp do ... end`).
|
|
189
|
+
The RFC defers this; we keep the dynamic-class approach.
|
|
190
|
+
- Caching `:base` and `:test` variants under separate constant names.
|
|
191
|
+
The cache currently keys on platform alone; preserved.
|
|
192
|
+
- Any change to GCP behavior. `gcp.rb`'s methods move byte-for-byte
|
|
193
|
+
into the new namespace.
|
|
194
|
+
|
|
195
|
+
## Acceptance Criteria
|
|
196
|
+
|
|
197
|
+
From RFC 0008 (`docs/rfcs/0008-namespace-platform-classes.md`), plus
|
|
198
|
+
the mixin relocation:
|
|
199
|
+
|
|
200
|
+
- [ ] `Platform.platform_class` registers the dynamic class under
|
|
201
|
+
`CemAcpt::Platform`, not on `Object`.
|
|
202
|
+
- [ ] No top-level `::Gcp` constant is created at runtime, verified
|
|
203
|
+
by spec.
|
|
204
|
+
- [ ] No top-level `::Platform` mixin is created at runtime — the
|
|
205
|
+
mixin lives at `CemAcpt::Platform::Mixin::Gcp`. Verified by
|
|
206
|
+
spec.
|
|
207
|
+
- [ ] Multi-word platform names (`aws_govcloud`, `azure-arm`) produce
|
|
208
|
+
valid CamelCase constant names.
|
|
209
|
+
- [ ] `docs/ARCHITECTURE.md` §6 and §19 (item 5) updated.
|
|
210
|
+
- [ ] `spec/cem_acpt/platform/gcp_spec.rb` updated to reference the
|
|
211
|
+
new mixin name (`CemAcpt::Platform::Mixin::Gcp`) and continues
|
|
212
|
+
to pass.
|
|
213
|
+
|
|
214
|
+
## Files Touched
|
|
215
|
+
|
|
216
|
+
- `lib/cem_acpt/platform.rb` — declare `module Mixin` namespace;
|
|
217
|
+
rewrite `platform_class` per the proposal.
|
|
218
|
+
- `lib/cem_acpt/platform/gcp.rb` — relocate the mixin from top-level
|
|
219
|
+
`module Platform` to `CemAcpt::Platform::Mixin::Gcp`.
|
|
220
|
+
- `spec/cem_acpt/platform_spec.rb` — **new** spec covering the
|
|
221
|
+
namespace, const-defined behavior, cache, multi-word naming, and
|
|
222
|
+
the absence of top-level `::Gcp` / `::Platform` constants.
|
|
223
|
+
- `spec/cem_acpt/platform/gcp_spec.rb` — update `RSpec.describe`,
|
|
224
|
+
`host.extend(...)`, and any other references from `Platform` to
|
|
225
|
+
`CemAcpt::Platform::Mixin::Gcp`.
|
|
226
|
+
- `docs/ARCHITECTURE.md` — §6 prose update (drop the "top-level
|
|
227
|
+
constant `Gcp`" note, describe the new
|
|
228
|
+
`CemAcpt::Platform::Gcp` / `CemAcpt::Platform::Mixin::Gcp` split)
|
|
229
|
+
and §19 item 5 update / removal.
|
|
230
|
+
- `specifications/CEM-6717.md` — this file.
|
|
231
|
+
|
|
232
|
+
## Test Plan
|
|
233
|
+
|
|
234
|
+
1. `bundle exec rake spec SPEC=spec/cem_acpt/platform_spec.rb` for the
|
|
235
|
+
new namespacing tests.
|
|
236
|
+
2. `bundle exec rake spec SPEC=spec/cem_acpt/platform/gcp_spec.rb` to
|
|
237
|
+
confirm the GCP mixin spec passes after the rename.
|
|
238
|
+
3. `bundle exec rake spec` for the full suite.
|
|
239
|
+
4. `rubocop lib/cem_acpt/platform.rb lib/cem_acpt/platform/gcp.rb spec/cem_acpt/platform_spec.rb spec/cem_acpt/platform/gcp_spec.rb`.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# CEM-6718 — Wire up or remove orphaned Bolt log formatters
|
|
2
|
+
|
|
3
|
+
Implements RFC 0009 (`docs/rfcs/0009-bolt-log-formatter-cleanup.md`),
|
|
4
|
+
**Option B** (delete the orphaned formatters).
|
|
5
|
+
|
|
6
|
+
## Summary
|
|
7
|
+
|
|
8
|
+
Two formatter classes — `BoltOutputFormatter` and `BoltErrorFormatter`
|
|
9
|
+
— exist under `lib/cem_acpt/test_runner/log_formatter/` but are never
|
|
10
|
+
matched by `LogFormatter.new_formatter`'s `case`. `BoltOutputFormatter`
|
|
11
|
+
is `require_relative`'d in `log_formatter.rb` but unused;
|
|
12
|
+
`BoltErrorFormatter` is neither required nor referenced. Both have
|
|
13
|
+
shipped in this dead state for several releases.
|
|
14
|
+
|
|
15
|
+
Per the RFC's recommendation, delete both files and the dangling
|
|
16
|
+
`require_relative`. Add a one-line comment on
|
|
17
|
+
`BoltSummaryResultsFormatter` documenting that it is the canonical
|
|
18
|
+
Bolt formatter. Update `docs/ARCHITECTURE.md` §11 and §19 to reflect
|
|
19
|
+
the cleanup.
|
|
20
|
+
|
|
21
|
+
## Functional Behavior
|
|
22
|
+
|
|
23
|
+
### File deletions
|
|
24
|
+
|
|
25
|
+
- Delete `lib/cem_acpt/test_runner/log_formatter/bolt_output_formatter.rb`.
|
|
26
|
+
- Delete `lib/cem_acpt/test_runner/log_formatter/bolt_error_formatter.rb`.
|
|
27
|
+
|
|
28
|
+
### `lib/cem_acpt/test_runner/log_formatter.rb`
|
|
29
|
+
|
|
30
|
+
Remove the dangling `require_relative`:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
require_relative 'log_formatter/bolt_output_formatter' # remove
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The `case` dispatch is unchanged — it already routes
|
|
37
|
+
`CemAcpt::Bolt::SummaryResults` to `BoltSummaryResultsFormatter` and
|
|
38
|
+
falls through to `StandardErrorFormatter` for `StandardError`.
|
|
39
|
+
|
|
40
|
+
### `lib/cem_acpt/test_runner/log_formatter/bolt_summary_results_formatter.rb`
|
|
41
|
+
|
|
42
|
+
Add a one-line YARD comment above the class declaration noting that
|
|
43
|
+
it is the canonical Bolt formatter. Replace the existing one-line
|
|
44
|
+
comment so the file does not gain extra noise. Target text:
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
# Canonical formatter for Bolt subsystem results. Wired into
|
|
48
|
+
# LogFormatter.new_formatter for CemAcpt::Bolt::SummaryResults.
|
|
49
|
+
class BoltSummaryResultsFormatter < Base
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### `docs/ARCHITECTURE.md`
|
|
53
|
+
|
|
54
|
+
**§11 (Result aggregation & log formatting):** drop the paragraph at
|
|
55
|
+
lines 691–694 that calls out `BoltOutputFormatter` /
|
|
56
|
+
`BoltErrorFormatter` as orphaned files. The formatter table above it
|
|
57
|
+
is unchanged.
|
|
58
|
+
|
|
59
|
+
**§19 (item 6):** remove item 6 entirely (the "BoltOutputFormatter
|
|
60
|
+
and BoltErrorFormatter are dead" entry). Renumber the subsequent item
|
|
61
|
+
(currently item 7, "`lib/terraform/image/gcp/windows/` is an empty
|
|
62
|
+
directory") to item 6.
|
|
63
|
+
|
|
64
|
+
## Input/Output Contracts
|
|
65
|
+
|
|
66
|
+
No public-API changes. The dispatcher signature
|
|
67
|
+
`LogFormatter.new_formatter(result, *args, **_kwargs)` and its return
|
|
68
|
+
contract are unchanged. The deleted classes were never wired in; no
|
|
69
|
+
caller constructs them.
|
|
70
|
+
|
|
71
|
+
## Edge Cases
|
|
72
|
+
|
|
73
|
+
- A caller that pushes a hypothetical `Bolt::Cmd::Output` or
|
|
74
|
+
`Bolt::Cmd::OutputError` onto the results queue would have hit the
|
|
75
|
+
`StandardError` branch (or the `else` raise) before this change and
|
|
76
|
+
continues to do so after. Behavior is unchanged.
|
|
77
|
+
- No spec file references either deleted class (verified by grep);
|
|
78
|
+
removing them does not break the test suite.
|
|
79
|
+
|
|
80
|
+
## Constraints
|
|
81
|
+
|
|
82
|
+
- RuboCop must remain clean.
|
|
83
|
+
- `bundle exec rake spec` must pass with no new failures.
|
|
84
|
+
|
|
85
|
+
## Invariants
|
|
86
|
+
|
|
87
|
+
- `LogFormatter.new_formatter` continues to dispatch the same four
|
|
88
|
+
result types it dispatches today (`Goss::Api::ActionResponse`,
|
|
89
|
+
`Bolt::SummaryResults`, `StandardError`, else-raise).
|
|
90
|
+
- The remaining formatter files under
|
|
91
|
+
`lib/cem_acpt/test_runner/log_formatter/` (`base.rb`,
|
|
92
|
+
`bolt_summary_results_formatter.rb`, `goss_action_response.rb`,
|
|
93
|
+
`goss_error_formatter.rb`, `standard_error_formatter.rb`) are
|
|
94
|
+
unchanged in behavior.
|
|
95
|
+
|
|
96
|
+
## Error Handling
|
|
97
|
+
|
|
98
|
+
No new error paths. The pre-existing `else raise ArgumentError`
|
|
99
|
+
branch in `new_formatter` is unchanged.
|
|
100
|
+
|
|
101
|
+
## Non-Goals
|
|
102
|
+
|
|
103
|
+
- Re-implementing `BoltOutputFormatter` / `BoltErrorFormatter`. If the
|
|
104
|
+
need re-emerges, RFC 0009 documents Option A as the alternative
|
|
105
|
+
path; reintroducing them would be a small, separate patch.
|
|
106
|
+
- Refactoring or restructuring `BoltSummaryResultsFormatter` itself.
|
|
107
|
+
- Touching the Goss formatters.
|
|
108
|
+
- Updating the RFC's status (the RFC remains the historical record of
|
|
109
|
+
the decision; the implementation closes the ticket).
|
|
110
|
+
|
|
111
|
+
## Acceptance Criteria
|
|
112
|
+
|
|
113
|
+
- [ ] `lib/cem_acpt/test_runner/log_formatter/bolt_output_formatter.rb` deleted.
|
|
114
|
+
- [ ] `lib/cem_acpt/test_runner/log_formatter/bolt_error_formatter.rb` deleted.
|
|
115
|
+
- [ ] `require_relative 'log_formatter/bolt_output_formatter'` removed from `lib/cem_acpt/test_runner/log_formatter.rb`.
|
|
116
|
+
- [ ] One-line comment added to `BoltSummaryResultsFormatter`.
|
|
117
|
+
- [ ] `docs/ARCHITECTURE.md` §11 paragraph about orphaned formatters removed.
|
|
118
|
+
- [ ] `docs/ARCHITECTURE.md` §19 item 6 removed; subsequent item renumbered.
|
|
119
|
+
- [ ] `bundle exec rake spec` passes.
|
|
120
|
+
- [ ] `rubocop` is clean.
|