forked from TrueCloudLab/distribution
e0281dc609
gofumpt (https://github.com/mvdan/gofumpt) provides a supserset of `gofmt` / `go fmt`, and addresses various formatting issues that linters may be checking for. We can consider enabling the `gofumpt` linter to verify the formatting in CI, although not every developer may have it installed, so for now this runs it once to get formatting in shape. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
274 lines
7.5 KiB
Go
274 lines
7.5 KiB
Go
package proxy
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/distribution/distribution/v3"
|
|
"github.com/distribution/distribution/v3/manifest"
|
|
"github.com/distribution/distribution/v3/manifest/schema1"
|
|
"github.com/distribution/distribution/v3/reference"
|
|
"github.com/distribution/distribution/v3/registry/client/auth"
|
|
"github.com/distribution/distribution/v3/registry/client/auth/challenge"
|
|
"github.com/distribution/distribution/v3/registry/proxy/scheduler"
|
|
"github.com/distribution/distribution/v3/registry/storage"
|
|
"github.com/distribution/distribution/v3/registry/storage/cache/memory"
|
|
"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
|
|
"github.com/distribution/distribution/v3/testutil"
|
|
"github.com/docker/libtrust"
|
|
"github.com/opencontainers/go-digest"
|
|
)
|
|
|
|
type statsManifest struct {
|
|
manifests distribution.ManifestService
|
|
stats map[string]int
|
|
}
|
|
|
|
type manifestStoreTestEnv struct {
|
|
manifestDigest digest.Digest // digest of the signed manifest in the local storage
|
|
manifests proxyManifestStore
|
|
}
|
|
|
|
func (te manifestStoreTestEnv) LocalStats() *map[string]int {
|
|
ls := te.manifests.localManifests.(statsManifest).stats
|
|
return &ls
|
|
}
|
|
|
|
func (te manifestStoreTestEnv) RemoteStats() *map[string]int {
|
|
rs := te.manifests.remoteManifests.(statsManifest).stats
|
|
return &rs
|
|
}
|
|
|
|
func (sm statsManifest) Delete(ctx context.Context, dgst digest.Digest) error {
|
|
sm.stats["delete"]++
|
|
return sm.manifests.Delete(ctx, dgst)
|
|
}
|
|
|
|
func (sm statsManifest) Exists(ctx context.Context, dgst digest.Digest) (bool, error) {
|
|
sm.stats["exists"]++
|
|
return sm.manifests.Exists(ctx, dgst)
|
|
}
|
|
|
|
func (sm statsManifest) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) {
|
|
sm.stats["get"]++
|
|
return sm.manifests.Get(ctx, dgst)
|
|
}
|
|
|
|
func (sm statsManifest) Put(ctx context.Context, manifest distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) {
|
|
sm.stats["put"]++
|
|
return sm.manifests.Put(ctx, manifest)
|
|
}
|
|
|
|
type mockChallenger struct {
|
|
sync.Mutex
|
|
count int
|
|
}
|
|
|
|
// Called for remote operations only
|
|
func (m *mockChallenger) tryEstablishChallenges(context.Context) error {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
m.count++
|
|
return nil
|
|
}
|
|
|
|
func (m *mockChallenger) credentialStore() auth.CredentialStore {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockChallenger) challengeManager() challenge.Manager {
|
|
return nil
|
|
}
|
|
|
|
func newManifestStoreTestEnv(t *testing.T, name, tag string) *manifestStoreTestEnv {
|
|
nameRef, err := reference.WithName(name)
|
|
if err != nil {
|
|
t.Fatalf("unable to parse reference: %s", err)
|
|
}
|
|
k, err := libtrust.GenerateECP256PrivateKey()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
truthRegistry, err := storage.NewRegistry(ctx, inmemory.New(),
|
|
storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider(memory.UnlimitedSize)),
|
|
storage.Schema1SigningKey(k),
|
|
storage.EnableSchema1)
|
|
if err != nil {
|
|
t.Fatalf("error creating registry: %v", err)
|
|
}
|
|
truthRepo, err := truthRegistry.Repository(ctx, nameRef)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error getting repo: %v", err)
|
|
}
|
|
tr, err := truthRepo.Manifests(ctx)
|
|
if err != nil {
|
|
t.Fatal(err.Error())
|
|
}
|
|
truthManifests := statsManifest{
|
|
manifests: tr,
|
|
stats: make(map[string]int),
|
|
}
|
|
|
|
manifestDigest, err := populateRepo(ctx, t, truthRepo, name, tag)
|
|
if err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
localRegistry, err := storage.NewRegistry(ctx, inmemory.New(), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider(memory.UnlimitedSize)), storage.EnableRedirect, storage.DisableDigestResumption, storage.Schema1SigningKey(k), storage.EnableSchema1)
|
|
if err != nil {
|
|
t.Fatalf("error creating registry: %v", err)
|
|
}
|
|
localRepo, err := localRegistry.Repository(ctx, nameRef)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error getting repo: %v", err)
|
|
}
|
|
lr, err := localRepo.Manifests(ctx)
|
|
if err != nil {
|
|
t.Fatal(err.Error())
|
|
}
|
|
|
|
localManifests := statsManifest{
|
|
manifests: lr,
|
|
stats: make(map[string]int),
|
|
}
|
|
|
|
s := scheduler.New(ctx, inmemory.New(), "/scheduler-state.json")
|
|
return &manifestStoreTestEnv{
|
|
manifestDigest: manifestDigest,
|
|
manifests: proxyManifestStore{
|
|
ctx: ctx,
|
|
localManifests: localManifests,
|
|
remoteManifests: truthManifests,
|
|
scheduler: s,
|
|
repositoryName: nameRef,
|
|
authChallenger: &mockChallenger{},
|
|
},
|
|
}
|
|
}
|
|
|
|
func populateRepo(ctx context.Context, t *testing.T, repository distribution.Repository, name, tag string) (digest.Digest, error) {
|
|
m := schema1.Manifest{
|
|
Versioned: manifest.Versioned{
|
|
SchemaVersion: 1,
|
|
},
|
|
Name: name,
|
|
Tag: tag,
|
|
}
|
|
|
|
for i := 0; i < 2; i++ {
|
|
wr, err := repository.Blobs(ctx).Create(ctx)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error creating test upload: %v", err)
|
|
}
|
|
|
|
rs, dgst, err := testutil.CreateRandomTarFile()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error generating test layer file")
|
|
}
|
|
if _, err := io.Copy(wr, rs); err != nil {
|
|
t.Fatalf("unexpected error copying to upload: %v", err)
|
|
}
|
|
|
|
if _, err := wr.Commit(ctx, distribution.Descriptor{Digest: dgst}); err != nil {
|
|
t.Fatalf("unexpected error finishing upload: %v", err)
|
|
}
|
|
}
|
|
|
|
pk, err := libtrust.GenerateECP256PrivateKey()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error generating private key: %v", err)
|
|
}
|
|
|
|
sm, err := schema1.Sign(&m, pk)
|
|
if err != nil {
|
|
t.Fatalf("error signing manifest: %v", err)
|
|
}
|
|
|
|
ms, err := repository.Manifests(ctx)
|
|
if err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
dgst, err := ms.Put(ctx, sm)
|
|
if err != nil {
|
|
t.Fatalf("unexpected errors putting manifest: %v", err)
|
|
}
|
|
|
|
return dgst, nil
|
|
}
|
|
|
|
// TestProxyManifests contains basic acceptance tests
|
|
// for the pull-through behavior
|
|
func TestProxyManifests(t *testing.T) {
|
|
name := "foo/bar"
|
|
env := newManifestStoreTestEnv(t, name, "latest")
|
|
|
|
localStats := env.LocalStats()
|
|
remoteStats := env.RemoteStats()
|
|
|
|
ctx := context.Background()
|
|
// Stat - must check local and remote
|
|
exists, err := env.manifests.Exists(ctx, env.manifestDigest)
|
|
if err != nil {
|
|
t.Fatalf("Error checking existence")
|
|
}
|
|
if !exists {
|
|
t.Errorf("Unexpected non-existent manifest")
|
|
}
|
|
|
|
if (*localStats)["exists"] != 1 && (*remoteStats)["exists"] != 1 {
|
|
t.Errorf("Unexpected exists count : \n%v \n%v", localStats, remoteStats)
|
|
}
|
|
|
|
if env.manifests.authChallenger.(*mockChallenger).count != 1 {
|
|
t.Fatalf("Expected 1 auth challenge, got %#v", env.manifests.authChallenger)
|
|
}
|
|
|
|
// Get - should succeed and pull manifest into local
|
|
_, err = env.manifests.Get(ctx, env.manifestDigest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if (*localStats)["get"] != 1 && (*remoteStats)["get"] != 1 {
|
|
t.Errorf("Unexpected get count")
|
|
}
|
|
|
|
if (*localStats)["put"] != 1 {
|
|
t.Errorf("Expected local put")
|
|
}
|
|
|
|
if env.manifests.authChallenger.(*mockChallenger).count != 2 {
|
|
t.Fatalf("Expected 2 auth challenges, got %#v", env.manifests.authChallenger)
|
|
}
|
|
|
|
// Stat - should only go to local
|
|
exists, err = env.manifests.Exists(ctx, env.manifestDigest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !exists {
|
|
t.Errorf("Unexpected non-existent manifest")
|
|
}
|
|
|
|
if (*localStats)["exists"] != 2 && (*remoteStats)["exists"] != 1 {
|
|
t.Errorf("Unexpected exists count")
|
|
}
|
|
|
|
if env.manifests.authChallenger.(*mockChallenger).count != 2 {
|
|
t.Fatalf("Expected 2 auth challenges, got %#v", env.manifests.authChallenger)
|
|
}
|
|
|
|
// Get proxied - won't require another authchallenge
|
|
_, err = env.manifests.Get(ctx, env.manifestDigest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if env.manifests.authChallenger.(*mockChallenger).count != 2 {
|
|
t.Fatalf("Expected 2 auth challenges, got %#v", env.manifests.authChallenger)
|
|
}
|
|
}
|