package proxy import ( "context" "reflect" "sort" "sync" "testing" "github.com/docker/distribution" ) type mockTagStore struct { mapping map[string]distribution.Descriptor sync.Mutex } var _ distribution.TagService = &mockTagStore{} func (m *mockTagStore) Get(ctx context.Context, tag string) (distribution.Descriptor, error) { m.Lock() defer m.Unlock() if d, ok := m.mapping[tag]; ok { return d, nil } return distribution.Descriptor{}, distribution.ErrTagUnknown{} } func (m *mockTagStore) Tag(ctx context.Context, tag string, desc distribution.Descriptor) error { m.Lock() defer m.Unlock() m.mapping[tag] = desc return nil } func (m *mockTagStore) Untag(ctx context.Context, tag string) error { m.Lock() defer m.Unlock() if _, ok := m.mapping[tag]; ok { delete(m.mapping, tag) return nil } return distribution.ErrTagUnknown{} } func (m *mockTagStore) All(ctx context.Context) ([]string, error) { m.Lock() defer m.Unlock() var tags []string for tag := range m.mapping { tags = append(tags, tag) } return tags, nil } func (m *mockTagStore) Lookup(ctx context.Context, digest distribution.Descriptor) ([]string, error) { panic("not implemented") } func testProxyTagService(local, remote map[string]distribution.Descriptor) *proxyTagService { if local == nil { local = make(map[string]distribution.Descriptor) } if remote == nil { remote = make(map[string]distribution.Descriptor) } return &proxyTagService{ localTags: &mockTagStore{mapping: local}, remoteTags: &mockTagStore{mapping: remote}, authChallenger: &mockChallenger{}, } } func TestGet(t *testing.T) { remoteDesc := distribution.Descriptor{Size: 42} remoteTag := "remote" proxyTags := testProxyTagService(map[string]distribution.Descriptor{remoteTag: remoteDesc}, nil) ctx := context.Background() // Get pre-loaded tag d, err := proxyTags.Get(ctx, remoteTag) if err != nil { t.Fatal(err) } if proxyTags.authChallenger.(*mockChallenger).count != 1 { t.Fatalf("Expected 1 auth challenge call, got %#v", proxyTags.authChallenger) } if !reflect.DeepEqual(d, remoteDesc) { t.Fatal("unable to get put tag") } local, err := proxyTags.localTags.Get(ctx, remoteTag) if err != nil { t.Fatal("remote tag not pulled into store") } if !reflect.DeepEqual(local, remoteDesc) { t.Fatalf("unexpected descriptor pulled through") } // Manually overwrite remote tag newRemoteDesc := distribution.Descriptor{Size: 43} err = proxyTags.remoteTags.Tag(ctx, remoteTag, newRemoteDesc) if err != nil { t.Fatal(err) } d, err = proxyTags.Get(ctx, remoteTag) if err != nil { t.Fatal(err) } if proxyTags.authChallenger.(*mockChallenger).count != 2 { t.Fatalf("Expected 2 auth challenge calls, got %#v", proxyTags.authChallenger) } if !reflect.DeepEqual(d, newRemoteDesc) { t.Fatal("unable to get put tag") } _, err = proxyTags.localTags.Get(ctx, remoteTag) if err != nil { t.Fatal("remote tag not pulled into store") } // untag, ensure it's removed locally, but present in remote err = proxyTags.Untag(ctx, remoteTag) if err != nil { t.Fatal(err) } _, err = proxyTags.localTags.Get(ctx, remoteTag) if err == nil { t.Fatalf("Expected error getting Untag'd tag") } _, err = proxyTags.remoteTags.Get(ctx, remoteTag) if err != nil { t.Fatalf("remote tag should not be untagged with proxyTag.Untag") } _, err = proxyTags.Get(ctx, remoteTag) if err != nil { t.Fatal("untagged tag should be pulled through") } if proxyTags.authChallenger.(*mockChallenger).count != 3 { t.Fatalf("Expected 3 auth challenge calls, got %#v", proxyTags.authChallenger) } // Add another tag. Ensure both tags appear in 'All' err = proxyTags.remoteTags.Tag(ctx, "funtag", distribution.Descriptor{Size: 42}) if err != nil { t.Fatal(err) } all, err := proxyTags.All(ctx) if err != nil { t.Fatal(err) } if len(all) != 2 { t.Fatalf("Unexpected tag length returned from All() : %d ", len(all)) } sort.Strings(all) if all[0] != "funtag" && all[1] != "remote" { t.Fatalf("Unexpected tags returned from All() : %v ", all) } if proxyTags.authChallenger.(*mockChallenger).count != 4 { t.Fatalf("Expected 4 auth challenge calls, got %#v", proxyTags.authChallenger) } }