Publish AMI artifact IDs to SSM

This commit is contained in:
Timothy J. Aveni
2026-06-28 17:41:21 -07:00
parent 70797543e4
commit 30d6ba2332
3 changed files with 109 additions and 6 deletions
+15
View File
@@ -58,6 +58,14 @@ The final AMI is tagged with:
- `NixSnapshotId`
- `NixosSystemClosure`
The publisher also writes SSM parameters that point to the AMI ID:
- `/quixos/amis/<name>/<architecture>/recommended`
- `/quixos/amis/<name>/<architecture>/by-builder-rev/<rev>`
- `/quixos/amis/<name>/<architecture>/by-input-hash/<hash>`
Central services read the `recommended` parameter by default.
Do not deregister the AMI or delete either snapshot while runtime deployments
use it.
@@ -69,6 +77,13 @@ List published AMIs and cross-reference qx-deploy runtime registry records:
nix run ./tofu/ami-builder#ami-artifacts -- list
```
Publish SSM parameters for an existing AMI, useful for AMIs created before SSM
publication was added:
```bash
nix run ./tofu/ami-builder#ami-artifacts -- publish-ssm --ami-id ami-...
```
Delete an unused AMI and its root and `/nix` snapshots:
```bash
+76 -5
View File
@@ -10,14 +10,15 @@ const repoRoot = process.env.QUIXOS_REPO_ROOT ?? path.resolve(sourceScriptDir, "
const amiBuilderDir = process.env.QUIXOS_AMI_BUILDER_DIR ?? path.resolve(repoRoot, "tofu/ami-builder");
const centralDir = path.resolve(amiBuilderDir, "../central");
const usage = `Usage: nix run ./tofu/ami-builder#ami-artifacts -- <list|delete> [options]
const usage = `Usage: nix run ./tofu/ami-builder#ami-artifacts -- <list|delete|publish-ssm> [options]
Commands:
list List published AMI artifacts and deployment references
delete --ami-id <ami> Deregister an AMI and delete its root and /nix snapshots
list List published AMI artifacts and deployment references
delete --ami-id <ami> Deregister an AMI and delete its root and /nix snapshots
publish-ssm --ami-id <ami> Publish an existing AMI into the SSM artifact registry
Options:
--ami-id <ami> AMI to delete
--ami-id <ami> AMI to operate on
--aws-region <region> AWS region (default: AWS_REGION or us-west-2)
--name <name> AMI project tag (default: nixos-zfs-ec2)
--architecture <arm64|x86_64> Filter by architecture
@@ -62,7 +63,7 @@ const parseArgs = (argv) => {
console.error(usage.trimEnd());
process.exit(command ? 0 : 1);
}
if (command !== "list" && command !== "delete") {
if (command !== "list" && command !== "delete" && command !== "publish-ssm") {
throw new Error(`Unknown command: ${command}\n\n${usage}`);
}
@@ -287,6 +288,72 @@ const deleteArtifact = (state) => {
}
};
const getImage = (state) => {
const result = awsJson([
"ec2",
"describe-images",
"--region",
state.awsRegion,
"--owners",
"self",
"--image-ids",
state.amiId,
]);
const image = result?.Images?.[0];
if (!image) {
throw new Error(`AMI not found: ${state.amiId}`);
}
return image;
};
const putSsmParameter = (state, name, value) => {
run("aws", [
"ssm",
"put-parameter",
"--region",
state.awsRegion,
"--name",
name,
"--type",
"String",
"--value",
value,
"--overwrite",
]);
};
const publishSsmArtifact = (state) => {
if (!state.amiId) {
throw new Error("Missing --ami-id");
}
const image = getImage(state);
const tags = amiTags(image);
const architecture = tags.Architecture ?? image.Architecture ?? "";
const builderGitRev = tags.BuilderGitRev ?? "";
const inputHash = tags.InputHash ?? "";
if (!architecture) {
throw new Error(`AMI ${state.amiId} is missing Architecture metadata`);
}
if (!builderGitRev) {
throw new Error(`AMI ${state.amiId} is missing BuilderGitRev tag`);
}
if (!inputHash) {
throw new Error(`AMI ${state.amiId} is missing InputHash tag`);
}
const prefix = `/quixos/amis/${state.name}/${architecture}`;
const parameters = [
`${prefix}/recommended`,
`${prefix}/by-builder-rev/${builderGitRev}`,
`${prefix}/by-input-hash/${inputHash}`,
];
for (const parameter of parameters) {
console.error(`Writing ${parameter} = ${state.amiId}`);
putSsmParameter(state, parameter, state.amiId);
}
};
const main = () => {
const state = parseArgs(process.argv.slice(2));
requireCommand("aws");
@@ -296,6 +363,10 @@ const main = () => {
listArtifacts(state);
return;
}
if (state.command === "publish-ssm") {
publishSsmArtifact(state);
return;
}
deleteArtifact(state);
};
+18 -1
View File
@@ -271,6 +271,20 @@ aws ec2 create-tags \
"Key=RootSnapshotId,Value=${root_snapshot_id}" \
"Key=NixSnapshotId,Value=${nix_snapshot_id}"
ssm_ami_prefix="/quixos/amis/${name}/${architecture}"
for ssm_ami_parameter_name in \
"${ssm_ami_prefix}/recommended" \
"${ssm_ami_prefix}/by-builder-rev/${builder_git_rev}" \
"${ssm_ami_prefix}/by-input-hash/${input_hash}"
do
aws ssm put-parameter \
--region "$aws_region" \
--name "$ssm_ami_parameter_name" \
--type String \
--value "$image_id" \
--overwrite >/dev/null
done
aws ec2 deregister-image --region "$aws_region" --image-id "$capture_image_id" >/dev/null
jq -n \
@@ -285,6 +299,7 @@ jq -n \
--arg rootDeviceName "$root_device_name" \
--arg nixDeviceName "$nix_device_name" \
--arg nixPool "$nix_pool" \
--arg ssmRecommendedParameter "${ssm_ami_prefix}/recommended" \
'{
architecture: $architecture,
system: $system,
@@ -296,7 +311,8 @@ jq -n \
nixSnapshotId: $nixSnapshotId,
rootDeviceName: $rootDeviceName,
nixDeviceName: $nixDeviceName,
nixPool: $nixPool
nixPool: $nixPool,
ssmRecommendedParameter: $ssmRecommendedParameter
}' > artifact.json
if [ "$keep_builder" = false ]; then
@@ -313,3 +329,4 @@ if [ "$keep_builder" = false ]; then
fi
echo "Published AMI: $image_id"
echo "Published SSM parameter: ${ssm_ami_prefix}/recommended"