2017-06-15 14:41:54 -04:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
2018-05-03 21:02:44 -04:00
|
|
|
"context"
|
2017-06-15 14:41:54 -04:00
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
manifesttypes "github.com/docker/cli/cli/manifest/types"
|
|
|
|
"github.com/docker/distribution"
|
|
|
|
"github.com/docker/distribution/reference"
|
|
|
|
distributionclient "github.com/docker/distribution/registry/client"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
registrytypes "github.com/docker/docker/api/types/registry"
|
|
|
|
"github.com/opencontainers/go-digest"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
// RegistryClient is a client used to communicate with a Docker distribution
|
|
|
|
// registry
|
|
|
|
type RegistryClient interface {
|
|
|
|
GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error)
|
|
|
|
GetManifestList(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error)
|
|
|
|
MountBlob(ctx context.Context, source reference.Canonical, target reference.Named) error
|
|
|
|
PutManifest(ctx context.Context, ref reference.Named, manifest distribution.Manifest) (digest.Digest, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewRegistryClient returns a new RegistryClient with a resolver
|
|
|
|
func NewRegistryClient(resolver AuthConfigResolver, userAgent string, insecure bool) RegistryClient {
|
|
|
|
return &client{
|
|
|
|
authConfigResolver: resolver,
|
|
|
|
insecureRegistry: insecure,
|
|
|
|
userAgent: userAgent,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AuthConfigResolver returns Auth Configuration for an index
|
|
|
|
type AuthConfigResolver func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig
|
|
|
|
|
|
|
|
// PutManifestOptions is the data sent to push a manifest
|
|
|
|
type PutManifestOptions struct {
|
|
|
|
MediaType string
|
|
|
|
Payload []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
type client struct {
|
|
|
|
authConfigResolver AuthConfigResolver
|
|
|
|
insecureRegistry bool
|
|
|
|
userAgent string
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrBlobCreated returned when a blob mount request was created
|
|
|
|
type ErrBlobCreated struct {
|
|
|
|
From reference.Named
|
|
|
|
Target reference.Named
|
|
|
|
}
|
|
|
|
|
|
|
|
func (err ErrBlobCreated) Error() string {
|
|
|
|
return fmt.Sprintf("blob mounted from: %v to: %v",
|
|
|
|
err.From, err.Target)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrHTTPProto returned if attempting to use TLS with a non-TLS registry
|
|
|
|
type ErrHTTPProto struct {
|
|
|
|
OrigErr string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (err ErrHTTPProto) Error() string {
|
|
|
|
return err.OrigErr
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ RegistryClient = &client{}
|
|
|
|
|
|
|
|
// MountBlob into the registry, so it can be referenced by a manifest
|
|
|
|
func (c *client) MountBlob(ctx context.Context, sourceRef reference.Canonical, targetRef reference.Named) error {
|
|
|
|
repoEndpoint, err := newDefaultRepositoryEndpoint(targetRef, c.insecureRegistry)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
repo, err := c.getRepositoryForReference(ctx, targetRef, repoEndpoint)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
lu, err := repo.Blobs(ctx).Create(ctx, distributionclient.WithMountFrom(sourceRef))
|
|
|
|
switch err.(type) {
|
|
|
|
case distribution.ErrBlobMounted:
|
|
|
|
logrus.Debugf("mount of blob %s succeeded", sourceRef)
|
|
|
|
return nil
|
|
|
|
case nil:
|
|
|
|
default:
|
|
|
|
return errors.Wrapf(err, "failed to mount blob %s to %s", sourceRef, targetRef)
|
|
|
|
}
|
|
|
|
lu.Cancel(ctx)
|
|
|
|
logrus.Debugf("mount of blob %s created", sourceRef)
|
|
|
|
return ErrBlobCreated{From: sourceRef, Target: targetRef}
|
|
|
|
}
|
|
|
|
|
|
|
|
// PutManifest sends the manifest to a registry and returns the new digest
|
|
|
|
func (c *client) PutManifest(ctx context.Context, ref reference.Named, manifest distribution.Manifest) (digest.Digest, error) {
|
|
|
|
repoEndpoint, err := newDefaultRepositoryEndpoint(ref, c.insecureRegistry)
|
|
|
|
if err != nil {
|
|
|
|
return digest.Digest(""), err
|
|
|
|
}
|
|
|
|
|
|
|
|
repo, err := c.getRepositoryForReference(ctx, ref, repoEndpoint)
|
|
|
|
if err != nil {
|
|
|
|
return digest.Digest(""), err
|
|
|
|
}
|
|
|
|
|
|
|
|
manifestService, err := repo.Manifests(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return digest.Digest(""), err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, opts, err := getManifestOptionsFromReference(ref)
|
|
|
|
if err != nil {
|
|
|
|
return digest.Digest(""), err
|
|
|
|
}
|
|
|
|
|
|
|
|
dgst, err := manifestService.Put(ctx, manifest, opts...)
|
|
|
|
return dgst, errors.Wrapf(err, "failed to put manifest %s", ref)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) getRepositoryForReference(ctx context.Context, ref reference.Named, repoEndpoint repositoryEndpoint) (distribution.Repository, error) {
|
|
|
|
httpTransport, err := c.getHTTPTransportForRepoEndpoint(ctx, repoEndpoint)
|
|
|
|
if err != nil {
|
|
|
|
if strings.Contains(err.Error(), "server gave HTTP response to HTTPS client") {
|
|
|
|
return nil, ErrHTTPProto{OrigErr: err.Error()}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
repoName, err := reference.WithName(repoEndpoint.Name())
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "failed to parse repo name from %s", ref)
|
|
|
|
}
|
|
|
|
return distributionclient.NewRepository(ctx, repoName, repoEndpoint.BaseURL(), httpTransport)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *client) getHTTPTransportForRepoEndpoint(ctx context.Context, repoEndpoint repositoryEndpoint) (http.RoundTripper, error) {
|
|
|
|
httpTransport, err := getHTTPTransport(
|
|
|
|
c.authConfigResolver(ctx, repoEndpoint.info.Index),
|
|
|
|
repoEndpoint.endpoint,
|
|
|
|
repoEndpoint.Name(),
|
|
|
|
c.userAgent)
|
|
|
|
return httpTransport, errors.Wrap(err, "failed to configure transport")
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetManifest returns an ImageManifest for the reference
|
|
|
|
func (c *client) GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
|
|
|
|
var result manifesttypes.ImageManifest
|
|
|
|
fetch := func(ctx context.Context, repo distribution.Repository, ref reference.Named) (bool, error) {
|
|
|
|
var err error
|
|
|
|
result, err = fetchManifest(ctx, repo, ref)
|
|
|
|
return result.Ref != nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err := c.iterateEndpoints(ctx, ref, fetch)
|
|
|
|
return result, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetManifestList returns a list of ImageManifest for the reference
|
|
|
|
func (c *client) GetManifestList(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error) {
|
|
|
|
result := []manifesttypes.ImageManifest{}
|
|
|
|
fetch := func(ctx context.Context, repo distribution.Repository, ref reference.Named) (bool, error) {
|
|
|
|
var err error
|
|
|
|
result, err = fetchList(ctx, repo, ref)
|
|
|
|
return len(result) > 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err := c.iterateEndpoints(ctx, ref, fetch)
|
|
|
|
return result, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func getManifestOptionsFromReference(ref reference.Named) (digest.Digest, []distribution.ManifestServiceOption, error) {
|
|
|
|
if tagged, isTagged := ref.(reference.NamedTagged); isTagged {
|
|
|
|
tag := tagged.Tag()
|
|
|
|
return "", []distribution.ManifestServiceOption{distribution.WithTag(tag)}, nil
|
|
|
|
}
|
|
|
|
if digested, isDigested := ref.(reference.Canonical); isDigested {
|
|
|
|
return digested.Digest(), []distribution.ManifestServiceOption{}, nil
|
|
|
|
}
|
|
|
|
return "", nil, errors.Errorf("%s no tag or digest", ref)
|
|
|
|
}
|