Files
mesh-catalog/modules/mesh-build-image/cmd/mesh-build-image/push_test.go
T

157 lines
6.0 KiB
Go

package main
import (
"context"
"os"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
// 1703d/AC (ADR 0487 §5): a candidate's build leaves the registry unchanged: nothing is pushed, nothing is asked of it.
func TestACandidatesBuildLeavesTheRegistryUnchanged(t *testing.T) {
m, f, states := setup(t, same)
res, st := runBuild(t, m, states, nil) // no publish
if st.State != delivery.StepPassed || res.Pushed {
t.Fatalf("step = %+v result = %+v", st, res)
}
if len(f.called("push")) != 0 || len(f.registry) != 0 || f.pushes != 0 {
t.Errorf("a candidate's build reached the registry: pushes %d, registry %v, calls %v", f.pushes, f.registry, f.lines())
}
}
// 1703d/AC: a trunk build adds exactly one digest, under <module>/<artifact> and a tag of its own (the digest's first 12
// hex digits), after the build has ended, with TLS verification off (plain HTTP) and no credential; the key carries the
// digest the registry serves and the reference pinned by it.
func TestATrunkBuildPushesOnceAndAnswersTheRegistrysDigest(t *testing.T) {
m, f, states := setup(t, same)
res, st := runBuild(t, m, states, map[string]any{"publish": true})
if st.State != delivery.StepPassed {
t.Fatalf("step = %+v", st)
}
tag := "111111111111"
if len(f.registry) != 1 || f.registry["fixture-image/server:"+tag] != imageDigest || f.pushes != 1 {
t.Fatalf("registry = %v after %d pushes, want exactly fixture-image/server:%s", f.registry, f.pushes, tag)
}
if !res.Pushed || res.Digest != imageDigest || res.Reference != f.address()+"/fixture-image/server@"+imageDigest {
t.Errorf("result = %+v", res)
}
push := f.called("push")
if len(push) != 1 || !strings.Contains(push[0].line, "--tls-verify=false") || !strings.Contains(push[0].line, "docker://"+f.address()+"/fixture-image/server:"+tag) {
t.Fatalf("push = %v", push)
}
for _, bad := range []string{"--creds", "--authfile", "--sign"} {
if strings.Contains(push[0].line, bad) {
t.Errorf("the push carries %s", bad)
}
}
// The push comes after the build has ended: bud, then images, then push.
order := []string{}
for _, l := range f.lines() {
for _, v := range []string{" bud ", " images ", " push ", " rmi "} {
if strings.Contains(l, "buildah") && strings.Contains(l, v) {
order = append(order, strings.TrimSpace(v))
}
}
}
if strings.Join(order, ",") != "bud,images,push,rmi" {
t.Errorf("order = %v", order)
}
}
// A tag the call gives is the one used; one that is not a tag is refused.
func TestATagOfTheCallsOwn(t *testing.T) {
m, f, states := setup(t, same)
if _, st := runBuild(t, m, states, map[string]any{"publish": true, "tag": "fp-abc123"}); st.State != delivery.StepPassed {
t.Fatalf("step = %+v", st)
}
if _, ok := f.registry["fixture-image/server:fp-abc123"]; !ok || len(f.registry) != 1 {
t.Errorf("registry = %v", f.registry)
}
m2, f2, states2 := setup(t, same)
if _, st := runBuild(t, m2, states2, map[string]any{"publish": true, "tag": "a/b:c"}); st.State != delivery.StepFailed || len(f2.called("bud")) != 0 {
t.Errorf("step = %+v", st)
}
}
// issue 457: a registry held still (connection refused) is waited for, and the push then goes through once.
func TestAPushWaitsForARegistryHeldStill(t *testing.T) {
m, f, states := setup(t, same)
f.refuse = 2
res, st := runBuild(t, m, states, map[string]any{"publish": true})
if st.State != delivery.StepPassed || !res.Pushed {
t.Fatalf("step = %+v result = %+v", st, res)
}
if f.pushes != 1 || len(f.registry) != 1 || len(f.called("push")) != 3 {
t.Errorf("pushes %d, registry %v, push calls %d; want two refused and one made", f.pushes, f.registry, len(f.called("push")))
}
}
// A registry that stays down past the wait fails the step saying how long it was refused; nothing is claimed pushed.
func TestARegistryDownPastTheWaitFailsTheStep(t *testing.T) {
m, f, states := setup(t, same)
f.refuse = 1 << 20
m.RegistryWait = 50 * time.Millisecond
_, st := runBuild(t, m, states, map[string]any{"publish": true})
if st.State != delivery.StepFailed || !strings.Contains(st.Reason, "refused every connection") {
t.Errorf("step = %+v", st)
}
if len(f.registry) != 0 {
t.Errorf("registry = %v", f.registry)
}
}
// The digest the registry serves is the answer: when it differs from the one the push reported, the step fails.
func TestAnAnswerThatDisagreesWithThePushFails(t *testing.T) {
m, f, states := setup(t, same)
run := m.Run
m.Run = func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
r := run(ctx, env, name, args...)
if name == "buildah" && strings.Contains(strings.Join(args, " "), " push ") {
f.mu.Lock()
for k := range f.registry {
f.registry[k] = "sha256:2222222222222222222222222222222222222222222222222222222222222222"
}
f.mu.Unlock()
}
return r
}
_, st := runBuild(t, m, states, map[string]any{"publish": true})
if st.State != delivery.StepFailed || !strings.Contains(st.Reason, "the registry serves") {
t.Errorf("step = %+v", st)
}
}
func TestTheRegistrysAddressMustBeBound(t *testing.T) {
for _, bad := range []string{"", "${bound:artifact-store:at}:5000", ":5000"} {
m, _, _ := setup(t, same)
writeFile(t, m.RegistryFile, bad)
if _, err := m.registry(); err == nil {
t.Errorf("%q was taken as the registry's address", bad)
}
}
}
func writeFile(t *testing.T, path, body string) {
t.Helper()
if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
// A push that succeeded wrote its digest file: one missing, unreadable or empty is a fault, and the registry's digest is
// never answered unchecked (review round 1).
func TestAPushWithoutAReadableDigestFileFailsTheStep(t *testing.T) {
for _, kind := range []string{"none", "empty"} {
m, f, states := setup(t, same)
f.digestFile = kind
_, st := runBuild(t, m, states, map[string]any{"publish": true})
if st.State != delivery.StepFailed || !strings.Contains(st.Reason, "digest") {
t.Errorf("%s: step = %+v, want failed naming the digest file", kind, st)
}
}
}