search.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. package registry // import "github.com/docker/docker/registry"
  2. import (
  3. "context"
  4. "net/http"
  5. "strconv"
  6. "strings"
  7. "github.com/docker/docker/api/types/filters"
  8. "github.com/docker/docker/api/types/registry"
  9. "github.com/docker/docker/errdefs"
  10. "github.com/containerd/containerd/log"
  11. "github.com/docker/distribution/registry/client/auth"
  12. "github.com/pkg/errors"
  13. )
  14. var acceptedSearchFilterTags = map[string]bool{
  15. "is-automated": true,
  16. "is-official": true,
  17. "stars": true,
  18. }
  19. // Search queries the public registry for repositories matching the specified
  20. // search term and filters.
  21. func (s *Service) Search(ctx context.Context, searchFilters filters.Args, term string, limit int, authConfig *registry.AuthConfig, headers map[string][]string) ([]registry.SearchResult, error) {
  22. if err := searchFilters.Validate(acceptedSearchFilterTags); err != nil {
  23. return nil, err
  24. }
  25. isAutomated, err := searchFilters.GetBoolOrDefault("is-automated", false)
  26. if err != nil {
  27. return nil, err
  28. }
  29. isOfficial, err := searchFilters.GetBoolOrDefault("is-official", false)
  30. if err != nil {
  31. return nil, err
  32. }
  33. hasStarFilter := 0
  34. if searchFilters.Contains("stars") {
  35. hasStars := searchFilters.Get("stars")
  36. for _, hasStar := range hasStars {
  37. iHasStar, err := strconv.Atoi(hasStar)
  38. if err != nil {
  39. return nil, errdefs.InvalidParameter(errors.Wrapf(err, "invalid filter 'stars=%s'", hasStar))
  40. }
  41. if iHasStar > hasStarFilter {
  42. hasStarFilter = iHasStar
  43. }
  44. }
  45. }
  46. unfilteredResult, err := s.searchUnfiltered(ctx, term, limit, authConfig, headers)
  47. if err != nil {
  48. return nil, err
  49. }
  50. filteredResults := []registry.SearchResult{}
  51. for _, result := range unfilteredResult.Results {
  52. if searchFilters.Contains("is-automated") {
  53. if isAutomated != result.IsAutomated {
  54. continue
  55. }
  56. }
  57. if searchFilters.Contains("is-official") {
  58. if isOfficial != result.IsOfficial {
  59. continue
  60. }
  61. }
  62. if searchFilters.Contains("stars") {
  63. if result.StarCount < hasStarFilter {
  64. continue
  65. }
  66. }
  67. filteredResults = append(filteredResults, result)
  68. }
  69. return filteredResults, nil
  70. }
  71. func (s *Service) searchUnfiltered(ctx context.Context, term string, limit int, authConfig *registry.AuthConfig, headers http.Header) (*registry.SearchResults, error) {
  72. // TODO Use ctx when searching for repositories
  73. if hasScheme(term) {
  74. return nil, invalidParamf("invalid repository name: repository name (%s) should not have a scheme", term)
  75. }
  76. indexName, remoteName := splitReposSearchTerm(term)
  77. // Search is a long-running operation, just lock s.config to avoid block others.
  78. s.mu.RLock()
  79. index, err := newIndexInfo(s.config, indexName)
  80. s.mu.RUnlock()
  81. if err != nil {
  82. return nil, err
  83. }
  84. if index.Official {
  85. // If pull "library/foo", it's stored locally under "foo"
  86. remoteName = strings.TrimPrefix(remoteName, "library/")
  87. }
  88. endpoint, err := newV1Endpoint(index, headers)
  89. if err != nil {
  90. return nil, err
  91. }
  92. var client *http.Client
  93. if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" {
  94. creds := NewStaticCredentialStore(authConfig)
  95. scopes := []auth.Scope{
  96. auth.RegistryScope{
  97. Name: "catalog",
  98. Actions: []string{"search"},
  99. },
  100. }
  101. // TODO(thaJeztah); is there a reason not to include other headers here? (originally added in 19d48f0b8ba59eea9f2cac4ad1c7977712a6b7ac)
  102. modifiers := Headers(headers.Get("User-Agent"), nil)
  103. v2Client, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
  104. if err != nil {
  105. return nil, err
  106. }
  107. // Copy non transport http client features
  108. v2Client.Timeout = endpoint.client.Timeout
  109. v2Client.CheckRedirect = endpoint.client.CheckRedirect
  110. v2Client.Jar = endpoint.client.Jar
  111. log.G(ctx).Debugf("using v2 client for search to %s", endpoint.URL)
  112. client = v2Client
  113. } else {
  114. client = endpoint.client
  115. if err := authorizeClient(client, authConfig, endpoint); err != nil {
  116. return nil, err
  117. }
  118. }
  119. return newSession(client, endpoint).searchRepositories(remoteName, limit)
  120. }