119 lines
4.9 KiB
Go
119 lines
4.9 KiB
Go
package preflight
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/aritmolab/thothii/tools/tht/internal/compose"
|
|
"github.com/aritmolab/thothii/tools/tht/internal/config"
|
|
)
|
|
|
|
type fakeDocker struct {
|
|
calls [][]string
|
|
fail string
|
|
effective string
|
|
}
|
|
|
|
func (f *fakeDocker) Run(_ context.Context, args []string, _ io.Reader) (compose.Result, error) {
|
|
f.calls = append(f.calls, args)
|
|
if strings.Contains(strings.Join(args, " "), f.fail) && f.fail != "" {
|
|
return compose.Result{Stderr: "PRIVATE_SENTINEL"}, errors.New("PRIVATE_SENTINEL")
|
|
}
|
|
switch args[0] {
|
|
case "info":
|
|
return compose.Result{Stdout: `{"OSType":"linux","Architecture":"x86_64","NCPU":4,"MemTotal":17179869184}`}, nil
|
|
case "compose":
|
|
if args[len(args)-1] == "json" {
|
|
if f.effective != "" {
|
|
return compose.Result{Stdout: f.effective}, nil
|
|
}
|
|
return compose.Result{Stdout: `{"services":{"core":{"image":"example/core:latest"}}}`}, nil
|
|
}
|
|
return compose.Result{Stdout: "2.39.0"}, nil
|
|
case "manifest":
|
|
return compose.Result{Stdout: `{"Descriptor":{"digest":"sha256:` + strings.Repeat("a", 64) + `","platform":{"os":"linux","architecture":"amd64"}}}`}, nil
|
|
}
|
|
return compose.Result{}, errors.New("unexpected command")
|
|
}
|
|
|
|
func TestComposeRejectsIncompleteMutableServiceSet(t *testing.T) {
|
|
r := CheckCompose(context.Background(), &fakeDocker{}, config.Installation{ProjectDirectory: "/private", EnvFile: "/private/operator.env"}, Manifest{Compose: []string{"compose.yaml"}}, "/release/manifest.json", "linux/amd64")
|
|
if r.OK {
|
|
t.Fatal("unreleased service set accepted")
|
|
}
|
|
}
|
|
|
|
func TestComposeRejectsPlatformOverrideAgainstSelectedImage(t *testing.T) {
|
|
m := Manifest{Images: map[string]map[string]string{}, Compose: []string{"compose.yaml"}}
|
|
services := map[string]map[string]string{}
|
|
for service, role := range map[string]string{"core": "core", "frontend": "frontend", "catalog-db": "catalog", "catalog-migrate": "core", "workspace-maintenance": "core", "qdrant": "qdrant", "embedding": "embedding", "embedding-model-init": "embedding"} {
|
|
m.Images[role] = map[string]string{"linux/amd64": "docker.io/example/" + role + "@sha256:" + strings.Repeat("a", 64)}
|
|
services[service] = map[string]string{"image": m.Images[role]["linux/amd64"]}
|
|
}
|
|
services["core"]["platform"] = "linux/arm64"
|
|
data, _ := json.Marshal(map[string]any{"services": services})
|
|
r := CheckCompose(context.Background(), &fakeDocker{effective: string(data)}, config.Installation{}, m, "/release/manifest.json", "linux/amd64")
|
|
if r.OK {
|
|
t.Fatal("incompatible Compose platform accepted")
|
|
}
|
|
}
|
|
|
|
func TestHostChecksAreReadOnlyAndRejectUnavailableDocker(t *testing.T) {
|
|
f := &fakeDocker{}
|
|
host := Host{OS: "linux", Arch: "amd64", Kernel: "6.6-microsoft-standard-WSL2", Distribution: "ubuntu", FreeBytes: 30 << 30}
|
|
r := CheckHost(context.Background(), f, host, Requirements{CPUs: 2, MemoryBytes: 4 << 30, DiskBytes: 10 << 30})
|
|
if !r.OK {
|
|
t.Fatalf("host rejected: %+v", r)
|
|
}
|
|
for _, args := range f.calls {
|
|
if args[0] != "info" && !(args[0] == "compose" && args[1] == "version") {
|
|
t.Fatalf("mutating call: %v", args)
|
|
}
|
|
}
|
|
f.fail = "info"
|
|
r = CheckHost(context.Background(), f, host, Requirements{CPUs: 2, MemoryBytes: 4 << 30, DiskBytes: 10 << 30})
|
|
if r.OK || strings.Contains(r.JSON(), "PRIVATE_SENTINEL") {
|
|
t.Fatalf("unsafe success/report: %s", r.JSON())
|
|
}
|
|
host.Kernel = "4.4-microsoft"
|
|
if CheckHost(context.Background(), &fakeDocker{}, host, Requirements{CPUs: 2, MemoryBytes: 4 << 30, DiskBytes: 10 << 30}).OK {
|
|
t.Fatal("WSL1 accepted")
|
|
}
|
|
host.OS = "windows"
|
|
if CheckHost(context.Background(), &fakeDocker{}, host, Requirements{}).OK {
|
|
t.Fatal("native Windows accepted instead of WSL2")
|
|
}
|
|
}
|
|
|
|
func TestReleaseChecksEveryImmutableImageAndPlatform(t *testing.T) {
|
|
m := Manifest{SchemaVersion: 1, Version: "1.0.0", Revision: strings.Repeat("b", 40), ValidatorProtocol: 1, Requirements: Requirements{CPUs: 2, MemoryBytes: 4 << 30, DiskBytes: 10 << 30}, Components: []string{"pi", "catalog-migrations", "workspace-maintenance"}, Images: map[string]map[string]string{}}
|
|
for _, service := range []string{"core", "frontend", "catalog", "qdrant", "embedding"} {
|
|
m.Images[service] = map[string]string{"linux/amd64": "docker.io/example/" + service + "@sha256:" + strings.Repeat("a", 64)}
|
|
}
|
|
m.Files = map[string]string{"deploy/compose.yaml": strings.Repeat("c", 64)}
|
|
m.Compose = []string{"deploy/compose.yaml"}
|
|
if err := m.Validate(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
f := &fakeDocker{}
|
|
r := CheckImages(context.Background(), f, m, "linux/amd64")
|
|
if !r.OK || len(f.calls) != 5 {
|
|
t.Fatalf("images not checked: %s calls=%d", r.JSON(), len(f.calls))
|
|
}
|
|
if CheckImages(context.Background(), f, m, "linux/arm64").OK {
|
|
t.Fatal("unsupported release architecture accepted")
|
|
}
|
|
f.fail = "frontend"
|
|
if CheckImages(context.Background(), f, m, "linux/amd64").OK {
|
|
t.Fatal("missing image accepted")
|
|
}
|
|
m.Images["core"]["linux/amd64"] = "example/core:latest"
|
|
if m.Validate() == nil {
|
|
t.Fatal("mutable tag accepted")
|
|
}
|
|
}
|