124 lines
4.8 KiB
Go
124 lines
4.8 KiB
Go
package builder
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http/httptest"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// A built image's bases are linked into its repository before it is pushed (novox/hq issue 694): a
|
|
// runtime keeping containerd's image store may not hold a base layer it never had to fetch, and its push
|
|
// asks this machine only for what the repository lacks.
|
|
|
|
// aBaseForThisMachine puts an index into the registry under `repository`: one manifest for this
|
|
// machine's platform, one for another, and answers the index's digest and each manifest's blobs.
|
|
func aBaseForThisMachine(t *testing.T, dst *aRegistryOfRepositories, repository string) (string, []string, []string) {
|
|
t.Helper()
|
|
put := func(arch string) (string, []string) {
|
|
config := []byte(`{"architecture":"` + arch + `"}`)
|
|
layer := []byte(arch + " layer bytes")
|
|
for _, b := range [][]byte{config, layer} {
|
|
dst.blobs[digestOf(b)] = b
|
|
dst.link(repository, digestOf(b))
|
|
}
|
|
m, _ := json.Marshal(map[string]any{
|
|
"schemaVersion": 2, "mediaType": mediaManifestOCI,
|
|
"config": map[string]any{"mediaType": "application/vnd.oci.image.config.v1+json", "digest": digestOf(config), "size": len(config)},
|
|
"layers": []map[string]any{{"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "digest": digestOf(layer), "size": len(layer)}},
|
|
})
|
|
dst.manifests[repository+"@"+digestOf(m)] = m
|
|
return digestOf(m), []string{digestOf(config), digestOf(layer)}
|
|
}
|
|
other := "s390x"
|
|
if runtime.GOARCH == other {
|
|
other = "amd64"
|
|
}
|
|
mine, mineBlobs := put(runtime.GOARCH)
|
|
theirs, theirBlobs := put(other)
|
|
index, _ := json.Marshal(map[string]any{
|
|
"schemaVersion": 2, "mediaType": mediaIndexOCI,
|
|
"manifests": []map[string]any{
|
|
{"mediaType": mediaManifestOCI, "digest": mine, "size": 1, "platform": map[string]string{"os": runtime.GOOS, "architecture": runtime.GOARCH}},
|
|
{"mediaType": mediaManifestOCI, "digest": theirs, "size": 1, "platform": map[string]string{"os": "linux", "architecture": other}},
|
|
},
|
|
})
|
|
dst.manifests[repository+"@"+digestOf(index)] = index
|
|
return digestOf(index), mineBlobs, theirBlobs
|
|
}
|
|
|
|
func TestABuildsBaseIsLinkedIntoItsRepositoryBeforeThePush(t *testing.T) {
|
|
dst := newRegistryOfRepositories()
|
|
server := httptest.NewServer(dst.handler())
|
|
defer server.Close()
|
|
address := strings.TrimPrefix(server.URL, "http://")
|
|
r := Registry{Address: address}
|
|
|
|
const from = "upstream/docker.io/library/golang"
|
|
index, mine, theirs := aBaseForThisMachine(t, dst, from)
|
|
bases := []string{
|
|
address + "/" + from + "@" + index,
|
|
// Not bases this registry holds: a package version, a tag, an image held nowhere here.
|
|
"@novox/mesh-sdk@1.2.3",
|
|
"golang:1-alpine",
|
|
"elsewhere.invalid/thing@sha256:" + strings.Repeat("e", 64),
|
|
}
|
|
n, err := r.LendBases(context.Background(), "mesh-tools-go/build", bases)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Every blob of this machine's platform is in the image's repository, so a push asks this machine's
|
|
// store for none of them; another platform's are not, since nothing built here stands on them.
|
|
for _, blob := range mine {
|
|
if !dst.links["mesh-tools-go/build"][blob] {
|
|
t.Fatalf("the base's blob %s is not in the image's repository", blob)
|
|
}
|
|
}
|
|
for _, blob := range theirs {
|
|
if dst.links["mesh-tools-go/build"][blob] {
|
|
t.Fatalf("another platform's blob %s was linked", blob)
|
|
}
|
|
}
|
|
if n != len(mine) || dst.mounts != len(mine) || dst.uploads != 0 {
|
|
t.Fatalf("%d linked, %d mounts, %d uploads; want %d mounts and no bytes moved", n, dst.mounts, dst.uploads, len(mine))
|
|
}
|
|
|
|
// Asked again, nothing is mounted twice.
|
|
if n, err := r.LendBases(context.Background(), "mesh-tools-go/build", bases); err != nil || n != 0 {
|
|
t.Fatalf("a second lending mounted %d: %v", n, err)
|
|
}
|
|
}
|
|
|
|
// A registry that will not mount is said, not hidden: the push is still tried, from the store.
|
|
func TestABaseTheRegistryWillNotMountIsAnError(t *testing.T) {
|
|
dst := newRegistryOfRepositories()
|
|
dst.noMount = true
|
|
server := httptest.NewServer(dst.handler())
|
|
defer server.Close()
|
|
address := strings.TrimPrefix(server.URL, "http://")
|
|
index, _, _ := aBaseForThisMachine(t, dst, "upstream/thing")
|
|
_, err := Registry{Address: address}.LendBases(context.Background(), "hello/server",
|
|
[]string{address + "/upstream/thing@" + index})
|
|
if err == nil || !strings.Contains(err.Error(), "did not mount") {
|
|
t.Fatalf("a refused mount passed silently: %v", err)
|
|
}
|
|
}
|
|
|
|
// lending records what a build lent, and when, against the push.
|
|
type lending struct {
|
|
*mirroring
|
|
order []string
|
|
}
|
|
|
|
func (l *lending) LendBases(_ context.Context, repository string, bases []string) (int, error) {
|
|
l.order = append(l.order, "lend "+repository+" "+strings.Join(bases, " "))
|
|
return len(bases), nil
|
|
}
|
|
|
|
func (l *lending) PublishImage(ctx context.Context, localTag, repository string) (string, error) {
|
|
l.order = append(l.order, "push "+repository)
|
|
return l.mirroring.PublishImage(ctx, localTag, repository)
|
|
}
|