registry: remove v1 leftovers, and refactor to reduce public api/interface
This is a follow-up to https://github.com/moby/moby/pull/43183

The registry had a large "public" API, large parts of which were related to previous support of both V1 and V2 registries, and (no longer) used. This pull request reduces the public API of this package, and removes un-used bits (as well as general code cleanup, and some improvements around locking).

More changes will have to be done after this; this package is currently used both for registry interactions (push/pull) as well as to provide "search" (to back the `docker search` command). Searching was part of the V1 registry spec, but not part of the OCI distribution spec v2, however, is still provided by Docker Hub (and could be implemented by private/custom registries). Docker Hub already provides newer search APIs (V2 and V3), which are proprietary (but providing API docs is on the roadmap), and we may want to switch to those APIs at some point (to be decided wether that should be through the daemon, or a pure client-side implementation).

That said, we should (at least for now) preserve the functionality to use V1 search  (for backward compatibility, and to allow search for private/custom registries), but should separate the search code out from the main registry package.

This PR is a stepping-stone for that; making more code "private", and reducing  this package's public API will make it easier to refactor, and gives a clearer picture of "what's used" and what not.

Here's the "before" and "after" of the package's public API 

```bash
GO111MODULE=off godoc -http=localhost:6060

# then visit : http://localhost:6060/pkg/github.com/docker/docker/registry/
```

### before

<img width="1233" alt="before" src="https://user-images.githubusercontent.com/1804568/155883111-dc494847-76f4-4e3d-a891-ae13dc7ed63e.png">

### after

<img width="829" alt="after" src="https://user-images.githubusercontent.com/1804568/155883115-24796480-8b03-4ba5-b5b1-cf8b0fd06187.png">



**- Description for the changelog**
<!--
Write a short (one line) summary that describes the changes in this
pull request for inclusion in the changelog:
-->


**- A picture of a cute animal (not mandatory but encouraged)**


daemon/images/image_search_test.go
package images // import "github.com/docker/docker/daemon/images"

import (
	"context"
	"errors"
	"strings"
	"testing"

	"github.com/docker/docker/api/types"
	registrytypes "github.com/docker/docker/api/types/registry"
	"github.com/docker/docker/registry"
)

type FakeService struct {
	registry.DefaultService

	shouldReturnError bool

	term    string
	results []registrytypes.SearchResult
}

func (s *FakeService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
	if s.shouldReturnError {
		return nil, errors.New("Search unknown error")
	}
	return &registrytypes.SearchResults{
		Query:      s.term,
		NumResults: len(s.results),
		Results:    s.results,
	}, nil
}

func TestSearchRegistryForImagesErrors(t *testing.T) {
	errorCases := []struct {
		filtersArgs       string
		shouldReturnError bool
		expectedError     string
	}{
		{
			expectedError:     "Search unknown error",
			shouldReturnError: true,
		},
		{
			filtersArgs:   "invalid json",
			expectedError: "invalid character 'i' looking for beginning of value",
		},
		{
			filtersArgs:   `{"type":{"custom":true}}`,
			expectedError: "Invalid filter 'type'",
		},
		{
			filtersArgs:   `{"is-automated":{"invalid":true}}`,
			expectedError: "Invalid filter 'is-automated=[invalid]'",
		},
		{
			filtersArgs:   `{"is-automated":{"true":true,"false":true}}`,
			expectedError: "Invalid filter 'is-automated",
		},
		{
			filtersArgs:   `{"is-official":{"invalid":true}}`,
			expectedError: "Invalid filter 'is-official=[invalid]'",
		},
		{
			filtersArgs:   `{"is-official":{"true":true,"false":true}}`,
			expectedError: "Invalid filter 'is-official",
		},
		{
			filtersArgs:   `{"stars":{"invalid":true}}`,
			expectedError: "Invalid filter 'stars=invalid'",
		},
		{
			filtersArgs:   `{"stars":{"1":true,"invalid":true}}`,
			expectedError: "Invalid filter 'stars=invalid'",
		},
	}
	for index, e := range errorCases {
		daemon := &ImageService{
			registryService: &FakeService{
				shouldReturnError: e.shouldReturnError,
			},
		}
		_, err := daemon.SearchRegistryForImages(context.Background(), e.filtersArgs, "term", 25, nil, map[string][]string{})
		if err == nil {
			t.Errorf("%d: expected an error, got nothing", index)
		}
		if !strings.Contains(err.Error(), e.expectedError) {
			t.Errorf("%d: expected error to contain %s, got %s", index, e.expectedError, err.Error())
		}
	}
}

