From eb77c94db32390f46f781b69ba5b7332903ed2cd Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Wed, 1 Apr 2026 12:45:43 -0300 Subject: [PATCH] image: support zip sources and align class k3s versions --- inventory.titan-rpi4.yaml | 8 ++-- pkg/image/download.go | 52 ++++++++++++++++++++++++++ pkg/image/download_test.go | 76 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 4 deletions(-) diff --git a/inventory.titan-rpi4.yaml b/inventory.titan-rpi4.yaml index 8c0d389..813cc36 100644 --- a/inventory.titan-rpi4.yaml +++ b/inventory.titan-rpi4.yaml @@ -24,7 +24,7 @@ classes: os: ubuntu-24.04-raspi image: ${METIS_IMAGE_RPI5_UBUNTU_WORKER} checksum: ${METIS_IMAGE_RPI5_UBUNTU_WORKER_SHA256} - k3s_version: v1.31.5+k3s1 + k3s_version: v1.33.3+k3s1 default_labels: hardware: rpi5 node-role.kubernetes.io/worker: "true" @@ -33,7 +33,7 @@ classes: os: ubuntu-20.04-tegra image: ${METIS_IMAGE_JETSON_UBUNTU_ACCELERATOR} checksum: ${METIS_IMAGE_JETSON_UBUNTU_ACCELERATOR_SHA256} - k3s_version: v1.31.5+k3s1 + k3s_version: v1.33.3+k3s1 default_labels: hardware: jetson jetson: "true" @@ -43,7 +43,7 @@ classes: os: ubuntu-24.04-raspi image: ${METIS_IMAGE_RPI5_UBUNTU_CONTROL} checksum: ${METIS_IMAGE_RPI5_UBUNTU_CONTROL_SHA256} - k3s_version: v1.31.5+k3s1 + k3s_version: v1.33.3+k3s1 default_labels: hardware: rpi5 node-role.kubernetes.io/control-plane: "true" @@ -54,7 +54,7 @@ classes: os: debian-13 image: ${METIS_IMAGE_AMD64_DEBIAN_WORKER} checksum: ${METIS_IMAGE_AMD64_DEBIAN_WORKER_SHA256} - k3s_version: v1.31.5+k3s1 + k3s_version: v1.33.3+k3s1 default_labels: hardware: amd64 node-role.kubernetes.io/worker: "true" diff --git a/pkg/image/download.go b/pkg/image/download.go index ae3aa92..6c3fee6 100644 --- a/pkg/image/download.go +++ b/pkg/image/download.go @@ -1,6 +1,7 @@ package image import ( + "archive/zip" "crypto/sha256" "encoding/hex" "errors" @@ -35,6 +36,16 @@ func DownloadAndVerify(url, dest, checksum string) (string, error) { } return dest, nil } + if strings.HasSuffix(url, ".zip") { + archive := dest + ".zip" + if err := ensureVerifiedFile(url, archive, checksum); err != nil { + return "", err + } + if err := decompressZIP(archive, dest); err != nil { + return "", err + } + return dest, nil + } if err := ensureVerifiedFile(url, dest, checksum); err != nil { return "", err } @@ -107,6 +118,47 @@ func decompressXZ(src, dest string) error { return out.Sync() } +func decompressZIP(src, dest string) error { + reader, err := zip.OpenReader(src) + if err != nil { + return err + } + defer reader.Close() + + var target *zip.File + for _, file := range reader.File { + if file.FileInfo().IsDir() { + continue + } + if strings.HasSuffix(strings.ToLower(file.Name), ".img") { + target = file + break + } + if target == nil { + target = file + } + } + if target == nil { + return fmt.Errorf("zip archive %s does not contain a file entry", src) + } + + in, err := target.Open() + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(dest) + if err != nil { + return err + } + defer out.Close() + if _, err := io.Copy(out, in); err != nil { + return err + } + return out.Sync() +} + // VerifyChecksum checks sha256 in the form "sha256:". func VerifyChecksum(path, checksum string) error { if checksum == "" { diff --git a/pkg/image/download_test.go b/pkg/image/download_test.go index 107a386..be07235 100644 --- a/pkg/image/download_test.go +++ b/pkg/image/download_test.go @@ -1,6 +1,7 @@ package image import ( + "archive/zip" "crypto/sha256" "encoding/hex" "os" @@ -107,3 +108,78 @@ func TestDownloadAndVerifyReplacesStaleBadArchiveCache(t *testing.T) { t.Fatalf("unexpected decompressed content: %q", string(data)) } } + +func TestDownloadDecompressesZIPFileURLs(t *testing.T) { + dir := t.TempDir() + archive := filepath.Join(dir, "base.zip") + if err := writeTestZIP(archive, map[string]string{ + "nested/base.img": "metis-zip-test", + }); err != nil { + t.Fatalf("writeTestZIP: %v", err) + } + dest := filepath.Join(dir, "copy.img") + if err := Download("file://"+archive, dest); err != nil { + t.Fatalf("Download: %v", err) + } + data, err := os.ReadFile(dest) + if err != nil { + t.Fatalf("ReadFile: %v", err) + } + if string(data) != "metis-zip-test" { + t.Fatalf("unexpected decompressed content: %q", string(data)) + } +} + +func TestDownloadAndVerifyUsesArchiveChecksumForZIP(t *testing.T) { + dir := t.TempDir() + archive := filepath.Join(dir, "base.zip") + if err := writeTestZIP(archive, map[string]string{ + "base.img": "metis-zip-test", + }); err != nil { + t.Fatalf("writeTestZIP: %v", err) + } + archiveBytes, err := os.ReadFile(archive) + if err != nil { + t.Fatalf("ReadFile archive: %v", err) + } + archiveSum := sha256.Sum256(archiveBytes) + dest := filepath.Join(dir, "copy.img") + localPath, err := DownloadAndVerify("file://"+archive, dest, "sha256:"+hex.EncodeToString(archiveSum[:])) + if err != nil { + t.Fatalf("DownloadAndVerify: %v", err) + } + if localPath != dest { + t.Fatalf("expected local path %s, got %s", dest, localPath) + } + data, err := os.ReadFile(dest) + if err != nil { + t.Fatalf("ReadFile dest: %v", err) + } + if string(data) != "metis-zip-test" { + t.Fatalf("unexpected decompressed content: %q", string(data)) + } +} + +func writeTestZIP(path string, files map[string]string) error { + out, err := os.Create(path) + if err != nil { + return err + } + defer out.Close() + zw := zip.NewWriter(out) + for name, content := range files { + w, err := zw.Create(name) + if err != nil { + _ = zw.Close() + return err + } + if _, err := w.Write([]byte(content)); err != nil { + _ = zw.Close() + return err + } + } + if err := zw.Close(); err != nil { + return err + } + return out.Sync() +}