github.com/thajeztah/cli@v0.0.0-20240223162942-dc6bfac81a8b/cli/registry/client/client.go (about) 1 package client 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "strings" 8 9 "github.com/distribution/reference" 10 manifesttypes "github.com/docker/cli/cli/manifest/types" 11 "github.com/docker/cli/cli/trust" 12 "github.com/docker/distribution" 13 distributionclient "github.com/docker/distribution/registry/client" 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) registrytypes.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 repoEndpoint.actions = trust.ActionsPushAndPull 82 repo, err := c.getRepositoryForReference(ctx, targetRef, repoEndpoint) 83 if err != nil { 84 return err 85 } 86 lu, err := repo.Blobs(ctx).Create(ctx, distributionclient.WithMountFrom(sourceRef)) 87 switch err.(type) { 88 case distribution.ErrBlobMounted: 89 logrus.Debugf("mount of blob %s succeeded", sourceRef) 90 return nil 91 case nil: 92 default: 93 return errors.Wrapf(err, "failed to mount blob %s to %s", sourceRef, targetRef) 94 } 95 lu.Cancel(ctx) 96 logrus.Debugf("mount of blob %s created", sourceRef) 97 return ErrBlobCreated{From: sourceRef, Target: targetRef} 98 } 99 100 // PutManifest sends the manifest to a registry and returns the new digest 101 func (c *client) PutManifest(ctx context.Context, ref reference.Named, manifest distribution.Manifest) (digest.Digest, error) { 102 repoEndpoint, err := newDefaultRepositoryEndpoint(ref, c.insecureRegistry) 103 if err != nil { 104 return digest.Digest(""), err 105 } 106 107 repoEndpoint.actions = trust.ActionsPushAndPull 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) getRepositoryForReference(ctx context.Context, ref reference.Named, repoEndpoint repositoryEndpoint) (distribution.Repository, error) { 128 repoName, err := reference.WithName(repoEndpoint.Name()) 129 if err != nil { 130 return nil, errors.Wrapf(err, "failed to parse repo name from %s", ref) 131 } 132 httpTransport, err := c.getHTTPTransportForRepoEndpoint(ctx, repoEndpoint) 133 if err != nil { 134 if !strings.Contains(err.Error(), "server gave HTTP response to HTTPS client") { 135 return nil, err 136 } 137 if !repoEndpoint.endpoint.TLSConfig.InsecureSkipVerify { 138 return nil, ErrHTTPProto{OrigErr: err.Error()} 139 } 140 // --insecure was set; fall back to plain HTTP 141 if url := repoEndpoint.endpoint.URL; url != nil && url.Scheme == "https" { 142 url.Scheme = "http" 143 httpTransport, err = c.getHTTPTransportForRepoEndpoint(ctx, repoEndpoint) 144 if err != nil { 145 return nil, err 146 } 147 } 148 } 149 return distributionclient.NewRepository(repoName, repoEndpoint.BaseURL(), httpTransport) 150 } 151 152 func (c *client) getHTTPTransportForRepoEndpoint(ctx context.Context, repoEndpoint repositoryEndpoint) (http.RoundTripper, error) { 153 httpTransport, err := getHTTPTransport( 154 c.authConfigResolver(ctx, repoEndpoint.info.Index), 155 repoEndpoint.endpoint, 156 repoEndpoint.Name(), 157 c.userAgent, 158 repoEndpoint.actions, 159 ) 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 }