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` - `NixSnapshotId`
- `NixosSystemClosure` - `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 Do not deregister the AMI or delete either snapshot while runtime deployments
use it. 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 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: Delete an unused AMI and its root and `/nix` snapshots:
```bash ```bash
+74 -3
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 amiBuilderDir = process.env.QUIXOS_AMI_BUILDER_DIR ?? path.resolve(repoRoot, "tofu/ami-builder");
const centralDir = path.resolve(amiBuilderDir, "../central"); 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: Commands:
list List published AMI artifacts and deployment references list List published AMI artifacts and deployment references
delete --ami-id <ami> Deregister an AMI and delete its root and /nix snapshots 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: 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) --aws-region <region> AWS region (default: AWS_REGION or us-west-2)
--name <name> AMI project tag (default: nixos-zfs-ec2) --name <name> AMI project tag (default: nixos-zfs-ec2)
--architecture <arm64|x86_64> Filter by architecture --architecture <arm64|x86_64> Filter by architecture
@@ -62,7 +63,7 @@ const parseArgs = (argv) => {
console.error(usage.trimEnd()); console.error(usage.trimEnd());
process.exit(command ? 0 : 1); 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}`); 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 main = () => {
const state = parseArgs(process.argv.slice(2)); const state = parseArgs(process.argv.slice(2));
requireCommand("aws"); requireCommand("aws");
@@ -296,6 +363,10 @@ const main = () => {
listArtifacts(state); listArtifacts(state);
return; return;
} }
if (state.command === "publish-ssm") {
publishSsmArtifact(state);
return;
}
deleteArtifact(state); deleteArtifact(state);
}; };
+18 -1
View File
@@ -271,6 +271,20 @@ aws ec2 create-tags \
"Key=RootSnapshotId,Value=${root_snapshot_id}" \ "Key=RootSnapshotId,Value=${root_snapshot_id}" \
"Key=NixSnapshotId,Value=${nix_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 aws ec2 deregister-image --region "$aws_region" --image-id "$capture_image_id" >/dev/null
jq -n \ jq -n \
@@ -285,6 +299,7 @@ jq -n \
--arg rootDeviceName "$root_device_name" \ --arg rootDeviceName "$root_device_name" \
--arg nixDeviceName "$nix_device_name" \ --arg nixDeviceName "$nix_device_name" \
--arg nixPool "$nix_pool" \ --arg nixPool "$nix_pool" \
--arg ssmRecommendedParameter "${ssm_ami_prefix}/recommended" \
'{ '{
architecture: $architecture, architecture: $architecture,
system: $system, system: $system,
@@ -296,7 +311,8 @@ jq -n \
nixSnapshotId: $nixSnapshotId, nixSnapshotId: $nixSnapshotId,
rootDeviceName: $rootDeviceName, rootDeviceName: $rootDeviceName,
nixDeviceName: $nixDeviceName, nixDeviceName: $nixDeviceName,
nixPool: $nixPool nixPool: $nixPool,
ssmRecommendedParameter: $ssmRecommendedParameter
}' > artifact.json }' > artifact.json
if [ "$keep_builder" = false ]; then if [ "$keep_builder" = false ]; then
@@ -313,3 +329,4 @@ if [ "$keep_builder" = false ]; then
fi fi
echo "Published AMI: $image_id" echo "Published AMI: $image_id"
echo "Published SSM parameter: ${ssm_ami_prefix}/recommended"