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 }