157 lines
6.0 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|