cs-image-system-bash-mod-plugin 0.1.5__tar.gz → 0.1.7__tar.gz
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.
- {cs_image_system_bash_mod_plugin-0.1.5 → cs_image_system_bash_mod_plugin-0.1.7}/.gitignore +4 -0
- {cs_image_system_bash_mod_plugin-0.1.5 → cs_image_system_bash_mod_plugin-0.1.7}/PKG-INFO +110 -133
- {cs_image_system_bash_mod_plugin-0.1.5 → cs_image_system_bash_mod_plugin-0.1.7}/README.md +107 -130
- {cs_image_system_bash_mod_plugin-0.1.5 → cs_image_system_bash_mod_plugin-0.1.7}/pyproject.toml +3 -3
- {cs_image_system_bash_mod_plugin-0.1.5 → cs_image_system_bash_mod_plugin-0.1.7}/src/cs_image_system/bash_mod_plugin/bash_builder.py +8 -10
- {cs_image_system_bash_mod_plugin-0.1.5 → cs_image_system_bash_mod_plugin-0.1.7}/src/cs_image_system/bash_mod_plugin/bash_models.py +10 -8
- {cs_image_system_bash_mod_plugin-0.1.5 → cs_image_system_bash_mod_plugin-0.1.7}/src/cs_image_system/bash_mod_plugin/main.py +4 -2
- {cs_image_system_bash_mod_plugin-0.1.5 → cs_image_system_bash_mod_plugin-0.1.7}/tests/test_bash_provisioner.py +5 -3
- {cs_image_system_bash_mod_plugin-0.1.5 → cs_image_system_bash_mod_plugin-0.1.7}/LICENSE +0 -0
- {cs_image_system_bash_mod_plugin-0.1.5 → cs_image_system_bash_mod_plugin-0.1.7}/src/cs_image_system/bash_mod_plugin/__init__.py +0 -0
- {cs_image_system_bash_mod_plugin-0.1.5 → cs_image_system_bash_mod_plugin-0.1.7}/src/cs_image_system/py.typed +0 -0
|
@@ -13,6 +13,8 @@ build/
|
|
|
13
13
|
.venv/
|
|
14
14
|
.DS_Store
|
|
15
15
|
packages/**/*.code-workspace
|
|
16
|
+
# the IDE rewrites the root workspace file whenever folders are opened: it is the editor's, not the repository's (hygiene XXI)
|
|
17
|
+
/*.code-workspace
|
|
16
18
|
trial
|
|
17
19
|
tfmodules/tftest
|
|
18
20
|
YAML_DUMP.yaml
|
|
@@ -24,3 +26,5 @@ target/
|
|
|
24
26
|
tfplan
|
|
25
27
|
*.tfstate
|
|
26
28
|
*.tfstate.backup
|
|
29
|
+
REEXAMINE*
|
|
30
|
+
TODO2.md
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cs-image-system-bash-mod-plugin
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: The shell modification plugin: inline script, script files and an ensure form become Packer shell provisioners
|
|
5
5
|
Project-URL: Repository, https://github.com/infrastructurebuilder/cs-image-system-3
|
|
6
6
|
Project-URL: Documentation, https://github.com/infrastructurebuilder/cs-image-system-3/blob/develop/docs/OPERATIONS.md
|
|
@@ -9,8 +9,8 @@ Author-email: Mykel Alvis <mykelalvis@infrastructurebuilder.org>
|
|
|
9
9
|
License-Expression: Apache-2.0
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Python: >=3.13
|
|
12
|
-
Requires-Dist: cs-image-system-base==0.1.
|
|
13
|
-
Requires-Dist: cs-image-system-contract
|
|
12
|
+
Requires-Dist: cs-image-system-base==0.1.7
|
|
13
|
+
Requires-Dist: cs-image-system-contract<0.5,>=0.4
|
|
14
14
|
Requires-Dist: pydantic>=2.13
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
|
|
@@ -55,18 +55,15 @@ How a YAML entry selects these classes:
|
|
|
55
55
|
looks the builder up by name or alias, reads the builder's own `type`
|
|
56
56
|
(`bash-remote`) and loads the item into the class registered under that
|
|
57
57
|
service key for `MOD_BUILDER_ITEM_MODEL`: `BashModItemModel`. The
|
|
58
|
-
registry alone decides
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
empty `helpers.py`.
|
|
62
|
-
- **The item's `type` is rewritten to the builder's `name` at load**
|
|
63
|
-
(stage 63 item 15). The image builder later fetches the builder with
|
|
58
|
+
registry alone decides.
|
|
59
|
+
- **The item's `type` is rewritten to the builder's `name` at load.**
|
|
60
|
+
The image builder later fetches the builder with
|
|
64
61
|
`ctx.mod_builders.get(mod.get_type())`, a map keyed by builder NAME
|
|
65
|
-
only, so an alias must not survive the load
|
|
66
|
-
|
|
67
|
-
`type: bash-remote`. Before 2026-09-25 the alias was kept, and
|
|
68
|
-
generation stopped with
|
|
62
|
+
only, so an alias must not survive the load: a kept alias would stop
|
|
63
|
+
generation with
|
|
69
64
|
`AssertionError: No mod builder bash found in context for <image>`.
|
|
65
|
+
An item declared `type: bash` (the fixture builder's alias) is loaded
|
|
66
|
+
as `type: bash-remote`.
|
|
70
67
|
- An `executables:` entry named `bash` is version-checked by
|
|
71
68
|
`BashVersionChecker` (regex `.*version\s+([\d\.]+)` over
|
|
72
69
|
`bash --version`).
|
|
@@ -80,21 +77,31 @@ Defined in [bash_models.py](src/cs_image_system/bash_mod_plugin/bash_models.py).
|
|
|
80
77
|
[`ModBuilderModel`](../base/src/cs_image_system/base/models/mod_builder.py),
|
|
81
78
|
which adds nothing to
|
|
82
79
|
[`BuilderModel`](../base/src/cs_image_system/base/models/builder_model.py)
|
|
83
|
-
(`name`, `type`, `description`, `aliases`,
|
|
84
|
-
`config`, `gitignore`, `tags
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
80
|
+
(the common builder fields: `name`, `type`, `description`, `aliases`,
|
|
81
|
+
`executable`, `is_default`, `config`, `gitignore`, `tags`;
|
|
82
|
+
[CONFIGURATION section 4.1](../../docs/CONFIGURATION.md#41-fields-common-to-every-builder)
|
|
83
|
+
holds that table). `BashModBuilderModel` is the registered subclass; it
|
|
84
|
+
adds no fields.
|
|
85
|
+
|
|
86
|
+
The keys this plugin adds are `execute_command`, `configuration_user`,
|
|
87
|
+
`environment_vars` and `expect_disconnect`: each is a Packer
|
|
88
|
+
shell-provisioner argument, emitted on every shell provisioner of every
|
|
89
|
+
item of the builder.
|
|
90
|
+
[CONFIGURATION section 7.2](../../docs/CONFIGURATION.md#72-bash-remote)
|
|
91
|
+
holds the field table; [Configuration reference](#configuration-reference)
|
|
92
|
+
below gives this plugin's reading of each key. Rules only this plugin
|
|
93
|
+
enforces:
|
|
94
|
+
|
|
95
|
+
- `execute_command` (Packer's command template, for example
|
|
96
|
+
`sudo -E bash '{{.Path}}'`) is the way to change how the script is
|
|
97
|
+
invoked. `configuration_user` writes its own: every shell provisioner
|
|
98
|
+
of the builder gets `execute_command = "chmod +x {{ .Path }}; sudo su -
|
|
99
|
+
<user> -c '{{ .Vars }} {{ .Path }}'"`, so each item's lines run as that
|
|
100
|
+
user and the bake user only launches them. Because both say how the
|
|
101
|
+
script runs, declaring them together is refused at load.
|
|
102
|
+
- `extra_arguments` is not a field: a builder that declares it fails to
|
|
103
|
+
load with the unknown-key refusal. Use `execute_command` to change the
|
|
104
|
+
invocation.
|
|
98
105
|
|
|
99
106
|
Base fields this plugin constrains: `executable` should name the `bash`
|
|
100
107
|
executable (the fixture does), though the emission does not invoke it;
|
|
@@ -105,13 +112,28 @@ base-model rule). `config:` on the builder is carried but never read.
|
|
|
105
112
|
|
|
106
113
|
Same file. Extends
|
|
107
114
|
[`ModItemModel`](../base/src/cs_image_system/base/models/moditem_type.py)
|
|
108
|
-
(`SubRootItem` -> `RootItem` -> `NameTyped`).
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
(`SubRootItem` -> `RootItem` -> `NameTyped`). The item's own keys are
|
|
116
|
+
`script`, `scripts` and `ensure`; the inherited ones (`name`, `type`,
|
|
117
|
+
`description`, `aliases`, `tags`, `config`) are the modification item's,
|
|
118
|
+
tabled in
|
|
119
|
+
[CONFIGURATION section 7.3](../../docs/CONFIGURATION.md#73-the-modification-item-inside-an-images-modifications),
|
|
120
|
+
and [Configuration reference](#configuration-reference) below reads every
|
|
121
|
+
key as this plugin does.
|
|
122
|
+
|
|
123
|
+
- `script` is the list of inline command lines, run in order in one
|
|
124
|
+
`provisioner "shell"` (`inline = [...]`). Literal shell; no templating.
|
|
125
|
+
Blank entries are dropped.
|
|
126
|
+
- `scripts` is the list of script files, relative to the configuration
|
|
127
|
+
root (the process's working directory, which the configuration load
|
|
128
|
+
sets to the root), copied beside the Packer root and run as
|
|
129
|
+
`scripts = [...]`. Blank entries are dropped.
|
|
130
|
+
- `ensure` is the declarative form. Allowed keys: `packages` (list of
|
|
131
|
+
names), `files` (list of `{path, content, mode}`; `mode` defaults to
|
|
132
|
+
`0644`), `services` (list of unit names, enabled and started),
|
|
133
|
+
`commands` (list of `{run, unless}`; `run` executes only when `unless`
|
|
134
|
+
fails). Every entry is validated at load by `validate_ensure`: a wrong
|
|
135
|
+
shape, an unknown key, a bad mode or an all-empty mapping raises
|
|
136
|
+
`ValueError` naming the item and the entry.
|
|
115
137
|
|
|
116
138
|
Validation at load: at least one of `script`, `scripts`, `ensure` must be
|
|
117
139
|
non-empty, otherwise `ValueError` ("must supply 'script' (inline lines),
|
|
@@ -134,15 +156,13 @@ Derived behaviour:
|
|
|
134
156
|
- `remap_self_with_copied_assets(copied)`: replaces each `scripts` entry
|
|
135
157
|
with its copied path when present in the mapping.
|
|
136
158
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
| `config` | `RootItem` | `{}` | Not read by this plugin. It takes part in the lineage content hash of the modification and nothing else. |
|
|
145
|
-
| `_model_id` | `ModItemModel` | set at load | The owning image's global id; `identified_model` resolves to the image. |
|
|
159
|
+
Of the inherited fields, `name` names the provisioner comment and the
|
|
160
|
+
local bundle directory (`NN-<name>`); `type` is the foreign key to a
|
|
161
|
+
`mod_builders:` entry (see above); `description`, `aliases` and `tags`
|
|
162
|
+
are carried and not emitted; `config` is not read by this plugin and
|
|
163
|
+
takes part in the lineage content hash of the modification and nothing
|
|
164
|
+
else; `_model_id`, set at load, is the owning image's global id, and
|
|
165
|
+
`identified_model` resolves to the image.
|
|
146
166
|
|
|
147
167
|
### A modification item with only `config:`
|
|
148
168
|
|
|
@@ -183,8 +203,8 @@ calls, in order:
|
|
|
183
203
|
4. `generate_items_during_modification(image, mod, image_builder, phase, build_path)`:
|
|
184
204
|
- `inline` = `ensure_lines()` followed by the `script` lines; `scripts`
|
|
185
205
|
= the script files. When both lists are empty it logs a warning and
|
|
186
|
-
emits nothing;
|
|
187
|
-
|
|
206
|
+
emits nothing; an all-empty `ensure` is refused at load, so an item
|
|
207
|
+
cannot reach this.
|
|
188
208
|
- Appends `# Modifications for <item name> of type <item type> (shell)`.
|
|
189
209
|
- Then, because Packer's shell provisioner takes either `scripts` or
|
|
190
210
|
`inline` but never both, one or two `provisioner "shell"` blocks:
|
|
@@ -197,7 +217,7 @@ calls, in order:
|
|
|
197
217
|
with a `configuration_user`, `chmod +x {{ .Path }}; sudo su - <user>
|
|
198
218
|
-c '{{ .Vars }} {{ .Path }}'` (Packer's `{{ .Vars }}`, the
|
|
199
219
|
environment variables, travel inside the command because `su -`
|
|
200
|
-
starts a fresh login environment), else none
|
|
220
|
+
starts a fresh login environment), else none.
|
|
201
221
|
- Every string is rendered as an HCL literal: backslashes and double
|
|
202
222
|
quotes are escaped, and `${` / `%{` become `$${` / `%%{` so shell text
|
|
203
223
|
such as `dpkg-query -f='${Package}'` survives Packer's template parser.
|
|
@@ -212,9 +232,7 @@ modification bundle records the item: its directory
|
|
|
212
232
|
`run.sh` that runs `sh '<script>'` per script file and then
|
|
213
233
|
`sh ./inline.sh`, and a `MANIFEST.yaml` with `operation: bash` and the
|
|
214
234
|
item's `idempotent` value. `csis-mods rerun` on the image therefore
|
|
215
|
-
re-applies the declarative `ensure` form too
|
|
216
|
-
the `ensure` lines were not in the bundle and a rerun replayed only the
|
|
217
|
-
script files and the `script` lines.
|
|
235
|
+
re-applies the declarative `ensure` form too.
|
|
218
236
|
|
|
219
237
|
## Emission
|
|
220
238
|
|
|
@@ -316,8 +334,8 @@ entry in `cfg/executables.yml`
|
|
|
316
334
|
([the fixture's](../../tests/fixtures/config/cfg/executables.yml) is
|
|
317
335
|
`name: bash`, `binary: /usr/local/bin/bash`, no `version`), because
|
|
318
336
|
`validate` checks every builder's `executable` against that list and every
|
|
319
|
-
run checks every declared entry (
|
|
320
|
-
|
|
337
|
+
run checks every declared entry (the binary must exist as an absolute
|
|
338
|
+
path or a name on `PATH`, `BashVersionChecker` must parse a version
|
|
321
339
|
from `<binary> --version`, and when the entry declares `version:` the parsed
|
|
322
340
|
version must satisfy it). That is the whole extent of the plugin's use of a
|
|
323
341
|
local bash: it is looked at, never run. The fixture declares no floor;
|
|
@@ -365,7 +383,7 @@ variable, profile or file of its own.
|
|
|
365
383
|
|
|
366
384
|
Every key the model accepts. An unknown key is refused at load
|
|
367
385
|
(`extra="forbid"` in the shared model config), and `parameters:` is refused
|
|
368
|
-
by name
|
|
386
|
+
by name.
|
|
369
387
|
|
|
370
388
|
| Field | Type | Default | Meaning |
|
|
371
389
|
|---|---|---|---|
|
|
@@ -379,24 +397,24 @@ by name (retired in stage 26).
|
|
|
379
397
|
| `gitignore` | list[str] | `[]` | Accepted by the base model; nothing in this plugin reads it. |
|
|
380
398
|
| `tags` | mapping | `{}` | Accepted, not read. |
|
|
381
399
|
| `execute_command` | str or null | `null` | Emitted verbatim (HCL-escaped) as `execute_command` on every shell block of every item of this builder. Refused together with `configuration_user`. |
|
|
382
|
-
| `configuration_user` | str or null | `null` |
|
|
400
|
+
| `configuration_user` | str or null | `null` | Every shell block of this builder runs its lines as this user through `execute_command = "chmod +x {{ .Path }}; sudo su - <user> -c '{{ .Vars }} {{ .Path }}'"`. Refused together with `execute_command` (`... both say how the script runs; declare one`). |
|
|
383
401
|
| `environment_vars` | list[str] | `[]` | Emitted as `environment_vars = [...]` on every shell block. |
|
|
384
402
|
| `expect_disconnect` | bool | `false` | When true, `expect_disconnect = true` on every shell block. |
|
|
385
|
-
| `extra_arguments` | | |
|
|
403
|
+
| `extra_arguments` | | | Not a field; refused at load as an unknown key. Use `execute_command`. |
|
|
386
404
|
|
|
387
405
|
### The item (an image's `modifications:` entry whose `type:` names this builder)
|
|
388
406
|
|
|
389
407
|
| Field | Type | Default | Meaning |
|
|
390
408
|
|---|---|---|---|
|
|
391
409
|
| `name` | str | required | Unique within the image. Names the comment line, the bundle directory `NN-<name>` and the lineage record. |
|
|
392
|
-
| `type` | str | `default` | The builder's name, an alias, or `default` (or omitted). Rewritten to the builder's name at load
|
|
410
|
+
| `type` | str | `default` | The builder's name, an alias, or `default` (or omitted). Rewritten to the builder's name at load. Omitted or `default` with no `is_default: true` mod builder: refused at load. |
|
|
393
411
|
| `description` | str or null | `null` | Accepted, not read. |
|
|
394
412
|
| `aliases` | list[str] | `[]` | Accepted, not read. |
|
|
395
413
|
| `tags` | mapping | `{}` | Accepted, not read. |
|
|
396
414
|
| `config` | mapping | `{}` | Not read by the plugin. Part of the modification's content hash, so a change re-bakes the image. Not passed to the shell. |
|
|
397
415
|
| `script` | list[str] | `[]` | Inline lines, literal, in order, after the `ensure` lines. Blank and whitespace-only entries are dropped. |
|
|
398
416
|
| `scripts` | list[str] | `[]` | Script files; each is copied beside the Packer root and run first, in order. Blank entries are dropped. An absolute path is not copied and is emitted as is. |
|
|
399
|
-
| `ensure` | mapping | `{}` | Only the four keys below are allowed; any other key is refused at load. Every entry's values are validated at load
|
|
417
|
+
| `ensure` | mapping | `{}` | Only the four keys below are allowed; any other key is refused at load. Every entry's values are validated at load (`validate_ensure`; see "What it tests and verifies"). |
|
|
400
418
|
|
|
401
419
|
The `ensure` keys:
|
|
402
420
|
|
|
@@ -417,11 +435,11 @@ The `ensure` keys:
|
|
|
417
435
|
`idempotent: unknown`, the same as free-form alone. `ensure` lines always
|
|
418
436
|
precede the `script` lines in the inline block.
|
|
419
437
|
- **`ensure` whose lists are all empty** (`ensure: {packages: []}`) is
|
|
420
|
-
refused at load
|
|
421
|
-
|
|
422
|
-
`Bash modification <name> has no script lines or files; nothing emitted
|
|
423
|
-
|
|
424
|
-
|
|
438
|
+
refused at load, because such an item would count as `declared` and
|
|
439
|
+
emit nothing: the builder would log
|
|
440
|
+
`Bash modification <name> has no script lines or files; nothing emitted`,
|
|
441
|
+
the bake would proceed without a provisioner for it, and the bundle
|
|
442
|
+
directory would hold a `run.sh` that runs nothing.
|
|
425
443
|
- **`commands` with `unless`** is guarded; **without `unless`** the command
|
|
426
444
|
runs on every bake and on every `csis-mods rerun`, so the item is
|
|
427
445
|
idempotent only if the command is.
|
|
@@ -431,7 +449,7 @@ The `ensure` keys:
|
|
|
431
449
|
- **`execute_command` set** puts that template on every block of every
|
|
432
450
|
item of the builder; **`configuration_user` set** instead puts
|
|
433
451
|
`chmod +x {{ .Path }}; sudo su - <user> -c '{{ .Vars }} {{ .Path }}'`
|
|
434
|
-
there, so the lines run as that user
|
|
452
|
+
there, so the lines run as that user; **both set** is refused
|
|
435
453
|
at load; **neither**, Packer's default applies and the lines run as the
|
|
436
454
|
bake user. The same for
|
|
437
455
|
`environment_vars` (emitted only when non-empty) and `expect_disconnect`
|
|
@@ -440,11 +458,10 @@ The `ensure` keys:
|
|
|
440
458
|
`type`** reaches whichever mod builder is `is_default: true`; in the
|
|
441
459
|
fixture that is `ansible-default`, so the item is loaded as an ANSIBLE
|
|
442
460
|
item and its `script`/`scripts`/`ensure` keys are refused as unknown.
|
|
443
|
-
With no `is_default: true` mod builder at all, the load is refused
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
for an item that reaches it anyway.
|
|
447
|
-
became an `AttributeError` at generation.
|
|
461
|
+
With no `is_default: true` mod builder at all, the load is refused:
|
|
462
|
+
`No default builder found in mod_builder list.` first, and the
|
|
463
|
+
orchestrator's `` modification '<name>' names no builder (`type`) ... ``
|
|
464
|
+
for an item that reaches it anyway.
|
|
448
465
|
**`type: <alias>`** behaves exactly as the builder's name: the loader
|
|
449
466
|
rewrites it.
|
|
450
467
|
- **AWS versus GCE runtime**: the lines are identical; only the `only`
|
|
@@ -479,7 +496,7 @@ integer mode no higher than `0777`, and not every kind empty); an item
|
|
|
479
496
|
with none of `script`, `scripts`, `ensure`; an item `type:` that names no
|
|
480
497
|
builder; an item with no `type:` when no mod builder is the default; a
|
|
481
498
|
builder declaring both `configuration_user` and `execute_command`, or
|
|
482
|
-
|
|
499
|
+
`extra_arguments`. Each is a
|
|
483
500
|
`ValueError` (or `KeyError` for the type) that stops the load; the CLI
|
|
484
501
|
prints it and exits 1. Nothing loads, so nothing is generated.
|
|
485
502
|
|
|
@@ -532,81 +549,41 @@ failure prints `FAIL <image>/<mod>: rc=...` on stderr and exits 1. A
|
|
|
532
549
|
|
|
533
550
|
## When it fails
|
|
534
551
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
`ansible-default` with no content; the bash builder wrote raw lines and an
|
|
542
|
-
`exit 0` straight into the build file, so any real bash item failed
|
|
543
|
-
`packer validate`; and `scripts:` was not a field, so files were dropped.
|
|
544
|
-
Fixed at gate 8; guarded by
|
|
545
|
-
[test_v2_gate8_modifications.py](../../tests/test_v2_gate8_modifications.py)
|
|
546
|
-
and [tests/test_bash_provisioner.py](tests/test_bash_provisioner.py). If
|
|
547
|
-
a comment line ever appears outside a `provisioner "shell" {` block, or
|
|
548
|
-
`exit 0` appears in a build file, this regressed.
|
|
549
|
-
- **2026-09-10, the first `full-test`'s mod-test leg could not start a
|
|
550
|
-
container** for an EL 10 image: Docker Hub's `rockylinux` library stops at
|
|
551
|
-
9. The stand-in for `rhel` 10 and above is `almalinux:<version>` since
|
|
552
|
-
(`test_image_for` in [mod_tests.py](../base/src/cs_image_system/base/mod_tests.py)).
|
|
553
|
-
Symptom then: `docker run` failed to pull and the bash item's test never
|
|
554
|
-
ran. If `test-mods` reports `unsupported` with
|
|
555
|
-
`no local test image for the root OS family`, the family/version has no
|
|
556
|
-
stand-in; set `local_test_image` on the OS builder.
|
|
557
|
-
- **Found live, undated, in the sibling shell emitter for image tests**
|
|
558
|
-
([v2_provisioners.py](../packer-plugin/src/cs_image_system/packer_plugin/v2_provisioners.py)):
|
|
559
|
-
an `rpm --qf '%{NAME}'` line broke `packer fmt`, because `%{` and `${`
|
|
560
|
-
open Packer template directives. This plugin's `_hcl_string` escapes both
|
|
561
|
-
the same way. If `packer fmt` or `validate` ever reports
|
|
562
|
-
`Invalid template directive` or `Invalid template interpolation` on a
|
|
563
|
-
line you wrote, the escape regressed; the line in `generated/` should
|
|
564
|
-
read `%%{` / `$${`.
|
|
565
|
-
- **2026-09-20, an assertion that could not fail** (image `tests:`, not this
|
|
566
|
-
plugin, but the same shell and the same trap;
|
|
567
|
-
[OPERATIONS.md](../../docs/OPERATIONS.md), "What a green bake proves").
|
|
568
|
-
Under `sh -e`, a command inside an AND-OR list other than the last does
|
|
569
|
-
not abort the script, so a `script:` line written `check || { echo no; }`
|
|
570
|
-
never fails the build however false `check` is. Two bakes went green
|
|
571
|
-
asserting a package that did not exist. If a `script` line must be able
|
|
572
|
-
to stop the bake, end it in something that exits nonzero (`|| exit 1`),
|
|
573
|
-
not in a braced group that returns 0. The plugin's own `commands` form,
|
|
574
|
-
`( unless ) || { run; }`, is deliberate: the group is the LAST element,
|
|
575
|
-
so a failing `run` does abort.
|
|
576
|
-
- **2026-09-23, an item declared with the builder's alias failed at
|
|
577
|
-
generation** (found while writing this README, on a copy of the fixture
|
|
578
|
-
with `type: bash` on `derivative-setup`). Load and `validate` passed; the
|
|
579
|
-
run log then showed
|
|
580
|
-
`AssertionError: No mod builder bash found in context for imgfile-basic-dask`
|
|
581
|
-
and the summary was `ok: false` with no validation errors. Fixed by
|
|
582
|
-
stage 63 item 15 (2026-09-25): the loader rewrites the alias to the
|
|
583
|
-
builder's name, and `tests/test_v2_defects_loading.py` generates that
|
|
584
|
-
same fixture copy.
|
|
585
|
-
|
|
586
|
-
Failures the code raises that have not been seen outside tests:
|
|
552
|
+
Incidents that have happened, with their cause and fix, are recorded on
|
|
553
|
+
[INCIDENTS.md](../../docs/INCIDENTS.md); what follows keeps each
|
|
554
|
+
failure's symptom, its meaning and what to do.
|
|
555
|
+
|
|
556
|
+
Symptoms of the emission and of the bake, with what each means and what
|
|
557
|
+
to do:
|
|
587
558
|
|
|
588
559
|
| What you see | Where | Meaning and what to do |
|
|
589
560
|
|---|---|---|
|
|
561
|
+
| A `# Modifications for ...` comment line outside a `provisioner "shell" {` block, or `exit 0` in a build file | `generated/`; `packer validate` fails | The builder wrote raw lines into the build file instead of provisioner blocks. [tests/test_bash_provisioner.py](tests/test_bash_provisioner.py) and [test_modifications.py](../../tests/test_modifications.py) pin the block form; this is a regression of the plugin, not of the item. |
|
|
562
|
+
| A bash item recorded in lineage as `ansible-default` with no content, or its script files dropped | `meta-state/lineage.yaml` after a bake | The item was resolved to the default mod builder instead of the builder its `type:` names. The same two tests pin the lookup by the item's `type`; check the item's `type:` names this builder. |
|
|
563
|
+
| `test-mods` reports `unsupported` with `no local test image for the root OS family` | `test-mods` | The OS family and version have no stand-in container image (`test_image_for` in [mod_tests.py](../base/src/cs_image_system/base/mod_tests.py)): Docker Hub's `rockylinux` library stops at 9, so `rhel` 10 and above stand in as `almalinux:<version>`, and a family outside that table has none. Set `local_test_image` on the OS builder. |
|
|
564
|
+
| `Invalid template directive` or `Invalid template interpolation` from `packer fmt` or `validate` on a line you wrote | the Packer plugin's run-now commands | `%{` and `${` open Packer template directives; this plugin's `_hcl_string` escapes both as `%%{` / `$${` (the sibling shell emitter for image tests, [v2_provisioners.py](../packer-plugin/src/cs_image_system/packer_plugin/v2_provisioners.py), does the same). If the line in `generated/` does not read so, the escape regressed. |
|
|
565
|
+
| A `script:` line written `check || { echo no; }` never fails the build however false `check` is | the bake, green | Under `sh -e` a command inside an AND-OR list other than the last does not abort the script, so a bake can go green asserting a package that does not exist (the same trap holds for an image's `tests:`; [OPERATIONS.md](../../docs/OPERATIONS.md), "What a green bake proves"). If a `script` line must be able to stop the bake, end it in something that exits nonzero (`\|\| exit 1`), not in a braced group that returns 0. The plugin's own `commands` form, `( unless ) \|\| { run; }`, is deliberate: the group is the LAST element, so a failing `run` does abort. |
|
|
566
|
+
| `AssertionError: No mod builder bash found in context for <image>` in the run log, summary `ok: false` with no validation errors, after load and `validate` passed | generation | An item's `type:` reached generation as the builder's alias; the loader rewrites an alias to the builder's name at load (`tests/test_modifications.py` generates a fixture copy with `type: bash`), so a tree whose loader does not is on an older form ([the stage index](../../docs/CHANGES.md#s63)). Write the builder's name, or update. |
|
|
590
567
|
| `Bash modification '<name>' must supply 'script' (inline lines), 'scripts' (script files) and/or 'ensure' (declarative, idempotent)` | load; CLI exit 1 | The item has none of the three (a `config:`-only item). Add one or remove the item. |
|
|
591
568
|
| `Bash modification '<name>': unknown ensure keys ['users']` | load; CLI exit 1 | Only `packages`, `files`, `services`, `commands` exist. Move anything else to `commands`. |
|
|
592
569
|
| A pydantic error naming a key (`Extra inputs are not permitted`) on the builder or the item | load; CLI exit 1 | A misspelt or foreign key (`playbooks:` on a bash item, `script:` on an ansible item). Check the item's `type:` reaches the builder you meant; `type: default` reaches the default builder, which in the fixture is ansible. |
|
|
593
|
-
| ``<builder>: `parameters` was retired
|
|
570
|
+
| ``<builder>: `parameters` was retired ...`` | load; CLI exit 1 | Delete `parameters:` from the builder. |
|
|
594
571
|
| `KeyError: Could not resolve builder '<type>' referenced in Image(...)` | load; CLI exit 1 | The item's `type:` names no `mod_builders:` entry. |
|
|
595
|
-
| `` mod builder '<name>': `configuration_user` and `execute_command` both say how the script runs; declare one `` | load; CLI exit 1 |
|
|
596
|
-
| A pydantic unknown-key error naming `extra_arguments` on a `bash-remote` builder | load; CLI exit 1 |
|
|
597
|
-
| `No default builder found in mod_builder list.` | load; CLI exit 1 | No mod builder says `is_default: true`. Mark one, or give every item a `type:`. The orchestrator's own form, for an item that reaches it, is `` modification '<name>' names no builder (`type`) and no mod builder is `is_default: true`; name one with `type:` or mark a default
|
|
572
|
+
| `` mod builder '<name>': `configuration_user` and `execute_command` both say how the script runs; declare one `` | load; CLI exit 1 | `configuration_user` writes its own `execute_command`; keep one. To run as another user with a custom command, put the `sudo su - <user>` in your `execute_command`. |
|
|
573
|
+
| A pydantic unknown-key error naming `extra_arguments` on a `bash-remote` builder | load; CLI exit 1 | The field does not exist. Delete it; change the invocation with `execute_command`. |
|
|
574
|
+
| `No default builder found in mod_builder list.` | load; CLI exit 1 | No mod builder says `is_default: true`. Mark one, or give every item a `type:`. The orchestrator's own form, for an item that reaches it, is `` modification '<name>' names no builder (`type`) and no mod builder is `is_default: true`; name one with `type:` or mark a default ``. |
|
|
598
575
|
| `Executable bash specified for provider bash-remote not found in executables list.` | `validate`; exit 1 | Add a `bash` entry to `cfg/executables.yml` or change the builder's `executable:`. |
|
|
599
576
|
| `bash: binary '/usr/local/bin/bash' not found (declared in cfg/executables.yml; an absolute path, or a name on PATH)` | `validate` and every run; exit 1 | The pinned path is wrong on this machine. Symlink the binary there or fix `binary:`. |
|
|
600
577
|
| ``bash: BashVersionChecker could not parse a version from `/usr/local/bin/bash --version` `` | `validate` and every run | The first line of `--version` has no `version N.N`. Something other than bash is at that path. |
|
|
601
578
|
| `bash 5.3.15 does not meet its requirement <spec> (cfg/executables.yml)` | `validate` and every run | The floor moved or the machine is behind. |
|
|
602
579
|
| `FileNotFoundError: Bash modification script mod_image.sh does not exist or is not a file` | generation; run `ok: false`, traceback in the log | The path is resolved from the configuration root, not from the image file's directory or the shell's. Fix the path or `--root-dir`. |
|
|
603
580
|
| `Bash modification '<name>': ensure declares [...] but every one is empty, so it would emit nothing` | load; CLI exit 1 | Every `ensure` list is empty. Fill one or drop `ensure`. |
|
|
604
|
-
| `Bash modification '<name>': ensure.files[N]: `path` is required` (or `ensure.commands[N]: `run` is required`) | load; CLI exit 1 | A `files` entry without `path`, or a `commands` entry without `run`.
|
|
605
|
-
| `Bash modification '<name>': ensure.packages must be a list of package names, not str 'git'` | load; CLI exit 1 | `packages:` (or `services:`) given as a string
|
|
581
|
+
| `Bash modification '<name>': ensure.files[N]: `path` is required` (or `ensure.commands[N]: `run` is required`) | load; CLI exit 1 | A `files` entry without `path`, or a `commands` entry without `run`. |
|
|
582
|
+
| `Bash modification '<name>': ensure.packages must be a list of package names, not str 'git'` | load; CLI exit 1 | `packages:` (or `services:`) given as a string, which shell would iterate letter by letter. Write a list (`packages: [git]`). |
|
|
606
583
|
| `Bash modification '<name>': ensure.files[N].mode 644 is not a mode YAML could have read from octal` | load; CLI exit 1 | An unquoted `644`, which YAML reads as a decimal number. Write `mode: "0644"`. |
|
|
607
584
|
| `Bash modification '<name>': ensure.files[N]: unknown keys [...]` | load; CLI exit 1 | Only `path`, `content`, `mode` on a file and `run`, `unless` on a command. |
|
|
608
585
|
| `csis ensure: no dnf, yum or apt-get to install <pkg>` | apply / `test-mods` | The image has none of the three package managers. Use a `script` line with the one it has. |
|
|
609
|
-
| `dnf` (or `yum`, `apt-get`) error on a package step | apply / `test-mods` | The first package manager found could not install the package, and its message is the real one (no match, no repository, no network from the build subnet)
|
|
586
|
+
| `dnf` (or `yum`, `apt-get`) error on a package step | apply / `test-mods` | The first package manager found could not install the package, and its message is the real one (no match, no repository, no network from the build subnet); the others are never tried, so no misleading `apt-get: command not found` follows it on an EL host. |
|
|
610
587
|
| `cmp: command not found` or `base64: command not found` | apply | The image lacks diffutils / coreutils. Add the package to an earlier item's `ensure.packages` or a `script` line. |
|
|
611
588
|
| `sudo: a terminal is required to read the password` | apply | The bake ssh user has no passwordless `sudo`. This is an OS builder / runtime matter, not the item's. |
|
|
612
589
|
| `Failed to enable unit: Unit <svc>.service does not exist` | apply | `services:` names a unit the image has not installed yet; install it earlier in the same item's `packages` (packages run before services). |
|
|
@@ -638,20 +615,20 @@ Failures the code raises that have not been seen outside tests:
|
|
|
638
615
|
[abstract_version_checker.py](../base/src/cs_image_system/base/basic/abstract_version_checker.py).
|
|
639
616
|
- The image builder that calls it and hosts its output:
|
|
640
617
|
[packer-plugin](../packer-plugin)
|
|
641
|
-
([
|
|
618
|
+
([packer_image_builder.py](../packer-plugin/src/cs_image_system/packer_plugin/packer_image_builder.py),
|
|
642
619
|
[v2_local_mods.py](../packer-plugin/src/cs_image_system/packer_plugin/v2_local_mods.py)).
|
|
643
620
|
- Lineage records for modifications:
|
|
644
621
|
[lineage.py](../base/src/cs_image_system/base/lineage.py) (`mod_records`);
|
|
645
622
|
the release gate in [release.py](../base/src/cs_image_system/base/release.py).
|
|
646
623
|
- Tests: [tests/test_bash_provisioner.py](tests/test_bash_provisioner.py)
|
|
647
624
|
(two blocks for files plus inline, builder options, script copying);
|
|
648
|
-
[
|
|
625
|
+
[test_modifications.py](../../tests/test_modifications.py)
|
|
649
626
|
(both kinds emitted, unknown type refused, the load rule);
|
|
650
|
-
[
|
|
627
|
+
[test_modifications.py](../../tests/test_modifications.py)
|
|
651
628
|
(`ensure` guards, idempotence recorded, the bundle);
|
|
652
|
-
[
|
|
629
|
+
[test_mod_tests.py](../../tests/test_mod_tests.py)
|
|
653
630
|
(the container tests on the fixture's bash item);
|
|
654
|
-
[
|
|
631
|
+
[test_version_checks.py](../../tests/test_version_checks.py)
|
|
655
632
|
(`BashVersionChecker` found by name, versions judged).
|
|
656
633
|
- No terraform modules and no hashicorp-utils usage: the plugin writes
|
|
657
634
|
Packer provisioner lines directly.
|
|
@@ -39,18 +39,15 @@ How a YAML entry selects these classes:
|
|
|
39
39
|
looks the builder up by name or alias, reads the builder's own `type`
|
|
40
40
|
(`bash-remote`) and loads the item into the class registered under that
|
|
41
41
|
service key for `MOD_BUILDER_ITEM_MODEL`: `BashModItemModel`. The
|
|
42
|
-
registry alone decides
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
empty `helpers.py`.
|
|
46
|
-
- **The item's `type` is rewritten to the builder's `name` at load**
|
|
47
|
-
(stage 63 item 15). The image builder later fetches the builder with
|
|
42
|
+
registry alone decides.
|
|
43
|
+
- **The item's `type` is rewritten to the builder's `name` at load.**
|
|
44
|
+
The image builder later fetches the builder with
|
|
48
45
|
`ctx.mod_builders.get(mod.get_type())`, a map keyed by builder NAME
|
|
49
|
-
only, so an alias must not survive the load
|
|
50
|
-
|
|
51
|
-
`type: bash-remote`. Before 2026-09-25 the alias was kept, and
|
|
52
|
-
generation stopped with
|
|
46
|
+
only, so an alias must not survive the load: a kept alias would stop
|
|
47
|
+
generation with
|
|
53
48
|
`AssertionError: No mod builder bash found in context for <image>`.
|
|
49
|
+
An item declared `type: bash` (the fixture builder's alias) is loaded
|
|
50
|
+
as `type: bash-remote`.
|
|
54
51
|
- An `executables:` entry named `bash` is version-checked by
|
|
55
52
|
`BashVersionChecker` (regex `.*version\s+([\d\.]+)` over
|
|
56
53
|
`bash --version`).
|
|
@@ -64,21 +61,31 @@ Defined in [bash_models.py](src/cs_image_system/bash_mod_plugin/bash_models.py).
|
|
|
64
61
|
[`ModBuilderModel`](../base/src/cs_image_system/base/models/mod_builder.py),
|
|
65
62
|
which adds nothing to
|
|
66
63
|
[`BuilderModel`](../base/src/cs_image_system/base/models/builder_model.py)
|
|
67
|
-
(`name`, `type`, `description`, `aliases`,
|
|
68
|
-
`config`, `gitignore`, `tags
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
64
|
+
(the common builder fields: `name`, `type`, `description`, `aliases`,
|
|
65
|
+
`executable`, `is_default`, `config`, `gitignore`, `tags`;
|
|
66
|
+
[CONFIGURATION section 4.1](../../docs/CONFIGURATION.md#41-fields-common-to-every-builder)
|
|
67
|
+
holds that table). `BashModBuilderModel` is the registered subclass; it
|
|
68
|
+
adds no fields.
|
|
69
|
+
|
|
70
|
+
The keys this plugin adds are `execute_command`, `configuration_user`,
|
|
71
|
+
`environment_vars` and `expect_disconnect`: each is a Packer
|
|
72
|
+
shell-provisioner argument, emitted on every shell provisioner of every
|
|
73
|
+
item of the builder.
|
|
74
|
+
[CONFIGURATION section 7.2](../../docs/CONFIGURATION.md#72-bash-remote)
|
|
75
|
+
holds the field table; [Configuration reference](#configuration-reference)
|
|
76
|
+
below gives this plugin's reading of each key. Rules only this plugin
|
|
77
|
+
enforces:
|
|
78
|
+
|
|
79
|
+
- `execute_command` (Packer's command template, for example
|
|
80
|
+
`sudo -E bash '{{.Path}}'`) is the way to change how the script is
|
|
81
|
+
invoked. `configuration_user` writes its own: every shell provisioner
|
|
82
|
+
of the builder gets `execute_command = "chmod +x {{ .Path }}; sudo su -
|
|
83
|
+
<user> -c '{{ .Vars }} {{ .Path }}'"`, so each item's lines run as that
|
|
84
|
+
user and the bake user only launches them. Because both say how the
|
|
85
|
+
script runs, declaring them together is refused at load.
|
|
86
|
+
- `extra_arguments` is not a field: a builder that declares it fails to
|
|
87
|
+
load with the unknown-key refusal. Use `execute_command` to change the
|
|
88
|
+
invocation.
|
|
82
89
|
|
|
83
90
|
Base fields this plugin constrains: `executable` should name the `bash`
|
|
84
91
|
executable (the fixture does), though the emission does not invoke it;
|
|
@@ -89,13 +96,28 @@ base-model rule). `config:` on the builder is carried but never read.
|
|
|
89
96
|
|
|
90
97
|
Same file. Extends
|
|
91
98
|
[`ModItemModel`](../base/src/cs_image_system/base/models/moditem_type.py)
|
|
92
|
-
(`SubRootItem` -> `RootItem` -> `NameTyped`).
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
(`SubRootItem` -> `RootItem` -> `NameTyped`). The item's own keys are
|
|
100
|
+
`script`, `scripts` and `ensure`; the inherited ones (`name`, `type`,
|
|
101
|
+
`description`, `aliases`, `tags`, `config`) are the modification item's,
|
|
102
|
+
tabled in
|
|
103
|
+
[CONFIGURATION section 7.3](../../docs/CONFIGURATION.md#73-the-modification-item-inside-an-images-modifications),
|
|
104
|
+
and [Configuration reference](#configuration-reference) below reads every
|
|
105
|
+
key as this plugin does.
|
|
106
|
+
|
|
107
|
+
- `script` is the list of inline command lines, run in order in one
|
|
108
|
+
`provisioner "shell"` (`inline = [...]`). Literal shell; no templating.
|
|
109
|
+
Blank entries are dropped.
|
|
110
|
+
- `scripts` is the list of script files, relative to the configuration
|
|
111
|
+
root (the process's working directory, which the configuration load
|
|
112
|
+
sets to the root), copied beside the Packer root and run as
|
|
113
|
+
`scripts = [...]`. Blank entries are dropped.
|
|
114
|
+
- `ensure` is the declarative form. Allowed keys: `packages` (list of
|
|
115
|
+
names), `files` (list of `{path, content, mode}`; `mode` defaults to
|
|
116
|
+
`0644`), `services` (list of unit names, enabled and started),
|
|
117
|
+
`commands` (list of `{run, unless}`; `run` executes only when `unless`
|
|
118
|
+
fails). Every entry is validated at load by `validate_ensure`: a wrong
|
|
119
|
+
shape, an unknown key, a bad mode or an all-empty mapping raises
|
|
120
|
+
`ValueError` naming the item and the entry.
|
|
99
121
|
|
|
100
122
|
Validation at load: at least one of `script`, `scripts`, `ensure` must be
|
|
101
123
|
non-empty, otherwise `ValueError` ("must supply 'script' (inline lines),
|
|
@@ -118,15 +140,13 @@ Derived behaviour:
|
|
|
118
140
|
- `remap_self_with_copied_assets(copied)`: replaces each `scripts` entry
|
|
119
141
|
with its copied path when present in the mapping.
|
|
120
142
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
| `config` | `RootItem` | `{}` | Not read by this plugin. It takes part in the lineage content hash of the modification and nothing else. |
|
|
129
|
-
| `_model_id` | `ModItemModel` | set at load | The owning image's global id; `identified_model` resolves to the image. |
|
|
143
|
+
Of the inherited fields, `name` names the provisioner comment and the
|
|
144
|
+
local bundle directory (`NN-<name>`); `type` is the foreign key to a
|
|
145
|
+
`mod_builders:` entry (see above); `description`, `aliases` and `tags`
|
|
146
|
+
are carried and not emitted; `config` is not read by this plugin and
|
|
147
|
+
takes part in the lineage content hash of the modification and nothing
|
|
148
|
+
else; `_model_id`, set at load, is the owning image's global id, and
|
|
149
|
+
`identified_model` resolves to the image.
|
|
130
150
|
|
|
131
151
|
### A modification item with only `config:`
|
|
132
152
|
|
|
@@ -167,8 +187,8 @@ calls, in order:
|
|
|
167
187
|
4. `generate_items_during_modification(image, mod, image_builder, phase, build_path)`:
|
|
168
188
|
- `inline` = `ensure_lines()` followed by the `script` lines; `scripts`
|
|
169
189
|
= the script files. When both lists are empty it logs a warning and
|
|
170
|
-
emits nothing;
|
|
171
|
-
|
|
190
|
+
emits nothing; an all-empty `ensure` is refused at load, so an item
|
|
191
|
+
cannot reach this.
|
|
172
192
|
- Appends `# Modifications for <item name> of type <item type> (shell)`.
|
|
173
193
|
- Then, because Packer's shell provisioner takes either `scripts` or
|
|
174
194
|
`inline` but never both, one or two `provisioner "shell"` blocks:
|
|
@@ -181,7 +201,7 @@ calls, in order:
|
|
|
181
201
|
with a `configuration_user`, `chmod +x {{ .Path }}; sudo su - <user>
|
|
182
202
|
-c '{{ .Vars }} {{ .Path }}'` (Packer's `{{ .Vars }}`, the
|
|
183
203
|
environment variables, travel inside the command because `su -`
|
|
184
|
-
starts a fresh login environment), else none
|
|
204
|
+
starts a fresh login environment), else none.
|
|
185
205
|
- Every string is rendered as an HCL literal: backslashes and double
|
|
186
206
|
quotes are escaped, and `${` / `%{` become `$${` / `%%{` so shell text
|
|
187
207
|
such as `dpkg-query -f='${Package}'` survives Packer's template parser.
|
|
@@ -196,9 +216,7 @@ modification bundle records the item: its directory
|
|
|
196
216
|
`run.sh` that runs `sh '<script>'` per script file and then
|
|
197
217
|
`sh ./inline.sh`, and a `MANIFEST.yaml` with `operation: bash` and the
|
|
198
218
|
item's `idempotent` value. `csis-mods rerun` on the image therefore
|
|
199
|
-
re-applies the declarative `ensure` form too
|
|
200
|
-
the `ensure` lines were not in the bundle and a rerun replayed only the
|
|
201
|
-
script files and the `script` lines.
|
|
219
|
+
re-applies the declarative `ensure` form too.
|
|
202
220
|
|
|
203
221
|
## Emission
|
|
204
222
|
|
|
@@ -300,8 +318,8 @@ entry in `cfg/executables.yml`
|
|
|
300
318
|
([the fixture's](../../tests/fixtures/config/cfg/executables.yml) is
|
|
301
319
|
`name: bash`, `binary: /usr/local/bin/bash`, no `version`), because
|
|
302
320
|
`validate` checks every builder's `executable` against that list and every
|
|
303
|
-
run checks every declared entry (
|
|
304
|
-
|
|
321
|
+
run checks every declared entry (the binary must exist as an absolute
|
|
322
|
+
path or a name on `PATH`, `BashVersionChecker` must parse a version
|
|
305
323
|
from `<binary> --version`, and when the entry declares `version:` the parsed
|
|
306
324
|
version must satisfy it). That is the whole extent of the plugin's use of a
|
|
307
325
|
local bash: it is looked at, never run. The fixture declares no floor;
|
|
@@ -349,7 +367,7 @@ variable, profile or file of its own.
|
|
|
349
367
|
|
|
350
368
|
Every key the model accepts. An unknown key is refused at load
|
|
351
369
|
(`extra="forbid"` in the shared model config), and `parameters:` is refused
|
|
352
|
-
by name
|
|
370
|
+
by name.
|
|
353
371
|
|
|
354
372
|
| Field | Type | Default | Meaning |
|
|
355
373
|
|---|---|---|---|
|
|
@@ -363,24 +381,24 @@ by name (retired in stage 26).
|
|
|
363
381
|
| `gitignore` | list[str] | `[]` | Accepted by the base model; nothing in this plugin reads it. |
|
|
364
382
|
| `tags` | mapping | `{}` | Accepted, not read. |
|
|
365
383
|
| `execute_command` | str or null | `null` | Emitted verbatim (HCL-escaped) as `execute_command` on every shell block of every item of this builder. Refused together with `configuration_user`. |
|
|
366
|
-
| `configuration_user` | str or null | `null` |
|
|
384
|
+
| `configuration_user` | str or null | `null` | Every shell block of this builder runs its lines as this user through `execute_command = "chmod +x {{ .Path }}; sudo su - <user> -c '{{ .Vars }} {{ .Path }}'"`. Refused together with `execute_command` (`... both say how the script runs; declare one`). |
|
|
367
385
|
| `environment_vars` | list[str] | `[]` | Emitted as `environment_vars = [...]` on every shell block. |
|
|
368
386
|
| `expect_disconnect` | bool | `false` | When true, `expect_disconnect = true` on every shell block. |
|
|
369
|
-
| `extra_arguments` | | |
|
|
387
|
+
| `extra_arguments` | | | Not a field; refused at load as an unknown key. Use `execute_command`. |
|
|
370
388
|
|
|
371
389
|
### The item (an image's `modifications:` entry whose `type:` names this builder)
|
|
372
390
|
|
|
373
391
|
| Field | Type | Default | Meaning |
|
|
374
392
|
|---|---|---|---|
|
|
375
393
|
| `name` | str | required | Unique within the image. Names the comment line, the bundle directory `NN-<name>` and the lineage record. |
|
|
376
|
-
| `type` | str | `default` | The builder's name, an alias, or `default` (or omitted). Rewritten to the builder's name at load
|
|
394
|
+
| `type` | str | `default` | The builder's name, an alias, or `default` (or omitted). Rewritten to the builder's name at load. Omitted or `default` with no `is_default: true` mod builder: refused at load. |
|
|
377
395
|
| `description` | str or null | `null` | Accepted, not read. |
|
|
378
396
|
| `aliases` | list[str] | `[]` | Accepted, not read. |
|
|
379
397
|
| `tags` | mapping | `{}` | Accepted, not read. |
|
|
380
398
|
| `config` | mapping | `{}` | Not read by the plugin. Part of the modification's content hash, so a change re-bakes the image. Not passed to the shell. |
|
|
381
399
|
| `script` | list[str] | `[]` | Inline lines, literal, in order, after the `ensure` lines. Blank and whitespace-only entries are dropped. |
|
|
382
400
|
| `scripts` | list[str] | `[]` | Script files; each is copied beside the Packer root and run first, in order. Blank entries are dropped. An absolute path is not copied and is emitted as is. |
|
|
383
|
-
| `ensure` | mapping | `{}` | Only the four keys below are allowed; any other key is refused at load. Every entry's values are validated at load
|
|
401
|
+
| `ensure` | mapping | `{}` | Only the four keys below are allowed; any other key is refused at load. Every entry's values are validated at load (`validate_ensure`; see "What it tests and verifies"). |
|
|
384
402
|
|
|
385
403
|
The `ensure` keys:
|
|
386
404
|
|
|
@@ -401,11 +419,11 @@ The `ensure` keys:
|
|
|
401
419
|
`idempotent: unknown`, the same as free-form alone. `ensure` lines always
|
|
402
420
|
precede the `script` lines in the inline block.
|
|
403
421
|
- **`ensure` whose lists are all empty** (`ensure: {packages: []}`) is
|
|
404
|
-
refused at load
|
|
405
|
-
|
|
406
|
-
`Bash modification <name> has no script lines or files; nothing emitted
|
|
407
|
-
|
|
408
|
-
|
|
422
|
+
refused at load, because such an item would count as `declared` and
|
|
423
|
+
emit nothing: the builder would log
|
|
424
|
+
`Bash modification <name> has no script lines or files; nothing emitted`,
|
|
425
|
+
the bake would proceed without a provisioner for it, and the bundle
|
|
426
|
+
directory would hold a `run.sh` that runs nothing.
|
|
409
427
|
- **`commands` with `unless`** is guarded; **without `unless`** the command
|
|
410
428
|
runs on every bake and on every `csis-mods rerun`, so the item is
|
|
411
429
|
idempotent only if the command is.
|
|
@@ -415,7 +433,7 @@ The `ensure` keys:
|
|
|
415
433
|
- **`execute_command` set** puts that template on every block of every
|
|
416
434
|
item of the builder; **`configuration_user` set** instead puts
|
|
417
435
|
`chmod +x {{ .Path }}; sudo su - <user> -c '{{ .Vars }} {{ .Path }}'`
|
|
418
|
-
there, so the lines run as that user
|
|
436
|
+
there, so the lines run as that user; **both set** is refused
|
|
419
437
|
at load; **neither**, Packer's default applies and the lines run as the
|
|
420
438
|
bake user. The same for
|
|
421
439
|
`environment_vars` (emitted only when non-empty) and `expect_disconnect`
|
|
@@ -424,11 +442,10 @@ The `ensure` keys:
|
|
|
424
442
|
`type`** reaches whichever mod builder is `is_default: true`; in the
|
|
425
443
|
fixture that is `ansible-default`, so the item is loaded as an ANSIBLE
|
|
426
444
|
item and its `script`/`scripts`/`ensure` keys are refused as unknown.
|
|
427
|
-
With no `is_default: true` mod builder at all, the load is refused
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
for an item that reaches it anyway.
|
|
431
|
-
became an `AttributeError` at generation.
|
|
445
|
+
With no `is_default: true` mod builder at all, the load is refused:
|
|
446
|
+
`No default builder found in mod_builder list.` first, and the
|
|
447
|
+
orchestrator's `` modification '<name>' names no builder (`type`) ... ``
|
|
448
|
+
for an item that reaches it anyway.
|
|
432
449
|
**`type: <alias>`** behaves exactly as the builder's name: the loader
|
|
433
450
|
rewrites it.
|
|
434
451
|
- **AWS versus GCE runtime**: the lines are identical; only the `only`
|
|
@@ -463,7 +480,7 @@ integer mode no higher than `0777`, and not every kind empty); an item
|
|
|
463
480
|
with none of `script`, `scripts`, `ensure`; an item `type:` that names no
|
|
464
481
|
builder; an item with no `type:` when no mod builder is the default; a
|
|
465
482
|
builder declaring both `configuration_user` and `execute_command`, or
|
|
466
|
-
|
|
483
|
+
`extra_arguments`. Each is a
|
|
467
484
|
`ValueError` (or `KeyError` for the type) that stops the load; the CLI
|
|
468
485
|
prints it and exits 1. Nothing loads, so nothing is generated.
|
|
469
486
|
|
|
@@ -516,81 +533,41 @@ failure prints `FAIL <image>/<mod>: rc=...` on stderr and exits 1. A
|
|
|
516
533
|
|
|
517
534
|
## When it fails
|
|
518
535
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
`ansible-default` with no content; the bash builder wrote raw lines and an
|
|
526
|
-
`exit 0` straight into the build file, so any real bash item failed
|
|
527
|
-
`packer validate`; and `scripts:` was not a field, so files were dropped.
|
|
528
|
-
Fixed at gate 8; guarded by
|
|
529
|
-
[test_v2_gate8_modifications.py](../../tests/test_v2_gate8_modifications.py)
|
|
530
|
-
and [tests/test_bash_provisioner.py](tests/test_bash_provisioner.py). If
|
|
531
|
-
a comment line ever appears outside a `provisioner "shell" {` block, or
|
|
532
|
-
`exit 0` appears in a build file, this regressed.
|
|
533
|
-
- **2026-09-10, the first `full-test`'s mod-test leg could not start a
|
|
534
|
-
container** for an EL 10 image: Docker Hub's `rockylinux` library stops at
|
|
535
|
-
9. The stand-in for `rhel` 10 and above is `almalinux:<version>` since
|
|
536
|
-
(`test_image_for` in [mod_tests.py](../base/src/cs_image_system/base/mod_tests.py)).
|
|
537
|
-
Symptom then: `docker run` failed to pull and the bash item's test never
|
|
538
|
-
ran. If `test-mods` reports `unsupported` with
|
|
539
|
-
`no local test image for the root OS family`, the family/version has no
|
|
540
|
-
stand-in; set `local_test_image` on the OS builder.
|
|
541
|
-
- **Found live, undated, in the sibling shell emitter for image tests**
|
|
542
|
-
([v2_provisioners.py](../packer-plugin/src/cs_image_system/packer_plugin/v2_provisioners.py)):
|
|
543
|
-
an `rpm --qf '%{NAME}'` line broke `packer fmt`, because `%{` and `${`
|
|
544
|
-
open Packer template directives. This plugin's `_hcl_string` escapes both
|
|
545
|
-
the same way. If `packer fmt` or `validate` ever reports
|
|
546
|
-
`Invalid template directive` or `Invalid template interpolation` on a
|
|
547
|
-
line you wrote, the escape regressed; the line in `generated/` should
|
|
548
|
-
read `%%{` / `$${`.
|
|
549
|
-
- **2026-09-20, an assertion that could not fail** (image `tests:`, not this
|
|
550
|
-
plugin, but the same shell and the same trap;
|
|
551
|
-
[OPERATIONS.md](../../docs/OPERATIONS.md), "What a green bake proves").
|
|
552
|
-
Under `sh -e`, a command inside an AND-OR list other than the last does
|
|
553
|
-
not abort the script, so a `script:` line written `check || { echo no; }`
|
|
554
|
-
never fails the build however false `check` is. Two bakes went green
|
|
555
|
-
asserting a package that did not exist. If a `script` line must be able
|
|
556
|
-
to stop the bake, end it in something that exits nonzero (`|| exit 1`),
|
|
557
|
-
not in a braced group that returns 0. The plugin's own `commands` form,
|
|
558
|
-
`( unless ) || { run; }`, is deliberate: the group is the LAST element,
|
|
559
|
-
so a failing `run` does abort.
|
|
560
|
-
- **2026-09-23, an item declared with the builder's alias failed at
|
|
561
|
-
generation** (found while writing this README, on a copy of the fixture
|
|
562
|
-
with `type: bash` on `derivative-setup`). Load and `validate` passed; the
|
|
563
|
-
run log then showed
|
|
564
|
-
`AssertionError: No mod builder bash found in context for imgfile-basic-dask`
|
|
565
|
-
and the summary was `ok: false` with no validation errors. Fixed by
|
|
566
|
-
stage 63 item 15 (2026-09-25): the loader rewrites the alias to the
|
|
567
|
-
builder's name, and `tests/test_v2_defects_loading.py` generates that
|
|
568
|
-
same fixture copy.
|
|
569
|
-
|
|
570
|
-
Failures the code raises that have not been seen outside tests:
|
|
536
|
+
Incidents that have happened, with their cause and fix, are recorded on
|
|
537
|
+
[INCIDENTS.md](../../docs/INCIDENTS.md); what follows keeps each
|
|
538
|
+
failure's symptom, its meaning and what to do.
|
|
539
|
+
|
|
540
|
+
Symptoms of the emission and of the bake, with what each means and what
|
|
541
|
+
to do:
|
|
571
542
|
|
|
572
543
|
| What you see | Where | Meaning and what to do |
|
|
573
544
|
|---|---|---|
|
|
545
|
+
| A `# Modifications for ...` comment line outside a `provisioner "shell" {` block, or `exit 0` in a build file | `generated/`; `packer validate` fails | The builder wrote raw lines into the build file instead of provisioner blocks. [tests/test_bash_provisioner.py](tests/test_bash_provisioner.py) and [test_modifications.py](../../tests/test_modifications.py) pin the block form; this is a regression of the plugin, not of the item. |
|
|
546
|
+
| A bash item recorded in lineage as `ansible-default` with no content, or its script files dropped | `meta-state/lineage.yaml` after a bake | The item was resolved to the default mod builder instead of the builder its `type:` names. The same two tests pin the lookup by the item's `type`; check the item's `type:` names this builder. |
|
|
547
|
+
| `test-mods` reports `unsupported` with `no local test image for the root OS family` | `test-mods` | The OS family and version have no stand-in container image (`test_image_for` in [mod_tests.py](../base/src/cs_image_system/base/mod_tests.py)): Docker Hub's `rockylinux` library stops at 9, so `rhel` 10 and above stand in as `almalinux:<version>`, and a family outside that table has none. Set `local_test_image` on the OS builder. |
|
|
548
|
+
| `Invalid template directive` or `Invalid template interpolation` from `packer fmt` or `validate` on a line you wrote | the Packer plugin's run-now commands | `%{` and `${` open Packer template directives; this plugin's `_hcl_string` escapes both as `%%{` / `$${` (the sibling shell emitter for image tests, [v2_provisioners.py](../packer-plugin/src/cs_image_system/packer_plugin/v2_provisioners.py), does the same). If the line in `generated/` does not read so, the escape regressed. |
|
|
549
|
+
| A `script:` line written `check || { echo no; }` never fails the build however false `check` is | the bake, green | Under `sh -e` a command inside an AND-OR list other than the last does not abort the script, so a bake can go green asserting a package that does not exist (the same trap holds for an image's `tests:`; [OPERATIONS.md](../../docs/OPERATIONS.md), "What a green bake proves"). If a `script` line must be able to stop the bake, end it in something that exits nonzero (`\|\| exit 1`), not in a braced group that returns 0. The plugin's own `commands` form, `( unless ) \|\| { run; }`, is deliberate: the group is the LAST element, so a failing `run` does abort. |
|
|
550
|
+
| `AssertionError: No mod builder bash found in context for <image>` in the run log, summary `ok: false` with no validation errors, after load and `validate` passed | generation | An item's `type:` reached generation as the builder's alias; the loader rewrites an alias to the builder's name at load (`tests/test_modifications.py` generates a fixture copy with `type: bash`), so a tree whose loader does not is on an older form ([the stage index](../../docs/CHANGES.md#s63)). Write the builder's name, or update. |
|
|
574
551
|
| `Bash modification '<name>' must supply 'script' (inline lines), 'scripts' (script files) and/or 'ensure' (declarative, idempotent)` | load; CLI exit 1 | The item has none of the three (a `config:`-only item). Add one or remove the item. |
|
|
575
552
|
| `Bash modification '<name>': unknown ensure keys ['users']` | load; CLI exit 1 | Only `packages`, `files`, `services`, `commands` exist. Move anything else to `commands`. |
|
|
576
553
|
| A pydantic error naming a key (`Extra inputs are not permitted`) on the builder or the item | load; CLI exit 1 | A misspelt or foreign key (`playbooks:` on a bash item, `script:` on an ansible item). Check the item's `type:` reaches the builder you meant; `type: default` reaches the default builder, which in the fixture is ansible. |
|
|
577
|
-
| ``<builder>: `parameters` was retired
|
|
554
|
+
| ``<builder>: `parameters` was retired ...`` | load; CLI exit 1 | Delete `parameters:` from the builder. |
|
|
578
555
|
| `KeyError: Could not resolve builder '<type>' referenced in Image(...)` | load; CLI exit 1 | The item's `type:` names no `mod_builders:` entry. |
|
|
579
|
-
| `` mod builder '<name>': `configuration_user` and `execute_command` both say how the script runs; declare one `` | load; CLI exit 1 |
|
|
580
|
-
| A pydantic unknown-key error naming `extra_arguments` on a `bash-remote` builder | load; CLI exit 1 |
|
|
581
|
-
| `No default builder found in mod_builder list.` | load; CLI exit 1 | No mod builder says `is_default: true`. Mark one, or give every item a `type:`. The orchestrator's own form, for an item that reaches it, is `` modification '<name>' names no builder (`type`) and no mod builder is `is_default: true`; name one with `type:` or mark a default
|
|
556
|
+
| `` mod builder '<name>': `configuration_user` and `execute_command` both say how the script runs; declare one `` | load; CLI exit 1 | `configuration_user` writes its own `execute_command`; keep one. To run as another user with a custom command, put the `sudo su - <user>` in your `execute_command`. |
|
|
557
|
+
| A pydantic unknown-key error naming `extra_arguments` on a `bash-remote` builder | load; CLI exit 1 | The field does not exist. Delete it; change the invocation with `execute_command`. |
|
|
558
|
+
| `No default builder found in mod_builder list.` | load; CLI exit 1 | No mod builder says `is_default: true`. Mark one, or give every item a `type:`. The orchestrator's own form, for an item that reaches it, is `` modification '<name>' names no builder (`type`) and no mod builder is `is_default: true`; name one with `type:` or mark a default ``. |
|
|
582
559
|
| `Executable bash specified for provider bash-remote not found in executables list.` | `validate`; exit 1 | Add a `bash` entry to `cfg/executables.yml` or change the builder's `executable:`. |
|
|
583
560
|
| `bash: binary '/usr/local/bin/bash' not found (declared in cfg/executables.yml; an absolute path, or a name on PATH)` | `validate` and every run; exit 1 | The pinned path is wrong on this machine. Symlink the binary there or fix `binary:`. |
|
|
584
561
|
| ``bash: BashVersionChecker could not parse a version from `/usr/local/bin/bash --version` `` | `validate` and every run | The first line of `--version` has no `version N.N`. Something other than bash is at that path. |
|
|
585
562
|
| `bash 5.3.15 does not meet its requirement <spec> (cfg/executables.yml)` | `validate` and every run | The floor moved or the machine is behind. |
|
|
586
563
|
| `FileNotFoundError: Bash modification script mod_image.sh does not exist or is not a file` | generation; run `ok: false`, traceback in the log | The path is resolved from the configuration root, not from the image file's directory or the shell's. Fix the path or `--root-dir`. |
|
|
587
564
|
| `Bash modification '<name>': ensure declares [...] but every one is empty, so it would emit nothing` | load; CLI exit 1 | Every `ensure` list is empty. Fill one or drop `ensure`. |
|
|
588
|
-
| `Bash modification '<name>': ensure.files[N]: `path` is required` (or `ensure.commands[N]: `run` is required`) | load; CLI exit 1 | A `files` entry without `path`, or a `commands` entry without `run`.
|
|
589
|
-
| `Bash modification '<name>': ensure.packages must be a list of package names, not str 'git'` | load; CLI exit 1 | `packages:` (or `services:`) given as a string
|
|
565
|
+
| `Bash modification '<name>': ensure.files[N]: `path` is required` (or `ensure.commands[N]: `run` is required`) | load; CLI exit 1 | A `files` entry without `path`, or a `commands` entry without `run`. |
|
|
566
|
+
| `Bash modification '<name>': ensure.packages must be a list of package names, not str 'git'` | load; CLI exit 1 | `packages:` (or `services:`) given as a string, which shell would iterate letter by letter. Write a list (`packages: [git]`). |
|
|
590
567
|
| `Bash modification '<name>': ensure.files[N].mode 644 is not a mode YAML could have read from octal` | load; CLI exit 1 | An unquoted `644`, which YAML reads as a decimal number. Write `mode: "0644"`. |
|
|
591
568
|
| `Bash modification '<name>': ensure.files[N]: unknown keys [...]` | load; CLI exit 1 | Only `path`, `content`, `mode` on a file and `run`, `unless` on a command. |
|
|
592
569
|
| `csis ensure: no dnf, yum or apt-get to install <pkg>` | apply / `test-mods` | The image has none of the three package managers. Use a `script` line with the one it has. |
|
|
593
|
-
| `dnf` (or `yum`, `apt-get`) error on a package step | apply / `test-mods` | The first package manager found could not install the package, and its message is the real one (no match, no repository, no network from the build subnet)
|
|
570
|
+
| `dnf` (or `yum`, `apt-get`) error on a package step | apply / `test-mods` | The first package manager found could not install the package, and its message is the real one (no match, no repository, no network from the build subnet); the others are never tried, so no misleading `apt-get: command not found` follows it on an EL host. |
|
|
594
571
|
| `cmp: command not found` or `base64: command not found` | apply | The image lacks diffutils / coreutils. Add the package to an earlier item's `ensure.packages` or a `script` line. |
|
|
595
572
|
| `sudo: a terminal is required to read the password` | apply | The bake ssh user has no passwordless `sudo`. This is an OS builder / runtime matter, not the item's. |
|
|
596
573
|
| `Failed to enable unit: Unit <svc>.service does not exist` | apply | `services:` names a unit the image has not installed yet; install it earlier in the same item's `packages` (packages run before services). |
|
|
@@ -622,20 +599,20 @@ Failures the code raises that have not been seen outside tests:
|
|
|
622
599
|
[abstract_version_checker.py](../base/src/cs_image_system/base/basic/abstract_version_checker.py).
|
|
623
600
|
- The image builder that calls it and hosts its output:
|
|
624
601
|
[packer-plugin](../packer-plugin)
|
|
625
|
-
([
|
|
602
|
+
([packer_image_builder.py](../packer-plugin/src/cs_image_system/packer_plugin/packer_image_builder.py),
|
|
626
603
|
[v2_local_mods.py](../packer-plugin/src/cs_image_system/packer_plugin/v2_local_mods.py)).
|
|
627
604
|
- Lineage records for modifications:
|
|
628
605
|
[lineage.py](../base/src/cs_image_system/base/lineage.py) (`mod_records`);
|
|
629
606
|
the release gate in [release.py](../base/src/cs_image_system/base/release.py).
|
|
630
607
|
- Tests: [tests/test_bash_provisioner.py](tests/test_bash_provisioner.py)
|
|
631
608
|
(two blocks for files plus inline, builder options, script copying);
|
|
632
|
-
[
|
|
609
|
+
[test_modifications.py](../../tests/test_modifications.py)
|
|
633
610
|
(both kinds emitted, unknown type refused, the load rule);
|
|
634
|
-
[
|
|
611
|
+
[test_modifications.py](../../tests/test_modifications.py)
|
|
635
612
|
(`ensure` guards, idempotence recorded, the bundle);
|
|
636
|
-
[
|
|
613
|
+
[test_mod_tests.py](../../tests/test_mod_tests.py)
|
|
637
614
|
(the container tests on the fixture's bash item);
|
|
638
|
-
[
|
|
615
|
+
[test_version_checks.py](../../tests/test_version_checks.py)
|
|
639
616
|
(`BashVersionChecker` found by name, versions judged).
|
|
640
617
|
- No terraform modules and no hashicorp-utils usage: the plugin writes
|
|
641
618
|
Packer provisioner lines directly.
|
{cs_image_system_bash_mod_plugin-0.1.5 → cs_image_system_bash_mod_plugin-0.1.7}/pyproject.toml
RENAMED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cs-image-system-bash-mod-plugin"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.7"
|
|
8
8
|
description = "The shell modification plugin: inline script, script files and an ensure form become Packer shell provisioners"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Mykel Alvis", email = "mykelalvis@infrastructurebuilder.org" }]
|
|
@@ -12,8 +12,8 @@ license = "Apache-2.0"
|
|
|
12
12
|
license-files = ["LICENSE"]
|
|
13
13
|
requires-python = ">=3.13"
|
|
14
14
|
dependencies = [
|
|
15
|
-
"cs-image-system-contract
|
|
16
|
-
"cs-image-system-base==0.1.
|
|
15
|
+
"cs-image-system-contract>=0.4,<0.5",
|
|
16
|
+
"cs-image-system-base==0.1.7",
|
|
17
17
|
"pydantic>=2.13",
|
|
18
18
|
]
|
|
19
19
|
|
|
@@ -9,17 +9,15 @@ import shutil
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import TypeVar, cast
|
|
11
11
|
|
|
12
|
-
from cs_image_system.contract.assets import AssetSet
|
|
13
|
-
from cs_image_system.base.bake_steps import execute_command_line
|
|
14
|
-
|
|
15
|
-
from .bash_models import BASH_BUILDER, BASH_EXECUTABLE, BashBuilderModel, BashModItemModel, BashModBuilderModel
|
|
16
12
|
from cs_image_system.base.basic.abstract_version_checker import AbstractVersionChecker
|
|
13
|
+
from cs_image_system.base.basic.builder_base_image import ImageBuilderBase, build_target_label_of
|
|
17
14
|
from cs_image_system.base.basic.builder_base_mod import ModBuilderBase
|
|
18
|
-
from cs_image_system.contract.lifecycle import ExecutionLifecyclePhase
|
|
19
15
|
from cs_image_system.base.models.image import Image
|
|
20
|
-
from cs_image_system.base.basic.builder_base_image import ImageBuilderBase
|
|
21
16
|
from cs_image_system.base.models.moditem_type import ModItemModel
|
|
22
|
-
from cs_image_system.
|
|
17
|
+
from cs_image_system.contract.assets import AssetSet
|
|
18
|
+
from cs_image_system.contract.lifecycle import ExecutionLifecyclePhase
|
|
19
|
+
|
|
20
|
+
from .bash_models import BASH_BUILDER, BASH_EXECUTABLE, BashBuilderModel, BashModBuilderModel, BashModItemModel
|
|
23
21
|
|
|
24
22
|
log = logging.getLogger(__name__)
|
|
25
23
|
|
|
@@ -81,14 +79,14 @@ class BashModBuilder(ModBuilderBase[Q]):
|
|
|
81
79
|
for body in parts:
|
|
82
80
|
lines += ['provisioner "shell" {', f' only = ["{build_target_label_of(ibb, image)}"]']
|
|
83
81
|
lines += body
|
|
84
|
-
command = self.model.effective_execute_command() #
|
|
82
|
+
command = self.model.effective_execute_command() # configuration_user
|
|
85
83
|
if command:
|
|
86
84
|
lines.append(f" execute_command = {_hcl_string(command)}")
|
|
87
85
|
else:
|
|
88
|
-
#
|
|
86
|
+
# through the bake's step runner, so `ensure: packages`
|
|
89
87
|
# and a script's own installs wait out a held package database;
|
|
90
88
|
# a builder that says how its script runs keeps its own command
|
|
91
|
-
lines.append(execute_command_line(" "))
|
|
89
|
+
lines.append(self._get_context().services.commands.execute_command_line(" "))
|
|
92
90
|
if self.model.environment_vars:
|
|
93
91
|
lines.append(" environment_vars = [" + ", ".join(_hcl_string(v) for v in self.model.environment_vars) + "]")
|
|
94
92
|
if self.model.expect_disconnect:
|
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
6
|
from dataclasses import field
|
|
7
|
-
from cs_image_system.contract.model_config import CSIS_MODEL_CONFIG
|
|
8
|
-
from pydantic.dataclasses import dataclass # stage 23: validation at construction
|
|
9
7
|
from pathlib import Path
|
|
10
8
|
from typing import Any
|
|
11
9
|
|
|
10
|
+
from pydantic.dataclasses import dataclass # validation at construction
|
|
11
|
+
|
|
12
12
|
from cs_image_system.base.models.mod_builder import ModBuilderModel
|
|
13
13
|
from cs_image_system.base.models.moditem_type import ModItemModel
|
|
14
|
+
from cs_image_system.contract.model_config import CSIS_MODEL_CONFIG
|
|
15
|
+
|
|
14
16
|
BASH_EXECUTABLE: str = "bash"
|
|
15
17
|
|
|
16
18
|
BASH_BUILDER: str = "bash-remote"
|
|
@@ -31,8 +33,8 @@ def _mode_text(mode: Any) -> str:
|
|
|
31
33
|
"""A file mode as the four-digit octal text ``install -m`` takes.
|
|
32
34
|
|
|
33
35
|
YAML reads an unquoted ``0644`` as the integer 420, so an integer is
|
|
34
|
-
rendered back in octal
|
|
35
|
-
|
|
36
|
+
rendered back in octal; a string must already be three or four octal
|
|
37
|
+
digits."""
|
|
36
38
|
if isinstance(mode, bool):
|
|
37
39
|
raise ValueError(f"mode {mode!r} is not a file mode")
|
|
38
40
|
if isinstance(mode, int):
|
|
@@ -74,8 +76,8 @@ def _entries(label: str, kind: str, value: Any, allowed: set[str], required: str
|
|
|
74
76
|
|
|
75
77
|
|
|
76
78
|
def validate_ensure(label: str, ensure: Any) -> dict[str, Any]:
|
|
77
|
-
"""Every ``ensure`` entry checked at load, one rule per kind
|
|
78
|
-
|
|
79
|
+
"""Every ``ensure`` entry checked at load, one rule per kind. A valid
|
|
80
|
+
mapping comes back UNCHANGED except that an integer
|
|
79
81
|
file mode becomes its octal text: the lineage fingerprint hashes this
|
|
80
82
|
mapping, so a tree that was already valid keeps every fingerprint.
|
|
81
83
|
|
|
@@ -171,7 +173,7 @@ class BashModItemModel(ModItemModel):
|
|
|
171
173
|
pkgs = [str(p) for p in self.ensure.get("packages", []) or []]
|
|
172
174
|
if pkgs:
|
|
173
175
|
joined = " ".join(pkgs)
|
|
174
|
-
#
|
|
176
|
+
# the FIRST package manager that exists installs,
|
|
175
177
|
# and its failure is the failure (the old chain fell through dnf,
|
|
176
178
|
# yum and apt-get, so an EL host showed `apt-get: command not found`)
|
|
177
179
|
out.append(f"for p in {joined}; do rpm -q \"$p\" >/dev/null 2>&1 || dpkg -s \"$p\" >/dev/null 2>&1 || "
|
|
@@ -202,7 +204,7 @@ class BashBuilderModel(ModBuilderModel):
|
|
|
202
204
|
"""Dataclass representing a Bash script modification.
|
|
203
205
|
"""
|
|
204
206
|
type = BASH_BUILDER
|
|
205
|
-
#
|
|
207
|
+
# when set, every item's lines run as this user -- the shell
|
|
206
208
|
# provisioner's execute_command becomes `sudo su - <user> -c '...'`, so the
|
|
207
209
|
# bake user only launches them. Declaring it with execute_command is
|
|
208
210
|
# refused (one of the two decides how the script runs).
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
-
|
|
7
|
-
from .bash_models import BASH_BUILDER, BASH_EXECUTABLE, BashModItemModel, BashModBuilderModel
|
|
6
|
+
|
|
8
7
|
from cs_image_system.base.basic.abstract_plugin_metadata import AbstractPluginMetadata
|
|
9
8
|
|
|
9
|
+
from .bash_builder import BashModBuilder, BashVersionChecker
|
|
10
|
+
from .bash_models import BASH_BUILDER, BASH_EXECUTABLE, BashModBuilderModel, BashModItemModel
|
|
11
|
+
|
|
10
12
|
log = logging.getLogger(__name__)
|
|
11
13
|
|
|
12
14
|
class BashModTypes(AbstractPluginMetadata):
|
|
@@ -9,14 +9,14 @@ from typing import Any, cast
|
|
|
9
9
|
|
|
10
10
|
from cs_image_system.bash_mod_plugin.bash_builder import BashModBuilder
|
|
11
11
|
from cs_image_system.bash_mod_plugin.bash_models import BashModItemModel
|
|
12
|
-
from cs_image_system.
|
|
12
|
+
from cs_image_system.contract.lifecycle import ExecutionLifecyclePhase
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def _builder(**model_fields):
|
|
16
16
|
fields = {"execute_command": None, "environment_vars": [], "expect_disconnect": False}
|
|
17
17
|
fields.update(model_fields)
|
|
18
18
|
model = SimpleNamespace(**fields)
|
|
19
|
-
#
|
|
19
|
+
# the builder asks the model how the script runs
|
|
20
20
|
model.effective_execute_command = lambda: model.execute_command
|
|
21
21
|
b = BashModBuilder.__new__(BashModBuilder)
|
|
22
22
|
b._model = model # type: ignore[attr-defined]
|
|
@@ -25,8 +25,10 @@ def _builder(**model_fields):
|
|
|
25
25
|
|
|
26
26
|
def _emit(builder, mod):
|
|
27
27
|
image = SimpleNamespace(name="img")
|
|
28
|
+
# the image builder labels the build target through its runtime; a stub answers here
|
|
29
|
+
ibb = SimpleNamespace(build_target_label=lambda img: f"amazon-ebs.{img.name}")
|
|
28
30
|
items = builder.generate_items_during_modification(
|
|
29
|
-
image, mod, cast(Any,
|
|
31
|
+
image, mod, cast(Any, ibb), ExecutionLifecyclePhase.IMAGE_GENERATION, Path("build.pkr.hcl"))
|
|
30
32
|
return "\n".join(a.value for a in items)
|
|
31
33
|
|
|
32
34
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|