1
0
mirror of https://github.com/regclient/regclient.git synced 2025-04-17 11:37:11 +03:00
regclient/manifest.go
Brandon Mitchell 95c9152941
Chore: Modernize Go to the 1.22 specs
- Use "any" instead of an empty interface.
- Use range over an integer for for loops.
- Remove shadow variables in loops now that Go no longer reuses the variable.
- Use "slices.Contains", "slices.Delete", "slices.Equal", "slices.Index", "slices.SortFunc".
- Use "cmp.Or", "min", and "max".
- Use "fmt.Appendf" instead of "Sprintf" for generating a byte slice.
- Use "errors.Join" or "fmt.Errorf" with multiple "%w" for multiple errors.

Additionally, use modern regclient features:

- Use "ref.SetTag", "ref.SetDigest", and "ref.AddDigest".
- Call "regclient.ManifestGet" using "WithManifestDesc" instead of setting the digest on the reference.

Signed-off-by: Brandon Mitchell <git@bmitch.net>
2025-02-18 14:32:06 -05:00

207 lines
6.3 KiB
Go

package regclient
import (
"context"
"fmt"
"log/slog"
"github.com/regclient/regclient/scheme"
"github.com/regclient/regclient/types/descriptor"
"github.com/regclient/regclient/types/errs"
"github.com/regclient/regclient/types/manifest"
"github.com/regclient/regclient/types/platform"
"github.com/regclient/regclient/types/ref"
"github.com/regclient/regclient/types/warning"
)
type manifestOpt struct {
d descriptor.Descriptor
platform *platform.Platform
schemeOpts []scheme.ManifestOpts
requireDigest bool
}
// ManifestOpts define options for the Manifest* commands.
type ManifestOpts func(*manifestOpt)
// WithManifest passes a manifest to ManifestDelete.
func WithManifest(m manifest.Manifest) ManifestOpts {
return func(opts *manifestOpt) {
opts.schemeOpts = append(opts.schemeOpts, scheme.WithManifest(m))
}
}
// WithManifestCheckReferrers checks for referrers field on ManifestDelete.
// This will update the client managed referrer listing.
func WithManifestCheckReferrers() ManifestOpts {
return func(opts *manifestOpt) {
opts.schemeOpts = append(opts.schemeOpts, scheme.WithManifestCheckReferrers())
}
}
// WithManifestChild for ManifestPut indicates the manifest is not the top level manifest being copied.
// This is used by the ocidir scheme to determine what entries to include in the index.json.
func WithManifestChild() ManifestOpts {
return func(opts *manifestOpt) {
opts.schemeOpts = append(opts.schemeOpts, scheme.WithManifestChild())
}
}
// WithManifestDesc includes the descriptor for ManifestGet.
// This is used to automatically extract a Data field if available.
func WithManifestDesc(d descriptor.Descriptor) ManifestOpts {
return func(opts *manifestOpt) {
opts.d = d
}
}
// WithManifestPlatform resolves the platform specific manifest on Get and Head requests.
// This causes an additional GET query to a registry when an Index or Manifest List is encountered.
// This option is ignored if the retrieved manifest is not an Index or Manifest List.
func WithManifestPlatform(p platform.Platform) ManifestOpts {
return func(opts *manifestOpt) {
opts.platform = &p
}
}
// WithManifestRequireDigest falls back from a HEAD to a GET request when digest headers aren't received.
func WithManifestRequireDigest() ManifestOpts {
return func(opts *manifestOpt) {
opts.requireDigest = true
}
}
// ManifestDelete removes a manifest, including all tags pointing to that registry.
// The reference must include the digest to delete (see TagDelete for deleting a tag).
// All tags pointing to the manifest will be deleted.
func (rc *RegClient) ManifestDelete(ctx context.Context, r ref.Ref, opts ...ManifestOpts) error {
if !r.IsSet() {
return fmt.Errorf("ref is not set: %s%.0w", r.CommonName(), errs.ErrInvalidReference)
}
opt := manifestOpt{schemeOpts: []scheme.ManifestOpts{}}
for _, fn := range opts {
fn(&opt)
}
schemeAPI, err := rc.schemeGet(r.Scheme)
if err != nil {
return err
}
return schemeAPI.ManifestDelete(ctx, r, opt.schemeOpts...)
}
// ManifestGet retrieves a manifest.
func (rc *RegClient) ManifestGet(ctx context.Context, r ref.Ref, opts ...ManifestOpts) (manifest.Manifest, error) {
if !r.IsSet() {
return nil, fmt.Errorf("ref is not set: %s%.0w", r.CommonName(), errs.ErrInvalidReference)
}
opt := manifestOpt{schemeOpts: []scheme.ManifestOpts{}}
for _, fn := range opts {
fn(&opt)
}
if opt.d.Digest != "" {
r = r.AddDigest(opt.d.Digest.String())
data, err := opt.d.GetData()
if err == nil {
return manifest.New(
manifest.WithDesc(opt.d),
manifest.WithRaw(data),
manifest.WithRef(r),
)
}
}
// dedup warnings
if w := warning.FromContext(ctx); w == nil {
ctx = warning.NewContext(ctx, &warning.Warning{Hook: warning.DefaultHook()})
}
schemeAPI, err := rc.schemeGet(r.Scheme)
if err != nil {
return nil, err
}
m, err := schemeAPI.ManifestGet(ctx, r)
if err != nil {
return m, err
}
if opt.platform != nil && !m.IsList() {
rc.slog.Debug("ignoring platform option, image is not an index",
slog.String("platform", opt.platform.String()),
slog.String("ref", r.CommonName()))
}
// this will loop to handle a nested index
for opt.platform != nil && m.IsList() {
d, err := manifest.GetPlatformDesc(m, opt.platform)
if err != nil {
return m, err
}
r = r.SetDigest(d.Digest.String())
m, err = schemeAPI.ManifestGet(ctx, r)
if err != nil {
return m, err
}
}
return m, err
}
// ManifestHead queries for the existence of a manifest and returns metadata (digest, media-type, size).
func (rc *RegClient) ManifestHead(ctx context.Context, r ref.Ref, opts ...ManifestOpts) (manifest.Manifest, error) {
if !r.IsSet() {
return nil, fmt.Errorf("ref is not set: %s%.0w", r.CommonName(), errs.ErrInvalidReference)
}
opt := manifestOpt{schemeOpts: []scheme.ManifestOpts{}}
for _, fn := range opts {
fn(&opt)
}
// dedup warnings
if w := warning.FromContext(ctx); w == nil {
ctx = warning.NewContext(ctx, &warning.Warning{Hook: warning.DefaultHook()})
}
schemeAPI, err := rc.schemeGet(r.Scheme)
if err != nil {
return nil, err
}
m, err := schemeAPI.ManifestHead(ctx, r)
if err != nil {
return m, err
}
if opt.platform != nil && !m.IsList() {
rc.slog.Debug("ignoring platform option, image is not an index",
slog.String("platform", opt.platform.String()),
slog.String("ref", r.CommonName()))
}
// this will loop to handle a nested index
for opt.platform != nil && m.IsList() {
if !m.IsSet() {
m, err = schemeAPI.ManifestGet(ctx, r)
}
d, err := manifest.GetPlatformDesc(m, opt.platform)
if err != nil {
return m, err
}
r = r.SetDigest(d.Digest.String())
m, err = schemeAPI.ManifestHead(ctx, r)
if err != nil {
return m, err
}
}
if opt.requireDigest && m.GetDescriptor().Digest.String() == "" {
m, err = schemeAPI.ManifestGet(ctx, r)
}
return m, err
}
// ManifestPut pushes a manifest.
// Any descriptors referenced by the manifest typically need to be pushed first.
func (rc *RegClient) ManifestPut(ctx context.Context, r ref.Ref, m manifest.Manifest, opts ...ManifestOpts) error {
if !r.IsSetRepo() {
return fmt.Errorf("ref is not set: %s%.0w", r.CommonName(), errs.ErrInvalidReference)
}
opt := manifestOpt{schemeOpts: []scheme.ManifestOpts{}}
for _, fn := range opts {
fn(&opt)
}
schemeAPI, err := rc.schemeGet(r.Scheme)
if err != nil {
return err
}
return schemeAPI.ManifestPut(ctx, r, m, opt.schemeOpts...)
}