cs-image-system-ansible-plugin
The Ansible modification plugin. It registers one modification builder,
ansible, and the modification item model that goes with it. A
modification item under an image's modifications: names playbooks; for
each playbook the builder appends a provisioner "ansible" (preceded by a
small provisioner "shell" that pins a Python interpreter for Ansible) to
the image builder's Packer build file, scoped to that one image. The plugin
owns no files, roots or commands of its own: everything it produces lands
in the Packer root the image builder writes and runs when that root's
packer build . runs.
What it registers
Entry point (pyproject.toml):
[project.entry-points."cs_image_system.plugins.modification"]
ansible_plugin = "cs_image_system.ansible_plugin.main:initialize"
initialize() (main.py)
returns an AnsibleEbsTypes metadata object (metadata version 1, Python
3.13) with two service keys:
| Service key | Classes | Classification (VCT) |
|---|---|---|
ansible |
AnsiblePackerModBuilderModel, AnsiblePackerModBuilder, AnsibleModItemModel |
MOD_BUILDER_MODEL / MOD_BUILDER / MOD_BUILDER_ITEM_MODEL |
ansible-playbook |
AnsibleVersionChecker |
VERSION_CHECKER |
How a YAML entry selects these classes:
- A
mod_builders:entry usestype: ansible(no aliases) to get anAnsiblePackerModBuilderModeland itsAnsiblePackerModBuilder. - A modification item under an image's
modifications:usestype:to name a builder: the builder'sname, one of itsaliases:,default, or notype:at all (which meansdefault, the builder withis_default: true). The builder model'sget_target_deferred_type_by_VCT(MOD_BUILDER_ITEM_MODEL)returnsAnsibleModItemModel, which is the class the item is loaded into. The orchestrator then rewrites the item'stypeto the builder's name (an item that omittedtype:showsansible-defaultafterwards, and that is the name the emitted comment carries). - An
executables:entry namedansible-playbookis version-checked byAnsibleVersionChecker(regex\s+\[core\s([\d\.]+)over the first line ofansible-playbook --version).
Models
AnsibleBuilderModel and AnsiblePackerModBuilderModel (ansible)
Defined in ansible_models.py.
AnsibleBuilderModel extends
ModBuilderModel,
which adds nothing to
BuilderModel
(name, type, description, aliases, executable, is_default,
config, gitignore, tags). AnsiblePackerModBuilderModel is the
registered subclass; it adds no fields.
| Field | Type | Default | Meaning |
|---|---|---|---|
extra_arguments |
list[str] |
[] |
Prepended to every emitted provisioner "ansible"'s extra_arguments; each entry is HCL-escaped (stage 63). |
expect_disconnect |
bool |
False |
Emits expect_disconnect = true on every provisioner. |
ansible_connection |
str | None |
None |
Emits connection_type = "<value>" when set (for example docker). |
configuration_user |
str | None |
None |
When set, the user of every provisioner "ansible" this builder emits, beating the bake-user resolver (CONFIGURATION 5.1.1); the one place a modification connects as someone other than the bake user (stage 63; it was declared and not read until 2026-09-25). |
There is no builder-level playbooks field (stage 48.4 removed it: the
list was copied beside the Packer root and never run). Every model in the
system refuses unknown keys, so a builder that still declares
playbooks: fails to load. The playbooks that run are the items' own.
Base fields this plugin constrains: executable should name the
ansible-playbook executable (the fixture does), though the emission does
not invoke it; Packer runs Ansible. parameters: is refused at load (a
base-model rule). config: on the builder is carried but never read.
AnsibleModItemModel
Same file. Extends
ModItemModel
(SubRootItem -> RootItem -> NameTyped).
| Field | Type | Default | Meaning |
|---|---|---|---|
playbooks |
list[str] |
[] |
Playbook files, relative to the configuration root, run in order. One provisioner "ansible" per entry. An item with none is refused at load (see below). |
Inherited item fields:
| Base field | From | Default | Use in this plugin |
|---|---|---|---|
name |
NameTyped |
required | Names the provisioner comment and the local bundle directory (NN-<name>). |
type |
ModItemModel |
default |
Foreign key to a mod_builders: entry (see above). |
description, aliases, tags |
NameTyped / RootItem |
Carried; not emitted. | |
config |
RootItem |
{} |
Not read by this plugin. It takes part in the lineage content hash of the modification and nothing else. |
_model_id |
ModItemModel |
set at load | The owning image's global id (the orchestrator assigns it while structuring the image's modifications:). identified_model (and builder) therefore resolve to the image, not to the modification builder. |
remap_self_with_copied_assets(copied) de-duplicates the item's own
playbooks (first occurrence wins, order kept) and rewrites any entry
present in copied to its copied path. Since stage 48.4 the builder's
copy_external_assets returns an empty mapping, so in practice this call
only de-duplicates.
The builder
AnsiblePackerModBuilder
(ansible_builder.py)
extends
ModBuilderBase.
A modification builder implements the three per-item hooks the image
builder calls while it writes a build file; it does not implement the
lifecycle-level hooks (generate_items_before/during/after,
get_commands_to_run_*, pre/post_finalize_phase), so it emits no files
of its own, runs no generation-time commands and defers nothing to a
run-<lifecycle>.sh.
For every modification of an instance image, the Packer image builder (packer_ebs_builder.py) calls, in order:
copy_external_assets(block_dir, mod): returns{}. Nothing is copied here (stage 48.4); the item's playbooks are copied in step 4.mod.remap_self_with_copied_assets({})on the item (see above).generate_items_before_modification: returns an empty asset set.generate_items_during_modification(image, mod, image_builder, phase, build_path): for each playbook of the item, in order:- if the path is relative and the file exists (relative to the process
working directory, which the run sets to the configuration root),
copies it to
<generation root>/<block dir>/<playbook>so Packer finds it next to the HCL. An absolute path is emitted as given and not copied. A relative path that is not a file is refused here (stage 63):FileNotFoundErrorwithmodification '<name>': playbook '<path>' does not exist (a relative path is read from the configuration root). Until 2026-09-25 it was emitted anyway and failed only when packer ran; - appends to the build file a comment
# Modifications for <item name> of type <item type>; - a
provisioner "shell"withonly = ["<source type>.<image>"]whose single inline command creates/usr/local/bin/csis-ansible-pythonif absent: it triespython3.12,python3.11,python3.9(installing withsudo dnfwhen needed), then the newest/usr/bin/python3.N, thenpython3, and symlinks the winner. The command is guarded ontest -x, so the second and later playbooks of an image re-run it as a no-op; - a
provisioner "ansible"withonly,playbook_file = "<playbook>",user = "<name>"(the builder'sconfiguration_userwhen set, stage 63; otherwise the user the runtime names to provisioners throughbake_ssh_username(image), where the GCE runtime answers the user the packer source resolves for that image and the AWS runtime answers nothing, so no line),extra_arguments = [<builder extra_arguments>..., "-e", "ansible_python_interpreter=/usr/local/bin/csis-ansible-python"],connection_typewhenansible_connectionis set, andexpect_disconnect = truewhen set. Every string written into these lines (playbook_file,user, eachextra_argumentsentry,connection_type) goes through_hcl: backslashes and double quotes are escaped and packer's template openers${and%{are doubled to$${and%%{, so the value reaches ansible literally (stage 63; a"in any of them used to break the build file, 2026-09-25).
- if the path is relative and the file exists (relative to the process
working directory, which the run sets to the configuration root),
copies it to
generate_items_after_modification: returns an empty asset set.
The only label comes from build_target_label_of(image_builder, image):
the image builder's build_target_label(image) when it has one
(amazon-ebs.<image> for packer-ebs, googlecompute.<image> for
packer-gce), else the legacy amazon-ebs.<image>.
The provisioners run on the build VM when the image builder's deferred
packer build . executes. Separately, the Packer plugin's local
modification bundle
(v2_local_mods.py)
records the item: its directory csis-mods/<image>/NN-<name>/ receives a
copy of each playbook, a run.sh that runs
ansible-playbook -i localhost, -c local '<playbook>' per playbook (after
checking that ansible-playbook exists on the image, exit 3 when it does
not), and a MANIFEST.yaml with operation: ansible and
idempotent: ansible. Both values come from
lineage.py's mod_records,
which classifies any item carrying playbooks as an ansible operation and
hashes the playbook files' contents (or the path string, when the file is
not found) together with the item's type and config.
A modification item with only config: is refused
An item that declares config: and no playbooks: has nothing to modify
with: generate_items_during_modification emits one provisioner per
playbook, and no code in this plugin reads config:, so such an item
would render nothing while lineage, the Packer plugin's local bundle and
the modification tests all recorded it as present. The item model refuses
it at load, by name ("Ansible modification 'x' declares no playbooks: it
has nothing to modify with"), so validate, run and every other
configuration load stop there
(ansible_models.py).
The fixture carries no such item (stage 43 removed the two it had).
Emission
Nothing under generated/ belongs to this plugin alone; its output is
embedded in the Packer plugin's files. From the golden emission for
imgfile-basic-dask in
pckr-ebs-ans-image-generation-block-000-build.pkr.hcl:
# Modifications for dask-setup-jeffy of type ansible-default
provisioner "shell" {
only = ["amazon-ebs.imgfile-basic-dask"]
inline = [
"test -x /usr/local/bin/csis-ansible-python || { ... }",
]
}
provisioner "ansible" {
only = ["amazon-ebs.imgfile-basic-dask"]
playbook_file = "setup_dask.yml"
extra_arguments = ["-e", "ansible_python_interpreter=/usr/local/bin/csis-ansible-python"]
}
Files it causes to exist beside the Packer root
(generated/instance-image/pckr-ebs-ans/image-generation/block-000/):
| File | Origin |
|---|---|
setup_dask.yml, setup_data_science.yml |
Item playbooks, copied by generate_items_during_modification. |
csis-mods/imgfile-basic-dask/01-dask-setup-jeffy/ MANIFEST.yaml, run.sh, setup_dask.yml |
The local bundle entry the Packer plugin writes for this item. |
The GCE builder's root
(pckr-gce-ans/.../block-000)
carries the same provisioners with only = ["googlecompute.imgfile-basic-dask"]
and user = "packer".
Example configuration
The builder, from tests/fixtures/config/cfg/mod-builders.yml:
mod_builders:
- name: ansible-default
is_default: true
type: ansible
executable: ansible-playbook
config:
key1: value1
key2: value2
An item that selects it without a type: (so default), from
tests/fixtures/config/images/image1.yaml:
images:
- name: imgfile-data-science
group: stofs
aliases:
- ds-node
tags:
data_science: "true"
source_image: my-deb-12
description: "Data Science node with pre-installed libraries and tools."
runtimes:
- image_builder: pckr-ebs-ans
modifications:
- name: data-science-setup
playbooks:
- setup_data_science.yml
config:
python_version: "3.10"
r_version: "4.2.1"
The playbook path is relative to the configuration root
(tests/fixtures/config/setup_data_science.yml).
The same file's imgfile-basic-dask names the builder explicitly
(type: ansible-default) and carries an ansible item next to a bash one;
setup_dask.yml is a playbook
that has been through a live bake and shows the shape that works on the
pinned interpreter (see "When it fails").
Prerequisites and integration
The plugin has no credentials, accounts or network of its own and reads no environment variable. What must exist outside the system:
ansible-coreon the machine that runspacker build(the operator's machine, or the CI runner when performing). Packer's ansible provisioner runsansible-playbooklocally and connects to the build VM through the communicator packer already has; the target only needs Python (which the emitted shell step supplies). The plugin finds it through the configuration:cfg/executables.ymldeclares an entry namedansible-playbook(binary= path or a name on PATH,version= a PEP 440 requirement; the fixture pins/usr/local/bin/ansible-playbook>=2.16), and the mod builder'sexecutable: ansible-playbooknames that entry.validateand every run check the entry with this plugin'sAnsibleVersionChecker; the checker reads the[core x.y.z]figure, so the floor is an ansible-core version, not theansiblemeta-package's. The CI perform job installsansible-corewherecfg/executables.ymlpins it, after the record is pushed (docs/OPERATIONS.md, "CI performs on main").- Packer's
ansibleplugin on the same machine. It is declared per image builder underrequired_plugins:incfg/image-builders.yml(fixture:name: ansible,source: github.com/hashicorp/ansible,version: '>= 1.0.0'); the Packer plugin emits it into the root's plugins file andpacker initinstalls it. This plugin does not check that the declaration exists: an image builder without it fails atpacker buildwith packer's own "unknown provisioner" error. - On the build VM: passwordless
sudofor the communicator's user (the emitted interpreter step andbecomein playbooks both use it), and for EL-family images a reachable package repository, because the interpreter step maysudo dnf -y install python3.N. When nodnfexists or every install fails, the step falls back to the newest/usr/bin/python3.Npresent, thenpython3; a target with no Python at all fails at fact gathering. - The build VM's SSH user, on runtimes that fix one: the GCE runtime
builder's
bake_ssh_username(image), the user the packer source bakes that image as (the bake-user order, CONFIGURATION 5.1.1), is written as the provisioner'suser. Before stage 63 it read only the runtime'sssh_usernameand saidpackerotherwise, so an entry-level user baked as itself while ansible connected aspacker. The AWS runtime returnsNoneand the provisioner keeps packer's default (the communicator's user). Nothing else is needed from the runtime. - Reaching the build VM is the runtime's and the image builder's business (Session Manager on AWS, IAP on GCE), not this plugin's.
Python side: Python 3.13 or later, pydantic>=2.13, and
cs-image-system-system at the same version (pyproject.toml).
Configuration reference
mod_builders: entry with type: ansible
| Field | Type | Default | Meaning |
|---|---|---|---|
name |
str | required | The builder's name; what an item's type: names. Normalised by safe_name (no / or \; never default). |
type |
str | required | ansible. |
description |
str or null | null | Carried; not read by this plugin. |
aliases |
list[str] | [] |
Extra names an item's type: may use. |
is_default |
bool | false |
The builder an item with no type: (or type: default) resolves to. At most one mod builder should say so. |
executable |
str or null | null | The executables: entry for ansible-playbook. validate refuses a name that is not declared there; the emission never invokes it. |
config |
mapping | {} |
Accepted, not read. |
gitignore |
list[str] | [] |
Accepted, not read by this plugin. |
tags |
mapping | {} |
Accepted, not read by this plugin. |
extra_arguments |
list[str] | [] |
Emitted first in every provisioner's extra_arguments, before the system's -e ansible_python_interpreter=.... Each value is written as an escaped HCL string (backslash and " escaped, ${/%{ doubled; stage 63, they were written unescaped until 2026-09-25). |
expect_disconnect |
bool | false |
When true, every provisioner carries expect_disconnect = true. |
ansible_connection |
str or null | null | When set, every provisioner carries connection_type = "<value>" (escaped as above). |
configuration_user |
str or null | null | When set, every provisioner this builder emits carries user = "<value>", whatever the runtime would answer: ansible connects as that user instead of the bake user. Unset, the user comes from the runtime's bake SSH user as before. Read since stage 63 (accepted and ignored until 2026-09-25). The user must exist on the build VM and accept the bake's SSH key. |
parameters |
Refused at load (retired, stage 26). | ||
playbooks |
Refused at load as an unknown key (removed in stage 48.4). |
Modification item under an image's modifications: (ansible builder)
| Field | Type | Default | Meaning |
|---|---|---|---|
name |
str | required | Unique within the image; appears in the emitted comment and as the bundle directory NN-<name>. |
type |
str | default |
The mod builder: its name, an alias, default, or omitted. Rewritten to the builder's name at load. An unknown name is a KeyError at load. Omitted with no is_default: true mod builder: refused at load (stage 63). |
playbooks |
list[str] | [] |
One provisioner "ansible" per entry, in order; duplicates collapse to the first occurrence. Paths are relative to the configuration root (the run's working directory) or absolute; a relative path that does not exist is refused at generation (stage 63). Required in effect: an empty list is refused at load. |
description |
str or null | null | Accepted, not read. |
aliases |
list[str] | [] |
Accepted, not read. |
tags |
mapping | {} |
Accepted, not read. |
config |
mapping | {} |
Not read by this plugin and not passed to ansible. It is part of the modification's lineage content hash, so changing it makes the image due for a re-bake. |
Variations
- Runtime. When the image builder's runtime declares a bake SSH user
(GCE: the image's resolved bake user), the provisioner carries
user = "<that>"; when it does not (AWS), nouserline is emitted and packer's default applies. configuration_userdeclared on the builder (stage 63): every provisioner carriesuser = "<configuration_user>"on every runtime, AWS included, replacing whatever the runtime would have answered. Undeclared: the Runtime bullet above. Theonlylabel follows the image builder's source type (amazon-ebs.orgooglecompute.).extra_argumentsdeclared on the builder: they precede the system's two entries; otherwise the list is exactly["-e", "ansible_python_interpreter=/usr/local/bin/csis-ansible-python"].ansible_connectiondeclared: one extraconnection_typeline per provisioner; undeclared, packer picks the connection (ssh for the cloud sources).expect_disconnect: true: one extraexpect_disconnect = trueline per provisioner; false emits nothing.- Item
type:present vs absent. Present: resolved by name or alias, and an unknown name is a hard failure at load. Absent ordefault: theis_default: truemod builder. When no mod builder is default, the load is refused (stage 63): in practice the builder-list check refuses first withNo default builder found in mod_builder list., and the orchestrator's own refusal (modification '<name>' names no builder (`type`) and no mod builder is `is_default: true`; name one with `type:` or mark a default) covers an item that reaches it anyway. Until 2026-09-25 the item was left a plain mapping and the image builder's modification loop died on it with anAttributeError. - Relative vs absolute playbook path. Relative and existing: copied
beside the HCL and referenced by that relative path. Absolute: referenced
verbatim, never copied (packer then reads it from that path on the
machine running the bake; a missing absolute path still fails only at
packer). Relative and missing: refused at generation, naming the
modification and the path (stage 63; until 2026-09-25 it was
referenced verbatim with no error and
packer buildfailed when it validated the provisioner). - Several playbooks on one item: one shell + ansible pair per playbook; the shell step is guarded, so only the first does work.
- Several ansible items on one image: emitted in declaration order,
each with its own comment line; the local bundle numbers them
01-,02-, ... in that order together with the image's bash items. - Dry run vs real run. Generation is identical. The provisioners
execute only when the image builder's
packer buildruns, which the image builder defers to itsrun-<lifecycle>.shand executes only with--no-dry-run. This plugin has no dry-run branch of its own. - Apply flag on vs off. No difference in what this plugin emits; the bake itself is gated by the image builder and the run scope.
- Base images. The image builder ignores
modifications:on a base image (a warning, "base images do not receive modifications"); this plugin is never called for one. - Encrypted values. The plugin has no encryption-aware code: it emits
the strings it is given (HCL-escaped). Nothing in its fields is expected to be an
ENC[age:...]marker. config:on the item changed, playbooks unchanged: the content hash moves and the image re-bakes, but the emitted provisioner is byte for byte the same.
What it tests and verifies
- At load (pydantic, every command that loads the configuration):
field types; unknown keys refused (
extra="forbid");parameters:refused with the stage-26 message;name/aliasesfree of/and\and neverdefault; this plugin's own rule, an ansible item withoutplaybooks:is refused by name; and (base's, stage 63) an item with notype:when no mod builder isis_default: trueis refused. The verdict is the exception text on the command's output and a non-zero exit; no file is written. - At
validate(and at the start of every run): base's version check runs this plugin'sAnsibleVersionCheckerfor the executable namedansible-playbook: the binary must exist, its--versionfirst line must match[core x.y.z], and the version must satisfy the declared requirement. Base also checks that the mod builder'sexecutablenames a declared entry. Verdicts: one- <message>error line per failure and a failed validation; one INFO line listing every tool and version checked when all pass. - At generation: a relative playbook path that does not exist is
refused with a
FileNotFoundErrornaming the modification and the path (stage 63; see Variations). The strings written into the provisioner are escaped, so no configured value can break the build file's syntax. The emitted text is pinned by the golden fixture and bytests/test_v2_gate8_modifications.py(oneprovisioner "ansible"per item, correctonly,playbook_file) andtests/test_v2_explore_gcp.py(the GCE label), so a change in the emission failsjust test. - At apply: packer runs the provisioners; a failing task fails the
bake. This plugin adds no check; the image's own
tests:(base's in-bake verification, emitted as the last provisioner) andtests.post_bakeare where a playbook's effect is asserted. - After apply, in the state query, in meta-state: nothing. The plugin
has no post-finalize hook and writes no meta-state file. What records
the modification is base's lineage (
modsin the build record, content hash andoperation: ansible) and the Packer plugin's bundleMANIFEST.yamlon the image. - Before a bake, on request:
just test-mods(base'smod_tests.py) runs every ansible item twice in a throwaway container of the image's OS family throughansible-playbook -c docker, after the same interpreter preparation the bake uses; the first run must exit 0 and the second must change nothing (filesystem diff, plus ansible's ownchanged=recap). Results land inmeta-state/mod-tests.yamlkeyed by content hash. That harness is base's; this plugin contributes nothing to it beyond the item'splaybooks.
When it fails
Failures that have happened, newest first:
- 2026-09-17 (stage 48.1): the version checker had never run.
AnsibleVersionCheckerwas registered by name whilevalidatelooked the checker up by type in the wrong table, soansible-playbook's floor was never enforced. Since stage 48 it is: a run on a machine with an older ansible-core now fails validation withansible-playbook <found> does not meet its requirement <spec> (cfg/executables.yml). Fix: install the required ansible-core where the entry'sbinarypoints, or move the floor incfg/executables.ymlif the floor is the thing that is wrong. - 2026-09-17 (stage 48.4): builder-level playbooks were copied and never
run. The builder's
playbooks:were copied beside the Packer root bycopy_external_assetsbut no provisioner referenced them. The field is gone; a configuration that still declares it fails to load with pydantic's unknown-key error (extra_forbidden) namingplaybooks. Move the playbook onto each item that should run it. - 2026-09-16 (stage 43): an item with only
config:baked nothing. Two fixture items (dask-setup2,data-science-workstation-two) rendered no provisioner while lineage, the on-image bundle and the modification tests recorded them as applied. Now refused at load:Ansible modification '<name>' declares no playbooks: it has nothing to modify with (config alone provisions nothing). Give it 'playbooks:' ... or remove it.Give the item a playbook or delete it; the image's fingerprint changes either way and it re-bakes. - 2026-09-10 (ledger 71, GCE dask re-bake):
Failed to import the required Python library (packaging). Ansible'spipmodule importspackagingon the target's interpreter, and the pinned/usr/local/bin/csis-ansible-python(a barepython3.Nthe shell step installed) does not have it. The task before it (package) had passed. Seen in packer's output for the ansible provisioner; the bake fails and no build is recorded. Fix in the playbook, not the plugin: runpip3throughansible.builtin.commandguarded on an import check, as setup_dask.yml now does. Any module that needs a library on the target interpreter has the same problem. - 2026-09-10 (ledger 71, first live post-bake suite): a playbook that
does nothing passes the bake. The fixture's
setup_dask.ymlhad been a placeholder (onedebugtask) since the first bakes; the bake succeeded andpython3 -c 'import dask'failed on the launched instance. This plugin cannot tell an empty playbook from a full one; the image'stests.post_bakedid. Where it lands:meta-state/image-tests.yaml(ok false, the failing assertion named) andreleaserefusing the build. - 2026-09-05 (finding 48, GCE re-bake on feature/gce-launch-proof):
every task
unreachable/Failed to create temporary directory. Packer's ansible provisioner defaultsansible_userto the operator's LOCAL login, never the build VM's SSH user, so ansible built~/.ansible/tmpunder/home/<local-user>on the VM. Intermittent, because packer'suse_proxyauto-detection masked it until it flipped to direct mode. Fix: theuser = "<bake ssh user>"line, emitted when the runtime declares one (GCE). If the symptom appears on a runtime that returnsNonefrombake_ssh_username(image), that runtime needs the hook, not the playbook. - Stage 1 (before 2026-09-02, the first AWS bakes on RHEL 8): fact
gathering died with "The module interpreter..." because the target's
only Python was 3.6, which the controller's ansible-core no longer
supports. Fix: the emitted
provisioner "shell"that installs a newer Python and pins it at/usr/local/bin/csis-ansible-python, and the-e ansible_python_interpreter=...argument that names it. If a new OS family has nopython3.12/3.11/3.9package and nodnf, the step falls back to whateverpython3exists; check the family's Python against the ansible-core support matrix before blaming the playbook.
Failures the code can produce that have not been seen live:
Could not resolve builder '<type>' referenced in <image>(aKeyErrorat load): the item'stype:names no mod builder by name or alias. Checkcfg/mod-builders.yml.<name>: binary '<path>' not found (declared in cfg/executables.yml; an absolute path, or a name on PATH)atvalidate:ansible-playbookis not where the entry says. Install it there or fixbinary.ansible-playbook: AnsibleVersionChecker could not parse a version from '<binary> --version'atvalidate: the first line of the output has no[core x.y.z](a wrapper script, or a very oldansiblewhose output has a different shape). The regex is fixed; pointbinaryat the realansible-playbook.ansible-playbook: could not read its version (...): the binary exists but running it failed (permissions, a broken virtualenv). The exception text follows the colon.Executable ansible-playbook specified for provider ansible-default not found in executables list.atvalidate: the builder'sexecutablenames an entrycfg/executables.ymldoes not have.modification '<name>': playbook '<path>' does not exist (a relative path is read from the configuration root)(aFileNotFoundErrorat generation, stage 63): a relative playbook named on an item is not a file under the configuration root. Fix the path or add the file. Until 2026-09-25 this was no error at generation andpacker buildfailed later validatingplaybook_file. An ABSOLUTE path that does not exist still behaves that way: a warninglocal mods: <file> for <item> not found; the bundle will lack itfrom the Packer plugin, then packer fails.No default builder found in mod_builder list.at load: no mod builder saysis_default: true. Mark one, or give every item atype:. The orchestrator's own form of the same refusal, for an item that reaches it, ismodification '<name>' names no builder (`type`) and no mod builder is `is_default: true`; name one with `type:` or mark a default(stage 63; it used to surface as anAttributeErrorat generation).- Malformed HCL from a value containing a double quote was possible until
2026-09-25:
extra_argumentsentries,ansible_connectionand playbook paths were written between quotes without escaping. Since stage 63 every such string is escaped, so this failure no longer occurs. - A playbook task fails on the VM: packer reports the ansible recap and
exits non-zero; the image builder's
run-<lifecycle>.shpropagates the exit code, no build is recorded and packer cleans up its build VM. The playbook's own output is the place to look;just test-modsreproduces most task failures in a container without a cloud. - The interpreter step cannot make
/usr/local/bin/csis-ansible-python(nosudo, no writable/usr/local/bin): the shell provisioner exits non-zero and the bake stops before the first playbook. Look for thetest -x /usr/local/bin/csis-ansible-python || { ... }line in packer's output.
Related
- Base models extended: mod_builder.py, moditem_type.py, root_item.py, builder_model.py.
- Base builder class:
builder_base_mod.py;
the label helper
build_target_label_ofin builder_base_image.py. - The image builder that calls it and hosts its output: packer-plugin (packer_ebs_builder.py, v2_local_mods.py).
- Lineage records for modifications:
lineage.py (
mod_records). - The version check and the modification test harness: validate.py, mod_tests.py.
- Manuals: docs/CONFIGURATION.md (section 7,
cfg/mod-builders.yml), docs/OPERATIONS.md, docs/DESIGN.md (M2). - No terraform modules and no hashicorp-utils usage: the plugin writes Packer provisioner lines directly.
Metadata
Release files for cs-image-system-ansible-plugin 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cs_image_system_ansible_plugin-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Release files / cs_image_system_ansible_plugin-0.1.1-py3-none-any.whl
| Download URL | cs_image_system_ansible_plugin-0.1.1-py3-none-any.whl |
|---|---|
| Size | 23.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b32feeec2a0984cfebe5f2d9c93c78d3d929e17bc3c6f9d7c67ff4cbaf34e263
|
|
BLAKE2b-256 checksum How to use checksums |
fbf55a191be21be8af70f44a4e4b2d58773dc2ee8aa18c5e6013faea82cea6ed
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|