| 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/docker/distribution/registry/client/auth" |
| "github.com/pkg/errors" |
| "github.com/sirupsen/logrus" |
| ) |
| |
| var acceptedSearchFilterTags = map[string]bool{ |
| "is-automated": true, |
| "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 |
| } |
| |
| 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 |
| } |
| } |
| } |
| |
| 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 { |
| 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 |
| |
| logrus.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) |
| } |