From f35726b4108672f5ed1986c14e5d0da69d56cf11 Mon Sep 17 00:00:00 2001 From: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:22:02 +0100 Subject: [PATCH] feat: replace docker client code with regclient --- .../extensions/repositories/ocireg/blobs.go | 16 +- .../repositories/ocireg/namespace.go | 12 +- .../repositories/ocireg/repository.go | 124 ++++---- .../extensions/repositories/ocireg/utils.go | 8 +- api/tech/docker/lister.go | 5 +- api/tech/docker/pusher.go | 8 +- api/tech/docker/resolver.go | 10 +- api/tech/oras/client.go | 1 + api/tech/regclient/client.go | 273 ++++++++++++++++++ api/tech/regclient/client_test.go | 13 + .../resolve => regclient}/interface.go | 2 +- go.mod | 4 + go.sum | 4 + 13 files changed, 392 insertions(+), 88 deletions(-) create mode 100644 api/tech/oras/client.go create mode 100644 api/tech/regclient/client.go create mode 100644 api/tech/regclient/client_test.go rename api/tech/{docker/resolve => regclient}/interface.go (99%) diff --git a/api/oci/extensions/repositories/ocireg/blobs.go b/api/oci/extensions/repositories/ocireg/blobs.go index 0ecf1b299d..d65f7e7dc4 100644 --- a/api/oci/extensions/repositories/ocireg/blobs.go +++ b/api/oci/extensions/repositories/ocireg/blobs.go @@ -7,10 +7,10 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/opencontainers/go-digest" "github.com/sirupsen/logrus" + "ocm.software/ocm/api/tech/regclient" "ocm.software/ocm/api/oci/cpi" "ocm.software/ocm/api/oci/extensions/attrs/cacheattr" - "ocm.software/ocm/api/tech/docker/resolve" "ocm.software/ocm/api/utils/accessio" "ocm.software/ocm/api/utils/blobaccess/blobaccess" ) @@ -23,20 +23,20 @@ type BlobContainer interface { type blobContainer struct { accessio.StaticAllocatable - fetcher resolve.Fetcher - pusher resolve.Pusher + fetcher regclient.Fetcher + pusher regclient.Pusher mime string } type BlobContainers struct { lock sync.Mutex cache accessio.BlobCache - fetcher resolve.Fetcher - pusher resolve.Pusher + fetcher regclient.Fetcher + pusher regclient.Pusher mimes map[string]BlobContainer } -func NewBlobContainers(ctx cpi.Context, fetcher remotes.Fetcher, pusher resolve.Pusher) *BlobContainers { +func NewBlobContainers(ctx cpi.Context, fetcher remotes.Fetcher, pusher regclient.Pusher) *BlobContainers { return &BlobContainers{ cache: cacheattr.Get(ctx), fetcher: fetcher, @@ -73,7 +73,7 @@ func (c *BlobContainers) Release() error { return list.Result() } -func newBlobContainer(mime string, fetcher resolve.Fetcher, pusher resolve.Pusher) *blobContainer { +func newBlobContainer(mime string, fetcher regclient.Fetcher, pusher regclient.Pusher) *blobContainer { return &blobContainer{ mime: mime, fetcher: fetcher, @@ -81,7 +81,7 @@ func newBlobContainer(mime string, fetcher resolve.Fetcher, pusher resolve.Pushe } } -func NewBlobContainer(cache accessio.BlobCache, mime string, fetcher resolve.Fetcher, pusher resolve.Pusher) (BlobContainer, error) { +func NewBlobContainer(cache accessio.BlobCache, mime string, fetcher regclient.Fetcher, pusher regclient.Pusher) (BlobContainer, error) { c := newBlobContainer(mime, fetcher, pusher) if cache == nil { diff --git a/api/oci/extensions/repositories/ocireg/namespace.go b/api/oci/extensions/repositories/ocireg/namespace.go index 9ef8239979..43477b011a 100644 --- a/api/oci/extensions/repositories/ocireg/namespace.go +++ b/api/oci/extensions/repositories/ocireg/namespace.go @@ -7,12 +7,12 @@ import ( "github.com/containerd/errdefs" "github.com/mandelsoft/goutils/errors" "github.com/opencontainers/go-digest" + "ocm.software/ocm/api/tech/regclient" "ocm.software/ocm/api/oci/artdesc" "ocm.software/ocm/api/oci/cpi" "ocm.software/ocm/api/oci/cpi/support" "ocm.software/ocm/api/oci/extensions/actions/oci-repository-prepare" - "ocm.software/ocm/api/tech/docker/resolve" "ocm.software/ocm/api/utils/accessio" "ocm.software/ocm/api/utils/blobaccess/blobaccess" "ocm.software/ocm/api/utils/logging" @@ -22,10 +22,10 @@ import ( type NamespaceContainer struct { impl support.NamespaceAccessImpl repo *RepositoryImpl - resolver resolve.Resolver - lister resolve.Lister - fetcher resolve.Fetcher - pusher resolve.Pusher + resolver regclient.Resolver + lister regclient.Lister + fetcher regclient.Fetcher + pusher regclient.Pusher blobs *BlobContainers checked bool } @@ -69,7 +69,7 @@ func (n *NamespaceContainer) SetImplementation(impl support.NamespaceAccessImpl) n.impl = impl } -func (n *NamespaceContainer) getPusher(vers string) (resolve.Pusher, error) { +func (n *NamespaceContainer) getPusher(vers string) (regclient.Pusher, error) { err := n.assureCreated() if err != nil { return nil, err diff --git a/api/oci/extensions/repositories/ocireg/repository.go b/api/oci/extensions/repositories/ocireg/repository.go index bff1e51078..8d31108465 100644 --- a/api/oci/extensions/repositories/ocireg/repository.go +++ b/api/oci/extensions/repositories/ocireg/repository.go @@ -2,24 +2,19 @@ package ocireg import ( "context" - "crypto/tls" - "crypto/x509" - "net/http" "path" "strings" - "github.com/containerd/containerd/remotes/docker/config" "github.com/containerd/errdefs" "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/logging" + regconfig "github.com/regclient/regclient/config" "ocm.software/ocm/api/credentials" - "ocm.software/ocm/api/datacontext/attrs/rootcertsattr" "ocm.software/ocm/api/oci/artdesc" "ocm.software/ocm/api/oci/cpi" - "ocm.software/ocm/api/tech/docker" - "ocm.software/ocm/api/tech/docker/resolve" "ocm.software/ocm/api/tech/oci/identity" + "ocm.software/ocm/api/tech/regclient" "ocm.software/ocm/api/utils" ocmlog "ocm.software/ocm/api/utils/logging" "ocm.software/ocm/api/utils/refmgmt" @@ -114,7 +109,7 @@ func (r *RepositoryImpl) getCreds(comp string) (credentials.Credentials, error) return identity.GetCredentials(r.GetContext(), r.info.Locator, comp) } -func (r *RepositoryImpl) getResolver(comp string) (resolve.Resolver, error) { +func (r *RepositoryImpl) getResolver(comp string) (regclient.Resolver, error) { creds, err := r.getCreds(comp) if err != nil { if !errors.IsErrUnknownKind(err, credentials.KIND_CONSUMER) { @@ -126,57 +121,70 @@ func (r *RepositoryImpl) getResolver(comp string) (resolve.Resolver, error) { logger.Trace("no credentials") } - opts := docker.ResolverOptions{ - Hosts: docker.ConvertHosts(config.ConfigureHosts(context.Background(), config.HostOptions{ - UpdateClient: func(client *http.Client) error { - // copy from http.DefaultTransport with a roundtripper injection - client.Transport = ocmlog.NewRoundTripper(client.Transport, logger) - return nil - }, - Credentials: func(host string) (string, string, error) { - if creds != nil { - p := creds.GetProperty(credentials.ATTR_IDENTITY_TOKEN) - if p == "" { - p = creds.GetProperty(credentials.ATTR_PASSWORD) - } - pw := "" - if p != "" { - pw = "***" - } - logger.Trace("query credentials", ocmlog.ATTR_USER, creds.GetProperty(credentials.ATTR_USERNAME), "pass", pw) - return creds.GetProperty(credentials.ATTR_USERNAME), p, nil - } - logger.Trace("no credentials") - return "", "", nil - }, - DefaultScheme: r.info.Scheme, - //nolint:gosec // used like the default, there are OCI servers (quay.io) not working with min version. - DefaultTLS: func() *tls.Config { - if r.info.Scheme == "http" { - return nil - } - return &tls.Config{ - // MinVersion: tls.VersionTLS13, - RootCAs: func() *x509.CertPool { - var rootCAs *x509.CertPool - if creds != nil { - c := creds.GetProperty(credentials.ATTR_CERTIFICATE_AUTHORITY) - if c != "" { - rootCAs = x509.NewCertPool() - rootCAs.AppendCertsFromPEM([]byte(c)) - } - } - if rootCAs == nil { - rootCAs = rootcertsattr.Get(r.GetContext()).GetRootCertPool(true) - } - return rootCAs - }(), - } - }(), - })), + pass := creds.GetProperty(credentials.ATTR_IDENTITY_TOKEN) + if pass == "" { + pass = creds.GetProperty(credentials.ATTR_PASSWORD) } - - return docker.NewResolver(opts), nil + username := creds.GetProperty(credentials.ATTR_USERNAME) + opts := regclient.ClientOptions{ + Host: ®config.Host{ + Name: "ghcr.io", + User: username, + Pass: pass, + }, + Version: comp, + } + //opts := docker.ResolverOptions{ + // Hosts: docker.ConvertHosts(config.ConfigureHosts(context.Background(), config.HostOptions{ + // UpdateClient: func(client *http.Client) error { + // // copy from http.DefaultTransport with a roundtripper injection + // client.Transport = ocmlog.NewRoundTripper(client.Transport, logger) + // return nil + // }, + // Credentials: func(host string) (string, string, error) { + // if creds != nil { + // p := creds.GetProperty(credentials.ATTR_IDENTITY_TOKEN) + // if p == "" { + // p = creds.GetProperty(credentials.ATTR_PASSWORD) + // } + // pw := "" + // if p != "" { + // pw = "***" + // } + // logger.Trace("query credentials", ocmlog.ATTR_USER, creds.GetProperty(credentials.ATTR_USERNAME), "pass", pw) + // return creds.GetProperty(credentials.ATTR_USERNAME), p, nil + // } + // logger.Trace("no credentials") + // return "", "", nil + // }, + // DefaultScheme: r.info.Scheme, + // //nolint:gosec // used like the default, there are OCI servers (quay.io) not working with min version. + // DefaultTLS: func() *tls.Config { + // if r.info.Scheme == "http" { + // return nil + // } + // return &tls.Config{ + // // MinVersion: tls.VersionTLS13, + // RootCAs: func() *x509.CertPool { + // var rootCAs *x509.CertPool + // if creds != nil { + // c := creds.GetProperty(credentials.ATTR_CERTIFICATE_AUTHORITY) + // if c != "" { + // rootCAs = x509.NewCertPool() + // rootCAs.AppendCertsFromPEM([]byte(c)) + // } + // } + // if rootCAs == nil { + // rootCAs = rootcertsattr.Get(r.GetContext()).GetRootCertPool(true) + // } + // return rootCAs + // }(), + // } + // }(), + // })), + //} + + return regclient.New(opts), nil } func (r *RepositoryImpl) GetRef(comp, vers string) string { diff --git a/api/oci/extensions/repositories/ocireg/utils.go b/api/oci/extensions/repositories/ocireg/utils.go index 17a96f040a..76e98964b1 100644 --- a/api/oci/extensions/repositories/ocireg/utils.go +++ b/api/oci/extensions/repositories/ocireg/utils.go @@ -11,10 +11,10 @@ import ( "github.com/containerd/log" "github.com/opencontainers/go-digest" "github.com/sirupsen/logrus" + "ocm.software/ocm/api/tech/regclient" "ocm.software/ocm/api/oci/artdesc" "ocm.software/ocm/api/oci/cpi" - "ocm.software/ocm/api/tech/docker/resolve" "ocm.software/ocm/api/utils/accessio" "ocm.software/ocm/api/utils/blobaccess/blobaccess" "ocm.software/ocm/api/utils/logging" @@ -81,12 +81,12 @@ func readAll(reader io.ReadCloser, err error) ([]byte, error) { return data, nil } -func push(ctx context.Context, p resolve.Pusher, blob blobaccess.BlobAccess) error { +func push(ctx context.Context, p regclient.Pusher, blob blobaccess.BlobAccess) error { desc := *artdesc.DefaultBlobDescriptor(blob) return pushData(ctx, p, desc, blob) } -func pushData(ctx context.Context, p resolve.Pusher, desc artdesc.Descriptor, data blobaccess.DataAccess) error { +func pushData(ctx context.Context, p regclient.Pusher, desc artdesc.Descriptor, data blobaccess.DataAccess) error { key := remotes.MakeRefKey(ctx, desc) if desc.Size == 0 { desc.Size = -1 @@ -100,8 +100,10 @@ func pushData(ctx context.Context, p resolve.Pusher, desc artdesc.Descriptor, da return nil } + return fmt.Errorf("failed to push: %w", err) } + return req.Commit(ctx, desc.Size, desc.Digest) } diff --git a/api/tech/docker/lister.go b/api/tech/docker/lister.go index efd3b8e1e2..64845e27e1 100644 --- a/api/tech/docker/lister.go +++ b/api/tech/docker/lister.go @@ -9,8 +9,7 @@ import ( "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/log" "github.com/pkg/errors" - - "ocm.software/ocm/api/tech/docker/resolve" + "ocm.software/ocm/api/tech/regclient" ) var ErrObjectNotRequired = errors.New("object not required") @@ -24,7 +23,7 @@ type dockerLister struct { dockerBase *dockerBase } -func (r *dockerResolver) Lister(ctx context.Context, ref string) (resolve.Lister, error) { +func (r *dockerResolver) Lister(ctx context.Context, ref string) (regclient.Lister, error) { base, err := r.resolveDockerBase(ref) if err != nil { return nil, err diff --git a/api/tech/docker/pusher.go b/api/tech/docker/pusher.go index 708ad0f349..2c26f9b19b 100644 --- a/api/tech/docker/pusher.go +++ b/api/tech/docker/pusher.go @@ -17,9 +17,9 @@ import ( ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "ocm.software/ocm/api/tech/regclient" remoteserrors "ocm.software/ocm/api/tech/docker/errors" - "ocm.software/ocm/api/tech/docker/resolve" "ocm.software/ocm/api/utils/accessio" ) @@ -37,11 +37,11 @@ type dockerPusher struct { tracker StatusTracker } -func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor, src resolve.Source) (resolve.PushRequest, error) { +func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor, src regclient.Source) (regclient.PushRequest, error) { return p.push(ctx, desc, src, remotes.MakeRefKey(ctx, desc), false) } -func (p dockerPusher) push(ctx context.Context, desc ocispec.Descriptor, src resolve.Source, ref string, unavailableOnFail bool) (resolve.PushRequest, error) { +func (p dockerPusher) push(ctx context.Context, desc ocispec.Descriptor, src regclient.Source, ref string, unavailableOnFail bool) (regclient.PushRequest, error) { if l, ok := p.tracker.(StatusTrackLocker); ok { l.Lock(ref) defer l.Unlock(ref) @@ -322,7 +322,7 @@ type pushRequest struct { ref string responseC <-chan response - source resolve.Source + source regclient.Source isManifest bool expected digest.Digest diff --git a/api/tech/docker/resolver.go b/api/tech/docker/resolver.go index 292df03ae3..8647ded6bd 100644 --- a/api/tech/docker/resolver.go +++ b/api/tech/docker/resolver.go @@ -20,8 +20,8 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/net/context/ctxhttp" + "ocm.software/ocm/api/tech/regclient" - "ocm.software/ocm/api/tech/docker/resolve" "ocm.software/ocm/api/utils/accessio" ) @@ -118,7 +118,7 @@ type dockerResolver struct { } // NewResolver returns a new resolver to a Docker registry. -func NewResolver(options ResolverOptions) resolve.Resolver { +func NewResolver(options ResolverOptions) regclient.Resolver { if options.Tracker == nil { options.Tracker = NewInMemoryTracker() } @@ -202,7 +202,7 @@ func (r *countingReader) Read(p []byte) (int, error) { return n, err } -var _ resolve.Resolver = &dockerResolver{} +var _ regclient.Resolver = &dockerResolver{} func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocispec.Descriptor, error) { base, err := r.resolveDockerBase(ref) @@ -382,7 +382,7 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp return "", ocispec.Descriptor{}, firstErr } -func (r *dockerResolver) Fetcher(ctx context.Context, ref string) (resolve.Fetcher, error) { +func (r *dockerResolver) Fetcher(ctx context.Context, ref string) (regclient.Fetcher, error) { base, err := r.resolveDockerBase(ref) if err != nil { return nil, err @@ -393,7 +393,7 @@ func (r *dockerResolver) Fetcher(ctx context.Context, ref string) (resolve.Fetch }, nil } -func (r *dockerResolver) Pusher(ctx context.Context, ref string) (resolve.Pusher, error) { +func (r *dockerResolver) Pusher(ctx context.Context, ref string) (regclient.Pusher, error) { base, err := r.resolveDockerBase(ref) if err != nil { return nil, err diff --git a/api/tech/oras/client.go b/api/tech/oras/client.go new file mode 100644 index 0000000000..d2c233c248 --- /dev/null +++ b/api/tech/oras/client.go @@ -0,0 +1 @@ +package oras diff --git a/api/tech/regclient/client.go b/api/tech/regclient/client.go new file mode 100644 index 0000000000..f709b05c23 --- /dev/null +++ b/api/tech/regclient/client.go @@ -0,0 +1,273 @@ +package regclient + +import ( + "context" + "errors" + "fmt" + "io" + "strings" + "time" + + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/images" + "github.com/opencontainers/go-digest" + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/regclient/regclient" + "github.com/regclient/regclient/config" + "github.com/regclient/regclient/scheme/reg" + "github.com/regclient/regclient/types/descriptor" + "github.com/regclient/regclient/types/manifest" + "github.com/regclient/regclient/types/platform" + regref "github.com/regclient/regclient/types/ref" +) + +type ClientOptions struct { + Host *config.Host + Version string +} + +type Client struct { + rc *regclient.RegClient + ref regref.Ref +} + +type pushRequest struct { + rc *regclient.RegClient + desc descriptor.Descriptor + ref regref.Ref +} + +// Commit and Status are actually not really used. Commit is a second stage operation and Status is never called in +// the library. +func (p *pushRequest) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error { + return p.rc.Close(ctx, p.ref) +} + +func (p *pushRequest) Status() (content.Status, error) { + return content.Status{ + Ref: p.ref.Reference, + Total: p.desc.Size, + }, nil +} + +var _ PushRequest = &pushRequest{} + +var _ Resolver = &Client{} +var _ Fetcher = &Client{} +var _ Pusher = &Client{} +var _ Lister = &Client{} + +func New(opts ClientOptions) *Client { + rc := regclient.New( + regclient.WithConfigHost(*opts.Host), + regclient.WithDockerCerts(), + regclient.WithDockerCreds(), + regclient.WithUserAgent("containerd/"+opts.Version), + regclient.WithRegOpts( + //reg.WithCertDirs([]string{"."}), + reg.WithDelay(2*time.Second, 15*time.Second), + reg.WithRetryLimit(5), + reg.WithCache(5*time.Minute, 500), // built in cache!! Nice! + ), + ) + + return &Client{rc: rc} +} + +// Close must be called at the end of the operation. +func (c *Client) Close(ctx context.Context, ref regref.Ref) error { + return c.rc.Close(ctx, ref) +} + +func (c *Client) convertDescriptorToOCI(desc descriptor.Descriptor) ociv1.Descriptor { + var p *ociv1.Platform + if desc.Platform != nil { + p = &ociv1.Platform{ + Architecture: desc.Platform.Architecture, + OS: desc.Platform.OS, + OSVersion: desc.Platform.OSVersion, + OSFeatures: desc.Platform.OSFeatures, + Variant: desc.Platform.Variant, + } + } + + return ociv1.Descriptor{ + MediaType: desc.MediaType, + Size: desc.Size, + Digest: desc.Digest, + Platform: p, + URLs: desc.URLs, + Annotations: desc.Annotations, + Data: desc.Data, + ArtifactType: desc.ArtifactType, + } +} + +func (c *Client) convertDescriptorToRegClient(desc ociv1.Descriptor) descriptor.Descriptor { + var p *platform.Platform + if desc.Platform != nil { + p = &platform.Platform{ + Architecture: desc.Platform.Architecture, + OS: desc.Platform.OS, + OSVersion: desc.Platform.OSVersion, + OSFeatures: desc.Platform.OSFeatures, + Variant: desc.Platform.Variant, + } + } + + return descriptor.Descriptor{ + MediaType: desc.MediaType, + Size: desc.Size, + Digest: desc.Digest, + Platform: p, + URLs: desc.URLs, + Annotations: desc.Annotations, + Data: desc.Data, + ArtifactType: desc.ArtifactType, + } +} + +func (c *Client) Resolve(ctx context.Context, ref string) (string, ociv1.Descriptor, error) { + //TODO: figure out what to do about closing c.rc. + r, err := regref.New(ref) + if err != nil { + return "", ociv1.Descriptor{}, err + } + + // if digest is set it will use that. + m, err := c.rc.ManifestHead(ctx, r) + if err != nil { + if strings.Contains(err.Error(), "not found") { + // fallback to finding a blob if we have a digest + if r.Digest != "" { + blob, err := c.rc.BlobHead(ctx, r, descriptor.Descriptor{ + Digest: digest.Digest(r.Digest), + Size: -1, + }) + if err != nil { + if strings.Contains(err.Error(), "not found") { + return "", ociv1.Descriptor{}, errdefs.ErrNotFound + } + + return "", ociv1.Descriptor{}, err + } + + return ref, c.convertDescriptorToOCI(blob.GetDescriptor()), nil + } + + return "", ociv1.Descriptor{}, errdefs.ErrNotFound + } + + return "", ociv1.Descriptor{}, fmt.Errorf("failed to get manifest: %w", err) + } + + return ref, c.convertDescriptorToOCI(m.GetDescriptor()), nil +} + +func (c *Client) Fetcher(ctx context.Context, ref string) (Fetcher, error) { + var err error + c.ref, err = regref.New(ref) + if err != nil { + return nil, err + } + + return c, nil +} + +func (c *Client) Pusher(ctx context.Context, ref string) (Pusher, error) { + var err error + c.ref, err = regref.New(ref) + if err != nil { + return nil, err + } + + return c, nil +} + +func (c *Client) Lister(ctx context.Context, ref string) (Lister, error) { + var err error + c.ref, err = regref.New(ref) + if err != nil { + return nil, err + } + + return c, nil +} + +func (c *Client) Push(ctx context.Context, d ociv1.Descriptor, src Source) (PushRequest, error) { + reader, err := src.Reader() + if err != nil { + return nil, err + } + + isManifest := false + switch d.MediaType { + case images.MediaTypeDockerSchema2Manifest, images.MediaTypeDockerSchema2ManifestList, + ociv1.MediaTypeImageManifest, ociv1.MediaTypeImageIndex: + isManifest = true + } + if isManifest { + manifestContent, err := io.ReadAll(reader) + if err != nil { + return nil, fmt.Errorf("failed to read manifest: %w", err) + } + + m, err := manifest.New(manifest.WithDesc(c.convertDescriptorToRegClient(d)), manifest.WithRef(c.ref), manifest.WithRaw(manifestContent)) + if err != nil { + return nil, fmt.Errorf("failed to create a manifest: %w", err) + } + + if err := c.rc.ManifestPut(ctx, c.ref, m); err != nil { + return nil, err + } + + return &pushRequest{ + desc: c.convertDescriptorToRegClient(d), + rc: c.rc, + ref: c.ref, + }, nil + } + + desc, err := c.rc.BlobPut(ctx, c.ref, c.convertDescriptorToRegClient(d), reader) + if err != nil { + return nil, err + } + + return &pushRequest{ + desc: desc, + rc: c.rc, + ref: c.ref, + }, nil +} + +func (c *Client) Fetch(ctx context.Context, desc ociv1.Descriptor) (_ io.ReadCloser, err error) { + defer func() { + if cerr := c.rc.Close(ctx, c.ref); cerr != nil { + err = errors.Join(err, fmt.Errorf("failed to close the client after fetch: %w", cerr)) + } + }() + + // set up closing the client after fetching is done. + reader, err := c.rc.BlobGet(ctx, c.ref, c.convertDescriptorToRegClient(desc)) + if err != nil { + return nil, err + } + + return reader, nil +} + +func (c *Client) List(ctx context.Context) (_ []string, err error) { + defer func() { + if cerr := c.rc.Close(ctx, c.ref); cerr != nil { + err = errors.Join(err, fmt.Errorf("failed to close the client after list: %w", cerr)) + } + }() + + tags, err := c.rc.TagList(ctx, c.ref) + if err != nil { + return nil, err + } + + return tags.Tags, nil +} diff --git a/api/tech/regclient/client_test.go b/api/tech/regclient/client_test.go new file mode 100644 index 0000000000..0de7789ba5 --- /dev/null +++ b/api/tech/regclient/client_test.go @@ -0,0 +1,13 @@ +package regclient + +import ( + "testing" + + "github.com/regclient/regclient/config" + "github.com/stretchr/testify/require" +) + +func TestNew(t *testing.T) { + n := New(ClientOptions{Host: config.HostNew()}) + require.NotNil(t, n) +} diff --git a/api/tech/docker/resolve/interface.go b/api/tech/regclient/interface.go similarity index 99% rename from api/tech/docker/resolve/interface.go rename to api/tech/regclient/interface.go index 8476000012..d11f0648d3 100644 --- a/api/tech/docker/resolve/interface.go +++ b/api/tech/regclient/interface.go @@ -1,4 +1,4 @@ -package resolve +package regclient import ( "context" diff --git a/go.mod b/go.mod index 296f46db76..9ca55da23b 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.23.2 replace github.com/spf13/cobra => github.com/open-component-model/cobra v0.0.0-20230329075350-b1fd876abfb9 +//replace github.com/regclient/regclient => /Users/skarlso/goprojects/regclient + require ( dario.cat/mergo v1.0.1 github.com/DataDog/gostackparse v0.7.0 @@ -56,6 +58,7 @@ require ( github.com/opencontainers/image-spec v1.1.0 github.com/pkg/errors v0.9.1 github.com/redis/go-redis/v9 v9.7.0 + github.com/regclient/regclient v0.7.2 github.com/rogpeppe/go-internal v1.13.1 github.com/sigstore/cosign/v2 v2.4.1 github.com/sigstore/rekor v1.3.7 @@ -172,6 +175,7 @@ require ( github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/elliotchance/orderedmap v1.7.0 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect diff --git a/go.sum b/go.sum index 078bbf32c3..8eecd224af 100644 --- a/go.sum +++ b/go.sum @@ -785,6 +785,8 @@ github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olareg/olareg v0.1.1 h1:Ui7q93zjcoF+U9U71sgqgZWByDoZOpqHitUXEu2xV+g= +github.com/olareg/olareg v0.1.1/go.mod h1:w8NP4SWrHHtxsFaUiv1lnCnYPm4sN1seCd2h7FK/dc0= github.com/oleiade/reflections v1.1.0 h1:D+I/UsXQB4esMathlt0kkZRJZdUDmhv5zGi/HOwYTWo= github.com/oleiade/reflections v1.1.0/go.mod h1:mCxx0QseeVCHs5Um5HhJeCKVC7AwS8kO67tky4rdisA= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -874,6 +876,8 @@ github.com/redis/go-redis/extra/redisotel/v9 v9.5.3 h1:kuvuJL/+MZIEdvtb/kTBRiRgY github.com/redis/go-redis/extra/redisotel/v9 v9.5.3/go.mod h1:7f/FMrf5RRRVHXgfk7CzSVzXHiWeuOQUu2bsVqWoa+g= github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= +github.com/regclient/regclient v0.7.2 h1:vcldDAwBMLtighYVMeb6qNt5+0hKg3AN2IkCc0JIJNM= +github.com/regclient/regclient v0.7.2/go.mod h1:QlA7W9/pvmbblOXM4d49JgfuOTwVXcUMKt3bFuOSVIQ= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=