971083d419
The is-automated field is being deprecated by Docker Hub's search API, and will always be set to "false" in future. This patch deprecates the field and related filter for the Engine's API. In future, the `is-automated` filter will no longer yield any results when searching for `is-automated=true`, and will be ignored when searching for `is-automated=false`. Given that this field is deprecated by an external API, the deprecation will not be versioned, and will apply to any API version. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
141 lines
4.4 KiB
Go
141 lines
4.4 KiB
Go
package registry // import "github.com/docker/docker/registry"
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/api/types/filters"
|
|
"github.com/docker/docker/api/types/registry"
|
|
"github.com/docker/docker/errdefs"
|
|
|
|
"github.com/containerd/containerd/log"
|
|
"github.com/docker/distribution/registry/client/auth"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var acceptedSearchFilterTags = map[string]bool{
|
|
"is-automated": true, // Deprecated: the "is_automated" field is deprecated and will always be false in the future.
|
|
"is-official": true,
|
|
"stars": true,
|
|
}
|
|
|
|
// Search queries the public registry for repositories matching the specified
|
|
// search term and filters.
|
|
func (s *Service) Search(ctx context.Context, searchFilters filters.Args, term string, limit int, authConfig *registry.AuthConfig, headers map[string][]string) ([]registry.SearchResult, error) {
|
|
if err := searchFilters.Validate(acceptedSearchFilterTags); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO(thaJeztah): the "is-automated" field is deprecated; reset the field for the next release (v26.0.0). Return early when using "is-automated=true", and ignore "is-automated=false".
|
|
isAutomated, err := searchFilters.GetBoolOrDefault("is-automated", false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
isOfficial, err := searchFilters.GetBoolOrDefault("is-official", false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hasStarFilter := 0
|
|
if searchFilters.Contains("stars") {
|
|
hasStars := searchFilters.Get("stars")
|
|
for _, hasStar := range hasStars {
|
|
iHasStar, err := strconv.Atoi(hasStar)
|
|
if err != nil {
|
|
return nil, errdefs.InvalidParameter(errors.Wrapf(err, "invalid filter 'stars=%s'", hasStar))
|
|
}
|
|
if iHasStar > hasStarFilter {
|
|
hasStarFilter = iHasStar
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO(thaJeztah): the "is-automated" field is deprecated. Reset the field for the next release (v26.0.0) if any "true" values are present.
|
|
unfilteredResult, err := s.searchUnfiltered(ctx, term, limit, authConfig, headers)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
filteredResults := []registry.SearchResult{}
|
|
for _, result := range unfilteredResult.Results {
|
|
if searchFilters.Contains("is-automated") {
|
|
if isAutomated != result.IsAutomated { //nolint:staticcheck // ignore SA1019 for old API versions.
|
|
continue
|
|
}
|
|
}
|
|
if searchFilters.Contains("is-official") {
|
|
if isOfficial != result.IsOfficial {
|
|
continue
|
|
}
|
|
}
|
|
if searchFilters.Contains("stars") {
|
|
if result.StarCount < hasStarFilter {
|
|
continue
|
|
}
|
|
}
|
|
filteredResults = append(filteredResults, result)
|
|
}
|
|
|
|
return filteredResults, nil
|
|
}
|
|
|
|
func (s *Service) searchUnfiltered(ctx context.Context, term string, limit int, authConfig *registry.AuthConfig, headers http.Header) (*registry.SearchResults, error) {
|
|
// TODO Use ctx when searching for repositories
|
|
if hasScheme(term) {
|
|
return nil, invalidParamf("invalid repository name: repository name (%s) should not have a scheme", term)
|
|
}
|
|
|
|
indexName, remoteName := splitReposSearchTerm(term)
|
|
|
|
// Search is a long-running operation, just lock s.config to avoid block others.
|
|
s.mu.RLock()
|
|
index, err := newIndexInfo(s.config, indexName)
|
|
s.mu.RUnlock()
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if index.Official {
|
|
// If pull "library/foo", it's stored locally under "foo"
|
|
remoteName = strings.TrimPrefix(remoteName, "library/")
|
|
}
|
|
|
|
endpoint, err := newV1Endpoint(index, headers)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var client *http.Client
|
|
if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" {
|
|
creds := NewStaticCredentialStore(authConfig)
|
|
scopes := []auth.Scope{
|
|
auth.RegistryScope{
|
|
Name: "catalog",
|
|
Actions: []string{"search"},
|
|
},
|
|
}
|
|
|
|
// TODO(thaJeztah); is there a reason not to include other headers here? (originally added in 19d48f0b8ba59eea9f2cac4ad1c7977712a6b7ac)
|
|
modifiers := Headers(headers.Get("User-Agent"), nil)
|
|
v2Client, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Copy non transport http client features
|
|
v2Client.Timeout = endpoint.client.Timeout
|
|
v2Client.CheckRedirect = endpoint.client.CheckRedirect
|
|
v2Client.Jar = endpoint.client.Jar
|
|
|
|
log.G(ctx).Debugf("using v2 client for search to %s", endpoint.URL)
|
|
client = v2Client
|
|
} else {
|
|
client = endpoint.client
|
|
if err := authorizeClient(client, authConfig, endpoint); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return newSession(client, endpoint).searchRepositories(remoteName, limit)
|
|
}
|