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  }