func TestSearchRegistryForImages(t *testing.T) {
	term := "term"
	successCases := []struct {
		filtersArgs     string
		registryResults []registrytypes.SearchResult
		expectedResults []registrytypes.SearchResult
	}{
		{
			filtersArgs:     "",
			registryResults: []registrytypes.SearchResult{},
			expectedResults: []registrytypes.SearchResult{},
		},
		{
			filtersArgs: "",
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
				},
			},
			expectedResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
				},
			},
		},
		{
			filtersArgs: `{"is-automated":{"true":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
				},
			},
			expectedResults: []registrytypes.SearchResult{},
		},
		{
			filtersArgs: `{"is-automated":{"true":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsAutomated: true,
				},
			},
			expectedResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsAutomated: true,
				},
			},
		},
		{
			filtersArgs: `{"is-automated":{"false":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsAutomated: true,
				},
			},
			expectedResults: []registrytypes.SearchResult{},
		},
		{
			filtersArgs: `{"is-automated":{"false":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsAutomated: false,
				},
			},
			expectedResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsAutomated: false,
				},
			},
		},
		{
			filtersArgs: `{"is-official":{"true":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
				},
			},
			expectedResults: []registrytypes.SearchResult{},
		},
		{
			filtersArgs: `{"is-official":{"true":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsOfficial:  true,
				},
			},
			expectedResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsOfficial:  true,
				},
			},
		},
		{
			filtersArgs: `{"is-official":{"false":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsOfficial:  true,
				},
			},
			expectedResults: []registrytypes.SearchResult{},
		},
		{
			filtersArgs: `{"is-official":{"false":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsOfficial:  false,
				},
			},
			expectedResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsOfficial:  false,
				},
			},
		},
		{
			filtersArgs: `{"stars":{"0":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					StarCount:   0,
				},
			},
			expectedResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					StarCount:   0,
				},
			},
		},
		{
			filtersArgs: `{"stars":{"1":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					StarCount:   0,
				},
			},
			expectedResults: []registrytypes.SearchResult{},
		},
		{
			filtersArgs: `{"stars":{"1":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name0",
					Description: "description0",
					StarCount:   0,
				},
				{
					Name:        "name1",
					Description: "description1",
					StarCount:   1,
				},
			},
			expectedResults: []registrytypes.SearchResult{
				{
					Name:        "name1",
					Description: "description1",
					StarCount:   1,
				},
			},
		},
		{
			filtersArgs: `{"stars":{"1":true}, "is-official":{"true":true}, "is-automated":{"true":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name0",
					Description: "description0",
					StarCount:   0,
					IsOfficial:  true,
					IsAutomated: true,
				},
				{
					Name:        "name1",
					Description: "description1",
					StarCount:   1,
					IsOfficial:  false,
					IsAutomated: true,
				},
				{
					Name:        "name2",
					Description: "description2",
					StarCount:   1,
					IsOfficial:  true,
					IsAutomated: false,
				},
				{
					Name:        "name3",
					Description: "description3",
					StarCount:   2,
					IsOfficial:  true,
					IsAutomated: true,
				},
			},
			expectedResults: []registrytypes.SearchResult{
				{
					Name:        "name3",
					Description: "description3",
					StarCount:   2,
					IsOfficial:  true,
					IsAutomated: true,
				},
			},
		},
	}
	for index, s := range successCases {
		daemon := &ImageService{
			registryService: &FakeService{
				term:    term,
				results: s.registryResults,
			},
		}
		results, err := daemon.SearchRegistryForImages(context.Background(), s.filtersArgs, term, 25, nil, map[string][]string{})
		if err != nil {
			t.Errorf("%d: %v", index, err)
		}
		if results.Query != term {
			t.Errorf("%d: expected Query to be %s, got %s", index, term, results.Query)
		}
		if results.NumResults != len(s.expectedResults) {
			t.Errorf("%d: expected NumResults to be %d, got %d", index, len(s.expectedResults), results.NumResults)
		}
		for _, result := range results.Results {
			found := false
			for _, expectedResult := range s.expectedResults {
				if expectedResult.Name == result.Name &&
					expectedResult.Description == result.Description &&
					expectedResult.IsAutomated == result.IsAutomated &&
					expectedResult.IsOfficial == result.IsOfficial &&
					expectedResult.StarCount == result.StarCount {
					found = true
					break
				}
			}
			if !found {
				t.Errorf("%d: expected results %v, got %v", index, s.expectedResults, results.Results)
			}
		}
	}
}

