image: support zip sources and align class k3s versions

This commit is contained in:
Brad Stein 2026-04-01 12:45:43 -03:00
parent 8f0d5389c2
commit eb77c94db3
3 changed files with 132 additions and 4 deletions

View File

@ -24,7 +24,7 @@ classes:
os: ubuntu-24.04-raspi os: ubuntu-24.04-raspi
image: ${METIS_IMAGE_RPI5_UBUNTU_WORKER} image: ${METIS_IMAGE_RPI5_UBUNTU_WORKER}
checksum: ${METIS_IMAGE_RPI5_UBUNTU_WORKER_SHA256} checksum: ${METIS_IMAGE_RPI5_UBUNTU_WORKER_SHA256}
k3s_version: v1.31.5+k3s1 k3s_version: v1.33.3+k3s1
default_labels: default_labels:
hardware: rpi5 hardware: rpi5
node-role.kubernetes.io/worker: "true" node-role.kubernetes.io/worker: "true"
@ -33,7 +33,7 @@ classes:
os: ubuntu-20.04-tegra os: ubuntu-20.04-tegra
image: ${METIS_IMAGE_JETSON_UBUNTU_ACCELERATOR} image: ${METIS_IMAGE_JETSON_UBUNTU_ACCELERATOR}
checksum: ${METIS_IMAGE_JETSON_UBUNTU_ACCELERATOR_SHA256} checksum: ${METIS_IMAGE_JETSON_UBUNTU_ACCELERATOR_SHA256}
k3s_version: v1.31.5+k3s1 k3s_version: v1.33.3+k3s1
default_labels: default_labels:
hardware: jetson hardware: jetson
jetson: "true" jetson: "true"
@ -43,7 +43,7 @@ classes:
os: ubuntu-24.04-raspi os: ubuntu-24.04-raspi
image: ${METIS_IMAGE_RPI5_UBUNTU_CONTROL} image: ${METIS_IMAGE_RPI5_UBUNTU_CONTROL}
checksum: ${METIS_IMAGE_RPI5_UBUNTU_CONTROL_SHA256} checksum: ${METIS_IMAGE_RPI5_UBUNTU_CONTROL_SHA256}
k3s_version: v1.31.5+k3s1 k3s_version: v1.33.3+k3s1
default_labels: default_labels:
hardware: rpi5 hardware: rpi5
node-role.kubernetes.io/control-plane: "true" node-role.kubernetes.io/control-plane: "true"
@ -54,7 +54,7 @@ classes:
os: debian-13 os: debian-13
image: ${METIS_IMAGE_AMD64_DEBIAN_WORKER} image: ${METIS_IMAGE_AMD64_DEBIAN_WORKER}
checksum: ${METIS_IMAGE_AMD64_DEBIAN_WORKER_SHA256} checksum: ${METIS_IMAGE_AMD64_DEBIAN_WORKER_SHA256}
k3s_version: v1.31.5+k3s1 k3s_version: v1.33.3+k3s1
default_labels: default_labels:
hardware: amd64 hardware: amd64
node-role.kubernetes.io/worker: "true" node-role.kubernetes.io/worker: "true"

View File

@ -1,6 +1,7 @@
package image package image
import ( import (
"archive/zip"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"errors" "errors"
@ -35,6 +36,16 @@ func DownloadAndVerify(url, dest, checksum string) (string, error) {
} }
return dest, nil 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 { if err := ensureVerifiedFile(url, dest, checksum); err != nil {
return "", err return "", err
} }
@ -107,6 +118,47 @@ func decompressXZ(src, dest string) error {
return out.Sync() 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:<hex>". // VerifyChecksum checks sha256 in the form "sha256:<hex>".
func VerifyChecksum(path, checksum string) error { func VerifyChecksum(path, checksum string) error {
if checksum == "" { if checksum == "" {

View File

@ -1,6 +1,7 @@
package image package image
import ( import (
"archive/zip"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"os" "os"
@ -107,3 +108,78 @@ func TestDownloadAndVerifyReplacesStaleBadArchiveCache(t *testing.T) {
t.Fatalf("unexpected decompressed content: %q", string(data)) 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()
}