github.com/npaton/distribution@v2.3.1-rc.0+incompatible/registry/proxy/proxyregistry.go (about) 1 package proxy 2 3 import ( 4 "fmt" 5 "net/http" 6 "net/url" 7 "sync" 8 9 "github.com/docker/distribution" 10 "github.com/docker/distribution/configuration" 11 "github.com/docker/distribution/context" 12 "github.com/docker/distribution/reference" 13 "github.com/docker/distribution/registry/client" 14 "github.com/docker/distribution/registry/client/auth" 15 "github.com/docker/distribution/registry/client/transport" 16 "github.com/docker/distribution/registry/proxy/scheduler" 17 "github.com/docker/distribution/registry/storage" 18 "github.com/docker/distribution/registry/storage/driver" 19 ) 20 21 // proxyingRegistry fetches content from a remote registry and caches it locally 22 type proxyingRegistry struct { 23 embedded distribution.Namespace // provides local registry functionality 24 scheduler *scheduler.TTLExpirationScheduler 25 remoteURL string 26 authChallenger authChallenger 27 } 28 29 // NewRegistryPullThroughCache creates a registry acting as a pull through cache 30 func NewRegistryPullThroughCache(ctx context.Context, registry distribution.Namespace, driver driver.StorageDriver, config configuration.Proxy) (distribution.Namespace, error) { 31 _, err := url.Parse(config.RemoteURL) 32 if err != nil { 33 return nil, err 34 } 35 36 v := storage.NewVacuum(ctx, driver) 37 s := scheduler.New(ctx, driver, "/scheduler-state.json") 38 s.OnBlobExpire(func(ref reference.Reference) error { 39 var r reference.Canonical 40 var ok bool 41 if r, ok = ref.(reference.Canonical); !ok { 42 return fmt.Errorf("unexpected reference type : %T", ref) 43 } 44 45 repo, err := registry.Repository(ctx, r) 46 if err != nil { 47 return err 48 } 49 50 blobs := repo.Blobs(ctx) 51 52 // Clear the repository reference and descriptor caches 53 err = blobs.Delete(ctx, r.Digest()) 54 if err != nil { 55 return err 56 } 57 58 err = v.RemoveBlob(r.Digest().String()) 59 if err != nil { 60 return err 61 } 62 63 return nil 64 }) 65 66 s.OnManifestExpire(func(ref reference.Reference) error { 67 var r reference.Canonical 68 var ok bool 69 if r, ok = ref.(reference.Canonical); !ok { 70 return fmt.Errorf("unexpected reference type : %T", ref) 71 } 72 73 repo, err := registry.Repository(ctx, r) 74 if err != nil { 75 return err 76 } 77 78 manifests, err := repo.Manifests(ctx) 79 if err != nil { 80 return err 81 } 82 err = manifests.Delete(ctx, r.Digest()) 83 if err != nil { 84 return err 85 } 86 return nil 87 }) 88 89 err = s.Start() 90 if err != nil { 91 return nil, err 92 } 93 94 cs, err := configureAuth(config.Username, config.Password) 95 if err != nil { 96 return nil, err 97 } 98 99 return &proxyingRegistry{ 100 embedded: registry, 101 scheduler: s, 102 remoteURL: config.RemoteURL, 103 authChallenger: &remoteAuthChallenger{ 104 remoteURL: config.RemoteURL, 105 cm: auth.NewSimpleChallengeManager(), 106 cs: cs, 107 }, 108 }, nil 109 } 110 111 func (pr *proxyingRegistry) Scope() distribution.Scope { 112 return distribution.GlobalScope 113 } 114 115 func (pr *proxyingRegistry) Repositories(ctx context.Context, repos []string, last string) (n int, err error) { 116 return pr.embedded.Repositories(ctx, repos, last) 117 } 118 119 func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named) (distribution.Repository, error) { 120 c := pr.authChallenger 121 122 tr := transport.NewTransport(http.DefaultTransport, 123 auth.NewAuthorizer(c.challengeManager(), auth.NewTokenHandler(http.DefaultTransport, c.credentialStore(), name.Name(), "pull"))) 124 125 localRepo, err := pr.embedded.Repository(ctx, name) 126 if err != nil { 127 return nil, err 128 } 129 localManifests, err := localRepo.Manifests(ctx, storage.SkipLayerVerification()) 130 if err != nil { 131 return nil, err 132 } 133 134 remoteRepo, err := client.NewRepository(ctx, name, pr.remoteURL, tr) 135 if err != nil { 136 return nil, err 137 } 138 139 remoteManifests, err := remoteRepo.Manifests(ctx) 140 if err != nil { 141 return nil, err 142 } 143 144 return &proxiedRepository{ 145 blobStore: &proxyBlobStore{ 146 localStore: localRepo.Blobs(ctx), 147 remoteStore: remoteRepo.Blobs(ctx), 148 scheduler: pr.scheduler, 149 repositoryName: name, 150 authChallenger: pr.authChallenger, 151 }, 152 manifests: &proxyManifestStore{ 153 repositoryName: name, 154 localManifests: localManifests, // Options? 155 remoteManifests: remoteManifests, 156 ctx: ctx, 157 scheduler: pr.scheduler, 158 authChallenger: pr.authChallenger, 159 }, 160 name: name, 161 tags: &proxyTagService{ 162 localTags: localRepo.Tags(ctx), 163 remoteTags: remoteRepo.Tags(ctx), 164 authChallenger: pr.authChallenger, 165 }, 166 }, nil 167 } 168 169 // authChallenger encapsulates a request to the upstream to establish credential challenges 170 type authChallenger interface { 171 tryEstablishChallenges(context.Context) error 172 challengeManager() auth.ChallengeManager 173 credentialStore() auth.CredentialStore 174 } 175 176 type remoteAuthChallenger struct { 177 remoteURL string 178 sync.Mutex 179 cm auth.ChallengeManager 180 cs auth.CredentialStore 181 } 182 183 func (r *remoteAuthChallenger) credentialStore() auth.CredentialStore { 184 return r.cs 185 } 186 187 func (r *remoteAuthChallenger) challengeManager() auth.ChallengeManager { 188 return r.cm 189 } 190 191 // tryEstablishChallenges will attempt to get a challenge type for the upstream if none currently exist 192 func (r *remoteAuthChallenger) tryEstablishChallenges(ctx context.Context) error { 193 r.Lock() 194 defer r.Unlock() 195 196 remoteURL := r.remoteURL + "/v2/" 197 challenges, err := r.cm.GetChallenges(remoteURL) 198 if err != nil { 199 return err 200 } 201 202 if len(challenges) > 0 { 203 return nil 204 } 205 206 // establish challenge type with upstream 207 if err := ping(r.cm, remoteURL, challengeHeader); err != nil { 208 return err 209 } 210 211 context.GetLogger(ctx).Infof("Challenge established with upstream : %s %s", remoteURL, r.cm) 212 return nil 213 } 214 215 // proxiedRepository uses proxying blob and manifest services to serve content 216 // locally, or pulling it through from a remote and caching it locally if it doesn't 217 // already exist 218 type proxiedRepository struct { 219 blobStore distribution.BlobStore 220 manifests distribution.ManifestService 221 name reference.Named 222 tags distribution.TagService 223 } 224 225 func (pr *proxiedRepository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) { 226 return pr.manifests, nil 227 } 228 229 func (pr *proxiedRepository) Blobs(ctx context.Context) distribution.BlobStore { 230 return pr.blobStore 231 } 232 233 func (pr *proxiedRepository) Name() reference.Named { 234 return pr.name 235 } 236 237 func (pr *proxiedRepository) Tags(ctx context.Context) distribution.TagService { 238 return pr.tags 239 }