Python automx2, its database and its automx_rows tool go; the container keeps its name, network and port, gains an http health.
203 lines
7.3 KiB
Go
203 lines
7.3 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The digest route-proxy's `trust` artifact runs: one alpine image is held for both (ADR 0480 clause 3). It was
|
|
// read from mesh-controller's modules/route-proxy/module.json and is the one hello-web runs.
|
|
const alpineDigest = "alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b"
|
|
|
|
type manifest struct {
|
|
Tools []string `json:"tools"`
|
|
Resources []map[string]any `json:"resources"`
|
|
Listens []struct {
|
|
Name string `json:"name"`
|
|
Port int `json:"port"`
|
|
} `json:"listens"`
|
|
Data struct {
|
|
Own []struct {
|
|
ID string `json:"id"`
|
|
Path string `json:"path"`
|
|
} `json:"own"`
|
|
} `json:"data"`
|
|
Build map[string]any `json:"build"`
|
|
}
|
|
|
|
func readManifest(t *testing.T) manifest {
|
|
t.Helper()
|
|
var m manifest
|
|
if err := json.Unmarshal([]byte(readFile(t, "../../module.json")), &m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return m
|
|
}
|
|
|
|
func (m manifest) resource(id string) map[string]any {
|
|
for _, r := range m.Resources {
|
|
if r["id"] == id {
|
|
return r
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m manifest) artifact(name string) map[string]any {
|
|
arts, _ := m.Build["artifacts"].([]any)
|
|
for _, a := range arts {
|
|
if a, _ := a.(map[string]any); a["name"] == name {
|
|
return a
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func strs(v any) []string {
|
|
var out []string
|
|
l, _ := v.([]any)
|
|
for _, e := range l {
|
|
out = append(out, e.(string))
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Clause 2: a Go bundle from a package of the module's own go.mod, stamped for alpine; an archive resource unpacks
|
|
// it; the container mounts that read-only at /opt/mesh, calls the program there and names the archive in restart-on.
|
|
func TestAutomxIsAGoBundleForAlpineMountedAtOptMeshAndRestartedWithIt(t *testing.T) {
|
|
m := readManifest(t)
|
|
b := m.artifact("automx")
|
|
if b == nil || b["kind"] != "bundle" || b["language"] != "go" || b["system"] != "alpine" || b["from"] != "cmd/automx" || b["binary"] != "automx" {
|
|
t.Fatalf("artifact automx is %v, want a Go bundle, system alpine, from cmd/automx, binary automx", b)
|
|
}
|
|
if _, loads := b["loads"]; loads {
|
|
t.Errorf("a program run in a container is not loaded by the tool runner: %v", b["loads"])
|
|
}
|
|
goMod := readFile(t, "../../go.mod")
|
|
if !strings.HasPrefix(goMod, "module mailu\n") {
|
|
t.Errorf("the module's go.mod is %q; the bundle is a package of it", goMod)
|
|
}
|
|
if _, err := os.Stat(filepath.Join("..", "..", filepath.FromSlash(b["from"].(string)), "main.go")); err != nil {
|
|
t.Errorf("the bundle's package is not in the module's directory: %v", err)
|
|
}
|
|
|
|
unpack := m.resource("program-automx")
|
|
if unpack == nil || unpack["type"] != "archive" || unpack["artifact"] != "automx" || unpack["unknown"] != "program" {
|
|
t.Fatalf("resource program-automx is %v, want an archive of the bundle", unpack)
|
|
}
|
|
where, _ := unpack["path"].(string)
|
|
c := m.resource("automx")
|
|
if c == nil || c["type"] != "container" {
|
|
t.Fatalf("no container automx: %v", c)
|
|
}
|
|
if got := strs(c["volumes"]); len(got) != 1 || got[0] != where+":/opt/mesh:ro" {
|
|
t.Errorf("volumes %v, want the archive's place %q mounted read-only at /opt/mesh", got, where)
|
|
}
|
|
if got := strs(c["args"]); len(got) != 1 || got[0] != "/opt/mesh/automx" {
|
|
t.Errorf("args %v, want the program at /opt/mesh/automx", got)
|
|
}
|
|
if !contains(strs(c["restart-on"]), "program-automx") {
|
|
t.Errorf("restart-on %v does not name the archive: a new program would leave the old one running", c["restart-on"])
|
|
}
|
|
if !contains(strs(c["restart-on"]), "config-env") {
|
|
t.Errorf("restart-on %v does not name the environment file the program reads at its start", c["restart-on"])
|
|
}
|
|
}
|
|
|
|
// Clause 3: an upstream artifact pinned by digest, the one route-proxy's trust artifact runs; never an image
|
|
// built from a Dockerfile (clause 11), and no Dockerfile or base to build one on.
|
|
func TestAutomxRunsTheMirroredAlpineImageAndBuildsNoImage(t *testing.T) {
|
|
m := readManifest(t)
|
|
c := m.resource("automx")
|
|
base := m.artifact(c["artifact"].(string))
|
|
if base == nil || base["kind"] != "upstream" || base["from"] != alpineDigest {
|
|
t.Errorf("the container runs artifact %v, want an upstream artifact %s", base, alpineDigest)
|
|
}
|
|
if _, has := c["image"]; has {
|
|
t.Errorf("the container names an image %v, fetched from a registry on the machine", c["image"])
|
|
}
|
|
for _, a := range m.Build["artifacts"].([]any) {
|
|
if a.(map[string]any)["kind"] == "image" {
|
|
t.Errorf("artifact %v is an image built from a Dockerfile (ADR 0452)", a)
|
|
}
|
|
}
|
|
if _, has := m.Build["on"]; has {
|
|
t.Errorf("build.on %v: nothing is built on a base image any more", m.Build["on"])
|
|
}
|
|
for _, gone := range []string{"../../automx/Dockerfile", "../../automx"} {
|
|
if _, err := os.Stat(gone); err == nil {
|
|
t.Errorf("%s is still there", gone)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clause 1: the container stays as it was for what watches it.
|
|
func TestAutomxKeepsItsContainerNameNetworkPortAndEnvironment(t *testing.T) {
|
|
m := readManifest(t)
|
|
c := m.resource("automx")
|
|
if c["name"] != "mailu-automx" || c["network"] != "mailu" {
|
|
t.Errorf("container %v on network %v, want mailu-automx on mailu", c["name"], c["network"])
|
|
}
|
|
if got := strs(c["ports"]); len(got) != 1 || got[0] != "4243" {
|
|
t.Errorf("ports %v, want 4243", got)
|
|
}
|
|
if got := strs(c["env-file"]); len(got) != 1 || got[0] != "${dir:state}/mailu.env" {
|
|
t.Errorf("env-file %v, want mailu's environment only", got)
|
|
}
|
|
if !strings.Contains("\n"+readFile(t, "../../files/mailu.env"), "\nAUTOMX_PORT=4243\n") {
|
|
t.Errorf("mailu.env does not say AUTOMX_PORT=4243")
|
|
}
|
|
for _, l := range m.Listens {
|
|
if l.Name == "autoconfig" && l.Port == 4243 {
|
|
return
|
|
}
|
|
}
|
|
t.Errorf("the autoconfig endpoint is not on 4243: %v", m.Listens)
|
|
}
|
|
|
|
// Clause 10: a database nobody reads is gone from the declaration (the file stays on disk), and the container says
|
|
// whether it answers with one captured request and its captured answer.
|
|
func TestAutomxKeepsNoDatabaseAndSaysWhetherItAnswers(t *testing.T) {
|
|
m := readManifest(t)
|
|
if m.resource("data-automx") != nil {
|
|
t.Errorf("the data-automx directory is still declared")
|
|
}
|
|
for _, it := range m.Data.Own {
|
|
if strings.Contains(it.ID, "automx") || strings.Contains(it.Path, "data-automx") {
|
|
t.Errorf("data item %v is still declared", it)
|
|
}
|
|
}
|
|
for _, tool := range m.Tools {
|
|
if tool == "automx_rows" {
|
|
t.Errorf("the tool automx_rows reads a database that no longer exists (issue 1700)")
|
|
}
|
|
}
|
|
h, _ := m.resource("automx")["health"].(map[string]any)
|
|
if h["kind"] != "http" || h["endpoint"] != "autoconfig" {
|
|
t.Fatalf("health %v, want an http check of the autoconfig endpoint", h)
|
|
}
|
|
// The request is captured fixture 06 and the answer its status and body.
|
|
rq := readRequest(t, filepath.Join(captured, "06-autoconfig-root"))
|
|
if u := strings.SplitN(rq.url, "/", 4); "/"+u[len(u)-1] != h["path"] || rq.method != "GET" {
|
|
t.Errorf("health path %v is not captured request %s %s", h["path"], rq.method, rq.url)
|
|
}
|
|
if st := readFile(t, filepath.Join(captured, "06-autoconfig-root.status")); !strings.HasPrefix(st, "200 ") || h["status"] != float64(200) {
|
|
t.Errorf("health status %v, captured %q", h["status"], st)
|
|
}
|
|
if body, _ := h["body"].(string); body == "" || !strings.Contains(readFile(t, filepath.Join(captured, "06-autoconfig-root.body")), body) {
|
|
t.Errorf("health body %q is not in the captured answer", h["body"])
|
|
}
|
|
}
|
|
|
|
func contains(l []string, s string) bool {
|
|
for _, e := range l {
|
|
if e == s {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|