cs-image-system-aws-runtime-plugin 0.1.1__py3-none-any.whl
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/aws_runtime/__init__.py +3 -0
- cs_image_system/aws_runtime/aws_packer_source.py +180 -0
- cs_image_system/aws_runtime/aws_provider_specific_image.py +43 -0
- cs_image_system/aws_runtime/aws_runtime_builders.py +518 -0
- cs_image_system/aws_runtime/aws_runtime_models.py +177 -0
- cs_image_system/aws_runtime/aws_utils.py +476 -0
- cs_image_system/aws_runtime/bootstrap.py +367 -0
- cs_image_system/aws_runtime/main.py +28 -0
- cs_image_system/py.typed +0 -0
- cs_image_system_aws_runtime_plugin-0.1.1.dist-info/METADATA +1146 -0
- cs_image_system_aws_runtime_plugin-0.1.1.dist-info/RECORD +14 -0
- cs_image_system_aws_runtime_plugin-0.1.1.dist-info/WHEEL +4 -0
- cs_image_system_aws_runtime_plugin-0.1.1.dist-info/entry_points.txt +5 -0
- cs_image_system_aws_runtime_plugin-0.1.1.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,1146 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: cs-image-system-aws-runtime-plugin
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: The aws runtime type: Amazon EC2 as a place where images bake and instances run
|
|
5
|
+
Project-URL: Repository, https://github.com/infrastructurebuilder/cs-image-system-3
|
|
6
|
+
Project-URL: Documentation, https://github.com/infrastructurebuilder/cs-image-system-3/blob/develop/docs/OPERATIONS.md
|
|
7
|
+
Project-URL: Package README, https://github.com/infrastructurebuilder/cs-image-system-3/blob/develop/packages/aws-runtime-plugin/README.md
|
|
8
|
+
Author-email: Mykel Alvis <mykelalvis@infrastructurebuilder.org>
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Python: >=3.13
|
|
12
|
+
Requires-Dist: boto3>=1.43
|
|
13
|
+
Requires-Dist: botocore>=1.43
|
|
14
|
+
Requires-Dist: cs-image-system-hashicorp-utils==0.1.1
|
|
15
|
+
Requires-Dist: cs-image-system-system==0.1.1
|
|
16
|
+
Requires-Dist: pydantic>=2.13
|
|
17
|
+
Requires-Dist: python-hcl2>=8.1
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# cs-image-system-aws-runtime-plugin
|
|
21
|
+
|
|
22
|
+
The `aws` runtime type. A runtime is where images are baked and instances
|
|
23
|
+
run; this package makes Amazon EC2 one. It registers one runtime model
|
|
24
|
+
(`AwsCloudBuilderModel`), one runtime builder (`AwsCloudBuilder`) and one
|
|
25
|
+
version checker (`AwsCLIVersionChecker`, for the `aws` CLI). At load it
|
|
26
|
+
discovers the account's VPCs, subnets and security groups with boto3 and
|
|
27
|
+
checks the declared networking against them. During a run it resolves vendor
|
|
28
|
+
AMIs by owner and filter, emits the `amazon-ebs` packer source for every image
|
|
29
|
+
baked here, bakes and reaches instances through AWS Systems Manager (SSM),
|
|
30
|
+
answers the state query from `describe-images` / `describe-instances`, reads
|
|
31
|
+
and (for one bounded task at a time) sets an instance's power state, tags
|
|
32
|
+
released AMIs, and disposes of AMIs together with their snapshots.
|
|
33
|
+
|
|
34
|
+
## What it registers
|
|
35
|
+
|
|
36
|
+
The entry point is declared in [pyproject.toml](pyproject.toml):
|
|
37
|
+
|
|
38
|
+
```toml
|
|
39
|
+
[project.entry-points."cs_image_system.plugins.runtime"]
|
|
40
|
+
aws_plugin = "cs_image_system.aws_runtime.main:initialize"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`initialize()` in [main.py](src/cs_image_system/aws_runtime/main.py) returns an
|
|
44
|
+
`AwsRuntimePluginMetadata` whose services map the key `aws` to three classes
|
|
45
|
+
and whose `builders_for_models` binds the model to its builder.
|
|
46
|
+
|
|
47
|
+
| Class | `csis_name()` | `csis_classifier()` | Registered under |
|
|
48
|
+
|---|---|---|---|
|
|
49
|
+
| `AwsCloudBuilderModel` ([aws_runtime_models.py](src/cs_image_system/aws_runtime/aws_runtime_models.py)) | `aws` | `VCT.CLOUD_BUILDER_MODEL` | `cloud_builder_model` and, because the loader re-registers cloud models as runtimes, `runtime_builder_model` |
|
|
50
|
+
| `AwsCloudBuilder` ([aws_runtime_builders.py](src/cs_image_system/aws_runtime/aws_runtime_builders.py)) | `aws` | `VCT.RUNTIME_BUILDER` | `runtime_builder` |
|
|
51
|
+
| `AwsCLIVersionChecker` ([aws_runtime_builders.py](src/cs_image_system/aws_runtime/aws_runtime_builders.py)) | `aws-cli` | `VCT.VERSION_CHECKER` | `version_checker`, since stage 48.1 (2026-09-17) |
|
|
52
|
+
|
|
53
|
+
A YAML entry selects the model and builder with `type: aws` under
|
|
54
|
+
`runtime_builders:`. There are no type aliases; `aws` is the only type key.
|
|
55
|
+
An entry's own `aliases:` list gives that runtime extra names (the fixture's
|
|
56
|
+
`aws-east2-runtime` also answers to `aws`, `amazon` and `us-east-2`), and
|
|
57
|
+
other entries refer to it by any of them.
|
|
58
|
+
|
|
59
|
+
The version checker is selected differently: `validate` and every run look
|
|
60
|
+
up a checker for each entry of `cfg/executables.yml` under the entry's
|
|
61
|
+
`name` first, then its `type` (see
|
|
62
|
+
[validate.py](../base/src/cs_image_system/base/commands/validate.py)). An
|
|
63
|
+
executable named `aws-cli` therefore gets this checker, which runs
|
|
64
|
+
`<binary> --version` and reads the version from the regex
|
|
65
|
+
`aws-cli\/([\d\.]+)\s` on the first output line (`aws-cli/2.32.0 Python/...`).
|
|
66
|
+
An entry named anything else with `binary: aws` falls to the generic
|
|
67
|
+
checker.
|
|
68
|
+
|
|
69
|
+
Present in the package but not registered as a service:
|
|
70
|
+
|
|
71
|
+
- `AwsProviderSpecificImage` ([aws_provider_specific_image.py](src/cs_image_system/aws_runtime/aws_provider_specific_image.py)), reached through the builder's `provider_specific_image_class()` hook.
|
|
72
|
+
|
|
73
|
+
**The AWS section of the bootstrap** (stage 70 step 4), through the
|
|
74
|
+
`cs_image_system.bootstrap` entry-point group: `aws =
|
|
75
|
+
cs_image_system.aws_runtime.bootstrap:aws_section`. It owns the questions
|
|
76
|
+
and the terraform for this cloud's one-time initialisation -- the GitHub
|
|
77
|
+
OIDC identity provider, the READ-ONLY and WRITE roles of CI_SETUP.md 3.3
|
|
78
|
+
with their trust and permission documents, and the state bucket and the
|
|
79
|
+
Session Manager instance profile when the account lacks them
|
|
80
|
+
(`tfmodules/bootstrap_aws`). The section is wanted by default where the
|
|
81
|
+
tree declares an AWS runtime or an S3 state backend. Its defaults come
|
|
82
|
+
from the raw `cfg/runtime-builders.yml` and `cfg/state-backends.yml`
|
|
83
|
+
(read as YAML, never loaded; a starter's `REPLACE-ME` values and its
|
|
84
|
+
example account id are no defaults), and every "does it already exist?"
|
|
85
|
+
question is a probe of the account as the runtime's profile (`aws iam
|
|
86
|
+
get-open-id-connect-provider`, `get-role`, `get-instance-profile`,
|
|
87
|
+
`s3api head-bucket`): found, absent, or -- with no session -- unanswerable,
|
|
88
|
+
which `--quiet` refuses by name. A role that exists is adopted with an
|
|
89
|
+
`import` block in the root, and the subjects it already trusted that are
|
|
90
|
+
not this repository's are read from its trust document and kept (another
|
|
91
|
+
repository may share the role); the root's state binds to the tree's declared
|
|
92
|
+
S3 backend when the bucket exists. See OPERATIONS.md, "The bootstrap".
|
|
93
|
+
|
|
94
|
+
## Models
|
|
95
|
+
|
|
96
|
+
### `AwsCredentials`
|
|
97
|
+
|
|
98
|
+
Extends `CredentialsBase` in
|
|
99
|
+
[credentials.py](../base/src/cs_image_system/base/models/credentials.py).
|
|
100
|
+
The base declares no fields; each provider narrows it. Field names are
|
|
101
|
+
boto3 `Session` keyword arguments and pass straight through.
|
|
102
|
+
|
|
103
|
+
| Field | Type | Default | Meaning |
|
|
104
|
+
|---|---|---|---|
|
|
105
|
+
| `profile_name` | `str \| None` | `None` | A named profile in the operator's AWS configuration. The fixture uses `noaa`. |
|
|
106
|
+
| `aws_access_key_id` | `str \| None` | `None` | Key id; carries a template such as `{{ ENV['AWS_ACCESS_KEY_ID_east2'] }}`, never a literal. |
|
|
107
|
+
| `aws_secret_access_key` | `str \| None` | `None` | Secret; same rule. |
|
|
108
|
+
| `aws_session_token` | `str \| None` | `None` | Session token; same rule. |
|
|
109
|
+
|
|
110
|
+
`as_dict()` omits unset fields, so an absent key never reaches boto3 as
|
|
111
|
+
`None`. The shared model config forbids extra keys, so a misspelled key
|
|
112
|
+
inside `credentials:` is a validation error naming the field path. No
|
|
113
|
+
credential value belongs in the configuration tree: these fields name a
|
|
114
|
+
profile or read the environment.
|
|
115
|
+
|
|
116
|
+
### `AwsCloudNetworkingModel`
|
|
117
|
+
|
|
118
|
+
Extends `CloudNetworkingConfig` in
|
|
119
|
+
[cloud_builder.py](../base/src/cs_image_system/base/models/cloud_builder.py),
|
|
120
|
+
which is `RuntimeNetworkingModel` in
|
|
121
|
+
[runtime.py](../base/src/cs_image_system/base/models/runtime.py) under
|
|
122
|
+
another name.
|
|
123
|
+
|
|
124
|
+
Fields it adds:
|
|
125
|
+
|
|
126
|
+
| Field | Type | Default | Meaning |
|
|
127
|
+
|---|---|---|---|
|
|
128
|
+
| `addl_security_groups` | `list[str]` | `[]` | Existing groups every launched instance also wears, unmodified. Checked against the account at load and the only list counted toward the limit of five below. The instance module's `vpc_security_group_ids` lists them after the generated group; the EFS storage builder uses them as the mount targets' client groups. |
|
|
129
|
+
| `ssh_ingress_security_group_ids` | `list[str]` | `[]` | Groups whose members may reach port 22 on launched instances. The generated instance security group's ingress references only these groups, never a CIDR range. Not checked against the account. |
|
|
130
|
+
| `_model_id` | `str \| None` | `None` (not an init field) | Back-reference to the owning runtime model. |
|
|
131
|
+
|
|
132
|
+
Base fields it inherits:
|
|
133
|
+
|
|
134
|
+
| Field | Type | Default | Meaning |
|
|
135
|
+
|---|---|---|---|
|
|
136
|
+
| `name` | `str` | the network id | Empty or `default` falls back to `network`. |
|
|
137
|
+
| `network` | `str` | `default` | The VPC id. `default` resolves at load to the account's default VPC. |
|
|
138
|
+
| `subnets` | `list[RuntimeSubnetModel]` | required, non-empty | Each has `name`, `subnet_id`, `is_default`, `public`, `cidr`, `availability_zone`, `config`. The first entry with `is_default: true` is the default subnet; without one the model refuses to load. |
|
|
139
|
+
| `availability_zones` | `list[RuntimeAvailabilityZoneModel]` | `[]` | `name`, `is_default`. The default zone, when declared, becomes the packer source's `availability_zone`. |
|
|
140
|
+
|
|
141
|
+
It strips `name` and refuses one that is empty afterwards (only a
|
|
142
|
+
whitespace-only name can reach that check, because the base already
|
|
143
|
+
substitutes the network id for an empty or `default` name).
|
|
144
|
+
`get_subnet_id()` raises when no default subnet exists. It does not accept
|
|
145
|
+
security groups by name, only by id, and it refuses more than five groups in
|
|
146
|
+
`addl_security_groups`.
|
|
147
|
+
|
|
148
|
+
There is no `security_group_ids` field any more (stage 63). Until
|
|
149
|
+
2026-09-25 the model accepted it, checked its ids against the account and
|
|
150
|
+
counted them toward the limit of five, but no emitter ever read it, so a
|
|
151
|
+
group listed there reached neither the packer source nor the instance and
|
|
152
|
+
storage roots. It was removed rather than wired in, because
|
|
153
|
+
`addl_security_groups` already is the list that reaches the roots. Declaring
|
|
154
|
+
it is now an unknown-key refusal at load (the shared model config forbids
|
|
155
|
+
extra keys), and the error names the key; move the ids into
|
|
156
|
+
`addl_security_groups`. The accessor `get_security_group_ids()` went with
|
|
157
|
+
it; `get_addl_security_groups()` remains.
|
|
158
|
+
|
|
159
|
+
### `AwsCloudBuilderModel`
|
|
160
|
+
|
|
161
|
+
Extends `CloudBuilderModel` in
|
|
162
|
+
[cloud_builder.py](../base/src/cs_image_system/base/models/cloud_builder.py),
|
|
163
|
+
which extends `RuntimeBuilderModel` in
|
|
164
|
+
[runtime.py](../base/src/cs_image_system/base/models/runtime.py), which
|
|
165
|
+
extends `BuilderModel` and `NameTyped` in
|
|
166
|
+
[builder_model.py](../base/src/cs_image_system/base/models/builder_model.py).
|
|
167
|
+
|
|
168
|
+
Fields it adds:
|
|
169
|
+
|
|
170
|
+
| Field | Type | Default | Meaning |
|
|
171
|
+
|---|---|---|---|
|
|
172
|
+
| `account_id` | `str \| None` | `None` | The AWS account. Reported by `runtime describe`; usable in templates as `{{ this.account_id }}`. Nothing else reads it: the account boto3 acts in is the profile's. |
|
|
173
|
+
| `credentials` | `AwsCredentials` | empty | Narrows the base `CredentialsBase`. |
|
|
174
|
+
| `state_configuration` | `str` | `DEFAULT` | Foreign key to a state backend. The second rung of every storage and instance root's backend resolution on this runtime: a root whose own `state_configuration` is `default` inherits this one; `default` here falls through to the default backend (stage 46.2, [orchestrator.py](../base/src/cs_image_system/base/orchestrator.py)). |
|
|
175
|
+
| `ena_support` | `bool \| None` | `None` | When declared, written into the `amazon-ebs` packer source as `ena_support = true\|false`, so the baked AMI's Elastic Network Adapter flag is set explicitly; unset, nothing is written and packer's default applies (stage 63; until 2026-09-25 accepted and read by nothing). |
|
|
176
|
+
| `sriov_support` | `bool \| None` | `None` | When declared, written into the `amazon-ebs` packer source as `sriov_support = true\|false` (enhanced networking through the Intel SR-IOV interface); unset, nothing is written and packer's default applies (stage 63; until 2026-09-25 accepted and read by nothing). |
|
|
177
|
+
| `iam_instance_profile` | `str \| None` | `None` | Instance profile written into the packer source for build instances when `session_instance_profile` is unset (it is also the SSM fallback then). When both are set the SSM profile wins (stage 63 item 9, decided 2026-09-24; until then this one, written after the SSM block, replaced it). Never attached to launched instances. |
|
|
178
|
+
| `session_mechanism` | `str \| None` | `None` | `ssm` (any case, surrounding whitespace ignored) is the only value. Any other string is an error the first time the builder's `session_mechanism()` is called, which is at generation. |
|
|
179
|
+
| `session_instance_profile` | `str \| None` | `None` | The SSM-capable instance profile given to launched instances (through the instance plugin) and to build instances (through the packer source). |
|
|
180
|
+
| `ssh_username` | `str` | `DEFAULT` | The bake's SSH user for images on this runtime that name none more specifically: step 4 of the bake-user order ([CONFIGURATION 5.1.1](../../docs/CONFIGURATION.md#511-the-bake-ssh-user)). Until stage 63 it overrode every entry. |
|
|
181
|
+
| `networking` | `AwsCloudNetworkingModel \| None` | `None` | Narrows the base type and makes it optional. Missing networking logs a warning at load; image generation then fails (the packer source reads the VPC and subnet from it). |
|
|
182
|
+
| `vpc_map` | `dict` | discovered | Not an init field. `{vpc_id: {"subnets": [...]}}` from the account. |
|
|
183
|
+
| `default_vpc_id` | `str \| None` | discovered | Not an init field. |
|
|
184
|
+
| `all_security_groups` | `dict[str, dict]` | discovered | Not an init field. `{group_id: description}` from the account. |
|
|
185
|
+
|
|
186
|
+
Base fields it inherits, and what this runtime does with them:
|
|
187
|
+
|
|
188
|
+
| Field | From | Default | Meaning here |
|
|
189
|
+
|---|---|---|---|
|
|
190
|
+
| `name`, `type`, `description`, `aliases` | `NameTyped` | `name`/`type` required | `type` is `aws`. |
|
|
191
|
+
| `executable`, `is_default`, `config`, `gitignore`, `tags` | `BuilderModel` | | `tags` merge into every image's tags; `is_default` makes this the runtime `default` resolves to. |
|
|
192
|
+
| `region` | `CloudBuilderModel` | required | boto3 `region_name`, the packer `region`, the `aws` provider block, `--region` on the release and storage-transition commands. |
|
|
193
|
+
| `default_machine_type` | `RuntimeBuilderModel` | required | Instance type when neither an OS-builder runtime entry nor an image names one; the instance type of a launched instance that declares no `machine_type`. |
|
|
194
|
+
| `default_image_builder` | `RuntimeBuilderModel` | `DEFAULT` | The image builder an OS-builder runtime entry with `image_builder: default` resolves to. |
|
|
195
|
+
| `default_owners` | `RuntimeBuilderModel` | `None` | Joins every vendor-image query's owner list on this runtime, after the OS builder's owners and before the entry's own. Read since stage 63 item 22: the lookup in [os_builder_runtime_config.py](../base/src/cs_image_system/base/models/os_builder_runtime_config.py) keyed the runtime by the entry's `image_builder` name and never found it; it now takes the image builder's runtime. |
|
|
196
|
+
| `default_config_username` | `RuntimeBuilderModel` | `None` | The bake's SSH user when nothing more specific names one: step 5 of the bake-user order ([CONFIGURATION 5.1.1](../../docs/CONFIGURATION.md#511-the-bake-ssh-user)), read since stage 63 item 22. |
|
|
197
|
+
| `ephemeral` | `RuntimeBuilderModel` | `False` | See "Retention and the cycle". |
|
|
198
|
+
| `retention_keep` | `RuntimeBuilderModel` | `None` | Builds kept per series on this runtime when the image declares no retention. |
|
|
199
|
+
| `on_failure`, `teardown_after` | `RuntimeBuilderModel` | `None` | Runtime-level defaults for ephemeral instances. |
|
|
200
|
+
|
|
201
|
+
It overrides `finalize()` to run `update_networking()` after the base
|
|
202
|
+
finalization, and `csis_name()` to return `aws`. It carries the class
|
|
203
|
+
attribute `type = "aws"`.
|
|
204
|
+
|
|
205
|
+
Not supported: a top-level `profile:` key (the modelled key is
|
|
206
|
+
`credentials.profile_name`; unknown keys are rejected), a `parameters:` key
|
|
207
|
+
(retired; the load names the replacement), any session mechanism other than
|
|
208
|
+
`ssm`, and a run without a `region`.
|
|
209
|
+
|
|
210
|
+
## The builder
|
|
211
|
+
|
|
212
|
+
`AwsCloudBuilder` extends `CloudBuilderBase` in
|
|
213
|
+
[builder_base_cloud.py](../base/src/cs_image_system/base/basic/builder_base_cloud.py),
|
|
214
|
+
an empty specialisation of `RuntimeBuilderBase` in
|
|
215
|
+
[builder_base_runtime.py](../base/src/cs_image_system/base/basic/builder_base_runtime.py).
|
|
216
|
+
The runtime builder writes no files of its own: `generate_items_during`
|
|
217
|
+
defers to the base and returns nothing. Everything it contributes reaches
|
|
218
|
+
`generated/` through the image, instance and release builders that call its
|
|
219
|
+
hooks. In the order a run reaches them:
|
|
220
|
+
|
|
221
|
+
| When | Hook | What it does |
|
|
222
|
+
|---|---|---|
|
|
223
|
+
| Configuration load | `AwsCloudBuilderModel.finalize()` | Runs `update_networking()` (network discovery below). |
|
|
224
|
+
| Resolution | `provider_specific_image_class()` | Returns `AwsProviderSpecificImage`. |
|
|
225
|
+
| Resolution | `query_provider_image(subconfig)` | Finds the vendor AMI for one OS-builder runtime entry (image query below). Returns `(ami_id, owner, raw_result)` or `None`. |
|
|
226
|
+
| Resolution | `query_provider_image_by_id(subconfig, image_id)` | Stage 63: the AMI an entry pins with `image_id`, looked up with `DescribeImages` by id (`aws_utils._query_by_ami_id`) instead of the query. Same return shape; `None` when AWS does not know the id. |
|
|
227
|
+
| Image generation | `packer_source_type()` | `amazon-ebs`. |
|
|
228
|
+
| Image generation | `packer_source_blocks(...)` | The `data "amazon-ami"` lookup and the `source "amazon-ebs"` block for one image. |
|
|
229
|
+
| Image generation | `session_mechanism()` | `ssm` or `None`; raises on any other declared value. |
|
|
230
|
+
| Image generation | `session_agent_commands(os_family)` | Installs and enables the SSM agent on a base image; `dpkg` path for `debian`/`ubuntu`, `yum` path otherwise. Empty when no mechanism is declared. |
|
|
231
|
+
| Image generation | `session_verify_commands(os_family)` | Assertions that the agent is present and enabled. Empty when no mechanism is declared. |
|
|
232
|
+
| Image generation | `default_bake_user(family)`, `bake_ssh_username(image)`, `bake_finalize_commands()` | `default_bake_user`: the vendor AMI's user, `admin` for `debian`, `ubuntu` for `ubuntu`, `ec2-user` otherwise (the last step of the bake-user order). `bake_ssh_username` keeps the base `None`, so the ansible provisioner keeps its own user on AWS; no closing provisioner. |
|
|
233
|
+
| After a bake | `build_id_from_artifact(artifact_id)` | Base default: the packer manifest's `<region>:<ami>` becomes the AMI id. |
|
|
234
|
+
| After a bake | `retag_image(ami, tags)` | The packer plugin stamps the resolved `csis_parent` and `csis_fingerprint` onto every recorded build. |
|
|
235
|
+
| Instance generation | `session_instance_profile()` | The instance profile the instance module attaches when the mechanism is `ssm`; `None` otherwise. |
|
|
236
|
+
| Instance verification | `verify_instance(name, expected_build, expect_mounts, timeout)` | Over SSM: waits for the launch completion marker `/var/lib/csis/launch-applied`, counts mounts under `/mnt`, compares the booted AMI with the expected build. |
|
|
237
|
+
| Instance operations | `run_session_command(name, script, timeout)` | `AWS-RunShellScript` through SSM on the RUNNING instance whose `Name` tag matches; polls the invocation to completion. |
|
|
238
|
+
| Power state (stage 57) | `can_query_instance_power_state()`, `query_instance_power_state(name)` | `True`; EC2's `State.Name` mapped onto the system's vocabulary (`pending` -> starting, `running`, `stopping`/`shutting-down` -> stopping, `stopped`, `terminated` -> absent, no instance -> absent, anything else -> unknown); `None` only when the cloud could not be asked. |
|
|
239
|
+
| Power state (stage 57) | `can_set_instance_power_state()`, `start_instance(name, timeout)`, `stop_instance(name, timeout)` | `True`; `start_instances` / `stop_instances` followed by the EC2 `instance_running` / `instance_stopped` waiter (5 s delay, `timeout // 5` attempts). Only `running_for_task` calls them. |
|
|
240
|
+
| Identity (stage 58) | `can_query_instance_identity()`, `query_instance_identity(name)` | `True`; `{"instance_id": InstanceId, "provider_hostname": PrivateDnsName}` of the named instance whatever its power state, or `None`. |
|
|
241
|
+
| Release | `release_commands(build_id, tags)` | One `aws ec2 create-tags --region ... --resources <ami> --tags Key=..,Value=..` executable, with `--profile` when `credentials.profile_name` is set. Runs only under `--no-dry-run` and only when the global `apply_release` is true. |
|
|
242
|
+
| State reconciliation | `retag_image(image_id, tags)` | `create-tags` on one of the system's own AMIs (`lineage relabel`, `lineage restamp`). |
|
|
243
|
+
| Retention | `dispose_image(build_id)` | Deregisters the AMI and deletes every EBS snapshot in its block device mappings. A missing AMI returns `False` (already gone). |
|
|
244
|
+
| State query | `query_images(series)` | Every AMI owned by `self` that carries a `csis_series` tag: `{image_id, name, state, created, tags}`. |
|
|
245
|
+
| State query | `can_query_instance_boot_image()`, `query_instance_boot_image(name)` | `True`; the `ImageId` of the pending or running instance whose `Name` tag matches, or `None`. Read-only, never fatal. |
|
|
246
|
+
| `empty --runtime` | `inventory()` | Not implemented on AWS; the base raises `NotImplementedError`, so the emptiness check refuses this runtime. |
|
|
247
|
+
|
|
248
|
+
Every instance lookup above is by the EC2 `Name` tag, which the instance
|
|
249
|
+
module sets to the instance's declared name
|
|
250
|
+
(`tags = merge({ Name = var.name }, var.tags)` in
|
|
251
|
+
[tfmodules/aws_instance/main.tf](../../tfmodules/aws_instance/main.tf)). Two
|
|
252
|
+
lookups differ on purpose: `_running_instance` filters on `pending`/`running`
|
|
253
|
+
(the boot-image query, the session command), `_named_instance` takes any
|
|
254
|
+
state and prefers a non-`terminated` record (the power-state and identity
|
|
255
|
+
queries, start and stop), because EC2 keeps a `terminated` record visible for
|
|
256
|
+
about an hour after the machine is gone.
|
|
257
|
+
|
|
258
|
+
### Credentials
|
|
259
|
+
|
|
260
|
+
`AwsCloudBuilderModel.self_to_aws_client_config()` is the one conversion
|
|
261
|
+
point: `credentials.as_dict()` plus `region_name` from `region`. Every boto3
|
|
262
|
+
call in the plugin builds `boto3.Session(**config)` from it, so a
|
|
263
|
+
`profile_name` selects a profile and an empty `credentials:` lets boto3's
|
|
264
|
+
default chain run (environment variables, `AWS_PROFILE`, an instance role).
|
|
265
|
+
The same profile name is written into the packer source (`profile = "noaa"`
|
|
266
|
+
on both the data lookup and the source), passed as `--profile` to the release
|
|
267
|
+
command, and read by the state-backend, storage and instance plugins for
|
|
268
|
+
their provider blocks, backend files and transition scripts, so packer,
|
|
269
|
+
terraform and the plugin's own calls all authenticate the same way.
|
|
270
|
+
|
|
271
|
+
### Network discovery at load
|
|
272
|
+
|
|
273
|
+
`update_networking()` in
|
|
274
|
+
[aws_runtime_models.py](src/cs_image_system/aws_runtime/aws_runtime_models.py)
|
|
275
|
+
calls `get_vpc_map_and_default_vpc_id()` in
|
|
276
|
+
[aws_utils.py](src/cs_image_system/aws_runtime/aws_utils.py), which issues
|
|
277
|
+
`describe_vpcs`, `describe_subnets`, `describe_route_tables` and
|
|
278
|
+
`describe_security_groups` once each and builds:
|
|
279
|
+
|
|
280
|
+
- `vpc_map`: for every VPC, a `subnets` list of `{subnet_id, cidr, is_public, tags, config}`. A subnet is public when its effective route table (its own association, else the VPC's main table) has a `0.0.0.0/0` or `::/0` route whose gateway id starts with `igw-` or `vgw-`.
|
|
281
|
+
- `default_vpc_id`: the VPC with `IsDefault`.
|
|
282
|
+
- `all_security_groups`: every group by id.
|
|
283
|
+
|
|
284
|
+
Then it validates the declaration: `network: default` becomes the default
|
|
285
|
+
VPC (an error when the account has none); the VPC must exist; every
|
|
286
|
+
declared `subnets[].subnet_id` must be one of that VPC's subnets (stage 63,
|
|
287
|
+
below); every id in `addl_security_groups` must exist; more than five of
|
|
288
|
+
them is an error. The declared `subnets[].public` flag is the operator's
|
|
289
|
+
statement and is not overwritten by the discovered `is_public`.
|
|
290
|
+
|
|
291
|
+
The subnet check (stage 63) compares each declared `subnet_id` with the
|
|
292
|
+
subnets the account listed for the declared VPC in `vpc_map`. When a
|
|
293
|
+
declared id is not among them the load is refused with
|
|
294
|
+
`Subnet <id> (<name>) in networking configuration for AWS cloud builder
|
|
295
|
+
<runtime> is not in VPC <vpc>.` Until 2026-09-25 the declared subnet ids were
|
|
296
|
+
not checked at all, so a subnet from another VPC (or a typo) passed the load
|
|
297
|
+
and failed only at the first bake or apply. The check makes a claim only
|
|
298
|
+
when the account's answer lists subnets for the VPC: a VPC listed without
|
|
299
|
+
any subnets (an empty VPC, or a session that cannot see them) leaves every
|
|
300
|
+
declared subnet unchecked rather than refusing all of them. A load
|
|
301
|
+
therefore needs a live AWS session even for a dry run, and so does every
|
|
302
|
+
command that loads the configuration for another runtime (the GCE cycle
|
|
303
|
+
included).
|
|
304
|
+
|
|
305
|
+
### The image query
|
|
306
|
+
|
|
307
|
+
`query_provider_image()` takes one OS-builder runtime entry
|
|
308
|
+
(`OSBuilderBaseImageBuilderSubconfig` in
|
|
309
|
+
[os_builder_runtime_config.py](../base/src/cs_image_system/base/models/os_builder_runtime_config.py)).
|
|
310
|
+
`remap_for_image_query()` builds a `describe_images` request:
|
|
311
|
+
|
|
312
|
+
- `Owners` is the entry's `get_owners()`: the OS builder's owners, then the runtime's `default_owners`, then the entry's own, deduplicated in order (the runtime's were never found before stage 63 item 22).
|
|
313
|
+
- `query.filters` keys are mapped through `AWS_DI_MAP` (snake_case to EC2 filter names, `tag:<key>` kept), booleans lowercased, and `state: available` forced.
|
|
314
|
+
- Keys the map does not know go to a post-query exact-match filter on the result dictionaries.
|
|
315
|
+
- The rewrite works on a deep copy of the entry's `query` (stage 63). `get_query()` hands back the entry's own nested mappings, and until 2026-09-25 the rewrite injected `state: available` into the OS builder entry's own `filters` mapping on every resolution, so the loaded model changed each time an image was resolved. The entry is now left exactly as declared.
|
|
316
|
+
|
|
317
|
+
`query_image()` in [aws_utils.py](src/cs_image_system/aws_runtime/aws_utils.py)
|
|
318
|
+
runs that `DescribeImages` query (paging through `describe_images`), keeps
|
|
319
|
+
every match that agrees with the post-query filter, and returns the newest
|
|
320
|
+
by `CreationDate`, or `None` when nothing matches. Its docstring says so
|
|
321
|
+
since stage 63; the unreachable `raise` that used to follow the return is
|
|
322
|
+
gone. The builder then reads the owner straight from the AMI record,
|
|
323
|
+
`ImageOwnerAlias` (for example `amazon`) else `OwnerId` (the account id),
|
|
324
|
+
and returns `(ImageId, owner, raw)`; the base registers an
|
|
325
|
+
`AwsProviderSpecificImage` in the resolved state. A record with neither
|
|
326
|
+
field raises `Could not get owner alias or owner id ...`. Before stage 63 a
|
|
327
|
+
second, unreachable `Could not get owner` branch followed that one; it was
|
|
328
|
+
removed on 2026-09-25. No match returns `None`, and the base's resolve step
|
|
329
|
+
then fails the run.
|
|
330
|
+
|
|
331
|
+
When the entry declares `image_id` (stage 63), the base's resolve step
|
|
332
|
+
does not call `query_provider_image()` at all: it calls
|
|
333
|
+
`query_provider_image_by_id(entry, image_id)`, which runs `DescribeImages`
|
|
334
|
+
with `ImageIds=[<id>]` through `_query_by_ami_id` in
|
|
335
|
+
[aws_utils.py](src/cs_image_system/aws_runtime/aws_utils.py), with the
|
|
336
|
+
runtime's own client configuration (profile and region). The entry's
|
|
337
|
+
`owners` and `query` are not consulted. The owner is read from the record
|
|
338
|
+
the same way, `ImageOwnerAlias` else `OwnerId` (else `self`), and the
|
|
339
|
+
result is `(ImageId, owner, raw)`. An id AWS answers
|
|
340
|
+
`InvalidImageID.NotFound` or `InvalidImageID.Malformed` for, or one that
|
|
341
|
+
returns no image, gives `None`, and the base stops resolution with
|
|
342
|
+
`OS builder <name>: image_id '<id>' on runtime <rt> is not known to the
|
|
343
|
+
provider`; any other API refusal raises `AMIQueryError`. An AMI id is
|
|
344
|
+
regional, so a pinned id must exist in this runtime's region. Pinning
|
|
345
|
+
makes the base bake reproducible: the same vendor AMI every time, where
|
|
346
|
+
the query takes the newest match.
|
|
347
|
+
|
|
348
|
+
Stage 63 also removed helpers from
|
|
349
|
+
[aws_utils.py](src/cs_image_system/aws_runtime/aws_utils.py) that nothing
|
|
350
|
+
called: `get_ami_owner`, `get_ami_ssh_user`, `remap_for_aws`,
|
|
351
|
+
`_query_by_name` and `_build_ami_filters`. `_query_by_ami_id` (an exact
|
|
352
|
+
lookup by AMI id) was kept, and since stage 63 it is what
|
|
353
|
+
`query_provider_image_by_id()` calls.
|
|
354
|
+
|
|
355
|
+
`AwsProviderSpecificImage.get_query_assets()` gives the packer
|
|
356
|
+
`data "amazon-ami"` body:
|
|
357
|
+
|
|
358
|
+
- resolved: `filters = { image-id = "<ami>" }` plus `owners = ["<owner>"]` when known;
|
|
359
|
+
- deferred (an image a build produces later): `owners = ["self"]`, `filters = { name = "<pattern>*", root-device-type = "ebs", virtualization-type = "hvm", architecture = "<arch or x86_64>" }`, `most_recent = true`.
|
|
360
|
+
|
|
361
|
+
[tests/test_aws_provider_specific_image.py](tests/test_aws_provider_specific_image.py)
|
|
362
|
+
pins both shapes.
|
|
363
|
+
|
|
364
|
+
### The state query and instance hooks
|
|
365
|
+
|
|
366
|
+
`query_images`, `query_instance_boot_image`, `query_instance_power_state`,
|
|
367
|
+
`query_instance_identity`, `start_instance`, `stop_instance`,
|
|
368
|
+
`verify_instance`, `run_session_command`, `retag_image` and `dispose_image`
|
|
369
|
+
all go through `aws_utils.ec2_client()` / `aws_utils.aws_client("ssm", ...)`,
|
|
370
|
+
which build a client from the same session config. The state query in
|
|
371
|
+
[state_query.py](../base/src/cs_image_system/base/state_query.py) reads
|
|
372
|
+
`query_images` to classify `missing`, `foreign` and `changed` images, and
|
|
373
|
+
asks `query_instance_boot_image` only because `can_query_instance_boot_image()`
|
|
374
|
+
is `True`. When that answers `None` it asks `query_instance_power_state`
|
|
375
|
+
before calling the silence `unavailable`: a `stopped` machine becomes a
|
|
376
|
+
`note` in the report, not drift and not a failure to answer (stage 57).
|
|
377
|
+
`verify_instance` is the deferred verification step of the instance
|
|
378
|
+
lifecycle; `run_session_command` is how the system runs a script on an
|
|
379
|
+
instance (the unmount before a detach, the post-bake tests, the AltNames
|
|
380
|
+
write of stage 58). `query_instance_identity` feeds the generation ledger
|
|
381
|
+
and the alias pass in
|
|
382
|
+
[provider_aliases.py](../base/src/cs_image_system/base/provider_aliases.py).
|
|
383
|
+
|
|
384
|
+
### Retention and the cycle
|
|
385
|
+
|
|
386
|
+
The closing `retention` lifecycle in
|
|
387
|
+
[retention.py](../base/src/cs_image_system/base/retention.py) runs
|
|
388
|
+
`dispose image --retention` at execution time. For each series on this
|
|
389
|
+
runtime the number of builds kept is, in order: `0` when the runtime is
|
|
390
|
+
`ephemeral`, the image's `retention.keep`, the runtime's `retention_keep`,
|
|
391
|
+
else everything. Builds beyond that are disposed through `dispose_image()`,
|
|
392
|
+
except released builds and builds an instance is pinned to or was launched
|
|
393
|
+
from, which are reported as retention debt and kept. Declared storages are
|
|
394
|
+
never touched by retention.
|
|
395
|
+
|
|
396
|
+
`ephemeral: true` on an AWS runtime means nothing baked there survives a
|
|
397
|
+
successful run. The fixture's AWS runtimes are not ephemeral and declare no
|
|
398
|
+
`retention_keep`, so every AMI baked on them stands until an explicit
|
|
399
|
+
`dispose image`. `on_failure` and `teardown_after` on the runtime are the
|
|
400
|
+
defaults for its ephemeral instances: `keep` leaves a failed instance
|
|
401
|
+
standing and fails the run; `teardown` tears it down and still fails the
|
|
402
|
+
run; `teardown_after: "2h"` lets a later run tear a standing failure down.
|
|
403
|
+
|
|
404
|
+
### Cost posture
|
|
405
|
+
|
|
406
|
+
A run on an AWS runtime may leave standing: every AMI it baked and the
|
|
407
|
+
snapshots behind them (kept by default), every released AMI (exempt from
|
|
408
|
+
retention), the declared storages (managed by the storage plugins), every
|
|
409
|
+
non-ephemeral instance, and an ephemeral instance whose verification failed
|
|
410
|
+
under `on_failure: keep`. The packer build instance is terminated by packer;
|
|
411
|
+
`force_deregister = !var.release` lets a non-release rebuild replace an AMI
|
|
412
|
+
of the same name. Because `inventory()` is not implemented here,
|
|
413
|
+
`empty --runtime` cannot prove an AWS runtime empty; the state query and
|
|
414
|
+
`query_images` are the checks. An instance the operator switched off is
|
|
415
|
+
left off: the system never starts one except for a bounded task
|
|
416
|
+
(verification, the post-bake tests) and stops it again afterwards.
|
|
417
|
+
|
|
418
|
+
## Emission
|
|
419
|
+
|
|
420
|
+
The plugin's output is visible in the golden emission under
|
|
421
|
+
[tests/fixtures/v2_golden/generated](../../tests/fixtures/v2_golden/generated).
|
|
422
|
+
|
|
423
|
+
- [pckr-ebs-ans-image-generation-source-basic-rh-10-block-000.pkr.hcl](../../tests/fixtures/v2_golden/generated/base-image/pckr-ebs-ans/image-generation/block-000/pckr-ebs-ans-image-generation-source-basic-rh-10-block-000.pkr.hcl):
|
|
424
|
+
the `data "amazon-ami"` lookup (`filters`, `owners`, `profile`) and the
|
|
425
|
+
`source "amazon-ebs"` block written by `amazon_ebs_source()` in
|
|
426
|
+
[aws_packer_source.py](src/cs_image_system/aws_runtime/aws_packer_source.py):
|
|
427
|
+
`source_ami`, `ami_name`, `instance_type` (the runtime entry's machine
|
|
428
|
+
type, else the runtime default), `region`, `vpc_id`, `subnet_id` (the
|
|
429
|
+
default subnet), the merged lineage `tags`, `profile`, `ena_support` and
|
|
430
|
+
`sriov_support` only when the runtime declares them (stage 63; the
|
|
431
|
+
fixture declares neither, so the golden source carries neither), and, because the
|
|
432
|
+
runtime declares `session_mechanism: ssm`, `ssh_interface =
|
|
433
|
+
"session_manager"`, `iam_instance_profile`, `associate_public_ip_address =
|
|
434
|
+
false`, `ssh_timeout = "15m"` and a `user_data` script that installs the
|
|
435
|
+
SSM agent on vendor images that lack it. `ssh_username` is what
|
|
436
|
+
`cs_image_system.base.bake_user.resolve_bake_user` answers (the image's
|
|
437
|
+
entry, the chain root's entry, `config_username`, this runtime's
|
|
438
|
+
`ssh_username` and `default_config_username`, then the family's vendor
|
|
439
|
+
user: `admin` for debian, `ubuntu` for ubuntu, `ec2-user` otherwise). `launch_block_device_mappings` uses the source
|
|
440
|
+
AMI's real `RootDeviceName` (else `/dev/xvda` for debian and amazon roots,
|
|
441
|
+
`/dev/sda1` otherwise), `gp3`, and `volume_size` from
|
|
442
|
+
`lineage.bake_disk_size(ctx, image, runtime)` (stage 63), the one rule the
|
|
443
|
+
input fingerprint also hashes: this runtime declares no
|
|
444
|
+
`default_disk_size`, so for a base image it is the OS builder entry's
|
|
445
|
+
`default_primary_disk_size` for this runtime when declared, else the OS
|
|
446
|
+
builder's `default_primary_disk_size` (200); for an instance image its
|
|
447
|
+
own `primary_disk_size`. The fixture's base images bake with
|
|
448
|
+
`volume_size = 200`.
|
|
449
|
+
- [pckr-ebs-ans-image-generation-block-000-build.pkr.hcl](../../tests/fixtures/v2_golden/generated/base-image/pckr-ebs-ans/image-generation/block-000/pckr-ebs-ans-image-generation-block-000-build.pkr.hcl):
|
|
450
|
+
the `# debug session mechanism (ssm)` shell provisioner per base image
|
|
451
|
+
(`session_agent_commands`) and the `# verify: SSM agent baked` lines inside
|
|
452
|
+
the in-bake verification provisioner (`session_verify_commands`). The
|
|
453
|
+
`amazon` entry in the neighbouring `-plugins.pkr.hcl` comes from the image
|
|
454
|
+
builder's `required_plugins`, not from this package.
|
|
455
|
+
- [open-tofu-instance-generation.tf](../../tests/fixtures/v2_golden/generated/instance-image/open-tofu/instance-generation/open-tofu-instance-generation.tf)
|
|
456
|
+
and
|
|
457
|
+
[open-tofu-instance-generation-instance-test.tf](../../tests/fixtures/v2_golden/generated/instance-image/open-tofu/instance-generation/open-tofu-instance-generation-instance-test.tf):
|
|
458
|
+
written by the instance plugin from this model. `provider "aws" { region,
|
|
459
|
+
profile, alias }`; an `aws_security_group` whose port-22 ingress lists
|
|
460
|
+
`ssh_ingress_security_group_ids` and no CIDR; the instance module's
|
|
461
|
+
`subnet_id` (the default subnet), `vpc_security_group_ids` (the generated
|
|
462
|
+
group, then `addl_security_groups`), and `iam_instance_profile`
|
|
463
|
+
(`session_instance_profile`). The `.tfbackend.hcl` beside them carries the
|
|
464
|
+
same `profile`, written by the state-backend plugin.
|
|
465
|
+
- [run-release.sh](../../tests/fixtures/v2_golden/generated/release/run-release.sh):
|
|
466
|
+
carries the `aws ec2 create-tags` line from `release_commands()` when a
|
|
467
|
+
release is declared. The golden declares none, so it is empty of them.
|
|
468
|
+
- Runtime facts: `cs-image-system runtime describe aws-east2-runtime`
|
|
469
|
+
([runtime_facts.py](../base/src/cs_image_system/base/commands/runtime_facts.py))
|
|
470
|
+
reports `runtime`, `type`, `region`, `account_id`, `default_machine_type`,
|
|
471
|
+
`ephemeral`, `retention_keep`, the `images`, `storages`, `instances` and
|
|
472
|
+
`ephemeral_instances` declared on it, the `builders` bound to it and its
|
|
473
|
+
`emission` directories. It is a command, not a file.
|
|
474
|
+
|
|
475
|
+
## Example configuration
|
|
476
|
+
|
|
477
|
+
From
|
|
478
|
+
[tests/fixtures/config/cfg/runtime-builders.yml](../../tests/fixtures/config/cfg/runtime-builders.yml),
|
|
479
|
+
the default runtime:
|
|
480
|
+
|
|
481
|
+
```yaml
|
|
482
|
+
runtime_builders:
|
|
483
|
+
- name: aws-east2-runtime
|
|
484
|
+
is_default: true
|
|
485
|
+
aliases:
|
|
486
|
+
- aws
|
|
487
|
+
- amazon
|
|
488
|
+
- us-east-2
|
|
489
|
+
type: aws
|
|
490
|
+
account_id: "514190660293"
|
|
491
|
+
description: |
|
|
492
|
+
AWS runtime builder for the {{ this.region }} region account {{ this.account_id }}.
|
|
493
|
+
credentials:
|
|
494
|
+
profile_name: noaa
|
|
495
|
+
tags:
|
|
496
|
+
Project: MyProject
|
|
497
|
+
Environment: Development
|
|
498
|
+
region: us-east-2
|
|
499
|
+
# Debug sessions and bakes connect through SSM: no public IP, no SSH from outside.
|
|
500
|
+
session_mechanism: ssm
|
|
501
|
+
session_instance_profile: AmazonSSMRoleForInstancesQuickSetup
|
|
502
|
+
default_machine_type: t2.micro
|
|
503
|
+
default_image_builder: pckr-ebs-ans
|
|
504
|
+
networking:
|
|
505
|
+
# network is the VPC id
|
|
506
|
+
network: vpc-0c78d0d63b7a100df
|
|
507
|
+
# SSH reaches instances only through the session relay's group; no CIDR ingress
|
|
508
|
+
ssh_ingress_security_group_ids:
|
|
509
|
+
- sg-03015ec107ae5f81a
|
|
510
|
+
# existing groups every instance also wears (never modified here)
|
|
511
|
+
addl_security_groups:
|
|
512
|
+
- sg-03015ec107ae5f81a
|
|
513
|
+
subnets:
|
|
514
|
+
- name: east2-az1-private
|
|
515
|
+
subnet_id: subnet-09f79018af845358a
|
|
516
|
+
is_default: true
|
|
517
|
+
- name: east2-az2-private
|
|
518
|
+
subnet_id: subnet-00075cfbfcbc8f2cf
|
|
519
|
+
- name: east2-az1-public
|
|
520
|
+
subnet_id: subnet-01e56d2f9c1c95cc8
|
|
521
|
+
public: true
|
|
522
|
+
- name: east2-az2-public
|
|
523
|
+
subnet_id: subnet-0c127b762e075a94d
|
|
524
|
+
public: true
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
The same file declares two more `type: aws` runtimes (`aws-east1`, `west1`)
|
|
528
|
+
with `network: default` and no session mechanism; they show the minimum an
|
|
529
|
+
AWS runtime needs: `name`, `type`, `region`, `credentials`,
|
|
530
|
+
`default_machine_type` and a `networking` block with one default subnet. The
|
|
531
|
+
live tree adds `availability_zone` to each subnet so that `validate` can
|
|
532
|
+
refuse a zone mismatch between an instance, its zonal storages and its
|
|
533
|
+
runtime's default subnet.
|
|
534
|
+
|
|
535
|
+
## Prerequisites and integration
|
|
536
|
+
|
|
537
|
+
Everything below exists outside the system. The plugin creates none of it;
|
|
538
|
+
the rule in [docs/OPERATIONS.md](../../docs/OPERATIONS.md) ("The system
|
|
539
|
+
generates no IAM changes; IAP, SSM and impersonation are operator wiring")
|
|
540
|
+
applies to all of it.
|
|
541
|
+
|
|
542
|
+
**An AWS account and a region.** The plugin finds the region in the
|
|
543
|
+
runtime's `region` field. It acts in whatever account the credentials
|
|
544
|
+
resolve to; `account_id` is a fact for `runtime describe` and templates, not
|
|
545
|
+
a check.
|
|
546
|
+
|
|
547
|
+
**Credentials.** Found through `credentials:` on the runtime, converted by
|
|
548
|
+
`self_to_aws_client_config()`:
|
|
549
|
+
|
|
550
|
+
- `credentials.profile_name` names a profile in `~/.aws/config` (fixture and
|
|
551
|
+
live: `noaa`, an `sso-session` profile; `aws sso login --profile noaa`
|
|
552
|
+
opens it). This is the operator's path.
|
|
553
|
+
- `credentials.aws_access_key_id` / `aws_secret_access_key` /
|
|
554
|
+
`aws_session_token` carry `{{ ENV[...] }}` templates for static keys.
|
|
555
|
+
- An empty `credentials:` lets boto3's default chain run: `AWS_ACCESS_KEY_ID`
|
|
556
|
+
and friends, `AWS_PROFILE`, an instance role.
|
|
557
|
+
|
|
558
|
+
botocore drops the environment credential provider when a profile is named,
|
|
559
|
+
so a named profile must itself carry credentials: in CI the workflow writes
|
|
560
|
+
a `[noaa]` shim into `~/.aws/credentials` from the federated keys (the
|
|
561
|
+
`[noaa]` profile shim in [docs/OPERATIONS.md](../../docs/OPERATIONS.md)).
|
|
562
|
+
`preflight` reads the same field (else `AWS_PROFILE`) to find the SSO cache
|
|
563
|
+
under `~/.aws/sso/cache` (`CSIS_AWS_DIR` overrides the directory) and
|
|
564
|
+
reports the token's expiry; static keys have no readable expiry.
|
|
565
|
+
|
|
566
|
+
Because `update_networking()` runs at configuration load, the session must
|
|
567
|
+
be live for every load, dry runs and GCE-only work included. An
|
|
568
|
+
`sso-session` profile's token renews itself only while the browser portal
|
|
569
|
+
session lives; a session that lapses mid-run costs the legs that were
|
|
570
|
+
running (2026-09-08, and again 2026-09-21).
|
|
571
|
+
|
|
572
|
+
**IAM permissions for the caller.** What the plugin itself calls, all with
|
|
573
|
+
the session above: `ec2:DescribeVpcs`, `DescribeSubnets`,
|
|
574
|
+
`DescribeRouteTables`, `DescribeSecurityGroups` (every load);
|
|
575
|
+
`ec2:DescribeImages` (resolution, the state query, disposal);
|
|
576
|
+
`ec2:DescribeInstances` (every instance lookup); `ec2:CreateTags` (retag,
|
|
577
|
+
and the release command through the CLI); `ec2:DeregisterImage` and
|
|
578
|
+
`ec2:DeleteSnapshot` (disposal); `ec2:StartInstances`, `ec2:StopInstances`
|
|
579
|
+
(the bounded power-state exception); `ssm:SendCommand` and
|
|
580
|
+
`ssm:GetCommandInvocation` (verification, unmounts, post-bake tests, the
|
|
581
|
+
alias write). Packer's bake and the tofu roots need their own permissions
|
|
582
|
+
on top, including `iam:PassRole` for the instance profile and
|
|
583
|
+
`ssm:StartSession` for the Session Manager communicator; the CI table in
|
|
584
|
+
[docs/OPERATIONS.md](../../docs/OPERATIONS.md) (`AWS_ROLE_ARN` read-only,
|
|
585
|
+
`AWS_APPLY_ROLE_ARN` write) lists the two roles the live job uses.
|
|
586
|
+
|
|
587
|
+
**Network.** Found through `networking:`: the VPC (`network`, or `default`
|
|
588
|
+
for the account's default VPC), the subnets by id, the security groups by
|
|
589
|
+
id. The fixture's VPC has no internet gateway, which shapes everything else:
|
|
590
|
+
bakes and instances go on the default (private) subnet with
|
|
591
|
+
`associate_public_ip_address = false`, egress is the subnet's NAT, and both
|
|
592
|
+
packer and the operator reach machines through SSM. The default subnet must
|
|
593
|
+
route to the SSM endpoints and to the package repositories the bootstrap
|
|
594
|
+
downloads from (`s3.<region>.amazonaws.com`); SSH ingress is by
|
|
595
|
+
security-group reference only (`ssh_ingress_security_group_ids`, the Okta
|
|
596
|
+
gateway's group), and the gateway relay requires the target to wear that
|
|
597
|
+
same group (`addl_security_groups`).
|
|
598
|
+
|
|
599
|
+
**An SSM-capable instance profile.** Found in `session_instance_profile`
|
|
600
|
+
(live: `AmazonSSMRoleForInstancesQuickSetup`), attached to launched
|
|
601
|
+
instances by the instance plugin and to build instances by the packer
|
|
602
|
+
source. Without it, `session_mechanism: ssm` still bakes the agent but
|
|
603
|
+
packer keeps the SSH communicator and instances get no profile. The role
|
|
604
|
+
behind it needs the SSM managed policy; the caller needs `iam:PassRole` on
|
|
605
|
+
it.
|
|
606
|
+
|
|
607
|
+
**Tools.** Found through `cfg/executables.yml`
|
|
608
|
+
([executables.yml](../../tests/fixtures/config/cfg/executables.yml)):
|
|
609
|
+
|
|
610
|
+
- `aws` (the entry named `aws-cli`, fixture floor `>=2.32`): the release
|
|
611
|
+
command and the storage builders' transition scripts run it; its version
|
|
612
|
+
is read by `AwsCLIVersionChecker` at `validate` and at every run.
|
|
613
|
+
- `packer` with the `amazon` plugin: declared on the image builder's
|
|
614
|
+
`required_plugins`, not here; the source this package emits needs it.
|
|
615
|
+
- The AWS Session Manager plugin for packer's `ssh_interface =
|
|
616
|
+
"session_manager"`: installed on the machine that bakes (CI installs it
|
|
617
|
+
before the performing step). Not declared in the tree; packer fails
|
|
618
|
+
without it.
|
|
619
|
+
- `tofu` with the `hashicorp/aws` provider for the instance and storage
|
|
620
|
+
roots, declared on those builders.
|
|
621
|
+
- Python: `boto3>=1.43`, `botocore>=1.43`, `python-hcl2>=8.1`,
|
|
622
|
+
`pydantic>=2.13`, and the `cs-image-system-hashicorp-utils` package for
|
|
623
|
+
the HCL formatter ([pyproject.toml](pyproject.toml)).
|
|
624
|
+
|
|
625
|
+
**Vendor images.** Found through the OS builders' `owners` and `query`
|
|
626
|
+
(fixture: AlmaLinux 10 from owner `764336703387`, the Debian images by
|
|
627
|
+
family name). The account must be allowed to see and launch them (Allowed
|
|
628
|
+
AMIs, Marketplace subscriptions where they apply).
|
|
629
|
+
|
|
630
|
+
**Instances reachable by name.** The instance module tags every machine
|
|
631
|
+
`Name=<declared name>`; every lookup in this plugin depends on that tag and
|
|
632
|
+
on the SSM agent being registered on the machine (baked in by
|
|
633
|
+
`session_agent_commands`, bootstrapped by the source's `user_data` on a
|
|
634
|
+
vendor image that lacks it). The launch script writes
|
|
635
|
+
`/var/lib/csis/launch-applied` as its last act; verification waits for it.
|
|
636
|
+
|
|
637
|
+
**Nothing from the identity side.** Enrollment, OPA and `sft` are the
|
|
638
|
+
identity plugins' concern; this plugin only runs their scripts on the
|
|
639
|
+
machine over SSM when asked.
|
|
640
|
+
|
|
641
|
+
## Configuration reference
|
|
642
|
+
|
|
643
|
+
Every field the plugin reads from the YAML it owns (the `type: aws` entry
|
|
644
|
+
under `runtime_builders:`). The base's own fields are included because this
|
|
645
|
+
plugin is what reads them on AWS. Types are the model's; `default` in a
|
|
646
|
+
value column means the literal string `default`.
|
|
647
|
+
|
|
648
|
+
### The runtime entry
|
|
649
|
+
|
|
650
|
+
| Field | Type | Default | Meaning |
|
|
651
|
+
|---|---|---|---|
|
|
652
|
+
| `name` | str | required | The runtime's name; the value other builders' `runtime:` and the lineage records carry. |
|
|
653
|
+
| `type` | str | required | `aws`. |
|
|
654
|
+
| `description` | str or null | null | Free text; templates such as `{{ this.region }}` resolve. |
|
|
655
|
+
| `aliases` | list[str] | `[]` | Extra names the runtime answers to. |
|
|
656
|
+
| `is_default` | bool | `false` | The runtime `default` resolves to; at most one per class. |
|
|
657
|
+
| `tags` | mapping[str, str] | `{}` | Merged into every image's tags, so they reach the AMI and the packer source. |
|
|
658
|
+
| `region` | str | required | boto3 `region_name`; the packer `region`; the provider block; `--region` on the CLI commands the plugin and the storage builders emit. |
|
|
659
|
+
| `default_machine_type` | str | required | The bake instance type when neither the OS-builder runtime entry (`default_machine_type`) nor the image's runtime entry (`machine_type`) names one; the instance type of a launched instance without `machine_type`. |
|
|
660
|
+
| `default_image_builder` | str | `default` | What an OS-builder runtime entry with `image_builder: default` resolves to. |
|
|
661
|
+
| `credentials` | `AwsCredentials` | `{}` | See the model above. An unknown key is a validation error naming its path. |
|
|
662
|
+
| `credentials.profile_name` | str or null | null | The profile. Reaches boto3, the packer source, the release command, the provider blocks and the backend files. |
|
|
663
|
+
| `credentials.aws_access_key_id`, `aws_secret_access_key`, `aws_session_token` | str or null | null | Static keys through `{{ ENV[...] }}` templates; reach boto3 only. |
|
|
664
|
+
| `account_id` | str or null | null | Reported by `runtime describe`; a template value. A bare number is coerced to a string. |
|
|
665
|
+
| `state_configuration` | str | `default` | The backend a storage or instance root on this runtime inherits when its own is `default`; `default` here means the default backend. |
|
|
666
|
+
| `session_mechanism` | str or null | null | `ssm` bakes the SSM agent into base images, makes packer connect through Session Manager, attaches `session_instance_profile` to instances, and makes every instance operation possible (verification, unmount, post-bake tests, aliases). Any other value is refused at generation. |
|
|
667
|
+
| `session_instance_profile` | str or null | null | The instance profile for launched instances and (when `iam_instance_profile` is unset) build instances. Read only when the mechanism is `ssm`. |
|
|
668
|
+
| `iam_instance_profile` | str or null | null | The build instances' profile in the packer source when `session_instance_profile` is unset; with both set the SSM profile wins (stage 63 item 9). Never reaches launched instances. |
|
|
669
|
+
| `ssh_username` | str | `default` | The bake SSH user for images that name none more specifically (step 4 of the bake-user order, CONFIGURATION 5.1.1). |
|
|
670
|
+
| `networking` | mapping or null | null | See below. Null loads with a warning and fails at image generation. |
|
|
671
|
+
| `ephemeral` | bool | `false` | Nothing baked here survives a successful run: the closing retention disposes every build on this runtime. |
|
|
672
|
+
| `retention_keep` | int or null | null | Builds kept per series when the image declares no `retention`; null keeps all. |
|
|
673
|
+
| `on_failure` | str or null | null | Default failure policy for ephemeral instances here: `keep` or `teardown`. |
|
|
674
|
+
| `teardown_after` | str or null | null | Default grace for ephemeral instances here: `<number>` then `m`, `h` or `d`. |
|
|
675
|
+
| `default_owners` | list[str] or null | null | Owners added to every vendor-image query on this runtime (stage 63 item 22). |
|
|
676
|
+
| `default_config_username` | str or null | null | The bake SSH user when no image entry, OS entry, `config_username` or runtime `ssh_username` names one (stage 63 item 22). |
|
|
677
|
+
| `ena_support` | bool or null | null | Declared `true` or `false`: written as `ena_support = true\|false` in the `amazon-ebs` packer source of every image baked here. Null: not written; packer's default applies (stage 63; until 2026-09-25 accepted and read by nothing). |
|
|
678
|
+
| `sriov_support` | bool or null | null | Declared `true` or `false`: written as `sriov_support = true\|false` in the same source. Null: not written; packer's default applies (stage 63; until 2026-09-25 accepted and read by nothing). |
|
|
679
|
+
| `executable`, `config`, `gitignore` | | | Accepted from `BuilderModel`; this plugin reads none of them. |
|
|
680
|
+
| `profile`, `parameters`, `runtime_classifier`, `executables` | | | Refused (unknown keys); the fixture's comments record the replacements. |
|
|
681
|
+
|
|
682
|
+
### `networking`
|
|
683
|
+
|
|
684
|
+
| Field | Type | Default | Meaning |
|
|
685
|
+
|---|---|---|---|
|
|
686
|
+
| `name` | str | the network id | A label; stripped, and refused when empty afterwards. |
|
|
687
|
+
| `network` | str | `default` | The VPC id; `default` resolves at load to the account's default VPC. Written as the packer `vpc_id`, the instance root's `data "aws_vpc"` id and the EFS builder's `vpc_id`. |
|
|
688
|
+
| `subnets` | list | required, at least one | Exactly one entry with `is_default: true`; the load refuses none. |
|
|
689
|
+
| `subnets[].name` | str | the subnet id | Label. |
|
|
690
|
+
| `subnets[].subnet_id` | str | required | The subnet id. The default one is the packer `subnet_id` and the instance module's `subnet_id`; the EFS builder mounts on every non-public one (one per zone). Checked at load against the subnets the account lists for the declared VPC, and refused when it is not one of them (stage 63; until 2026-09-25 not checked). A VPC the account lists without subnets makes no claim. |
|
|
691
|
+
| `subnets[].is_default` | bool | `false` | The subnet bakes and instances use. |
|
|
692
|
+
| `subnets[].public` | bool | `false` | The operator's statement; not overwritten by discovery. The EFS builder skips public subnets. |
|
|
693
|
+
| `subnets[].cidr` | str or null | null | Informational. |
|
|
694
|
+
| `subnets[].availability_zone` | str or null | null | Declared, never inferred; `validate` refuses a zone incompatible with an instance's zonal storages. The EFS builder uses it to pick one subnet per zone. |
|
|
695
|
+
| `subnets[].config` | mapping | `{}` | Accepted, not read. |
|
|
696
|
+
| `availability_zones` | list | `[]` | `{name, is_default}`; the default one becomes the packer `availability_zone`. |
|
|
697
|
+
| `addl_security_groups` | list[str] | `[]` | Checked to exist at load; the only list counted toward the limit of five; appended to the instance's `vpc_security_group_ids`; the EFS mount targets' client groups. |
|
|
698
|
+
| `ssh_ingress_security_group_ids` | list[str] | `[]` | Not checked at load. The generated instance group's port-22 source; when empty the ingress is the VPC's own CIDR block instead. |
|
|
699
|
+
| `security_group_ids` | | | Refused (unknown key) since stage 63. Until 2026-09-25 it was checked and counted but reached no emitter; put the ids in `addl_security_groups`. |
|
|
700
|
+
|
|
701
|
+
### What other entries contribute
|
|
702
|
+
|
|
703
|
+
These are not this plugin's fields, but the packer source reads them:
|
|
704
|
+
|
|
705
|
+
| Where | Field | Effect on AWS |
|
|
706
|
+
|---|---|---|
|
|
707
|
+
| OS builder `runtimes[]` entry | `default_machine_type` | The bake instance type for that base (the fixture bakes on `t3.medium` because a `t2.micro` OOMs). |
|
|
708
|
+
| OS builder `runtimes[]` entry | `ssh_username` | The bake user for that OS on this runtime (step 2 of the bake-user order); unset, the order continues down to the chain root family's vendor user. |
|
|
709
|
+
| OS builder `runtimes[]` entry | `owners`, `query` | The `describe_images` request. |
|
|
710
|
+
| OS builder `runtimes[]` entry | `image_id` | Stage 63: an AMI id that replaces the query; looked up by id with `DescribeImages`. |
|
|
711
|
+
| OS builder `runtimes[]` entry | `default_primary_disk_size` | Stage 63: the base image's `volume_size` on this runtime when declared. |
|
|
712
|
+
| OS builder | `default_primary_disk_size` | The base image's `volume_size` when the entry declares none (200 by default). |
|
|
713
|
+
| image `runtimes[]` entry | `machine_type` | The bake instance type for that instance image. |
|
|
714
|
+
| image | `primary_disk_size` | The root volume size of an instance-image bake, in GB. |
|
|
715
|
+
| instance | `machine_type` | The launched instance type; the runtime default otherwise. |
|
|
716
|
+
|
|
717
|
+
### Variations
|
|
718
|
+
|
|
719
|
+
- **A queried vs a pinned vendor AMI.** Without `image_id` on the OS
|
|
720
|
+
builder's entry, resolution runs the `describe_images` query and takes
|
|
721
|
+
the newest match, so the next base bake may start from a newer vendor
|
|
722
|
+
AMI. With `image_id` (stage 63) the query is skipped and that one AMI is
|
|
723
|
+
looked up by id; it must exist in this runtime's region, and the id is
|
|
724
|
+
part of the fingerprint's vendor source.
|
|
725
|
+
- **Where the bake disk comes from.** `volume_size` is
|
|
726
|
+
`lineage.bake_disk_size` (stage 63): this runtime has no
|
|
727
|
+
`default_disk_size`, so a base image takes its entry's
|
|
728
|
+
`default_primary_disk_size`, else the OS builder's (200). Until
|
|
729
|
+
2026-09-25 the fingerprint hashed the entry's value (then defaulting to
|
|
730
|
+
100) while the bake used the OS builder's 200; the fingerprint now
|
|
731
|
+
hashes what is baked, so every AWS base image's recorded fingerprint
|
|
732
|
+
moved once and reads DUE until `lineage restamp --runtime <rt> --commit`
|
|
733
|
+
records the new one.
|
|
734
|
+
|
|
735
|
+
- **`session_mechanism: ssm` declared, with a profile.** The packer source
|
|
736
|
+
gets `ssh_interface = "session_manager"`, the profile as
|
|
737
|
+
`iam_instance_profile`, `associate_public_ip_address = false`,
|
|
738
|
+
`ssh_timeout = "15m"` and the agent-installing `user_data`; base images get
|
|
739
|
+
the agent provisioner and its verification lines; launched instances get
|
|
740
|
+
the profile; verification, unmounts, post-bake tests and alias writes work.
|
|
741
|
+
**Not declared:** the source keeps packer's SSH communicator with
|
|
742
|
+
`associate_public_ip_address = true` and no `user_data`, no agent
|
|
743
|
+
provisioner is emitted, instances get no profile, and every session-based
|
|
744
|
+
operation fails with SSM's `TargetNotConnected` / `InvalidInstanceId`.
|
|
745
|
+
**Declared without any profile:** the agent is baked and verified, but
|
|
746
|
+
the source keeps the SSH communicator (the SSM block needs a profile).
|
|
747
|
+
- **`iam_instance_profile` set beside `session_instance_profile`.** Both
|
|
748
|
+
build and launched instances wear `session_instance_profile` (the SSM
|
|
749
|
+
profile wins, stage 63 item 9); `iam_instance_profile` applies to build
|
|
750
|
+
instances only when the SSM profile is unset.
|
|
751
|
+
- **`networking.network: default`** resolves to the account's default VPC
|
|
752
|
+
at load and is refused when the account has none. **A VPC id** must be one
|
|
753
|
+
the session can describe. Either way, **a declared subnet in that VPC**
|
|
754
|
+
loads and **a subnet id from another VPC** (or a mistyped one) is refused
|
|
755
|
+
at load (stage 63; it used to pass and fail at the first bake or apply);
|
|
756
|
+
**a VPC the account lists without subnets** leaves the declared subnets
|
|
757
|
+
unchecked.
|
|
758
|
+
- **Security groups in `addl_security_groups`** are checked, counted
|
|
759
|
+
(at most five) and worn by every launched instance; **the same ids under
|
|
760
|
+
`security_group_ids`** are an unknown-key refusal at load (stage 63; until
|
|
761
|
+
2026-09-25 accepted, checked and counted, but never emitted).
|
|
762
|
+
- **`ena_support` / `sriov_support` declared** (`true` or `false`) are
|
|
763
|
+
written into the `amazon-ebs` source as that value; **not declared**, the
|
|
764
|
+
source carries neither line and packer's defaults decide (stage 63; until
|
|
765
|
+
2026-09-25 the fields were accepted and read by nothing, whatever their
|
|
766
|
+
value).
|
|
767
|
+
- **`ephemeral: true`** keeps nothing baked here past a successful run;
|
|
768
|
+
**`false`** (the fixture) keeps everything until `retention_keep`, an
|
|
769
|
+
image's `retention` or an explicit `dispose image`.
|
|
770
|
+
- **A pinned parent** (`parent_policy: pinned` with a pin, or a rebuild)
|
|
771
|
+
writes `filters = { image-id = "<pinned>" }`, `owners = ["self"]` into
|
|
772
|
+
the data lookup; **an unpinned or deferred parent** writes the
|
|
773
|
+
name-pattern / most-recent lookup from `get_query_assets()`; **a
|
|
774
|
+
resolved vendor image** writes its exact id and owner.
|
|
775
|
+
- **An entry's `ssh_username`** wins over the runtime's (stage 63 item 23;
|
|
776
|
+
the runtime's used to win); the OS builder's `config_username` wins over
|
|
777
|
+
the runtime's too. With nothing named anywhere, an image takes `admin`
|
|
778
|
+
(debian root), `ubuntu` (ubuntu root) or `ec2-user`.
|
|
779
|
+
- **A vendor query that resolved** gives the bake the real
|
|
780
|
+
`RootDeviceName`; **a deferred source** falls back by family (`/dev/xvda`
|
|
781
|
+
for debian and amazon, `/dev/sda1` otherwise).
|
|
782
|
+
- **Dry run versus real run.** A dry run still loads (network discovery
|
|
783
|
+
runs), still resolves vendor AMIs, still emits the source and provider
|
|
784
|
+
blocks, and still runs the state query's read-only hooks; it never bakes,
|
|
785
|
+
never tags, never disposes, never starts or stops a machine, and the
|
|
786
|
+
release and dispose commands print their plan. Only `--no-dry-run` runs
|
|
787
|
+
the runner scripts that call `aws ec2 create-tags` and `dispose image`.
|
|
788
|
+
- **`apply_release` on** (global `config`) adds the `create-tags`
|
|
789
|
+
executables to the release runner; **off**, `release` records the release
|
|
790
|
+
in meta-state and tags nothing in the cloud.
|
|
791
|
+
- **`apply_instances` on for this runtime** lets the instance root apply,
|
|
792
|
+
after which the plugin's instance hooks have something to find; **off**,
|
|
793
|
+
the lookups answer "absent" and the stage-58 alias pass does nothing.
|
|
794
|
+
- **A stopped instance** is a `note` in the state report, skips
|
|
795
|
+
verification and login proof unless the task may start it, and is left
|
|
796
|
+
off by the alias pass; **a running one** is verified and aliased; **a
|
|
797
|
+
terminated record** counts as absent.
|
|
798
|
+
- **An encrypted value** (`ENC[age:...]`) anywhere in the entry is
|
|
799
|
+
decrypted by the core before this plugin sees it; the plugin has no
|
|
800
|
+
behaviour of its own for encryption.
|
|
801
|
+
|
|
802
|
+
## What it tests and verifies
|
|
803
|
+
|
|
804
|
+
**At load (pydantic validators and `finalize`).**
|
|
805
|
+
|
|
806
|
+
- `AwsCredentials`: unknown keys refused (`extra="forbid"`), so
|
|
807
|
+
`profile_nme` is a validation error naming `credentials.profile_nme`.
|
|
808
|
+
- `RuntimeNetworkingModel.__post_init__`: `network` non-empty; at least one
|
|
809
|
+
subnet; exactly one default subnet. `AwsCloudNetworkingModel`: the name
|
|
810
|
+
non-empty after stripping. `RuntimeBuilderModel`: `default_machine_type`
|
|
811
|
+
present. `CloudBuilderModel`: `region` present.
|
|
812
|
+
- `update_networking()` (at `finalize`, after the base): discovers the
|
|
813
|
+
account (four describe calls) and refuses a `default` network with no
|
|
814
|
+
default VPC, a VPC the account does not have, a declared
|
|
815
|
+
`subnets[].subnet_id` that is not among the subnets the account lists for
|
|
816
|
+
that VPC (stage 63; skipped when the account lists none for it), a
|
|
817
|
+
security group in `addl_security_groups` the account does not have, and
|
|
818
|
+
more than five groups in `addl_security_groups`. Missing
|
|
819
|
+
`networking` is a warning, not an error. Every verdict is a `ValueError`
|
|
820
|
+
with the message logged at ERROR first; the load fails and `run` exits 1
|
|
821
|
+
(`validate` exits 1).
|
|
822
|
+
- `security_group_ids` under `networking:` is refused as an unknown key by
|
|
823
|
+
the shared model config, like any other undeclared key (stage 63; until
|
|
824
|
+
2026-09-25 it was a field that was validated and counted but never
|
|
825
|
+
emitted).
|
|
826
|
+
- The AWS CLI version: `validate` and every run look up the checker for
|
|
827
|
+
each declared executable; for the entry named `aws-cli` it is this
|
|
828
|
+
package's, and a missing binary, an unreadable version or one below the
|
|
829
|
+
floor fails validation by name.
|
|
830
|
+
|
|
831
|
+
**At `validate`.** Nothing of this plugin's own beyond the load and the
|
|
832
|
+
tool check. The base's N9 dead-end rule reads `session_mechanism()`: a base
|
|
833
|
+
image with no admin public key and no session mechanism on a runtime is a
|
|
834
|
+
validation error naming the runtime.
|
|
835
|
+
|
|
836
|
+
**At generation.** `session_mechanism()` raises `ValueError` on a value
|
|
837
|
+
other than `ssm`. `amazon_ebs_source()` asserts that the source image has
|
|
838
|
+
query assets (an `AssertionError` naming the image and runtime; it cannot
|
|
839
|
+
fire for a resolved or deferred PSI). `ena_support` and `sriov_support` are
|
|
840
|
+
written into the source only when declared, as the declared boolean; there
|
|
841
|
+
is no further check on them (stage 63). Resolution's `query_provider_image`
|
|
842
|
+
raises when the region is unset or the AMI record carries neither
|
|
843
|
+
`ImageOwnerAlias` nor `OwnerId`, and returns `None` on no match, which
|
|
844
|
+
[resolve.py](../base/src/cs_image_system/base/commands/resolve.py) turns
|
|
845
|
+
into a failed run. For an entry that pins `image_id`,
|
|
846
|
+
`query_provider_image_by_id` returns `None` when AWS does not know the id,
|
|
847
|
+
and resolve.py stops the run naming the id and the runtime (stage 63).
|
|
848
|
+
|
|
849
|
+
**At apply.** Nothing in this package runs at apply: packer and tofu do,
|
|
850
|
+
with what this package emitted. Packer's own in-bake verification carries
|
|
851
|
+
the two SSM assertions from `session_verify_commands` (the agent binary
|
|
852
|
+
present, the unit enabled); a failed assertion fails the bake.
|
|
853
|
+
|
|
854
|
+
**After apply (post-finalize hooks and deferred commands).**
|
|
855
|
+
|
|
856
|
+
- After a bake the packer plugin calls `retag_image()` for every recorded
|
|
857
|
+
build; the verdict is the AMI's tags (checked by the next state query).
|
|
858
|
+
- `verify instance <name>` (the ephemeral sequence, `just cloud-verify`,
|
|
859
|
+
the post-bake tests) calls `verify_instance()`: three checks named
|
|
860
|
+
`startup scripts` (the marker within `timeout`, polled every 15 s over
|
|
861
|
+
SSM), `booted image` (the running instance's `ImageId` against the
|
|
862
|
+
expected build, or against lineage when nothing is pinned yet) and, when
|
|
863
|
+
the instance declares storages, `data disks mounted` (`findmnt` targets
|
|
864
|
+
under `/mnt/` at least the declared count). The base appends
|
|
865
|
+
`declared tests` from `tests.post_bake`, run over `run_session_command`.
|
|
866
|
+
The verdict lands in `meta-state/verifications.yaml` with the last 20
|
|
867
|
+
lines of output as evidence; a failed verdict stops the runner and
|
|
868
|
+
`verify assert` fails the run.
|
|
869
|
+
- Before a detach, `unmount storage` runs the unmount script over
|
|
870
|
+
`run_session_command`; the verdict is the receipt under
|
|
871
|
+
`unmount-receipts/<instance>__<storage>.json` in the root's workspace,
|
|
872
|
+
which `gate-plan --require-unmounted` reads.
|
|
873
|
+
- After an applies-on instance run the alias pass asks
|
|
874
|
+
`query_instance_identity` and `query_instance_power_state`, and writes
|
|
875
|
+
AltNames over `run_session_command` to running machines only; the verdict
|
|
876
|
+
is a log line per instance and `reality.instances` in the state report.
|
|
877
|
+
The generation ledger (`meta-state/instance-state.yaml`) records the
|
|
878
|
+
provider's instance id from the same identity query.
|
|
879
|
+
- `verify login` skips a machine whose power state is not `running`.
|
|
880
|
+
|
|
881
|
+
**In the state query** (`state query`, `just cloud-preflight`, the start of
|
|
882
|
+
every run): `query_images` on this runtime feeds `missing`, `foreign` and
|
|
883
|
+
`changed`; `query_instance_boot_image` compares each pinned instance's
|
|
884
|
+
booted AMI with its pin (`changed` when they differ, a `note` when a
|
|
885
|
+
pending replacement explains it), and a standing ephemeral is reported;
|
|
886
|
+
`query_instance_power_state` turns a stopped machine into a `note` and a
|
|
887
|
+
stopped ephemeral into a STANDING line. A hook that raises makes an
|
|
888
|
+
`unavailable:` line, never a failure. The verdicts land in
|
|
889
|
+
`generated/state-report.json` (run-local, never committed) and on the
|
|
890
|
+
console; `--strict` exits 1 on anything but `stale`.
|
|
891
|
+
|
|
892
|
+
`tests/test_v2_bootstrap_aws.py` holds the bootstrap's AWS section: the
|
|
893
|
+
entry point and the tree-derived gate, the starter's placeholders and an
|
|
894
|
+
unaskable account refused by name under `--quiet`, the forks following a
|
|
895
|
+
stubbed account, the subject forms following the ids, the import blocks,
|
|
896
|
+
the backend (the declared bucket when it stands, local when the bootstrap
|
|
897
|
+
makes it, and the second interview that binds it), the module's
|
|
898
|
+
statements held to the guide's Sid for Sid, and a real `tofu validate` of
|
|
899
|
+
a root carrying the GitHub and AWS sections together.
|
|
900
|
+
|
|
901
|
+
## When it fails
|
|
902
|
+
|
|
903
|
+
Failures that have happened, oldest first, then the ones the code raises
|
|
904
|
+
that have not. Dates are from [docs/history/LEDGER.md](../../docs/history/LEDGER.md),
|
|
905
|
+
[docs/OPERATIONS.md](../../docs/OPERATIONS.md) and the tests.
|
|
906
|
+
|
|
907
|
+
### Seen live
|
|
908
|
+
|
|
909
|
+
- **2026-08-31, stage 1: packer's `data "amazon-ami"` found no credentials.**
|
|
910
|
+
`No valid credential sources found` from the datasource although the
|
|
911
|
+
operator's shell was logged in: packer does not read a profile it is not
|
|
912
|
+
told about. The source now carries `profile = "<profile_name>"` on both
|
|
913
|
+
the lookup and the source. If it recurs, the runtime's
|
|
914
|
+
`credentials.profile_name` is unset or the profile has no live session.
|
|
915
|
+
- **2026-08-31, stage 1: `Timeout waiting for SSH` on every bake.** The
|
|
916
|
+
VPC has no internet gateway, so packer's SSH communicator could never
|
|
917
|
+
reach the build instance. `session_mechanism: ssm` with a profile makes
|
|
918
|
+
packer connect through Session Manager; the same message today means the
|
|
919
|
+
Session Manager plugin is missing on the baking machine, the build
|
|
920
|
+
instance never registered with SSM (no route to the SSM endpoints from
|
|
921
|
+
the default subnet, the `user_data` bootstrap failed, or the profile
|
|
922
|
+
lacks the SSM policy), or the bake ran past `ssh_timeout = "15m"`. Look at
|
|
923
|
+
the packer log of the bake and at SSM Fleet Manager for the instance.
|
|
924
|
+
- **2026-08-31, stage 1: a Debian chain baked as `ec2-user` never
|
|
925
|
+
authenticated** (again `Timeout waiting for SSH`, over the SSM tunnel).
|
|
926
|
+
Only the vendor user's `authorized_keys` receives packer's key, so the
|
|
927
|
+
bake user now follows the chain root's family (`admin` for debian). A
|
|
928
|
+
runtime `ssh_username` or an entry `ssh_username` that names the wrong
|
|
929
|
+
user brings it back.
|
|
930
|
+
- **2026-08-31, stage 1: two root-slot volumes on the deb-11 chain.** A
|
|
931
|
+
hard-coded `/dev/sda1` device on a Debian root (whose root is
|
|
932
|
+
`/dev/xvda`) baked an AMI carrying two volumes in the root slot; instances
|
|
933
|
+
from it never booted. The mapping now uses the source AMI's real
|
|
934
|
+
`RootDeviceName`, else the family default. A deferred source still falls
|
|
935
|
+
back by family, so a chain whose root family is unknown to
|
|
936
|
+
`_chain_root_family` gets `/dev/sda1`.
|
|
937
|
+
- **2026-08-31 to 2026-09-01, stage 1: `t2.micro` out of memory** at bake
|
|
938
|
+
(`dnf update` killed) and on instances (the SSM agent killed, finding 25).
|
|
939
|
+
The OS builder entries bake on `t3.medium`; an instance declares its own
|
|
940
|
+
`machine_type`. The symptom is a bake that dies mid-provisioner or an
|
|
941
|
+
instance that stops answering SSM after boot.
|
|
942
|
+
- **Stage 1: `curl: (23)` re-downloading the SSM agent** on Debian during
|
|
943
|
+
the agent provisioner, because the `user_data` bootstrap had already left
|
|
944
|
+
a root-owned `/tmp/ssm.deb`. The provisioner now skips the install when
|
|
945
|
+
the agent is present and downloads to its own file name.
|
|
946
|
+
- **2026-09-01 to 2026-09-02, stage 1, findings 27 to 31: `sft ssh` hung
|
|
947
|
+
on the advertised private address.** Instances wore only the VPC default
|
|
948
|
+
group; the instance root now creates `csis-instances` with SSH ingress
|
|
949
|
+
from `ssh_ingress_security_group_ids` only (opening the shared CIDRs is
|
|
950
|
+
forbidden), and the gateway relay also needs the target in the gateway's
|
|
951
|
+
own group (`addl_security_groups`). An instance that cannot be reached by
|
|
952
|
+
the relay while SSM works points at those two lists.
|
|
953
|
+
- **2026-09-02 and 2026-09-03: orphan AMIs from failed and unrequested
|
|
954
|
+
bakes.** Eleven AMIs and twelve snapshots from stage 1, and two more from
|
|
955
|
+
stale runner scripts, appeared as `foreign` in the state query (they carry
|
|
956
|
+
our tags, no record has them). The operator deregistered them by hand;
|
|
957
|
+
`state import` adopts such an image, `dispose image` removes a recorded
|
|
958
|
+
one. A `foreign` line is never hard drift, but every line is actionable.
|
|
959
|
+
- **2026-09-08: a follow image kept its generation-time tags.** A child
|
|
960
|
+
baked in the same run as its parent was tagged with a `series-...`
|
|
961
|
+
parent and a placeholder fingerprint; the state query reported `changed`.
|
|
962
|
+
The packer plugin now calls `retag_image` after every bake, and `lineage
|
|
963
|
+
relabel --runtime <rt>` re-tags any build whose tags still disagree with
|
|
964
|
+
its record.
|
|
965
|
+
- **2026-09-08: the AWS SSO session lapsed mid-run.** A `tofu plan` failed
|
|
966
|
+
with `No valid credential sources found ... backend s3 ... the SSO
|
|
967
|
+
session has expired` after `cloud-preflight` had passed at the start;
|
|
968
|
+
the same lapse ends any load with `AWS Error: ...` from
|
|
969
|
+
`get_vpc_map_and_default_vpc_id`, on GCE work too. Environmental, not a
|
|
970
|
+
defect: `aws sso login --profile noaa`, then `just full-test-legs` or the
|
|
971
|
+
legs that were running. `preflight` and the strict state query flag a
|
|
972
|
+
session expiring within `config.preflight.expected_run_minutes`;
|
|
973
|
+
an `sso-session` profile reports no fixed expiry.
|
|
974
|
+
- **2026-09-09: the first live AWS disposal** (`dispose image <two EL8
|
|
975
|
+
AMIs> --no-dry-run --commit`) deregistered both and deleted their
|
|
976
|
+
snapshots; `describe-images` on both ids answered an empty list
|
|
977
|
+
afterwards. Not a failure, but the proof that a disposal's success is
|
|
978
|
+
read from the cloud, not from the log line.
|
|
979
|
+
- **2026-09-10, ledger 72: `InvocationDoesNotExist` right after
|
|
980
|
+
`send_command`.** The unmount had already run on the instance when
|
|
981
|
+
`get_command_invocation` raised, so no receipt was written and the gate
|
|
982
|
+
refused the detach. The poll now tolerates that code until its deadline
|
|
983
|
+
([test_v2_aws_parity.py](../../tests/test_v2_aws_parity.py)); any other
|
|
984
|
+
SSM error still raises.
|
|
985
|
+
- **2026-09-12, stage 17: `credentials.profile_nme` was silently dropped.**
|
|
986
|
+
With `credentials` a plain mapping the typo left the profile unset and
|
|
987
|
+
boto3 fell back to `AWS_PROFILE` or nothing. The typed `AwsCredentials`
|
|
988
|
+
makes it a validation error naming `credentials.profile_nme`.
|
|
989
|
+
- **2026-09-21, stage 57: a stopped machine was reported as "the provider
|
|
990
|
+
could not answer".** The boot-image probe filters on `running`, so a
|
|
991
|
+
machine the operator had switched off looked like an unreachable cloud
|
|
992
|
+
and `run_session_command` said `no running instance` for both. The
|
|
993
|
+
power-state hook separates them: the state report says `STOPPED (switched
|
|
994
|
+
off; not drift)` in a `note`, and a session command on a stopped machine
|
|
995
|
+
fails with `cannot run a command on '<name>': it is STOPPED (switched off;
|
|
996
|
+
not drift)`.
|
|
997
|
+
- **2026-09-22: the strict query refused the launch that finishes an
|
|
998
|
+
upgrade.** After `upgrade instance` moved the pin, the booted AMI was
|
|
999
|
+
behind it by design, and `cloud-launch`'s preflight called that
|
|
1000
|
+
`changed`. With the pending-replacement marker present it is now a
|
|
1001
|
+
`note`; without the marker it is drift again.
|
|
1002
|
+
- **2026-09-22, stage 58: the first live replacement wrote no aliases.**
|
|
1003
|
+
The alias pass ran before the new machine had enrolled; a machine launched
|
|
1004
|
+
in the same run is now waited for, and one that never enrolls gets no
|
|
1005
|
+
alias that run (the log says so).
|
|
1006
|
+
|
|
1007
|
+
### Raised by the code, not yet seen
|
|
1008
|
+
|
|
1009
|
+
Load (`ValueError`, logged at ERROR, `run`/`validate` exit 1):
|
|
1010
|
+
|
|
1011
|
+
```text
|
|
1012
|
+
No VPC ID specified in networking configuration for AWS cloud builder <name>, and no default VPC found in AWS account. ...
|
|
1013
|
+
VPC ID <vpc> specified in networking configuration for AWS cloud builder <name> not found in AWS account.
|
|
1014
|
+
Subnet <id> (<name>) in networking configuration for AWS cloud builder <runtime> is not in VPC <vpc>.
|
|
1015
|
+
Security group ID <sg> specified in networking configuration for AWS cloud builder <name> not found in AWS account.
|
|
1016
|
+
Total number of security groups specified in networking configuration for AWS cloud builder <name> is <n>, which may exceed limits for certain instance types. ...
|
|
1017
|
+
AWS Error: <botocore message>
|
|
1018
|
+
Networking configuration name cannot be empty for <model>.
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
The subnet line (stage 63) means a declared `subnets[].subnet_id` is not one
|
|
1022
|
+
of the subnets the account lists for the declared VPC: a typo, a subnet from
|
|
1023
|
+
another VPC or account, or a `network: default` that resolved to a VPC other
|
|
1024
|
+
than the one the subnets belong to. Fix the subnet id or the `network` in the
|
|
1025
|
+
tree. Before 2026-09-25 this passed the load and surfaced at the first bake
|
|
1026
|
+
or apply as a packer or tofu error about the subnet. The security-group
|
|
1027
|
+
count line counts only `addl_security_groups` since stage 63.
|
|
1028
|
+
|
|
1029
|
+
Declaring `networking.security_group_ids` fails the load as an unknown key
|
|
1030
|
+
(stage 63), with the shared model config's extra-key error naming
|
|
1031
|
+
`security_group_ids`. Until 2026-09-25 the field was accepted, checked and
|
|
1032
|
+
counted but never emitted; move its ids into `addl_security_groups`, which
|
|
1033
|
+
is the list that reaches the instance and storage roots.
|
|
1034
|
+
|
|
1035
|
+
`AWS Error:` wraps every `BotoCoreError`/`ClientError` of the discovery: an
|
|
1036
|
+
unknown profile (`The config profile (<p>) could not be found`), an expired
|
|
1037
|
+
token, a role without the four describe permissions
|
|
1038
|
+
(`UnauthorizedOperation`), no network. The message names the cause; the fix
|
|
1039
|
+
is on the credential side, never in the tree. The warning
|
|
1040
|
+
`No networking configuration provided for AWS cloud builder <name>` is not
|
|
1041
|
+
fatal at load but the image generation that follows raises on the missing
|
|
1042
|
+
VPC.
|
|
1043
|
+
|
|
1044
|
+
Resolution:
|
|
1045
|
+
|
|
1046
|
+
```text
|
|
1047
|
+
Failed to create EC2 client: <e> (AMIQueryError)
|
|
1048
|
+
Failed to query AMI with filters: <e> (AMIQueryError: a describe_images refusal, e.g. an invalid filter name)
|
|
1049
|
+
Region must be specified in builder config to resolve image identifier, but got None
|
|
1050
|
+
Could not get owner alias or owner id for image <ami> in region <region>, got <dict>
|
|
1051
|
+
No resolved Image identifiers for OS <os builder> in predefined_resolve (base: the query matched nothing)
|
|
1052
|
+
Failed to query AMI by ID: <e> (AMIQueryError: a DescribeImages refusal other than NotFound/Malformed, for a pinned image_id)
|
|
1053
|
+
OS builder <name>: image_id '<id>' on runtime <rt> is not known to the provider (base, stage 63: the pinned AMI is not in this region, is deregistered, or is malformed)
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
The `No resolved Image identifiers` line is the usual symptom of a vendor query that is too narrow, an
|
|
1057
|
+
owner id that is wrong, or a vendor image that went away; the request that
|
|
1058
|
+
was sent is logged at DEBUG (`Querying for AMI with filters: ...`) and the
|
|
1059
|
+
count found at INFO.
|
|
1060
|
+
|
|
1061
|
+
Generation:
|
|
1062
|
+
|
|
1063
|
+
```text
|
|
1064
|
+
AWS runtime <name>: unknown session mechanism '<value>' (supported: ssm)
|
|
1065
|
+
Provider-specific image for image <image> on runtime <rt> must have query assets ... (AssertionError)
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
Instance operations (`RuntimeError` unless noted; the caller records or
|
|
1069
|
+
reports it):
|
|
1070
|
+
|
|
1071
|
+
```text
|
|
1072
|
+
AWS runtime <name>: cannot run a command on '<instance>': it is STOPPED (switched off; not drift)
|
|
1073
|
+
AWS runtime <name>: cannot run a command on '<instance>': it is absent (no such machine)
|
|
1074
|
+
AWS runtime <name>: cannot run a command on '<instance>': it is power state unavailable (no runtime could answer)
|
|
1075
|
+
AWS runtime <name>: no instance named '<instance>' to start
|
|
1076
|
+
AWS runtime <name>: no instance named '<instance>' to stop
|
|
1077
|
+
ssm command <id> still <status> after <timeout>s (returned as exit 1 with this text, not raised)
|
|
1078
|
+
```
|
|
1079
|
+
|
|
1080
|
+
An SSM `send_command` on a running instance whose agent is not registered
|
|
1081
|
+
raises botocore's `InvalidInstanceId`; a machine that exists but cannot be
|
|
1082
|
+
reached shows as `TargetNotConnected` in the verification evidence. The EC2
|
|
1083
|
+
waiters raise `WaiterError` when a start or stop exceeds `timeout`;
|
|
1084
|
+
`running_for_task` then logs the machine as started but unreachable and
|
|
1085
|
+
skips the work, and a failed stop is logged at ERROR with the instruction to
|
|
1086
|
+
stop it by hand. An unrecognised `State.Name` is logged
|
|
1087
|
+
(`reports unrecognised state '<raw>'; treating it as unknown`) and answered
|
|
1088
|
+
as `unknown`.
|
|
1089
|
+
|
|
1090
|
+
Verification (`verify_instance`; the check's `detail` in
|
|
1091
|
+
`meta-state/verifications.yaml`):
|
|
1092
|
+
|
|
1093
|
+
```text
|
|
1094
|
+
no completion marker within <timeout>s
|
|
1095
|
+
booted <ami>, expected <build>
|
|
1096
|
+
<n> mount(s) under /mnt, <m> declared
|
|
1097
|
+
```
|
|
1098
|
+
|
|
1099
|
+
Disposal and tags: `AMI <id> not found; nothing to deregister` is a
|
|
1100
|
+
warning and returns `False` (the record is still dropped by the caller);
|
|
1101
|
+
any other `describe_images`, `deregister_image` or `delete_snapshot`
|
|
1102
|
+
refusal raises and stops `dispose image` with nothing recorded. A
|
|
1103
|
+
`create_tags` refusal in `retag_image` is caught by `lineage relabel`
|
|
1104
|
+
(`could not retag <id>`) and the state query keeps showing `changed`; in
|
|
1105
|
+
the packer plugin's post-bake retag it propagates.
|
|
1106
|
+
|
|
1107
|
+
The state query and the read-only probes never fail a run on this plugin's
|
|
1108
|
+
account: `query_images` raising becomes
|
|
1109
|
+
`state query images/<rt> unavailable: <e>` and an `unavailable:` line;
|
|
1110
|
+
`query_instance_boot_image`, `query_instance_power_state` and
|
|
1111
|
+
`query_instance_identity` swallow every exception into a DEBUG line and
|
|
1112
|
+
answer `None` (no claim).
|
|
1113
|
+
|
|
1114
|
+
Release: the runner script runs `aws ec2 create-tags ... --profile <p>`
|
|
1115
|
+
under `set -euo pipefail`; a missing `aws` binary, a profile without a
|
|
1116
|
+
session or a role without `ec2:CreateTags` fails the release lifecycle's
|
|
1117
|
+
apply (`failed` in `run-summary.json`) after meta-state already recorded
|
|
1118
|
+
the release. Re-run `run release` once the cause is fixed; the command is
|
|
1119
|
+
idempotent.
|
|
1120
|
+
|
|
1121
|
+
`empty --runtime <aws runtime>` prints
|
|
1122
|
+
`empty: AwsCloudBuilder cannot list its inventory` and exits 2; the
|
|
1123
|
+
runtime cannot be proved empty this way.
|
|
1124
|
+
|
|
1125
|
+
Tool check (`validate` and every run, exit 1):
|
|
1126
|
+
|
|
1127
|
+
```text
|
|
1128
|
+
aws-cli: binary 'aws' not found (declared in cfg/executables.yml; an absolute path, or a name on PATH)
|
|
1129
|
+
aws-cli: could not read its version (`aws --version`): <e>
|
|
1130
|
+
aws-cli: AwsCLIVersionChecker could not parse a version from `aws --version`
|
|
1131
|
+
```
|
|
1132
|
+
|
|
1133
|
+
and the base's version-floor message naming `aws-cli` and the requirement
|
|
1134
|
+
when the installed version is below it.
|
|
1135
|
+
|
|
1136
|
+
## Related
|
|
1137
|
+
|
|
1138
|
+
- [gcloud-runtime-plugin](../gcloud-runtime-plugin/README.md): the other runtime type, with the same hook surface on Compute Engine.
|
|
1139
|
+
- [default-os-plugin](../default-os-plugin/README.md): the OS builders whose runtime entries this plugin resolves to AMIs.
|
|
1140
|
+
- [packer-plugin](../packer-plugin/README.md): the image builder that asks this plugin for the `amazon-ebs` source and the session provisioners.
|
|
1141
|
+
- [tf-ebs-instance-plugin](../tf-ebs-instance-plugin/README.md): the instance and storage roots that read this model's networking, profile and session profile.
|
|
1142
|
+
- [dummy-plugin](../dummy-plugin/README.md): the extension template; an unregistered copy of its group model sits at the bottom of this package's models module.
|
|
1143
|
+
- Base classes: [runtime.py](../base/src/cs_image_system/base/models/runtime.py), [cloud_builder.py](../base/src/cs_image_system/base/models/cloud_builder.py), [credentials.py](../base/src/cs_image_system/base/models/credentials.py), [provider_specific_image.py](../base/src/cs_image_system/base/models/provider_specific_image.py), [builder_base_runtime.py](../base/src/cs_image_system/base/basic/builder_base_runtime.py), [abstract_version_checker.py](../base/src/cs_image_system/base/basic/abstract_version_checker.py).
|
|
1144
|
+
- Consumers of the hooks: [state_query.py](../base/src/cs_image_system/base/state_query.py), [retention.py](../base/src/cs_image_system/base/retention.py), [dispose.py](../base/src/cs_image_system/base/commands/dispose.py), [verify_instance.py](../base/src/cs_image_system/base/commands/verify_instance.py), [unmount.py](../base/src/cs_image_system/base/commands/unmount.py), [relabel.py](../base/src/cs_image_system/base/commands/relabel.py), [release.py](../base/src/cs_image_system/base/release.py), [power_state.py](../base/src/cs_image_system/base/power_state.py), [provider_aliases.py](../base/src/cs_image_system/base/provider_aliases.py), [runtime_facts.py](../base/src/cs_image_system/base/commands/runtime_facts.py).
|
|
1145
|
+
- [docs/OPERATIONS.md](../../docs/OPERATIONS.md) for the credential contract and the operator's cycles; [docs/DESIGN.md](../../docs/DESIGN.md) for the design; [docs/PLUGINS.md](../../docs/PLUGINS.md) for the package index.
|
|
1146
|
+
- [The configuration reference](../../docs/CONFIGURATION.md) -- every field of the YAML this plugin reads, with an example.
|