diff --git a/Makefile b/Makefile index b21c3c053..634790562 100644 --- a/Makefile +++ b/Makefile @@ -170,6 +170,7 @@ install-system: -o Dpkg::Options::="--force-confdef" \ -o Dpkg::Options::="--force-confold" \ nbdkit nbdkit-plugin-dev pkg-config $(QEMU_PACKAGES) qemu-utils qemu-kvm \ + ovmf qemu-efi-aarch64 \ libvirt-daemon-system libvirt-clients libvirt-dev make gcc jq curl \ iproute2 netcat-openbsd openssh-client wget git unzip sudo xz-utils file \ ovn-central ovn-host openvswitch-switch dhcpcd-base diff --git a/cmd/spinifex/cmd/admin.go b/cmd/spinifex/cmd/admin.go index f220a2644..34bef4be4 100644 --- a/cmd/spinifex/cmd/admin.go +++ b/cmd/spinifex/cmd/admin.go @@ -84,6 +84,14 @@ var supportedPlatforms = map[string]bool{ "Windows": true, } +// Mirrors the gateway RegisterImage allowlist so admin imports can't write an +// AMI with a boot mode that RegisterImage would reject. +var supportedBootModes = map[string]bool{ + "bios": true, + "uefi": true, + "uefi-preferred": true, +} + var adminCmd = &cobra.Command{ Use: "admin", Short: "Administrative commands for Spinifex platform management", @@ -304,6 +312,7 @@ func init() { imagesImportCmd.Flags().String("version", "", "Specified distro version (e.g 12)") imagesImportCmd.Flags().String("arch", "", "Specified distro arch (e.g aarch64, arm64, x86_64)") imagesImportCmd.Flags().String("platform", "Linux/UNIX", "Specified platform (e.g Linux/UNIX, Windows)") + imagesImportCmd.Flags().String("boot-mode", "", "Boot mode for the imported AMI (bios|uefi|uefi-preferred). Required with --file. Overrides the catalog value when used with --name.") imagesImportCmd.Flags().StringSlice("tag", nil, "Tag to apply to the imported AMI as key=value (repeatable; e.g. --tag spinifex:managed-by=elbv2)") imagesImportCmd.Flags().Bool("force", false, "Force command execution (overwrites existing files)") imagesImportCmd.Flags().Bool("skip-verify", false, "Skip catalog-image checksum verification (INSECURE; operator assumes integrity responsibility)") @@ -376,6 +385,22 @@ func runimagesImportCmd(cmd *cobra.Command, args []string) { } } + // --file imports have no catalog metadata to inherit from, so the operator + // must declare the boot mode explicitly — guessing would silently produce + // a BIOS AMI from a UEFI-only image (or vice versa) and fail at launch. + // --name imports inherit from the catalog; the flag overrides when set. + bootModeFlag, _ := cmd.Flags().GetString("boot-mode") + if bootModeFlag != "" { + if !supportedBootModes[bootModeFlag] { + fmt.Fprintf(os.Stderr, "Unsupported --boot-mode %q (expected bios|uefi|uefi-preferred)\n", bootModeFlag) + os.Exit(1) + } + image.BootMode = bootModeFlag + } else if imageName == "" { + fmt.Fprintf(os.Stderr, "--boot-mode is required when importing via --file (expected bios|uefi|uefi-preferred)\n") + os.Exit(1) + } + // --tag k=v (repeatable) merges user-supplied tags into the AMI. Overrides // catalog tags on key collision so operators can re-tag a known image. tagFlags, _ := cmd.Flags().GetStringSlice("tag") diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 8002643a9..4d45dbd45 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -89,7 +89,7 @@ Operational commands for inspecting cluster state. These fan out NATS requests t | Command | Flags | Prerequisites | Basic Logic | Test Cases | Status | |---------|-------|---------------|-------------|------------|--------| -| `spx admin images import` | `--name`, `--file`, `--force`, `--skip-verify` | Cluster must be running; either `--name` (catalog download) or `--file` (operator-supplied media) | Catalog imports (`--name`) download the image, fetch the catalog `Checksum` URL, and verify the SHA-256/SHA-512 digest before extraction. Mismatch fails closed; the cached file is left on disk and `--force` re-downloads. `--file` imports skip verification — operator-supplied media is outside Spinifex's trust boundary, and the skip is logged at INFO for audit. `--skip-verify` bypasses verification for catalog imports and emits a WARN slog + stderr notice; use only for debugging or when upstream mirrors are confirmed-broken. | 1. Import valid catalog image (verifies checksum)
2. Tampered cache hit fails with `ErrChecksumMismatch`
3. `--force` recovers after a mismatch
4. `--file` import skips verification
5. `--skip-verify` bypasses checksum with WARN | **DONE** | +| `spx admin images import` | `--name`, `--file`, `--distro`, `--version`, `--arch`, `--platform`, `--boot-mode` (bios/uefi/uefi-preferred), `--tag`, `--force`, `--skip-verify` | Cluster must be running; either `--name` (catalog download) or `--file` (operator-supplied media). `--file` mode additionally requires `--distro`, `--version`, `--arch`, `--boot-mode`. | Catalog imports (`--name`) download the image, fetch the catalog `Checksum` URL, verify the SHA-256/SHA-512 digest, and inherit `BootMode` from the catalog entry. `--boot-mode` overrides the catalog value when set. Mismatch fails closed; the cached file is left on disk and `--force` re-downloads. `--file` imports skip checksum verification (operator-supplied media is outside Spinifex's trust boundary, the skip is logged at INFO for audit) and require an explicit `--boot-mode` because there is no catalog metadata to inherit from. `--skip-verify` bypasses verification for catalog imports and emits a WARN slog + stderr notice; use only for debugging or when upstream mirrors are confirmed-broken. | 1. Import valid catalog image (verifies checksum)
2. Tampered cache hit fails with `ErrChecksumMismatch`
3. `--force` recovers after a mismatch
4. `--file` import skips verification
5. `--skip-verify` bypasses checksum with WARN
6. `--file` without `--boot-mode` rejected
7. `--boot-mode=invalid` rejected | **DONE** | | `spx admin images list` | — | None | Lists available OS images that can be imported or downloaded | 1. List available images | **DONE** | #### Image integrity verification (CMMC SI.L1-3.14.2) @@ -168,7 +168,7 @@ file, deferred to a later phase. |---------|-------------------|---------------|---------------|-------------|------------|--------| | `describe-images` | `--image-ids` (format validation only), `--owners` (self, account ID, alias), `--filters` (name, state, architecture, image-id, is-public, owner-id, description, image-type, tag:\*) | `--executable-users`, `--include-deprecated`, `--include-disabled`, `--max-results`, `--next-token`, `--dry-run` | None | NATS `ec2.DescribeImages` → daemon reads AMI metadata from Predastore S3 buckets (ami-*) → filters by ImageIds, Owners, and Filters → returns image list with state, architecture, block device mappings | 1. List all images
2. Filter by image ID
3. Filter by owner (self/amazon)
4. Non-existent AMI returns empty
5. Verify metadata fields (architecture, state, rootDeviceName)
6. Filter by name wildcard
7. Unknown filter returns InvalidParameterValue | **DONE** | | `create-image` | `--instance-id`, `--name`, `--description`, `--tag-specifications` | `--no-reboot`, `--block-device-mappings`, `--dry-run` | Instance must exist (running or stopped) | Gateway validates input → NATS `ec2.{instanceId}.CreateImage` (per-instance topic) → daemon extracts root volume → snapshots via `ebs.snapshot` NATS (running) or offline S3 copy (stopped) → creates AMI metadata in S3 (`{amiId}/config.json`) → stores tags → returns ami-ID. Duplicate AMI name validation enforced. | 1. Create image from running instance
2. Create image from stopped instance
3. Invalid instance ID (error)
4. Duplicate AMI name (error)
5. Verify new AMI appears in describe-images
6. Launch new instance from created AMI | **DONE** | -| `register-image` | `--name`, `--description`, `--architecture` (x86_64/arm64/i386), `--root-device-name`, `--virtualization-type` (hvm only), `--block-device-mappings` (root with `Ebs.SnapshotId`+optional `VolumeSize`), `--tag-specifications` | `--billing-products`, `--uefi-data` | Backing snapshot must exist in Predastore (`{snapshotId}/metadata.json`); caller must own snapshot or it must be system-owned | Gateway validates name length (3–128), `snap-` prefix, architecture/virtualization values → NATS `ec2.RegisterImage` → daemon checks AMI name uniqueness, reads snapshot metadata, verifies snapshot ownership, builds `viperblock.AMIMetadata` (defaults: `Architecture=x86_64`, `Virtualization=hvm`, `PlatformDetails=Linux/UNIX`, `RootDeviceType=ebs`), writes `{amiId}/config.json`. Pointer-only — never touches block data. `BootMode`, `KernelId`, `RamdiskId`, `TpmSupport`, `ImdsSupport`, `EnaSupport`, `SriovNetSupport`, `ImageLocation`, `paravirtual` virtualization rejected with `InvalidParameterValue`. `VolumeSize` smaller than snapshot rejected. | 1. Register with valid snapshot
2. Missing name/snapshot (error)
3. Duplicate AMI name (`InvalidAMIName.Duplicate`)
4. Snapshot not found (`InvalidSnapshot.NotFound`)
5. Cross-account snapshot (`UnauthorizedOperation`)
6. Tags from `TagSpecifications` persisted
7. Verify registered image in describe-images | **DONE** | +| `register-image` | `--name`, `--description`, `--architecture` (x86_64/arm64/i386), `--root-device-name`, `--virtualization-type` (hvm only), `--boot-mode` (bios/uefi/uefi-preferred), `--block-device-mappings` (root with `Ebs.SnapshotId`+optional `VolumeSize`), `--tag-specifications` | `--billing-products`, `--uefi-data` | Backing snapshot must exist in Predastore (`{snapshotId}/metadata.json`); caller must own snapshot or it must be system-owned | Gateway validates name length (3–128), `snap-` prefix, architecture/virtualization/boot-mode values → NATS `ec2.RegisterImage` → daemon checks AMI name uniqueness, reads snapshot metadata, verifies snapshot ownership, builds `viperblock.AMIMetadata` (defaults: `Architecture=x86_64`, `Virtualization=hvm`, `PlatformDetails=Linux/UNIX`, `RootDeviceType=ebs`), writes `{amiId}/config.json`. Pointer-only — never touches block data. `BootMode` outside `bios|uefi|uefi-preferred`, `KernelId`, `RamdiskId`, `TpmSupport`, `ImdsSupport`, `EnaSupport`, `SriovNetSupport`, `ImageLocation`, `paravirtual` virtualization rejected with `InvalidParameterValue`. `uefi-preferred` is treated as `uefi` at launch. `VolumeSize` smaller than snapshot rejected. | 1. Register with valid snapshot
2. Missing name/snapshot (error)
3. Duplicate AMI name (`InvalidAMIName.Duplicate`)
4. Snapshot not found (`InvalidSnapshot.NotFound`)
5. Cross-account snapshot (`UnauthorizedOperation`)
6. Tags from `TagSpecifications` persisted
7. Verify registered image in describe-images
8. `--boot-mode=uefi` accepted, returned by `describe-images`
9. `--boot-mode=invalid` rejected (`InvalidParameterValue`) | **DONE** | | `deregister-image` | `--image-id` | `--dry-run` | AMI must exist; caller must own it (system AMIs immutable via this API) | Gateway validates `ami-` prefix → NATS `ec2.DeregisterImage` → daemon hard-deletes `{amiId}/config.json` from Predastore. Backing snapshot is left intact (matches AWS — operators run `delete-snapshot` separately to reclaim block storage). Cross-account/system-AMI mutations rejected with `UnauthorizedOperation`. Re-deregister returns `InvalidAMIID.NotFound` (no tombstone). | 1. Deregister existing AMI
2. Deregister non-existent AMI (`InvalidAMIID.NotFound`)
3. Re-deregister already-deleted AMI (`InvalidAMIID.NotFound`)
4. Cross-account AMI (`UnauthorizedOperation`)
5. System AMI (`UnauthorizedOperation`)
6. Verify deregistered AMI not in describe-images
7. Backing snapshot untouched | **DONE** | | `copy-image` | `--source-image-id`, `--source-region` (must equal gateway region), `--name`, `--description`, `--client-token` (accepted, not honoured), `--copy-image-tags`, `--tag-specifications` (image only) | `--encrypted`, `--kms-key-id`, `--destination-outpost-arn`, `--dry-run` | Source AMI must exist and be owned by caller OR be a system AMI; backing snapshot must still exist | Gateway validates `ami-` prefix, name length (3–128), same-region — cross-region / `Encrypted` / `KmsKeyId` / `DestinationOutpostArn` rejected with `InvalidParameterValue`. NATS `ec2.CopyImage` → daemon checks name uniqueness, reads source `ami-xxx/config.json`, visibility-filters cross-account sources as `InvalidAMIID.NotFound`, reads source `{snap-xxx}/metadata.json` (missing or empty SnapshotID → `InvalidAMIID.NotFound`), writes new `snap-yyy/metadata.json` that inherits source `VolumeID` (no block copy), writes new `ami-yyy/config.json` owned by caller. `Description` inherits from source when unset. Tag merge: `CopyImageTags=true` seeds tags from source, explicit image-resource `TagSpecifications` override colliding keys and append new ones; non-image tag specs ignored. | 1. Same-region copy produces new `ami-`/`snap-` pair with distinct IDs
2. New snap shares source `VolumeID` (metadata-only)
3. New AMI owned by caller, source untouched
4. System AMI copied into caller's account
5. Cross-account source (`InvalidAMIID.NotFound`)
6. Missing source AMI (`InvalidAMIID.NotFound`)
7. Orphaned source (missing/empty `SnapshotID`) → `InvalidAMIID.NotFound`
8. Duplicate name (`InvalidAMIName.Duplicate`)
9. Cross-region / `Encrypted` / `KmsKeyId` rejected (`InvalidParameterValue`)
10. `CopyImageTags` true/false tag inheritance semantics | **DONE** | | `import-image` | — | `--disk-containers` (Format, Url/S3Bucket+S3Key), `--description`, `--architecture`, `--platform` | S3 bucket with disk image | Download disk image from S3 → convert format (VMDK/VHD/RAW→QCOW2) → create viperblock volume → register as AMI | 1. Import QCOW2 image
2. Import RAW image
3. Invalid format (error)
4. Verify imported image launchable | **NOT STARTED** | diff --git a/docs/getting-started/install-airgapped/README.md b/docs/getting-started/install-airgapped/README.md index 196a766d1..98ae17a67 100644 --- a/docs/getting-started/install-airgapped/README.md +++ b/docs/getting-started/install-airgapped/README.md @@ -57,6 +57,7 @@ sudo apt update sudo apt install --download-only -y \ nbdkit nbdkit-plugin-dev pkg-config \ qemu-system-x86 qemu-utils qemu-kvm \ + ovmf qemu-efi-aarch64 \ libvirt-daemon-system libvirt-clients libvirt-dev \ ovn-central ovn-host openvswitch-switch \ dhcpcd-base make gcc jq curl iproute2 netcat-openbsd \ @@ -141,7 +142,7 @@ Register the pre-staged image with Spinifex: ```bash sudo spx admin images import --file /mnt/usb/images/debian-13-amd64.qcow2 \ - --distro debian --version 13 --arch x86_64 + --distro debian --version 13 --arch x86_64 --boot-mode uefi ``` ## Step 9. Verify @@ -183,4 +184,4 @@ sudo INSTALL_SPINIFEX_TARBALL=/mnt/usb/tarball/spinifex-...tar.gz \ ### Image Import Fails -`spx admin images import --file` requires the distro/version/arch flags so the image registers in the catalogue. If the import succeeds but `aws ec2 describe-images` returns empty, check `journalctl -u spinifex-daemon -f` for predastore upload errors. +`spx admin images import --file` requires the distro/version/arch/boot-mode flags so the image registers in the catalogue. If the import succeeds but `aws ec2 describe-images` returns empty, check `journalctl -u spinifex-daemon -f` for predastore upload errors. diff --git a/docs/getting-started/setting-up-your-cluster/README.md b/docs/getting-started/setting-up-your-cluster/README.md index 7c899e2aa..20a372d22 100644 --- a/docs/getting-started/setting-up-your-cluster/README.md +++ b/docs/getting-started/setting-up-your-cluster/README.md @@ -58,10 +58,10 @@ spx admin images list ``` NAME | DISTRO | VERSION | ARCH | BOOT -debian-13-arm64 | debian | 13 | arm64 | bios -debian-13-x86_64 | debian | 13 | x86_64 | bios -ubuntu-26.04-arm64 | ubuntu | 26.04 | arm64 | bios -ubuntu-26.04-x86_64 | ubuntu | 26.04 | x86_64 | bios +debian-13-arm64 | debian | 13 | arm64 | uefi +debian-13-x86_64 | debian | 13 | x86_64 | uefi +ubuntu-26.04-arm64 | ubuntu | 26.04 | arm64 | uefi +ubuntu-26.04-x86_64 | ubuntu | 26.04 | x86_64 | uefi ``` Import an image: @@ -73,7 +73,7 @@ spx admin images import --name ubuntu-26.04-x86_64 Or import a local image file: ```bash -spx admin images import --file ~/images/ubuntu-26.04.img --distro ubuntu --version 26.04 --arch x86_64 +spx admin images import --file ~/images/ubuntu-26.04.img --distro ubuntu --version 26.04 --arch x86_64 --boot-mode uefi ``` Verify the import and note the AMI ID: diff --git a/scripts/ansible/roles/smoketest/tasks/main.yml b/scripts/ansible/roles/smoketest/tasks/main.yml index e30bcb0b4..ea0d62719 100644 --- a/scripts/ansible/roles/smoketest/tasks/main.yml +++ b/scripts/ansible/roles/smoketest/tasks/main.yml @@ -61,6 +61,7 @@ --distro {{ spinifex_smoketest_distro }} --version {{ spinifex_smoketest_version }} --arch {{ smoketest_arch }} + --boot-mode uefi register: smoketest_import_local until: >- smoketest_import_local.rc == 0 or diff --git a/scripts/build-system-image.sh b/scripts/build-system-image.sh index c55c2e819..8005ddd4d 100755 --- a/scripts/build-system-image.sh +++ b/scripts/build-system-image.sh @@ -75,6 +75,7 @@ case "$DISTRO" in OUTPUT_RAW="${BUILD_DIR}/${IMAGE_NAME}-alpine.raw" DISTRO_VERSION="${ALPINE_VERSION}" ROOT_PART="${NBD_DEV}" + BOOT_MODE="bios" ;; ubuntu) if [[ -z "${UBUNTU_VERSION:-}" ]]; then @@ -87,6 +88,7 @@ case "$DISTRO" in OUTPUT_RAW="${BUILD_DIR}/${IMAGE_NAME}-ubuntu.raw" DISTRO_VERSION="${UBUNTU_VERSION}" ROOT_PART="${NBD_DEV}p1" + BOOT_MODE="uefi" ;; *) echo "ERROR: Unknown DISTRO: $DISTRO (supported: alpine, ubuntu)" @@ -189,7 +191,7 @@ if [[ -f "$OUTPUT_RAW" ]] && [[ $(( $(date +%s) - $(stat -c %Y "$OUTPUT_RAW") )) if [[ "$DO_IMPORT" == true ]]; then echo "Importing as AMI..." rm -f "$OUTPUT_IMAGE" - IMPORT_ARGS=(--file "$OUTPUT_RAW" --distro "${DISTRO}" --version "${DISTRO_VERSION}" --arch x86_64) + IMPORT_ARGS=(--file "$OUTPUT_RAW" --distro "${DISTRO}" --version "${DISTRO_VERSION}" --arch x86_64 --boot-mode "${BOOT_MODE}") if [[ -n "${SYSTEM_TAG:-}" ]]; then IMPORT_ARGS+=(--tag "$SYSTEM_TAG") fi @@ -423,7 +425,7 @@ echo "" if [[ "$DO_IMPORT" == true ]]; then echo "Importing as AMI..." rm -f "$OUTPUT_IMAGE" - IMPORT_ARGS=(--file "$OUTPUT_RAW" --distro "${DISTRO}" --version "${DISTRO_VERSION}" --arch x86_64) + IMPORT_ARGS=(--file "$OUTPUT_RAW" --distro "${DISTRO}" --version "${DISTRO_VERSION}" --arch x86_64 --boot-mode "${BOOT_MODE}") if [[ -n "${SYSTEM_TAG:-}" ]]; then IMPORT_ARGS+=(--tag "$SYSTEM_TAG") fi @@ -433,9 +435,9 @@ else echo " spx admin images import \\" echo " --file $OUTPUT_RAW \\" if [[ -n "${SYSTEM_TAG:-}" ]]; then - echo " --distro ${DISTRO} --version ${DISTRO_VERSION} --arch x86_64 \\" + echo " --distro ${DISTRO} --version ${DISTRO_VERSION} --arch x86_64 --boot-mode ${BOOT_MODE} \\" echo " --tag ${SYSTEM_TAG}" else - echo " --distro ${DISTRO} --version ${DISTRO_VERSION} --arch x86_64" + echo " --distro ${DISTRO} --version ${DISTRO_VERSION} --arch x86_64 --boot-mode ${BOOT_MODE}" fi fi diff --git a/scripts/setup.sh b/scripts/setup.sh index e734b7a28..9abeef2bd 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -214,7 +214,7 @@ install_apt_deps() { DEBIAN_FRONTEND=noninteractive $SUDO apt-get install -y -qq \ nbdkit \ - $QEMU_PACKAGES qemu-utils qemu-kvm ovmf less \ + $QEMU_PACKAGES qemu-utils qemu-kvm ovmf qemu-efi-aarch64 less \ libvirt-daemon-system libvirt-clients \ pciutils \ jq curl iproute2 netcat-openbsd wget unzip xz-utils file \ diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index 2217fc9ac..83e1bb8c1 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -126,7 +126,7 @@ else if [ -f "$LOCAL_IMAGE" ]; then echo " Using local image: $LOCAL_IMAGE" sudo /usr/local/bin/spx admin images import \ - --file "$LOCAL_IMAGE" --distro ubuntu --version 26.04 --arch "$IMG_ARCH" + --file "$LOCAL_IMAGE" --distro ubuntu --version 26.04 --arch "$IMG_ARCH" --boot-mode uefi else echo " Downloading image: $IMAGE_NAME" sudo /usr/local/bin/spx admin images import --name "$IMAGE_NAME" diff --git a/spinifex/gateway/ec2/image/RegisterImage.go b/spinifex/gateway/ec2/image/RegisterImage.go index 5f221c15f..7870056e7 100644 --- a/spinifex/gateway/ec2/image/RegisterImage.go +++ b/spinifex/gateway/ec2/image/RegisterImage.go @@ -17,6 +17,14 @@ var validRegisterImageArchitectures = map[string]bool{ "i386": true, } +// uefi-preferred is accepted and treated as uefi at launch (single host, single +// firmware path — the BIOS-fallback ambiguity AWS resolves doesn't apply here). +var validRegisterImageBootModes = map[string]bool{ + "bios": true, + "uefi": true, + "uefi-preferred": true, +} + // ValidateRegisterImageInput rejects inputs that ask for behaviour spinifex // doesn't have (PV, S3 bundles, kernel/ramdisk, IMDS/TPM/ENA hints) rather // than silently discarding them. Registration is pointer-only: caller supplies @@ -48,7 +56,9 @@ func ValidateRegisterImageInput(input *ec2.RegisterImageInput) error { } if input.BootMode != nil && *input.BootMode != "" { - return errors.New(awserrors.ErrorInvalidParameterValue) + if !validRegisterImageBootModes[*input.BootMode] { + return errors.New(awserrors.ErrorInvalidParameterValue) + } } if input.KernelId != nil && *input.KernelId != "" { return errors.New(awserrors.ErrorInvalidParameterValue) diff --git a/spinifex/gateway/ec2/image/RegisterImage_test.go b/spinifex/gateway/ec2/image/RegisterImage_test.go index a8b026979..32b8a3982 100644 --- a/spinifex/gateway/ec2/image/RegisterImage_test.go +++ b/spinifex/gateway/ec2/image/RegisterImage_test.go @@ -124,8 +124,23 @@ func TestValidateRegisterImageInput(t *testing.T) { errMsg: awserrors.ErrorInvalidParameterValue, }, { - name: "BootModeRejected", + name: "BootModeAcceptedBios", + mutate: func(i *ec2.RegisterImageInput) { i.BootMode = aws.String("bios") }, + wantErr: false, + }, + { + name: "BootModeAcceptedUEFI", mutate: func(i *ec2.RegisterImageInput) { i.BootMode = aws.String("uefi") }, + wantErr: false, + }, + { + name: "BootModeAcceptedUEFIPreferred", + mutate: func(i *ec2.RegisterImageInput) { i.BootMode = aws.String("uefi-preferred") }, + wantErr: false, + }, + { + name: "BootModeRejectedInvalidValue", + mutate: func(i *ec2.RegisterImageInput) { i.BootMode = aws.String("legacy-bios") }, wantErr: true, errMsg: awserrors.ErrorInvalidParameterValue, }, diff --git a/spinifex/utils/images.go b/spinifex/utils/images.go index 8533e3718..f19a98722 100644 --- a/spinifex/utils/images.go +++ b/spinifex/utils/images.go @@ -285,7 +285,7 @@ var AvailableImages = map[string]Images{ URL: "https://cloud.debian.org/images/cloud/trixie/20260518-2482/debian-13-genericcloud-amd64-20260518-2482.tar.xz", Checksum: "https://cloud.debian.org/images/cloud/trixie/20260518-2482/SHA512SUMS", ChecksumType: "sha512", - BootMode: "bios", + BootMode: "uefi", }, "debian-13-arm64": { @@ -299,7 +299,7 @@ var AvailableImages = map[string]Images{ URL: "https://cloud.debian.org/images/cloud/trixie/20260518-2482/debian-13-genericcloud-arm64-20260518-2482.tar.xz", Checksum: "https://cloud.debian.org/images/cloud/trixie/20260518-2482/SHA512SUMS", ChecksumType: "sha512", - BootMode: "bios", + BootMode: "uefi", }, "ubuntu-26.04-x86_64": { @@ -313,7 +313,7 @@ var AvailableImages = map[string]Images{ URL: "https://cloud-images.ubuntu.com/resolute/20260421/resolute-server-cloudimg-amd64.img", Checksum: "https://cloud-images.ubuntu.com/resolute/20260421/SHA256SUMS", ChecksumType: "sha256", - BootMode: "bios", + BootMode: "uefi", }, "ubuntu-26.04-arm64": { @@ -327,41 +327,36 @@ var AvailableImages = map[string]Images{ URL: "https://cloud-images.ubuntu.com/resolute/20260421/resolute-server-cloudimg-arm64.img", Checksum: "https://cloud-images.ubuntu.com/resolute/20260421/SHA256SUMS", ChecksumType: "sha256", - BootMode: "bios", + BootMode: "uefi", }, - "alpine-3.22.2-x86_64": { - Name: "alpine-3.22.2-x86_64", - Description: "Alpine Linux 3.22.2 x86_64 cloud image", + "alpine-3.22.4-x86_64": { + Name: "alpine-3.22.4-x86_64", + Description: "Alpine Linux 3.22.4 x86_64 cloud image", Distro: "alpine", - Version: "3.22.2", + Version: "3.22.4", Arch: "x86_64", Platform: "Linux/UNIX", - CreatedAt: time.Date(2025, 10, 6, 0, 0, 0, 0, time.UTC), - URL: "https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/cloud/generic_alpine-3.22.2-x86_64-bios-cloudinit-r0.qcow2", - Checksum: "https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/cloud/generic_alpine-3.22.2-x86_64-bios-cloudinit-r0.qcow2.sha512", + CreatedAt: time.Date(2026, 4, 17, 0, 0, 0, 0, time.UTC), + URL: "https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/cloud/generic_alpine-3.22.4-x86_64-uefi-cloudinit-r0.qcow2", + Checksum: "https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/cloud/generic_alpine-3.22.4-x86_64-uefi-cloudinit-r0.qcow2.sha512", ChecksumType: "sha512", - BootMode: "bios", + BootMode: "uefi", }, - /* - "alpine-3.22.2-arm64": - // Alpine Linux (cloud init) arm64 (Requires UEFI boot, TODO) - { - Name: "alpine-3.22.2-arm64", - Description: "Alpine Linux 3.22.2 arm64 cloud image", - Distro: "alpine", - Version: "3.22.2", - Arch: "arm64", - Platform: "Linux/UNIX", - CreatedAt: time.Date(2025, 10, 6, 0, 0, 0, 0, time.UTC), - URL: "https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/cloud/gcp_alpine-3.22.2-aarch64-uefi-cloudinit-metal-r0.raw.tar.gz", - Checksum: "https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/cloud/gcp_alpine-3.22.2-aarch64-uefi-cloudinit-metal-r0.raw.tar.gz.sha512", - ChecksumType: "sha512", - BootMode: "uefi", - }, - */ - + "alpine-3.22.4-arm64": { + Name: "alpine-3.22.4-arm64", + Description: "Alpine Linux 3.22.4 arm64 cloud image", + Distro: "alpine", + Version: "3.22.4", + Arch: "arm64", + Platform: "Linux/UNIX", + CreatedAt: time.Date(2026, 4, 17, 0, 0, 0, 0, time.UTC), + URL: "https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/cloud/generic_alpine-3.22.4-aarch64-uefi-cloudinit-r0.qcow2", + Checksum: "https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/cloud/generic_alpine-3.22.4-aarch64-uefi-cloudinit-r0.qcow2.sha512", + ChecksumType: "sha512", + BootMode: "uefi", + }, } // AMI / image extraction utils diff --git a/tests/e2e/run-pseudo-multinode-e2e.sh b/tests/e2e/run-pseudo-multinode-e2e.sh index 5ba4e3e15..0e683d34f 100755 --- a/tests/e2e/run-pseudo-multinode-e2e.sh +++ b/tests/e2e/run-pseudo-multinode-e2e.sh @@ -295,6 +295,7 @@ IMPORT_LOG=$(./bin/spx admin images import \ --arch "$ARCH" \ --distro ubuntu \ --version "$UBUNTU_VER" \ + --boot-mode uefi \ --config "$HOME/node1/config/spinifex.toml" \ --spinifex-dir "$HOME/node1/" \ --force 2>/dev/null)