crazy-max_diun/pkg/registry/manifest.go

117 lines
3.6 KiB
Go

package registry
import (
"fmt"
"time"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/manifest"
"github.com/opencontainers/go-digest"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
// Manifest is the Docker image manifest information
type Manifest struct {
Name string
Tag string
MIMEType string
Digest digest.Digest
Created *time.Time
DockerVersion string
Labels map[string]string
Layers []string
Platform string
Raw []byte
}
// Manifest returns the manifest for a specific image
func (c *Client) Manifest(image Image, dbManifest Manifest) (Manifest, bool, error) {
ctx, cancel := c.timeoutContext()
defer cancel()
rmRef, err := ImageReference(image.String())
if err != nil {
return Manifest{}, false, errors.Wrap(err, "cannot parse reference")
}
// Retrieve remote digest through HEAD request
rmDigest, err := docker.GetDigest(ctx, c.sysCtx, rmRef)
if err != nil {
return Manifest{}, false, errors.Wrap(err, "cannot get image digest from HEAD request")
}
// Digest match, returns db manifest
if c.opts.CompareDigest && len(dbManifest.Digest) > 0 && dbManifest.Digest == rmDigest {
return dbManifest, false, nil
}
rmCloser, err := rmRef.NewImage(ctx, c.sysCtx)
if err != nil {
return Manifest{}, false, errors.Wrap(err, "cannot create image closer")
}
defer rmCloser.Close()
rmRawManifest, rmManifestMimeType, err := rmCloser.Manifest(ctx)
if err != nil {
return Manifest{}, false, errors.Wrap(err, "cannot get raw manifest")
}
// For manifests list compare also digest matching the platform
updated := dbManifest.Digest != rmDigest
if c.opts.CompareDigest && len(dbManifest.Raw) > 0 && dbManifest.isManifestList() && isManifestList(rmManifestMimeType) {
dbManifestList, err := manifest.ListFromBlob(dbManifest.Raw, dbManifest.MIMEType)
if err != nil {
return Manifest{}, false, errors.Wrap(err, "cannot parse manifest list")
}
dbManifestPlatformDigest, err := dbManifestList.ChooseInstance(c.sysCtx)
if err != nil {
return Manifest{}, false, errors.Wrapf(err, "error choosing image instance")
}
rmManifestList, err := manifest.ListFromBlob(rmRawManifest, rmManifestMimeType)
if err != nil {
return Manifest{}, false, errors.Wrap(err, "cannot parse manifest list")
}
rmManifestPlatformDigest, err := rmManifestList.ChooseInstance(c.sysCtx)
if err != nil {
return Manifest{}, false, errors.Wrapf(err, "error choosing image instance")
}
updated = dbManifestPlatformDigest != rmManifestPlatformDigest
}
// Metadata describing the Docker image
rmInspect, err := rmCloser.Inspect(ctx)
if err != nil {
return Manifest{}, false, errors.Wrap(err, "cannot inspect")
}
rmTag := rmInspect.Tag
if len(rmTag) == 0 {
rmTag = image.Tag
}
rmPlatform := fmt.Sprintf("%s/%s", rmInspect.Os, rmInspect.Architecture)
if rmInspect.Variant != "" {
rmPlatform = fmt.Sprintf("%s/%s", rmPlatform, rmInspect.Variant)
}
return Manifest{
Name: rmCloser.Reference().DockerReference().Name(),
Tag: rmTag,
MIMEType: rmManifestMimeType,
Digest: rmDigest,
Created: rmInspect.Created,
DockerVersion: rmInspect.DockerVersion,
Labels: rmInspect.Labels,
Layers: rmInspect.Layers,
Platform: rmPlatform,
Raw: rmRawManifest,
}, updated, nil
}
func (m Manifest) isManifestList() bool {
return isManifestList(m.MIMEType)
}
func isManifestList(mimeType string) bool {
return mimeType == manifest.DockerV2ListMediaType || mimeType == imgspecv1.MediaTypeImageIndex
}