mirror of
https://github.com/siderolabs/image-factory.git
synced 2025-11-05 02:41:13 +01:00
Lower min version to 1.2.0, pull in fixes for `imager` from Talos. Add new tests for v1.3.7 as an example. Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
248 lines
6.2 KiB
Go
248 lines
6.2 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
//go:build integration
|
|
|
|
package integration_test
|
|
|
|
import (
|
|
"archive/tar"
|
|
"context"
|
|
"crypto"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"sort"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-containerregistry/pkg/crane"
|
|
"github.com/google/go-containerregistry/pkg/name"
|
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
|
"github.com/siderolabs/gen/xslices"
|
|
"github.com/sigstore/cosign/v2/pkg/cosign"
|
|
"github.com/sigstore/sigstore/pkg/cryptoutils"
|
|
"github.com/sigstore/sigstore/pkg/signature"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"github.com/siderolabs/image-factory/pkg/client"
|
|
"github.com/siderolabs/image-factory/pkg/schematic"
|
|
)
|
|
|
|
func testInstallerImage(ctx context.Context, t *testing.T, registry name.Registry, talosVersion, schematic string, secureboot bool, platform v1.Platform, baseURL string) {
|
|
imageName := "installer"
|
|
if secureboot {
|
|
imageName += "-secureboot"
|
|
}
|
|
|
|
ref := registry.Repo(imageName, schematic).Tag(talosVersion)
|
|
|
|
_, err := remote.Head(ref)
|
|
require.NoError(t, err)
|
|
|
|
descriptor, err := remote.Get(ref, remote.WithPlatform(platform))
|
|
require.NoError(t, err)
|
|
|
|
index, err := descriptor.ImageIndex()
|
|
require.NoError(t, err)
|
|
|
|
manifest, err := index.IndexManifest()
|
|
require.NoError(t, err)
|
|
|
|
platforms := xslices.Map(manifest.Manifests, func(m v1.Descriptor) string {
|
|
return m.Platform.String()
|
|
})
|
|
|
|
sort.Strings(platforms)
|
|
|
|
assert.Equal(t, []string{"linux/amd64", "linux/arm64"}, platforms)
|
|
|
|
img, err := descriptor.Image()
|
|
require.NoError(t, err)
|
|
|
|
layers, err := img.Layers()
|
|
require.NoError(t, err)
|
|
|
|
if talosVersion != "v1.3.7" {
|
|
assert.Len(t, layers, 2, "installer image should have 2 layers: base and artifacts")
|
|
}
|
|
|
|
expectedFiles := map[string]struct{}{
|
|
"bin/installer": {},
|
|
}
|
|
|
|
if !secureboot {
|
|
expectedFiles[fmt.Sprintf("usr/install/%s/vmlinuz", platform.Architecture)] = struct{}{}
|
|
expectedFiles[fmt.Sprintf("usr/install/%s/initramfs.xz", platform.Architecture)] = struct{}{}
|
|
} else {
|
|
expectedFiles[fmt.Sprintf("usr/install/%s/vmlinuz.efi.signed", platform.Architecture)] = struct{}{}
|
|
expectedFiles[fmt.Sprintf("usr/install/%s/systemd-boot.efi", platform.Architecture)] = struct{}{}
|
|
}
|
|
|
|
if platform.Architecture == "arm64" {
|
|
if talosVersion != "v1.3.7" {
|
|
expectedFiles["usr/install/arm64/dtb/allwinner/sun50i-h616-x96-mate.dtb"] = struct{}{}
|
|
}
|
|
|
|
expectedFiles["usr/install/arm64/raspberrypi-firmware/boot/bootcode.bin"] = struct{}{}
|
|
expectedFiles["usr/install/arm64/u-boot/rockpi_4/rkspi_loader.img"] = struct{}{}
|
|
}
|
|
|
|
assertImageContainsFiles(t, img, expectedFiles)
|
|
|
|
// verify the image signature
|
|
assertImageSignature(ctx, t, ref, baseURL)
|
|
|
|
// try to get the image once again, it should be fast now, as the image got cached & signed
|
|
start := time.Now()
|
|
|
|
_, err = remote.Get(ref, remote.WithPlatform(platform))
|
|
require.NoError(t, err)
|
|
|
|
assert.Less(t, time.Since(start), 1*time.Second)
|
|
}
|
|
|
|
func assertImageContainsFiles(t *testing.T, img v1.Image, files map[string]struct{}) {
|
|
t.Helper()
|
|
|
|
r, w := io.Pipe()
|
|
|
|
var eg errgroup.Group
|
|
|
|
eg.Go(func() error {
|
|
defer w.Close() //nolint:errcheck
|
|
|
|
return crane.Export(img, w)
|
|
})
|
|
|
|
eg.Go(func() error {
|
|
tr := tar.NewReader(r)
|
|
|
|
for {
|
|
hdr, err := tr.Next()
|
|
if err != nil {
|
|
if errors.Is(err, io.EOF) {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("error reading tar header: %w", err)
|
|
}
|
|
|
|
delete(files, hdr.Name)
|
|
}
|
|
})
|
|
|
|
assert.NoError(t, eg.Wait())
|
|
assert.Empty(t, files)
|
|
}
|
|
|
|
func assertImageSignature(ctx context.Context, t *testing.T, ref name.Reference, baseURL string) {
|
|
t.Helper()
|
|
|
|
// download public key
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"/oci/cosign/signing-key.pub", nil)
|
|
require.NoError(t, err)
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
resp.Body.Close()
|
|
})
|
|
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
pub, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(pub)
|
|
require.NoError(t, err)
|
|
|
|
verifier, err := signature.LoadVerifier(pubKey, crypto.SHA256)
|
|
require.NoError(t, err)
|
|
|
|
checkOpts := &cosign.CheckOpts{
|
|
SigVerifier: verifier,
|
|
IgnoreSCT: true,
|
|
IgnoreTlog: true,
|
|
Offline: true,
|
|
}
|
|
|
|
_, _, err = cosign.VerifyImageSignatures(ctx, ref, checkOpts)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func testRegistryFrontend(ctx context.Context, t *testing.T, registryAddr string, baseURL string) {
|
|
talosVersions := []string{
|
|
"v1.3.7",
|
|
"v1.5.0",
|
|
"v1.5.1",
|
|
}
|
|
|
|
registry, err := name.NewRegistry(registryAddr)
|
|
require.NoError(t, err)
|
|
|
|
c, err := client.New("http://" + registryAddr)
|
|
require.NoError(t, err)
|
|
|
|
// create a new random schematic, so that we can make sure new installer is generated
|
|
randomKernelArg := hex.EncodeToString(randomBytes(t, 32))
|
|
|
|
randomSchematicID := createSchematicGetID(ctx, t, c,
|
|
schematic.Schematic{
|
|
Customization: schematic.Customization{
|
|
ExtraKernelArgs: []string{randomKernelArg},
|
|
},
|
|
},
|
|
)
|
|
|
|
for _, talosVersion := range talosVersions {
|
|
t.Run(talosVersion, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, secureboot := range []bool{false, true} {
|
|
t.Run(fmt.Sprintf("secureboot=%t", secureboot), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
if secureboot && talosVersion == "v1.3.7" {
|
|
t.Skip("secureboot is not supported in Talos v1.3.7")
|
|
}
|
|
|
|
for _, schematicID := range []string{
|
|
emptySchematicID,
|
|
systemExtensionsSchematicID,
|
|
randomSchematicID,
|
|
} {
|
|
t.Run(schematicID, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, platform := range []v1.Platform{
|
|
{
|
|
Architecture: "amd64",
|
|
OS: "linux",
|
|
},
|
|
{
|
|
Architecture: "arm64",
|
|
OS: "linux",
|
|
},
|
|
} {
|
|
t.Run(platform.String(), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testInstallerImage(ctx, t, registry, talosVersion, schematicID, secureboot, platform, baseURL)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|