package images // import "github.com/docker/docker/daemon/images"

import (
	"context"
	"errors"
	"strings"
	"testing"

	"github.com/docker/docker/api/types"
	registrytypes "github.com/docker/docker/api/types/registry"
	"github.com/docker/docker/registry"
)

type fakeService struct {
	registry.Service
	shouldReturnError bool

	term    string
	results []registrytypes.SearchResult
}

func (s *fakeService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
	if s.shouldReturnError {
		return nil, errors.New("Search unknown error")
	}
	return &registrytypes.SearchResults{
		Query:      s.term,
		NumResults: len(s.results),
		Results:    s.results,
	}, nil
}

func TestSearchRegistryForImagesErrors(t *testing.T) {
	errorCases := []struct {
		filtersArgs       string
		shouldReturnError bool
		expectedError     string
	}{
		{
			expectedError:     "Search unknown error",
			shouldReturnError: true,
		},
		{
			filtersArgs:   "invalid json",
			expectedError: "invalid character 'i' looking for beginning of value",
		},
		{
			filtersArgs:   `{"type":{"custom":true}}`,
			expectedError: "Invalid filter 'type'",
		},
		{
			filtersArgs:   `{"is-automated":{"invalid":true}}`,
			expectedError: "Invalid filter 'is-automated=[invalid]'",
		},
		{
			filtersArgs:   `{"is-automated":{"true":true,"false":true}}`,
			expectedError: "Invalid filter 'is-automated",
		},
		{
			filtersArgs:   `{"is-official":{"invalid":true}}`,
			expectedError: "Invalid filter 'is-official=[invalid]'",
		},
		{
			filtersArgs:   `{"is-official":{"true":true,"false":true}}`,
			expectedError: "Invalid filter 'is-official",
		},
		{
			filtersArgs:   `{"stars":{"invalid":true}}`,
			expectedError: "Invalid filter 'stars=invalid'",
		},
		{
			filtersArgs:   `{"stars":{"1":true,"invalid":true}}`,
			expectedError: "Invalid filter 'stars=invalid'",
		},
	}
	for index, e := range errorCases {
		daemon := &ImageService{
			registryService: &fakeService{
				shouldReturnError: e.shouldReturnError,
			},
		}
		_, err := daemon.SearchRegistryForImages(context.Background(), e.filtersArgs, "term", 25, nil, map[string][]string{})
		if err == nil {
			t.Errorf("%d: expected an error, got nothing", index)
		}
		if !strings.Contains(err.Error(), e.expectedError) {
			t.Errorf("%d: expected error to contain %s, got %s", index, e.expectedError, err.Error())
		}
	}
}

