github.com/itscaro/cli@v0.0.0-20190705081621-c9db0fe93829/cli/registry/client/client.go (about) 1 package client 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "strings" 8 9 manifesttypes "github.com/docker/cli/cli/manifest/types" 10 "github.com/docker/cli/cli/trust" 11 "github.com/docker/distribution" 12 "github.com/docker/distribution/reference" 13 distributionclient "github.com/docker/distribution/registry/client" 14 "github.com/docker/docker/api/types" 15 registrytypes "github.com/docker/docker/api/types/registry" 16 "github.com/opencontainers/go-digest" 17 "github.com/pkg/errors" 18 "github.com/sirupsen/logrus" 19 ) 20 21 // RegistryClient is a client used to communicate with a Docker distribution 22 // registry 23 type RegistryClient interface { 24 GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) 25 GetManifestList(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error) 26 MountBlob(ctx context.Context, source reference.Canonical, target reference.Named) error 27 PutManifest(ctx context.Context, ref reference.Named, manifest distribution.Manifest) (digest.Digest, error) 28 GetTags(ctx context.Context, ref reference.Named) ([]string, error) 29 } 30 31 // NewRegistryClient returns a new RegistryClient with a resolver 32 func NewRegistryClient(resolver AuthConfigResolver, userAgent string, insecure bool) RegistryClient { 33 return &client{ 34 authConfigResolver: resolver, 35 insecureRegistry: insecure, 36 userAgent: userAgent, 37 } 38 } 39 40 // AuthConfigResolver returns Auth Configuration for an index 41 type AuthConfigResolver func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig 42 43 // PutManifestOptions is the data sent to push a manifest 44 type PutManifestOptions struct { 45 MediaType string 46 Payload []byte 47 } 48 49 type client struct { 50 authConfigResolver AuthConfigResolver 51 insecureRegistry bool 52 userAgent string 53 } 54 55 // ErrBlobCreated returned when a blob mount request was created 56 type ErrBlobCreated struct { 57 From reference.Named 58 Target reference.Named 59 } 60 61 func (err ErrBlobCreated) Error() string { 62 return fmt.Sprintf("blob mounted from: %v to: %v", 63 err.From, err.Target) 64 } 65 66 // ErrHTTPProto returned if attempting to use TLS with a non-TLS registry 67 type ErrHTTPProto struct { 68 OrigErr string 69 } 70 71 func (err ErrHTTPProto) Error() string { 72 return err.OrigErr 73 } 74 75 var _ RegistryClient = &client{} 76 77 // MountBlob into the registry, so it can be referenced by a manifest 78 func (c *client) MountBlob(ctx context.Context, sourceRef reference.Canonical, targetRef reference.Named) error { 79 repoEndpoint, err := newDefaultRepositoryEndpoint(targetRef, c.insecureRegistry) 80 if err != nil { 81 return err 82 } 83 repo, err := c.getRepositoryForReference(ctx, targetRef, repoEndpoint) 84 if err != nil { 85 return err 86 } 87 lu, err := repo.Blobs(ctx).Create(ctx, distributionclient.WithMountFrom(sourceRef)) 88 switch err.(type) { 89 case distribution.ErrBlobMounted: 90 logrus.Debugf("mount of blob %s succeeded", sourceRef) 91 return nil 92 case nil: 93 default: 94 return errors.Wrapf(err, "failed to mount blob %s to %s", sourceRef, targetRef) 95 } 96 lu.Cancel(ctx) 97 logrus.Debugf("mount of blob %s created", sourceRef) 98 return ErrBlobCreated{From: sourceRef, Target: targetRef} 99 } 100 101 // PutManifest sends the manifest to a registry and returns the new digest 102 func (c *client) PutManifest(ctx context.Context, ref reference.Named, manifest distribution.Manifest) (digest.Digest, error) { 103 repoEndpoint, err := newDefaultRepositoryEndpoint(ref, c.insecureRegistry) 104 if err != nil { 105 return digest.Digest(""), err 106 } 107 108 repo, err := c.getRepositoryForReference(ctx, ref, repoEndpoint) 109 if err != nil { 110 return digest.Digest(""), err 111 } 112 113 manifestService, err := repo.Manifests(ctx) 114 if err != nil { 115 return digest.Digest(""), err 116 } 117 118 _, opts, err := getManifestOptionsFromReference(ref) 119 if err != nil { 120 return digest.Digest(""), err 121 } 122 123 dgst, err := manifestService.Put(ctx, manifest, opts...) 124 return dgst, errors.Wrapf(err, "failed to put manifest %s", ref) 125 } 126 127 func (c *client) GetTags(ctx context.Context, ref reference.Named) ([]string, error) { 128 repoEndpoint, err := newDefaultRepositoryEndpoint(ref, c.insecureRegistry) 129 if err != nil { 130 return nil, err 131 } 132 133 repo, err := c.getRepositoryForReference(ctx, ref, repoEndpoint) 134 if err != nil { 135 return nil, err 136 } 137 return repo.Tags(ctx).All(ctx) 138 } 139 140 func (c *client) getRepositoryForReference(ctx context.Context, ref reference.Named, repoEndpoint repositoryEndpoint) (distribution.Repository, error) { 141 httpTransport, err := c.getHTTPTransportForRepoEndpoint(ctx, repoEndpoint) 142 if err != nil { 143 if strings.Contains(err.Error(), "server gave HTTP response to HTTPS client") { 144 return nil, ErrHTTPProto{OrigErr: err.Error()} 145 } 146 } 147 repoName, err := reference.WithName(repoEndpoint.Name()) 148 if err != nil { 149 return nil, errors.Wrapf(err, "failed to parse repo name from %s", ref) 150 } 151 return distributionclient.NewRepository(repoName, repoEndpoint.BaseURL(), httpTransport) 152 } 153 154 func (c *client) getHTTPTransportForRepoEndpoint(ctx context.Context, repoEndpoint repositoryEndpoint) (http.RoundTripper, error) { 155 httpTransport, err := getHTTPTransport( 156 c.authConfigResolver(ctx, repoEndpoint.info.Index), 157 repoEndpoint.endpoint, 158 repoEndpoint.Name(), 159 c.userAgent) 160 return httpTransport, errors.Wrap(err, "failed to configure transport") 161 } 162 163 // GetManifest returns an ImageManifest for the reference 164 func (c *client) GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) { 165 var result manifesttypes.ImageManifest 166 fetch := func(ctx context.Context, repo distribution.Repository, ref reference.Named) (bool, error) { 167 var err error 168 result, err = fetchManifest(ctx, repo, ref) 169 return result.Ref != nil, err 170 } 171 172 err := c.iterateEndpoints(ctx, ref, fetch) 173 return result, err 174 } 175 176 // GetManifestList returns a list of ImageManifest for the reference 177 func (c *client) GetManifestList(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error) { 178 result := []manifesttypes.ImageManifest{} 179 fetch := func(ctx context.Context, repo distribution.Repository, ref reference.Named) (bool, error) { 180 var err error 181 result, err = fetchList(ctx, repo, ref) 182 return len(result) > 0, err 183 } 184 185 err := c.iterateEndpoints(ctx, ref, fetch) 186 return result, err 187 } 188 189 func getManifestOptionsFromReference(ref reference.Named) (digest.Digest, []distribution.ManifestServiceOption, error) { 190 if tagged, isTagged := ref.(reference.NamedTagged); isTagged { 191 tag := tagged.Tag() 192 return "", []distribution.ManifestServiceOption{distribution.WithTag(tag)}, nil 193 } 194 if digested, isDigested := ref.(reference.Canonical); isDigested { 195 return digested.Digest(), []distribution.ManifestServiceOption{}, nil 196 } 197 return "", nil, errors.Errorf("%s no tag or digest", ref) 198 } 199 200 // GetRegistryAuth returns the auth config given an input image 201 func GetRegistryAuth(ctx context.Context, resolver AuthConfigResolver, imageName string) (*types.AuthConfig, error) { 202 distributionRef, err := reference.ParseNormalizedNamed(imageName) 203 if err != nil { 204 return nil, fmt.Errorf("Failed to parse image name: %s: %s", imageName, err) 205 } 206 imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, nil, resolver, distributionRef.String()) 207 if err != nil { 208 return nil, fmt.Errorf("Failed to get imgRefAndAuth: %s", err) 209 } 210 return imgRefAndAuth.AuthConfig(), nil 211 }