Registry v2 mirror support.

The v2 registry will act as a pull-through cache, and needs to be
handled differently by the client to the v1 registry mirror.

See docker/distribution#459 for details

Configuration

Only one v2 registry can be configured as a mirror. Acceptable configurations
in this chanage are: 0...n v1 mirrors or 1 v2 mirror. A mixture of v1 and v2
mirrors is considered an error.

Pull

If a v2 mirror is configured, all pulls are redirected to that mirror. The
mirror will serve the content locally or attempt a pull from the upstream mirror,
cache it locally, and then serve to the client.

Push

If an image is tagged to a mirror, it will be pushed to the mirror and be
stored locally there. Otherwise, images are pushed to the hub. This is
unchanged behavior.

Signed-off-by: Richard Scothern <richard.scothern@gmail.com>
This commit is contained in:
Richard 2015-05-15 17:48:20 -07:00
parent fc679bebb9
commit 13deed3801
2 changed files with 107 additions and 6 deletions

View File

@ -55,6 +55,22 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf
}
defer s.poolRemove("pull", utils.ImageReference(repoInfo.LocalName, tag))
logName := repoInfo.LocalName
if tag != "" {
logName = utils.ImageReference(logName, tag)
}
v2mirrorEndpoint, v2mirrorRepoInfo, err := configureV2Mirror(repoInfo.Index.Mirrors, repoInfo, s.registryService)
if err != nil {
logrus.Errorf("Error configuring mirrors: %s", err)
return err
}
if v2mirrorEndpoint != nil {
logrus.Debugf("Attempting pull from v2 mirror: %s", v2mirrorEndpoint.URL)
return s.pullFromV2Mirror(v2mirrorEndpoint, v2mirrorRepoInfo, imagePullConfig, tag, sf, logName)
}
logrus.Debugf("pulling image from host %q with remote name %q", repoInfo.Index.Name, repoInfo.RemoteName)
endpoint, err := repoInfo.GetEndpoint(imagePullConfig.MetaHeaders)
@ -73,11 +89,6 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf
return err
}
logName := repoInfo.LocalName
if tag != "" {
logName = utils.ImageReference(logName, tag)
}
if len(repoInfo.Index.Mirrors) == 0 && (repoInfo.Index.Official || endpoint.Version == registry.APIVersion2) {
if repoInfo.Official {
s.trustService.UpdateBase()
@ -102,6 +113,94 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf
s.eventsService.Log("pull", logName, "")
return nil
}
func makeMirrorRepoInfo(repoInfo *registry.RepositoryInfo, mirror string) *registry.RepositoryInfo {
mirrorRepo := &registry.RepositoryInfo{
RemoteName: repoInfo.RemoteName,
LocalName: repoInfo.LocalName,
CanonicalName: repoInfo.CanonicalName,
Official: false,
Index: &registry.IndexInfo{
Official: false,
Secure: repoInfo.Index.Secure,
Name: mirror,
Mirrors: []string{},
},
}
return mirrorRepo
}
func configureV2Mirror(mirrors []string, repoInfo *registry.RepositoryInfo, s *registry.Service) (*registry.Endpoint, *registry.RepositoryInfo, error) {
if len(mirrors) > 0 {
// repoInfo
} else {
officialIndex, err := s.ResolveIndex(registry.IndexServerName())
if err != nil {
return nil, nil, err
}
mirrors = officialIndex.Mirrors
}
v1MirrorCount := 0
var v2MirrorEndpoint *registry.Endpoint
var v2MirrorRepoInfo *registry.RepositoryInfo
var lastErr error
for _, mirror := range mirrors {
mirrorRepoInfo := makeMirrorRepoInfo(repoInfo, mirror)
endpoint, err := registry.NewEndpoint(mirrorRepoInfo.Index, nil)
if err != nil {
logrus.Errorf("Unable to create endpoint for %s: %s", mirror, err)
lastErr = err
continue
}
if endpoint.Version == 2 {
if v2MirrorEndpoint == nil {
v2MirrorEndpoint = endpoint
v2MirrorRepoInfo = mirrorRepoInfo
} else {
// > 1 v2 mirrors given
return nil, nil, fmt.Errorf("multiple v2 mirrors configured")
}
} else {
v1MirrorCount++
}
}
if v1MirrorCount == len(mirrors) {
// OK, but mirrors are v1
return nil, nil, nil
}
if v2MirrorEndpoint != nil && v1MirrorCount == 0 {
// OK, 1 v2 mirror specified
return v2MirrorEndpoint, v2MirrorRepoInfo, nil
}
if v2MirrorEndpoint != nil && v1MirrorCount > 0 {
lastErr = fmt.Errorf("v1 and v2 mirrors configured")
}
return nil, nil, lastErr
}
func (s *TagStore) pullFromV2Mirror(mirrorEndpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo,
imagePullConfig *ImagePullConfig, tag string, sf *streamformatter.StreamFormatter, logName string) error {
tr := transport.NewTransport(
registry.NewTransport(registry.ReceiveTimeout, mirrorEndpoint.IsSecure),
registry.DockerHeaders(imagePullConfig.MetaHeaders)...,
)
client := registry.HTTPClient(tr)
mirrorSession, err := registry.NewSession(client, imagePullConfig.AuthConfig, mirrorEndpoint)
if err != nil {
return err
}
logrus.Debugf("Pulling v2 repository with local name %q from %s", repoInfo.LocalName, mirrorEndpoint.URL)
if err := s.pullV2Repository(mirrorSession, imagePullConfig.OutStream, repoInfo, tag, sf); err != nil {
return err
}
s.eventsService.Log("pull", logName, "")
return nil
}
func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, askedTag string, sf *streamformatter.StreamFormatter) error {

View File

@ -189,7 +189,7 @@ func ValidateMirror(val string) (string, error) {
return "", fmt.Errorf("Unsupported path/query/fragment at end of the URI")
}
return fmt.Sprintf("%s://%s/v1/", uri.Scheme, uri.Host), nil
return fmt.Sprintf("%s://%s/", uri.Scheme, uri.Host), nil
}
// ValidateIndexName validates an index name.
@ -358,7 +358,9 @@ func (config *ServiceConfig) NewRepositoryInfo(reposName string) (*RepositoryInf
// *TODO: Decouple index name from hostname (via registry configuration?)
repoInfo.LocalName = repoInfo.Index.Name + "/" + repoInfo.RemoteName
repoInfo.CanonicalName = repoInfo.LocalName
}
return repoInfo, nil
}