GitOps for k8s
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

client.go 4.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. package registry
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. officialMemcache "github.com/bradfitz/gomemcache/memcache"
  7. "github.com/go-kit/kit/log"
  8. wraperrors "github.com/pkg/errors"
  9. "github.com/weaveworks/flux"
  10. "github.com/weaveworks/flux/registry/memcache"
  11. "strings"
  12. "time"
  13. )
  14. // A client represents an entity that returns manifest and tags information.
  15. // It might be a chache, it might be a real registry.
  16. type Client interface {
  17. Tags(repository Repository) ([]string, error)
  18. Manifest(repository Repository, tag string) (flux.Image, error)
  19. Cancel()
  20. }
  21. // ---
  22. // An implementation of Client that represents a Remote registry.
  23. // E.g. docker hub.
  24. type Remote struct {
  25. Registry HerokuRegistryLibrary
  26. CancelFunc context.CancelFunc
  27. }
  28. // Return the tags for this repository.
  29. func (a *Remote) Tags(repository Repository) ([]string, error) {
  30. return a.Registry.Tags(repository.NamespaceImage())
  31. }
  32. // We need to do some adapting here to convert from the return values
  33. // from dockerregistry to our domain types.
  34. func (a *Remote) Manifest(repository Repository, tag string) (flux.Image, error) {
  35. img, err := flux.ParseImage(fmt.Sprintf("%s:%s", repository.String(), tag), time.Time{})
  36. if err != nil {
  37. return flux.Image{}, err
  38. }
  39. history, err := a.Registry.Manifest(repository.NamespaceImage(), tag)
  40. if err != nil || history == nil {
  41. return flux.Image{}, err
  42. }
  43. // the manifest includes some v1-backwards-compatibility data,
  44. // oddly called "History", which are layer metadata as JSON
  45. // strings; these appear most-recent (i.e., topmost layer) first,
  46. // so happily we can just decode the first entry to get a created
  47. // time.
  48. type v1image struct {
  49. Created time.Time `json:"created"`
  50. }
  51. var topmost v1image
  52. if len(history) > 0 {
  53. if err = json.Unmarshal([]byte(history[0].V1Compatibility), &topmost); err == nil {
  54. if !topmost.Created.IsZero() {
  55. img.CreatedAt = topmost.Created
  56. }
  57. }
  58. }
  59. return img, nil
  60. }
  61. // Cancel the remote request
  62. func (a *Remote) Cancel() {
  63. a.CancelFunc()
  64. }
  65. // ---
  66. // An implementation of Client backed by Memcache
  67. type Cache struct {
  68. creds Credentials
  69. expiry time.Duration
  70. Client memcache.MemcacheClient
  71. logger log.Logger
  72. }
  73. func (*Cache) Cancel() {
  74. return
  75. }
  76. func NewCache(creds Credentials, cache memcache.MemcacheClient, expiry time.Duration, logger log.Logger) Client {
  77. return &Cache{
  78. creds: creds,
  79. expiry: expiry,
  80. Client: cache,
  81. logger: logger,
  82. }
  83. }
  84. func (c *Cache) Manifest(repository Repository, tag string) (flux.Image, error) {
  85. img, err := flux.ParseImage(fmt.Sprintf("%s:%s", repository.String(), tag), time.Time{})
  86. if err != nil {
  87. return flux.Image{}, err
  88. }
  89. // Try the cache
  90. creds := c.creds.credsFor(repository.Host())
  91. key := manifestKey(creds.username, repository.String(), tag)
  92. cacheItem, err := c.Client.Get(key)
  93. if err != nil {
  94. if err != officialMemcache.ErrCacheMiss {
  95. c.logger.Log("err", wraperrors.Wrap(err, "Fetching tag from memcache"))
  96. }
  97. return flux.Image{}, err
  98. }
  99. err = json.Unmarshal(cacheItem.Value, &img)
  100. if err != nil {
  101. c.logger.Log("err", err.Error)
  102. return flux.Image{}, err
  103. }
  104. return img, nil
  105. }
  106. func (c *Cache) Tags(repository Repository) (tags []string, err error) {
  107. repo, err := ParseRepository(repository.String())
  108. if err != nil {
  109. c.logger.Log("err", wraperrors.Wrap(err, "Parsing repository"))
  110. return
  111. }
  112. creds := c.creds.credsFor(repo.Host())
  113. // Try the cache
  114. key := tagKey(creds.username, repo.String())
  115. cacheItem, err := c.Client.Get(key)
  116. if err != nil {
  117. if err != officialMemcache.ErrCacheMiss {
  118. c.logger.Log("err", wraperrors.Wrap(err, "Fetching tag from memcache"))
  119. }
  120. return
  121. }
  122. // Return the cache item
  123. err = json.Unmarshal(cacheItem.Value, &tags)
  124. if err != nil {
  125. c.logger.Log("err", err.Error)
  126. return
  127. }
  128. return
  129. }
  130. func manifestKey(username, repository, reference string) string {
  131. return strings.Join([]string{
  132. "registryhistoryv1", // Just to version in case we need to change format later.
  133. // Just the username here means we won't invalidate the cache when user
  134. // changes password, but that should be rare. And, it also means we're not
  135. // putting user passwords in plaintext into memcache.
  136. username,
  137. repository,
  138. reference,
  139. }, "|")
  140. }
  141. func tagKey(username, repository string) string {
  142. return strings.Join([]string{
  143. "registrytagsv1", // Just to version in case we need to change format later.
  144. // Just the username here means we won't invalidate the cache when user
  145. // changes password, but that should be rare. And, it also means we're not
  146. // putting user passwords in plaintext into memcache.
  147. username,
  148. repository,
  149. }, "|")
  150. }