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