mirror of
https://github.com/containers/podman.git
synced 2025-12-02 02:58:03 +08:00
Move to containers/image v5 and containers/buildah to v1.11.4. Replace an equality check with a type assertion when checking for a docker.ErrUnauthorizedForCredentials in `podman login`. Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
222 lines
7.8 KiB
Go
222 lines
7.8 KiB
Go
package manifest
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"runtime"
|
|
|
|
"github.com/containers/image/v5/types"
|
|
"github.com/opencontainers/go-digest"
|
|
imgspec "github.com/opencontainers/image-spec/specs-go"
|
|
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// OCI1Index is just an alias for the OCI index type, but one which we can
|
|
// provide methods for.
|
|
type OCI1Index struct {
|
|
imgspecv1.Index
|
|
}
|
|
|
|
// MIMEType returns the MIME type of this particular manifest index.
|
|
func (index *OCI1Index) MIMEType() string {
|
|
return imgspecv1.MediaTypeImageIndex
|
|
}
|
|
|
|
// Instances returns a slice of digests of the manifests that this index knows of.
|
|
func (index *OCI1Index) Instances() []digest.Digest {
|
|
results := make([]digest.Digest, len(index.Manifests))
|
|
for i, m := range index.Manifests {
|
|
results[i] = m.Digest
|
|
}
|
|
return results
|
|
}
|
|
|
|
// Instance returns the ListUpdate of a particular instance in the index.
|
|
func (index *OCI1Index) Instance(instanceDigest digest.Digest) (ListUpdate, error) {
|
|
for _, manifest := range index.Manifests {
|
|
if manifest.Digest == instanceDigest {
|
|
return ListUpdate{
|
|
Digest: manifest.Digest,
|
|
Size: manifest.Size,
|
|
MediaType: manifest.MediaType,
|
|
}, nil
|
|
}
|
|
}
|
|
return ListUpdate{}, errors.Errorf("unable to find instance %s in OCI1Index", instanceDigest)
|
|
}
|
|
|
|
// UpdateInstances updates the sizes, digests, and media types of the manifests
|
|
// which the list catalogs.
|
|
func (index *OCI1Index) UpdateInstances(updates []ListUpdate) error {
|
|
if len(updates) != len(index.Manifests) {
|
|
return errors.Errorf("incorrect number of update entries passed to OCI1Index.UpdateInstances: expected %d, got %d", len(index.Manifests), len(updates))
|
|
}
|
|
for i := range updates {
|
|
if err := updates[i].Digest.Validate(); err != nil {
|
|
return errors.Wrapf(err, "update %d of %d passed to OCI1Index.UpdateInstances contained an invalid digest", i+1, len(updates))
|
|
}
|
|
index.Manifests[i].Digest = updates[i].Digest
|
|
if updates[i].Size < 0 {
|
|
return errors.Errorf("update %d of %d passed to OCI1Index.UpdateInstances had an invalid size (%d)", i+1, len(updates), updates[i].Size)
|
|
}
|
|
index.Manifests[i].Size = updates[i].Size
|
|
if updates[i].MediaType == "" {
|
|
return errors.Errorf("update %d of %d passed to OCI1Index.UpdateInstances had no media type (was %q)", i+1, len(updates), index.Manifests[i].MediaType)
|
|
}
|
|
if err := SupportedOCI1MediaType(updates[i].MediaType); err != nil && SupportedSchema2MediaType(updates[i].MediaType) != nil && updates[i].MediaType != imgspecv1.MediaTypeImageIndex {
|
|
return errors.Wrapf(err, "update %d of %d passed to OCI1Index.UpdateInstances had an unsupported media type (was %q): %q", i+1, len(updates), index.Manifests[i].MediaType, updates[i].MediaType)
|
|
}
|
|
index.Manifests[i].MediaType = updates[i].MediaType
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ChooseInstance parses blob as an oci v1 manifest index, and returns the digest
|
|
// of the image which is appropriate for the current environment.
|
|
func (index *OCI1Index) ChooseInstance(ctx *types.SystemContext) (digest.Digest, error) {
|
|
wantedArch := runtime.GOARCH
|
|
if ctx != nil && ctx.ArchitectureChoice != "" {
|
|
wantedArch = ctx.ArchitectureChoice
|
|
}
|
|
wantedOS := runtime.GOOS
|
|
if ctx != nil && ctx.OSChoice != "" {
|
|
wantedOS = ctx.OSChoice
|
|
}
|
|
|
|
for _, d := range index.Manifests {
|
|
if d.Platform != nil && d.Platform.Architecture == wantedArch && d.Platform.OS == wantedOS {
|
|
return d.Digest, nil
|
|
}
|
|
}
|
|
for _, d := range index.Manifests {
|
|
if d.Platform == nil {
|
|
return d.Digest, nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("no image found in image index for architecture %s, OS %s", wantedArch, wantedOS)
|
|
}
|
|
|
|
// Serialize returns the index in a blob format.
|
|
// NOTE: Serialize() does not in general reproduce the original blob if this object was loaded from one, even if no modifications were made!
|
|
func (index *OCI1Index) Serialize() ([]byte, error) {
|
|
buf, err := json.Marshal(index)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error marshaling OCI1Index %#v", index)
|
|
}
|
|
return buf, nil
|
|
}
|
|
|
|
// OCI1IndexFromComponents creates an OCI1 image index instance from the
|
|
// supplied data.
|
|
func OCI1IndexFromComponents(components []imgspecv1.Descriptor, annotations map[string]string) *OCI1Index {
|
|
index := OCI1Index{
|
|
imgspecv1.Index{
|
|
Versioned: imgspec.Versioned{SchemaVersion: 2},
|
|
Manifests: make([]imgspecv1.Descriptor, len(components)),
|
|
Annotations: dupStringStringMap(annotations),
|
|
},
|
|
}
|
|
for i, component := range components {
|
|
var platform *imgspecv1.Platform
|
|
if component.Platform != nil {
|
|
platform = &imgspecv1.Platform{
|
|
Architecture: component.Platform.Architecture,
|
|
OS: component.Platform.OS,
|
|
OSVersion: component.Platform.OSVersion,
|
|
OSFeatures: dupStringSlice(component.Platform.OSFeatures),
|
|
Variant: component.Platform.Variant,
|
|
}
|
|
}
|
|
m := imgspecv1.Descriptor{
|
|
MediaType: component.MediaType,
|
|
Size: component.Size,
|
|
Digest: component.Digest,
|
|
URLs: dupStringSlice(component.URLs),
|
|
Annotations: dupStringStringMap(component.Annotations),
|
|
Platform: platform,
|
|
}
|
|
index.Manifests[i] = m
|
|
}
|
|
return &index
|
|
}
|
|
|
|
// OCI1IndexClone creates a deep copy of the passed-in index.
|
|
func OCI1IndexClone(index *OCI1Index) *OCI1Index {
|
|
return OCI1IndexFromComponents(index.Manifests, index.Annotations)
|
|
}
|
|
|
|
// ToOCI1Index returns the index encoded as an OCI1 index.
|
|
func (index *OCI1Index) ToOCI1Index() (*OCI1Index, error) {
|
|
return OCI1IndexClone(index), nil
|
|
}
|
|
|
|
// ToSchema2List returns the index encoded as a Schema2 list.
|
|
func (index *OCI1Index) ToSchema2List() (*Schema2List, error) {
|
|
components := make([]Schema2ManifestDescriptor, 0, len(index.Manifests))
|
|
for _, manifest := range index.Manifests {
|
|
platform := manifest.Platform
|
|
if platform == nil {
|
|
platform = &imgspecv1.Platform{
|
|
OS: runtime.GOOS,
|
|
Architecture: runtime.GOARCH,
|
|
}
|
|
}
|
|
converted := Schema2ManifestDescriptor{
|
|
Schema2Descriptor{
|
|
MediaType: manifest.MediaType,
|
|
Size: manifest.Size,
|
|
Digest: manifest.Digest,
|
|
URLs: dupStringSlice(manifest.URLs),
|
|
},
|
|
Schema2PlatformSpec{
|
|
OS: platform.OS,
|
|
Architecture: platform.Architecture,
|
|
OSFeatures: dupStringSlice(platform.OSFeatures),
|
|
OSVersion: platform.OSVersion,
|
|
Variant: platform.Variant,
|
|
},
|
|
}
|
|
components = append(components, converted)
|
|
}
|
|
s2 := Schema2ListFromComponents(components)
|
|
return s2, nil
|
|
}
|
|
|
|
// OCI1IndexFromManifest creates an OCI1 manifest index instance from marshalled
|
|
// JSON, presumably generated by encoding a OCI1 manifest index.
|
|
func OCI1IndexFromManifest(manifest []byte) (*OCI1Index, error) {
|
|
index := OCI1Index{
|
|
Index: imgspecv1.Index{
|
|
Versioned: imgspec.Versioned{SchemaVersion: 2},
|
|
Manifests: []imgspecv1.Descriptor{},
|
|
Annotations: make(map[string]string),
|
|
},
|
|
}
|
|
if err := json.Unmarshal(manifest, &index); err != nil {
|
|
return nil, errors.Wrapf(err, "error unmarshaling OCI1Index %q", string(manifest))
|
|
}
|
|
return &index, nil
|
|
}
|
|
|
|
// Clone returns a deep copy of this list and its contents.
|
|
func (index *OCI1Index) Clone() List {
|
|
return OCI1IndexClone(index)
|
|
}
|
|
|
|
// ConvertToMIMEType converts the passed-in image index to a manifest list of
|
|
// the specified type.
|
|
func (index *OCI1Index) ConvertToMIMEType(manifestMIMEType string) (List, error) {
|
|
switch normalized := NormalizedMIMEType(manifestMIMEType); normalized {
|
|
case DockerV2ListMediaType:
|
|
return index.ToSchema2List()
|
|
case imgspecv1.MediaTypeImageIndex:
|
|
return index.Clone(), nil
|
|
case DockerV2Schema1MediaType, DockerV2Schema1SignedMediaType, imgspecv1.MediaTypeImageManifest, DockerV2Schema2MediaType:
|
|
return nil, fmt.Errorf("Can not convert image index to MIME type %q, which is not a list type", manifestMIMEType)
|
|
default:
|
|
// Note that this may not be reachable, NormalizedMIMEType has a default for unknown values.
|
|
return nil, fmt.Errorf("Unimplemented manifest MIME type %s", manifestMIMEType)
|
|
}
|
|
}
|