func TestSearchRegistryForImages(t *testing.T) {
	term := "term"
	successCases := []struct {
		filtersArgs     string
		registryResults []registrytypes.SearchResult
		expectedResults []registrytypes.SearchResult
	}{
		{
			filtersArgs:     "",
			registryResults: []registrytypes.SearchResult{},
			expectedResults: []registrytypes.SearchResult{},
		},
		{
			filtersArgs: "",
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
				},
			},
			expectedResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
				},
			},
		},
		{
			filtersArgs: `{"is-automated":{"true":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
				},
			},
			expectedResults: []registrytypes.SearchResult{},
		},
		{
			filtersArgs: `{"is-automated":{"true":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsAutomated: true,
				},
			},
			expectedResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsAutomated: true,
				},
			},
		},
		{
			filtersArgs: `{"is-automated":{"false":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsAutomated: true,
				},
			},
			expectedResults: []registrytypes.SearchResult{},
		},
		{
			filtersArgs: `{"is-automated":{"false":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsAutomated: false,
				},
			},
			expectedResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsAutomated: false,
				},
			},
		},
		{
			filtersArgs: `{"is-official":{"true":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
				},
			},
			expectedResults: []registrytypes.SearchResult{},
		},
		{
			filtersArgs: `{"is-official":{"true":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsOfficial:  true,
				},
			},
			expectedResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsOfficial:  true,
				},
			},
		},
		{
			filtersArgs: `{"is-official":{"false":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsOfficial:  true,
				},
			},
			expectedResults: []registrytypes.SearchResult{},
		},
		{
			filtersArgs: `{"is-official":{"false":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsOfficial:  false,
				},
			},
			expectedResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					IsOfficial:  false,
				},
			},
		},
		{
			filtersArgs: `{"stars":{"0":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					StarCount:   0,
				},
			},
			expectedResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					StarCount:   0,
				},
			},
		},
		{
			filtersArgs: `{"stars":{"1":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name",
					Description: "description",
					StarCount:   0,
				},
			},
			expectedResults: []registrytypes.SearchResult{},
		},
		{
			filtersArgs: `{"stars":{"1":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name0",
					Description: "description0",
					StarCount:   0,
				},
				{
					Name:        "name1",
					Description: "description1",
					StarCount:   1,
				},
			},
			expectedResults: []registrytypes.SearchResult{
				{
					Name:        "name1",
					Description: "description1",
					StarCount:   1,
				},
			},
		},
		{
			filtersArgs: `{"stars":{"1":true}, "is-official":{"true":true}, "is-automated":{"true":true}}`,
			registryResults: []registrytypes.SearchResult{
				{
					Name:        "name0",
					Description: "description0",
					StarCount:   0,
					IsOfficial:  true,
					IsAutomated: true,
				},
				{
					Name:        "name1",
					Description: "description1",
					StarCount:   1,
					IsOfficial:  false,
					IsAutomated: true,
				},
				{
					Name:        "name2",
					Description: "description2",
					StarCount:   1,
					IsOfficial:  true,
					IsAutomated: false,
				},
				{
					Name:        "name3",
					Description: "description3",
					StarCount:   2,
					IsOfficial:  true,
					IsAutomated: true,
				},
			},
			expectedResults: []registrytypes.SearchResult{
				{
					Name:        "name3",
					Description: "description3",
					StarCount:   2,
					IsOfficial:  true,
					IsAutomated: true,
				},
			},
		},
	}
	for index, s := range successCases {
		daemon := &ImageService{
			registryService: &fakeService{
				term:    term,
				results: s.registryResults,
			},
		}
		results, err := daemon.SearchRegistryForImages(context.Background(), s.filtersArgs, term, 25, nil, map[string][]string{})
		if err != nil {
			t.Errorf("%d: %v", index, err)
		}
		if results.Query != term {
			t.Errorf("%d: expected Query to be %s, got %s", index, term, results.Query)
		}
		if results.NumResults != len(s.expectedResults) {
			t.Errorf("%d: expected NumResults to be %d, got %d", index, len(s.expectedResults), results.NumResults)
		}
		for _, result := range results.Results {
			found := false
			for _, expectedResult := range s.expectedResults {
				if expectedResult.Name == result.Name &&
					expectedResult.Description == result.Description &&
					expectedResult.IsAutomated == result.IsAutomated &&
					expectedResult.IsOfficial == result.IsOfficial &&
					expectedResult.StarCount == result.StarCount {
					found = true
					break
				}
			}
			if !found {
				t.Errorf("%d: expected results %v, got %v", index, s.expectedResults, results.Results)
			}
		}
	}
}

If the methods just need to exist on the type to satisfy an interface but are never expected to be called, you can embed the interface into the struct to avoid having to write method stubs. Calling the methods will panic, same as directly calling `MyInterface(nil).Method()`.
```suggestion
type fakeService struct {
	registry.Service
```