mirror of
https://github.com/containers/podman.git
synced 2025-06-17 06:57:43 +08:00
apiv2 addition of manifests
add endpoints for create, add, remove, inspect, and push. this allows manifests to be managed through the restful interfaces. also added go-bindings and tests Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
154
libpod/image/manifests.go
Normal file
154
libpod/image/manifests.go
Normal file
@ -0,0 +1,154 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containers/buildah/manifests"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Options for adding a manifest
|
||||
// swagger:model ManifestAddOpts
|
||||
type ManifestAddOpts struct {
|
||||
All bool `json:"all"`
|
||||
Annotation map[string]string `json:"annotation"`
|
||||
Arch string `json:"arch"`
|
||||
Features []string `json:"features"`
|
||||
Images []string `json:"images"`
|
||||
OSVersion string `json:"os_version"`
|
||||
Variant string `json:"variant"`
|
||||
}
|
||||
|
||||
// InspectManifest returns a dockerized version of the manifest list
|
||||
func (i *Image) InspectManifest() (*manifest.Schema2List, error) {
|
||||
list, err := i.getManifestList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list.Docker(), nil
|
||||
}
|
||||
|
||||
// RemoveManifest removes the given digest from the manifest list.
|
||||
func (i *Image) RemoveManifest(d digest.Digest) (string, error) {
|
||||
list, err := i.getManifestList()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := list.Remove(d); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return list.SaveToImage(i.imageruntime.store, i.ID(), nil, "")
|
||||
}
|
||||
|
||||
// getManifestList is a helper to obtain a manifest list
|
||||
func (i *Image) getManifestList() (manifests.List, error) {
|
||||
_, list, err := manifests.LoadFromImage(i.imageruntime.store, i.ID())
|
||||
return list, err
|
||||
}
|
||||
|
||||
// CreateManifestList creates a new manifest list and can optionally add given images
|
||||
// to the list
|
||||
func CreateManifestList(rt *Runtime, systemContext types.SystemContext, names []string, imgs []string, all bool) (string, error) {
|
||||
list := manifests.Create()
|
||||
opts := ManifestAddOpts{Images: names, All: all}
|
||||
for _, img := range imgs {
|
||||
var ref types.ImageReference
|
||||
newImage, err := rt.NewFromLocal(img)
|
||||
if err == nil {
|
||||
ir, err := newImage.toImageRef(context.Background())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if ir == nil {
|
||||
return "", errors.New("unable to convert image to ImageReference")
|
||||
}
|
||||
ref = ir.Reference()
|
||||
} else {
|
||||
ref, err = alltransports.ParseImageName(img)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
list, err = addManifestToList(ref, list, systemContext, opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return list.SaveToImage(rt.store, "", names, manifest.DockerV2ListMediaType)
|
||||
}
|
||||
|
||||
func addManifestToList(ref types.ImageReference, list manifests.List, systemContext types.SystemContext, opts ManifestAddOpts) (manifests.List, error) {
|
||||
d, err := list.Add(context.Background(), &systemContext, ref, opts.All)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(opts.OSVersion) > 0 {
|
||||
if err := list.SetOSVersion(d, opts.OSVersion); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(opts.Features) > 0 {
|
||||
if err := list.SetFeatures(d, opts.Features); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(opts.Arch) > 0 {
|
||||
if err := list.SetArchitecture(d, opts.Arch); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(opts.Variant) > 0 {
|
||||
if err := list.SetVariant(d, opts.Variant); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(opts.Annotation) > 0 {
|
||||
if err := list.SetAnnotations(&d, opts.Annotation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return list, err
|
||||
}
|
||||
|
||||
// AddManifest adds a manifest to a given manifest list.
|
||||
func (i *Image) AddManifest(systemContext types.SystemContext, opts ManifestAddOpts) (string, error) {
|
||||
var (
|
||||
ref types.ImageReference
|
||||
)
|
||||
newImage, err := i.imageruntime.NewFromLocal(opts.Images[0])
|
||||
if err == nil {
|
||||
ir, err := newImage.toImageRef(context.Background())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ref = ir.Reference()
|
||||
} else {
|
||||
ref, err = alltransports.ParseImageName(opts.Images[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
list, err := i.getManifestList()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
list, err = addManifestToList(ref, list, systemContext, opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return list.SaveToImage(i.imageruntime.store, i.ID(), nil, "")
|
||||
}
|
||||
|
||||
// PushManifest pushes a manifest to a destination
|
||||
func (i *Image) PushManifest(dest types.ImageReference, opts manifests.PushOptions) (digest.Digest, error) {
|
||||
list, err := i.getManifestList()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, d, err := list.Push(context.Background(), dest, opts)
|
||||
return d, err
|
||||
}
|
166
pkg/api/handlers/libpod/manifests.go
Normal file
166
pkg/api/handlers/libpod/manifests.go
Normal file
@ -0,0 +1,166 @@
|
||||
package libpod
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/containers/buildah/manifests"
|
||||
copy2 "github.com/containers/image/v5/copy"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/containers/libpod/libpod"
|
||||
"github.com/containers/libpod/libpod/image"
|
||||
"github.com/containers/libpod/pkg/api/handlers"
|
||||
"github.com/containers/libpod/pkg/api/handlers/utils"
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func ManifestCreate(w http.ResponseWriter, r *http.Request) {
|
||||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||||
query := struct {
|
||||
Name []string `schema:"name"`
|
||||
Image []string `schema:"image"`
|
||||
All bool `schema:"all"`
|
||||
}{
|
||||
// Add defaults here once needed.
|
||||
}
|
||||
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
||||
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
|
||||
errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
|
||||
return
|
||||
}
|
||||
rtc, err := runtime.GetConfig()
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false)
|
||||
manID, err := image.CreateManifestList(runtime.ImageRuntime(), *sc, query.Name, query.Image, query.All)
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: manID})
|
||||
}
|
||||
|
||||
func ManifestInspect(w http.ResponseWriter, r *http.Request) {
|
||||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||
name := utils.GetName(r)
|
||||
newImage, err := runtime.ImageRuntime().NewFromLocal(name)
|
||||
if err != nil {
|
||||
utils.ImageNotFound(w, name, err)
|
||||
return
|
||||
}
|
||||
data, err := newImage.InspectManifest()
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
utils.WriteResponse(w, http.StatusOK, data)
|
||||
}
|
||||
|
||||
func ManifestAdd(w http.ResponseWriter, r *http.Request) {
|
||||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||
var manifestInput image.ManifestAddOpts
|
||||
if err := json.NewDecoder(r.Body).Decode(&manifestInput); err != nil {
|
||||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
|
||||
return
|
||||
}
|
||||
name := utils.GetName(r)
|
||||
newImage, err := runtime.ImageRuntime().NewFromLocal(name)
|
||||
if err != nil {
|
||||
utils.ImageNotFound(w, name, err)
|
||||
return
|
||||
}
|
||||
rtc, err := runtime.GetConfig()
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false)
|
||||
newID, err := newImage.AddManifest(*sc, manifestInput)
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID})
|
||||
}
|
||||
|
||||
func ManifestRemove(w http.ResponseWriter, r *http.Request) {
|
||||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||||
query := struct {
|
||||
Digest string `schema:"digest"`
|
||||
}{
|
||||
// Add defaults here once needed.
|
||||
}
|
||||
name := utils.GetName(r)
|
||||
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
||||
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
|
||||
errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
|
||||
return
|
||||
}
|
||||
newImage, err := runtime.ImageRuntime().NewFromLocal(name)
|
||||
if err != nil {
|
||||
utils.ImageNotFound(w, name, err)
|
||||
return
|
||||
}
|
||||
d, err := digest.Parse(query.Digest)
|
||||
if err != nil {
|
||||
utils.Error(w, "invalid digest", http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
newID, err := newImage.RemoveManifest(d)
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID})
|
||||
}
|
||||
func ManifestPush(w http.ResponseWriter, r *http.Request) {
|
||||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||||
query := struct {
|
||||
All bool `schema:"all"`
|
||||
Destination string `schema:"destination"`
|
||||
}{
|
||||
// Add defaults here once needed.
|
||||
}
|
||||
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
||||
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
|
||||
errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
|
||||
return
|
||||
}
|
||||
name := utils.GetName(r)
|
||||
newImage, err := runtime.ImageRuntime().NewFromLocal(name)
|
||||
if err != nil {
|
||||
utils.ImageNotFound(w, name, err)
|
||||
return
|
||||
}
|
||||
dest, err := alltransports.ParseImageName(query.Destination)
|
||||
if err != nil {
|
||||
utils.Error(w, "invalid destination parameter", http.StatusBadRequest, errors.Errorf("invalid destination parameter %q", query.Destination))
|
||||
return
|
||||
}
|
||||
rtc, err := runtime.GetConfig()
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false)
|
||||
opts := manifests.PushOptions{
|
||||
ImageListSelection: copy2.CopySpecificImages,
|
||||
SystemContext: sc,
|
||||
}
|
||||
if query.All {
|
||||
opts.ImageListSelection = copy2.CopyAllImages
|
||||
}
|
||||
newD, err := newImage.PushManifest(dest, opts)
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
utils.WriteResponse(w, http.StatusOK, newD.String())
|
||||
}
|
@ -1,8 +1,17 @@
|
||||
package libpod
|
||||
|
||||
import "github.com/containers/image/v5/manifest"
|
||||
|
||||
// List Containers
|
||||
// swagger:response ListContainers
|
||||
type swagInspectPodResponse struct {
|
||||
// in:body
|
||||
Body []ListContainer
|
||||
}
|
||||
|
||||
// Inspect Manifest
|
||||
// swagger:response InspectManifest
|
||||
type swagInspectManifestResponse struct {
|
||||
// in:body
|
||||
Body manifest.List
|
||||
}
|
||||
|
@ -140,7 +140,9 @@ type VolumeCreateConfig struct {
|
||||
Opts map[string]string `schema:"opts"`
|
||||
}
|
||||
|
||||
// swagger:model IDResponse
|
||||
type IDResponse struct {
|
||||
// ID
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
|
145
pkg/api/server/register_manifest.go
Normal file
145
pkg/api/server/register_manifest.go
Normal file
@ -0,0 +1,145 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/containers/libpod/pkg/api/handlers/libpod"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (s *APIServer) registerManifestHandlers(r *mux.Router) error {
|
||||
// swagger:operation POST /libpod/manifests/create manifests Create
|
||||
// ---
|
||||
// summary: Create
|
||||
// description: Create a manifest list
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - in: query
|
||||
// name: name
|
||||
// type: string
|
||||
// description: manifest list name
|
||||
// required: true
|
||||
// - in: query
|
||||
// name: image
|
||||
// type: string
|
||||
// description: name of the image
|
||||
// - in: query
|
||||
// name: all
|
||||
// type: boolean
|
||||
// description: add all contents if given list
|
||||
// responses:
|
||||
// 200:
|
||||
// $ref: "#/definitions/IDResponse"
|
||||
// 400:
|
||||
// $ref: "#/responses/BadParamError"
|
||||
// 404:
|
||||
// $ref: "#/responses/NoSuchImage"
|
||||
// 500:
|
||||
// $ref: "#/responses/InternalError"
|
||||
r.Handle(VersionedPath("/libpod/manifests/create"), s.APIHandler(libpod.ManifestCreate)).Methods(http.MethodPost)
|
||||
// swagger:operation GET /libpod/manifests/{name}/json manifests Inspect
|
||||
// ---
|
||||
// summary: Inspect
|
||||
// description: Display a manifest list
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - in: path
|
||||
// name: name
|
||||
// type: string
|
||||
// required: true
|
||||
// description: the name or ID of the manifest
|
||||
// responses:
|
||||
// 200:
|
||||
// $ref: "#/responses/InspectManifest"
|
||||
// 404:
|
||||
// $ref: "#/responses/NoSuchManifest"
|
||||
// 500:
|
||||
// $ref: "#/responses/InternalError"
|
||||
r.Handle(VersionedPath("/libpod/manifests/{name:.*}/json"), s.APIHandler(libpod.ManifestInspect)).Methods(http.MethodGet)
|
||||
// swagger:operation POST /libpod/manifests/{name}/add manifests AddManifest
|
||||
// ---
|
||||
// description: Add an image to a manifest list
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - in: path
|
||||
// name: name
|
||||
// type: string
|
||||
// required: true
|
||||
// description: the name or ID of the manifest
|
||||
// - in: body
|
||||
// name: options
|
||||
// description: options for creating a manifest
|
||||
// schema:
|
||||
// $ref: "#/definitions/ManifestAddOpts"
|
||||
// responses:
|
||||
// 200:
|
||||
// $ref: "#/definitions/IDResponse"
|
||||
// 404:
|
||||
// $ref: "#/responses/NoSuchManifest"
|
||||
// 409:
|
||||
// $ref: "#/responses/BadParamError"
|
||||
// 500:
|
||||
// $ref: "#/responses/InternalError"
|
||||
r.Handle(VersionedPath("/libpod/manifests/{name:.*}/add"), s.APIHandler(libpod.ManifestAdd)).Methods(http.MethodPost)
|
||||
// swagger:operation DELETE /libpod/manifests/{name} manifests RemoveManifest
|
||||
// ---
|
||||
// summary: Remove
|
||||
// description: Remove an image from a manifest list
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - in: path
|
||||
// name: name
|
||||
// type: string
|
||||
// required: true
|
||||
// description: the image associated with the manifest
|
||||
// - in: query
|
||||
// name: digest
|
||||
// type: string
|
||||
// description: image digest to be removed
|
||||
// responses:
|
||||
// 200:
|
||||
// $ref: "#/definitions/IDResponse"
|
||||
// 400:
|
||||
// $ref: "#/responses/BadParamError"
|
||||
// 404:
|
||||
// $ref: "#/responses/NoSuchManifest"
|
||||
// 500:
|
||||
// $ref: "#/responses/InternalError"
|
||||
r.Handle(VersionedPath("/libpod/manifests/{name:.*}"), s.APIHandler(libpod.ManifestRemove)).Methods(http.MethodDelete)
|
||||
// swagger:operation POST /libpod/manifests/{name}/push manifests PushManifest
|
||||
// ---
|
||||
// summary: Push
|
||||
// description: Push a manifest list or image index to a registry
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - in: path
|
||||
// name: name
|
||||
// type: string
|
||||
// required: true
|
||||
// description: the name or ID of the manifest
|
||||
// - in: query
|
||||
// name: destination
|
||||
// type: string
|
||||
// required: true
|
||||
// description: the destination for the manifest
|
||||
// - in: query
|
||||
// name: all
|
||||
// description: push all images
|
||||
// type: boolean
|
||||
// responses:
|
||||
// 200:
|
||||
// $ref: "#/definitions/IDResponse"
|
||||
// 400:
|
||||
// $ref: "#/responses/BadParamError"
|
||||
// 404:
|
||||
// $ref: "#/responses/NoSuchManifest"
|
||||
// 500:
|
||||
// $ref: "#/responses/InternalError"
|
||||
r.Handle(VersionedPath("/libpod/manifests/{name}/push"), s.APIHandler(libpod.ManifestPush)).Methods(http.MethodPost)
|
||||
return nil
|
||||
}
|
@ -99,11 +99,12 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li
|
||||
server.registerAuthHandlers,
|
||||
server.registerContainersHandlers,
|
||||
server.registerDistributionHandlers,
|
||||
server.registerExecHandlers,
|
||||
server.registerEventsHandlers,
|
||||
server.registerExecHandlers,
|
||||
server.registerHealthCheckHandlers,
|
||||
server.registerImagesHandlers,
|
||||
server.registerInfoHandlers,
|
||||
server.registerManifestHandlers,
|
||||
server.registerMonitorHandlers,
|
||||
server.registerPingHandlers,
|
||||
server.registerPluginsHandlers,
|
||||
|
@ -51,6 +51,15 @@ type swagErrNoSuchPod struct {
|
||||
}
|
||||
}
|
||||
|
||||
// No such manifest
|
||||
// swagger:response NoSuchManifest
|
||||
type swagErrNoSuchManifest struct {
|
||||
// in:body
|
||||
Body struct {
|
||||
utils.ErrorModel
|
||||
}
|
||||
}
|
||||
|
||||
// Internal server error
|
||||
// swagger:response InternalError
|
||||
type swagInternalError struct {
|
||||
|
@ -6,6 +6,8 @@ tags:
|
||||
- name: images
|
||||
description: Actions related to images
|
||||
- name: pods
|
||||
description: Actions related to manifests
|
||||
- name: manifests
|
||||
description: Actions related to pods
|
||||
- name: volumes
|
||||
description: Actions related to volumes
|
||||
|
126
pkg/bindings/manifests/manifests.go
Normal file
126
pkg/bindings/manifests/manifests.go
Normal file
@ -0,0 +1,126 @@
|
||||
package manifests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/libpod/libpod/image"
|
||||
"github.com/containers/libpod/pkg/api/handlers"
|
||||
"github.com/containers/libpod/pkg/bindings"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
// Create creates a manifest for the given name. Optional images to be associated with
|
||||
// the new manifest can also be specified. The all boolean specifies to add all entries
|
||||
// of a list if the name provided is a manifest list. The ID of the new manifest list
|
||||
// is returned as a string.
|
||||
func Create(ctx context.Context, names, images []string, all *bool) (string, error) {
|
||||
var idr handlers.IDResponse
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(names) < 1 {
|
||||
return "", errors.New("creating a manifest requires at least one name argument")
|
||||
}
|
||||
params := url.Values{}
|
||||
if all != nil {
|
||||
params.Set("all", strconv.FormatBool(*all))
|
||||
}
|
||||
for _, name := range names {
|
||||
params.Add("name", name)
|
||||
}
|
||||
for _, i := range images {
|
||||
params.Add("image", i)
|
||||
}
|
||||
|
||||
response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/create", params)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return idr.ID, response.Process(&idr)
|
||||
}
|
||||
|
||||
// Inspect returns a manifest list for a given name.
|
||||
func Inspect(ctx context.Context, name string) (*manifest.Schema2List, error) {
|
||||
var list manifest.Schema2List
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response, err := conn.DoRequest(nil, http.MethodGet, "/manifests/%s/json", nil, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &list, response.Process(&list)
|
||||
}
|
||||
|
||||
// Add adds a manifest to a given manifest list. Additional options for the manifest
|
||||
// can also be specified. The ID of the new manifest list is returned as a string
|
||||
func Add(ctx context.Context, name string, options image.ManifestAddOpts) (string, error) {
|
||||
var idr handlers.IDResponse
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
optionsString, err := jsoniter.MarshalToString(options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
stringReader := strings.NewReader(optionsString)
|
||||
response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/add", nil, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return idr.ID, response.Process(&idr)
|
||||
}
|
||||
|
||||
// Remove deletes a manifest entry from a manifest list. Both name and the digest to be
|
||||
// removed are mandatory inputs. The ID of the new manifest list is returned as a string.
|
||||
func Remove(ctx context.Context, name, digest string) (string, error) {
|
||||
var idr handlers.IDResponse
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
params := url.Values{}
|
||||
params.Set("digest", digest)
|
||||
response, err := conn.DoRequest(nil, http.MethodDelete, "/manifests/%s", params, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return idr.ID, response.Process(&idr)
|
||||
}
|
||||
|
||||
// Push takes a manifest list and pushes to a destination. If the destination is not specified,
|
||||
// the name will be used instead. If the optional all boolean is specified, all images specified
|
||||
// in the list will be pushed as well.
|
||||
func Push(ctx context.Context, name string, destination *string, all *bool) (string, error) {
|
||||
var (
|
||||
idr handlers.IDResponse
|
||||
)
|
||||
dest := name
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
params := url.Values{}
|
||||
params.Set("image", name)
|
||||
if destination != nil {
|
||||
dest = name
|
||||
}
|
||||
params.Set("destination", dest)
|
||||
if all != nil {
|
||||
params.Set("all", strconv.FormatBool(*all))
|
||||
}
|
||||
response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return idr.ID, response.Process(&idr)
|
||||
}
|
124
pkg/bindings/test/manifests_test.go
Normal file
124
pkg/bindings/test/manifests_test.go
Normal file
@ -0,0 +1,124 @@
|
||||
package test_bindings
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/containers/libpod/libpod/image"
|
||||
"github.com/containers/libpod/pkg/bindings"
|
||||
"github.com/containers/libpod/pkg/bindings/images"
|
||||
"github.com/containers/libpod/pkg/bindings/manifests"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/gexec"
|
||||
)
|
||||
|
||||
var _ = Describe("Podman containers ", func() {
|
||||
var (
|
||||
bt *bindingTest
|
||||
s *gexec.Session
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
bt = newBindingTest()
|
||||
bt.RestoreImagesFromCache()
|
||||
s = bt.startAPIService()
|
||||
time.Sleep(1 * time.Second)
|
||||
err := bt.NewConnection()
|
||||
Expect(err).To(BeNil())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
s.Kill()
|
||||
bt.cleanup()
|
||||
})
|
||||
|
||||
It("create manifest", func() {
|
||||
// create manifest list without images
|
||||
id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
list, err := manifests.Inspect(bt.conn, id)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(len(list.Manifests)).To(BeZero())
|
||||
|
||||
// creating a duplicate should fail as a 500
|
||||
_, err = manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil)
|
||||
Expect(err).ToNot(BeNil())
|
||||
code, _ := bindings.CheckResponseCode(err)
|
||||
Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
|
||||
|
||||
_, err = images.Remove(bt.conn, id, nil)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
// create manifest list with images
|
||||
id, err = manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{alpine.name}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
list, err = manifests.Inspect(bt.conn, id)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(len(list.Manifests)).To(BeNumerically("==", 1))
|
||||
})
|
||||
|
||||
It("inspect bogus manifest", func() {
|
||||
_, err := manifests.Inspect(bt.conn, "larry")
|
||||
Expect(err).ToNot(BeNil())
|
||||
code, _ := bindings.CheckResponseCode(err)
|
||||
Expect(code).To(BeNumerically("==", http.StatusNotFound))
|
||||
})
|
||||
|
||||
It("add manifest", func() {
|
||||
// add to bogus should 404
|
||||
_, err := manifests.Add(bt.conn, "foobar", image.ManifestAddOpts{})
|
||||
Expect(err).ToNot(BeNil())
|
||||
code, _ := bindings.CheckResponseCode(err)
|
||||
Expect(code).To(BeNumerically("==", http.StatusNotFound))
|
||||
|
||||
id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
opts := image.ManifestAddOpts{Images: []string{alpine.name}}
|
||||
_, err = manifests.Add(bt.conn, id, opts)
|
||||
Expect(err).To(BeNil())
|
||||
list, err := manifests.Inspect(bt.conn, id)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(len(list.Manifests)).To(BeNumerically("==", 1))
|
||||
|
||||
// add bogus name to existing list should fail
|
||||
opts.Images = []string{"larry"}
|
||||
_, err = manifests.Add(bt.conn, id, opts)
|
||||
Expect(err).ToNot(BeNil())
|
||||
code, _ = bindings.CheckResponseCode(err)
|
||||
Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
|
||||
})
|
||||
|
||||
It("remove manifest", func() {
|
||||
// removal on bogus manifest list should be 404
|
||||
_, err := manifests.Remove(bt.conn, "larry", "1234")
|
||||
Expect(err).ToNot(BeNil())
|
||||
code, _ := bindings.CheckResponseCode(err)
|
||||
Expect(code).To(BeNumerically("==", http.StatusNotFound))
|
||||
|
||||
id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{alpine.name}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
data, err := manifests.Inspect(bt.conn, id)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(len(data.Manifests)).To(BeNumerically("==", 1))
|
||||
|
||||
// removal on a good manifest list with a bad digest should be 400
|
||||
_, err = manifests.Remove(bt.conn, id, "!234")
|
||||
Expect(err).ToNot(BeNil())
|
||||
code, _ = bindings.CheckResponseCode(err)
|
||||
Expect(code).To(BeNumerically("==", http.StatusBadRequest))
|
||||
|
||||
digest := data.Manifests[0].Digest.String()
|
||||
_, err = manifests.Remove(bt.conn, id, digest)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
// removal on good manifest with good digest should work
|
||||
data, err = manifests.Inspect(bt.conn, id)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(len(data.Manifests)).To(BeZero())
|
||||
})
|
||||
|
||||
It("push manifest", func() {
|
||||
Skip("TODO")
|
||||
})
|
||||
})
|
15
vendor/github.com/containers/buildah/manifests/copy.go
generated
vendored
Normal file
15
vendor/github.com/containers/buildah/manifests/copy.go
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
package manifests
|
||||
|
||||
import (
|
||||
"github.com/containers/image/v5/signature"
|
||||
)
|
||||
|
||||
var (
|
||||
// storageAllowedPolicyScopes overrides the policy for local storage
|
||||
// to ensure that we can read images from it.
|
||||
storageAllowedPolicyScopes = signature.PolicyTransportScopes{
|
||||
"": []signature.PolicyRequirement{
|
||||
signature.NewPRInsecureAcceptAnything(),
|
||||
},
|
||||
}
|
||||
)
|
397
vendor/github.com/containers/buildah/manifests/manifests.go
generated
vendored
Normal file
397
vendor/github.com/containers/buildah/manifests/manifests.go
generated
vendored
Normal file
@ -0,0 +1,397 @@
|
||||
package manifests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
stderrors "errors"
|
||||
"io"
|
||||
|
||||
"github.com/containers/buildah/pkg/manifests"
|
||||
"github.com/containers/buildah/pkg/supplemented"
|
||||
cp "github.com/containers/image/v5/copy"
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/image"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/signature"
|
||||
is "github.com/containers/image/v5/storage"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/transports/alltransports"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/storage"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const instancesData = "instances.json"
|
||||
|
||||
// ErrListImageUnknown is returned when we attempt to create an image reference
|
||||
// for a List that has not yet been saved to an image.
|
||||
var ErrListImageUnknown = stderrors.New("unable to determine which image holds the manifest list")
|
||||
|
||||
type list struct {
|
||||
manifests.List
|
||||
instances map[digest.Digest]string
|
||||
}
|
||||
|
||||
// List is a manifest list or image index, either created using Create(), or
|
||||
// loaded from local storage using LoadFromImage().
|
||||
type List interface {
|
||||
manifests.List
|
||||
SaveToImage(store storage.Store, imageID string, names []string, mimeType string) (string, error)
|
||||
Reference(store storage.Store, multiple cp.ImageListSelection, instances []digest.Digest) (types.ImageReference, error)
|
||||
Push(ctx context.Context, dest types.ImageReference, options PushOptions) (reference.Canonical, digest.Digest, error)
|
||||
Add(ctx context.Context, sys *types.SystemContext, ref types.ImageReference, all bool) (digest.Digest, error)
|
||||
}
|
||||
|
||||
// PushOptions includes various settings which are needed for pushing the
|
||||
// manifest list and its instances.
|
||||
type PushOptions struct {
|
||||
Store storage.Store
|
||||
SystemContext *types.SystemContext // github.com/containers/image/types.SystemContext
|
||||
ImageListSelection cp.ImageListSelection // set to either CopySystemImage, CopyAllImages, or CopySpecificImages
|
||||
Instances []digest.Digest // instances to copy if ImageListSelection == CopySpecificImages
|
||||
ReportWriter io.Writer // will be used to log the writing of the list and any blobs
|
||||
SignBy string // fingerprint of GPG key to use to sign images
|
||||
RemoveSignatures bool // true to discard signatures in images
|
||||
ManifestType string // the format to use when saving the list - possible options are oci, v2s1, and v2s2
|
||||
}
|
||||
|
||||
// Create creates a new list containing information about the specified image,
|
||||
// computing its manifest's digest, and retrieving OS and architecture
|
||||
// information from its configuration blob. Returns the new list, and the
|
||||
// instanceDigest for the initial image.
|
||||
func Create() List {
|
||||
return &list{
|
||||
List: manifests.Create(),
|
||||
instances: make(map[digest.Digest]string),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadFromImage reads the manifest list or image index, and additional
|
||||
// information about where the various instances that it contains live, from an
|
||||
// image record with the specified ID in local storage.
|
||||
func LoadFromImage(store storage.Store, image string) (string, List, error) {
|
||||
img, err := store.Image(image)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrapf(err, "error locating image %q for loading manifest list", image)
|
||||
}
|
||||
manifestBytes, err := store.ImageBigData(img.ID, storage.ImageDigestManifestBigDataNamePrefix)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrapf(err, "error locating image %q for loading manifest list", image)
|
||||
}
|
||||
manifestList, err := manifests.FromBlob(manifestBytes)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
list := &list{
|
||||
List: manifestList,
|
||||
instances: make(map[digest.Digest]string),
|
||||
}
|
||||
instancesBytes, err := store.ImageBigData(img.ID, instancesData)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrapf(err, "error locating image %q for loading instance list", image)
|
||||
}
|
||||
if err := json.Unmarshal(instancesBytes, &list.instances); err != nil {
|
||||
return "", nil, errors.Wrapf(err, "error decoding instance list for image %q", image)
|
||||
}
|
||||
list.instances[""] = img.ID
|
||||
return img.ID, list, err
|
||||
}
|
||||
|
||||
// SaveToImage saves the manifest list or image index as the manifest of an
|
||||
// Image record with the specified names in local storage, generating a random
|
||||
// image ID if none is specified. It also stores information about where the
|
||||
// images whose manifests are included in the list can be found.
|
||||
func (l *list) SaveToImage(store storage.Store, imageID string, names []string, mimeType string) (string, error) {
|
||||
manifestBytes, err := l.List.Serialize(mimeType)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
instancesBytes, err := json.Marshal(&l.instances)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
img, err := store.CreateImage(imageID, names, "", "", &storage.ImageOptions{})
|
||||
if err == nil || errors.Cause(err) == storage.ErrDuplicateID {
|
||||
created := (err == nil)
|
||||
if created {
|
||||
imageID = img.ID
|
||||
l.instances[""] = img.ID
|
||||
}
|
||||
err := store.SetImageBigData(imageID, storage.ImageDigestManifestBigDataNamePrefix, manifestBytes, manifest.Digest)
|
||||
if err != nil {
|
||||
if created {
|
||||
if _, err2 := store.DeleteImage(img.ID, true); err2 != nil {
|
||||
logrus.Errorf("error deleting image %q after failing to save manifest for it", img.ID)
|
||||
}
|
||||
}
|
||||
return "", errors.Wrapf(err, "error saving manifest list to image %q", imageID)
|
||||
}
|
||||
err = store.SetImageBigData(imageID, instancesData, instancesBytes, nil)
|
||||
if err != nil {
|
||||
if created {
|
||||
if _, err2 := store.DeleteImage(img.ID, true); err2 != nil {
|
||||
logrus.Errorf("error deleting image %q after failing to save instance locations for it", img.ID)
|
||||
}
|
||||
}
|
||||
return "", errors.Wrapf(err, "error saving instance list to image %q", imageID)
|
||||
}
|
||||
return imageID, nil
|
||||
}
|
||||
return "", errors.Wrapf(err, "error creating image to hold manifest list")
|
||||
}
|
||||
|
||||
// Reference returns an image reference for the composite image being built
|
||||
// in the list, or an error if the list has never been saved to a local image.
|
||||
func (l *list) Reference(store storage.Store, multiple cp.ImageListSelection, instances []digest.Digest) (types.ImageReference, error) {
|
||||
if l.instances[""] == "" {
|
||||
return nil, errors.Wrap(ErrListImageUnknown, "error building reference to list")
|
||||
}
|
||||
s, err := is.Transport.ParseStoreReference(store, l.instances[""])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating ImageReference from image %q", l.instances[""])
|
||||
}
|
||||
references := make([]types.ImageReference, 0, len(l.instances))
|
||||
whichInstances := make([]digest.Digest, 0, len(l.instances))
|
||||
switch multiple {
|
||||
case cp.CopyAllImages, cp.CopySystemImage:
|
||||
for instance := range l.instances {
|
||||
if instance != "" {
|
||||
whichInstances = append(whichInstances, instance)
|
||||
}
|
||||
}
|
||||
case cp.CopySpecificImages:
|
||||
for instance := range l.instances {
|
||||
for _, allowed := range instances {
|
||||
if instance == allowed {
|
||||
whichInstances = append(whichInstances, instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, instance := range whichInstances {
|
||||
imageName := l.instances[instance]
|
||||
ref, err := alltransports.ParseImageName(imageName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating ImageReference from image %q", imageName)
|
||||
}
|
||||
references = append(references, ref)
|
||||
}
|
||||
return supplemented.Reference(s, references, multiple, instances), nil
|
||||
}
|
||||
|
||||
// Push saves the manifest list and whichever blobs are needed to a destination location.
|
||||
func (l *list) Push(ctx context.Context, dest types.ImageReference, options PushOptions) (reference.Canonical, digest.Digest, error) {
|
||||
// Load the system signing policy.
|
||||
pushPolicy, err := signature.DefaultPolicy(options.SystemContext)
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrapf(err, "error obtaining default signature policy")
|
||||
}
|
||||
|
||||
// Override the settings for local storage to make sure that we can always read the source "image".
|
||||
pushPolicy.Transports[is.Transport.Name()] = storageAllowedPolicyScopes
|
||||
|
||||
policyContext, err := signature.NewPolicyContext(pushPolicy)
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrapf(err, "error creating new signature policy context")
|
||||
}
|
||||
defer func() {
|
||||
if err2 := policyContext.Destroy(); err2 != nil {
|
||||
logrus.Errorf("error destroying signature policy context: %v", err2)
|
||||
}
|
||||
}()
|
||||
|
||||
// If we were given a media type that corresponds to a multiple-images
|
||||
// type, reset it to a valid corresponding single-image type, since we
|
||||
// already expect the image library to infer the list type from the
|
||||
// image type that we're telling it to force.
|
||||
singleImageManifestType := options.ManifestType
|
||||
switch singleImageManifestType {
|
||||
case v1.MediaTypeImageIndex:
|
||||
singleImageManifestType = v1.MediaTypeImageManifest
|
||||
case manifest.DockerV2ListMediaType:
|
||||
singleImageManifestType = manifest.DockerV2Schema2MediaType
|
||||
}
|
||||
|
||||
// Build a source reference for our list and grab bag full of blobs.
|
||||
src, err := l.Reference(options.Store, options.ImageListSelection, options.Instances)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
copyOptions := &cp.Options{
|
||||
ImageListSelection: options.ImageListSelection,
|
||||
Instances: options.Instances,
|
||||
SourceCtx: options.SystemContext,
|
||||
DestinationCtx: options.SystemContext,
|
||||
ReportWriter: options.ReportWriter,
|
||||
RemoveSignatures: options.RemoveSignatures,
|
||||
SignBy: options.SignBy,
|
||||
ForceManifestMIMEType: singleImageManifestType,
|
||||
}
|
||||
|
||||
// Copy whatever we were asked to copy.
|
||||
manifestBytes, err := cp.Image(ctx, policyContext, dest, src, copyOptions)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
manifestDigest, err := manifest.Digest(manifestBytes)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return nil, manifestDigest, nil
|
||||
}
|
||||
|
||||
// Add adds information about the specified image to the list, computing the
|
||||
// image's manifest's digest, retrieving OS and architecture information from
|
||||
// the image's configuration, and recording the image's reference so that it
|
||||
// can be found at push-time. Returns the instanceDigest for the image. If
|
||||
// the reference points to an image list, either all instances are added (if
|
||||
// "all" is true), or the instance which matches "sys" (if "all" is false) will
|
||||
// be added.
|
||||
func (l *list) Add(ctx context.Context, sys *types.SystemContext, ref types.ImageReference, all bool) (digest.Digest, error) {
|
||||
src, err := ref.NewImageSource(ctx, sys)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error setting up to read manifest and configuration from %q", transports.ImageName(ref))
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
type instanceInfo struct {
|
||||
instanceDigest *digest.Digest
|
||||
OS, Architecture, OSVersion, Variant string
|
||||
Features, OSFeatures, Annotations []string
|
||||
Size int64
|
||||
}
|
||||
var instanceInfos []instanceInfo
|
||||
var manifestDigest digest.Digest
|
||||
|
||||
primaryManifestBytes, primaryManifestType, err := src.GetManifest(ctx, nil)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error reading manifest from %q", transports.ImageName(ref))
|
||||
}
|
||||
|
||||
if manifest.MIMETypeIsMultiImage(primaryManifestType) {
|
||||
lists, err := manifests.FromBlob(primaryManifestBytes)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error parsing manifest list in %q", transports.ImageName(ref))
|
||||
}
|
||||
if all {
|
||||
for i, instance := range lists.OCIv1().Manifests {
|
||||
platform := instance.Platform
|
||||
if platform == nil {
|
||||
platform = &v1.Platform{}
|
||||
}
|
||||
instanceDigest := instance.Digest
|
||||
instanceInfo := instanceInfo{
|
||||
instanceDigest: &instanceDigest,
|
||||
OS: platform.OS,
|
||||
Architecture: platform.Architecture,
|
||||
OSVersion: platform.OSVersion,
|
||||
Variant: platform.Variant,
|
||||
Features: append([]string{}, lists.Docker().Manifests[i].Platform.Features...),
|
||||
OSFeatures: append([]string{}, platform.OSFeatures...),
|
||||
Size: instance.Size,
|
||||
}
|
||||
instanceInfos = append(instanceInfos, instanceInfo)
|
||||
}
|
||||
} else {
|
||||
list, err := manifest.ListFromBlob(primaryManifestBytes, primaryManifestType)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error parsing manifest list in %q", transports.ImageName(ref))
|
||||
}
|
||||
instanceDigest, err := list.ChooseInstance(sys)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error selecting image from manifest list in %q", transports.ImageName(ref))
|
||||
}
|
||||
added := false
|
||||
for i, instance := range lists.OCIv1().Manifests {
|
||||
if instance.Digest != instanceDigest {
|
||||
continue
|
||||
}
|
||||
platform := instance.Platform
|
||||
if platform == nil {
|
||||
platform = &v1.Platform{}
|
||||
}
|
||||
instanceInfo := instanceInfo{
|
||||
instanceDigest: &instanceDigest,
|
||||
OS: platform.OS,
|
||||
Architecture: platform.Architecture,
|
||||
OSVersion: platform.OSVersion,
|
||||
Variant: platform.Variant,
|
||||
Features: append([]string{}, lists.Docker().Manifests[i].Platform.Features...),
|
||||
OSFeatures: append([]string{}, platform.OSFeatures...),
|
||||
Size: instance.Size,
|
||||
}
|
||||
instanceInfos = append(instanceInfos, instanceInfo)
|
||||
added = true
|
||||
}
|
||||
if !added {
|
||||
instanceInfo := instanceInfo{
|
||||
instanceDigest: &instanceDigest,
|
||||
}
|
||||
instanceInfos = append(instanceInfos, instanceInfo)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
instanceInfo := instanceInfo{
|
||||
instanceDigest: nil,
|
||||
}
|
||||
instanceInfos = append(instanceInfos, instanceInfo)
|
||||
}
|
||||
|
||||
for _, instanceInfo := range instanceInfos {
|
||||
if instanceInfo.OS == "" || instanceInfo.Architecture == "" {
|
||||
img, err := image.FromUnparsedImage(ctx, sys, image.UnparsedInstance(src, instanceInfo.instanceDigest))
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error reading configuration blob from %q", transports.ImageName(ref))
|
||||
}
|
||||
config, err := img.OCIConfig(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error reading info about config blob from %q", transports.ImageName(ref))
|
||||
}
|
||||
if instanceInfo.OS == "" {
|
||||
instanceInfo.OS = config.OS
|
||||
}
|
||||
if instanceInfo.Architecture == "" {
|
||||
instanceInfo.Architecture = config.Architecture
|
||||
}
|
||||
}
|
||||
manifestBytes, manifestType, err := src.GetManifest(ctx, instanceInfo.instanceDigest)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error reading manifest from %q, instance %q", transports.ImageName(ref), instanceInfo.instanceDigest)
|
||||
}
|
||||
if instanceInfo.instanceDigest == nil {
|
||||
manifestDigest, err = manifest.Digest(manifestBytes)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error computing digest of manifest from %q", transports.ImageName(ref))
|
||||
}
|
||||
instanceInfo.instanceDigest = &manifestDigest
|
||||
instanceInfo.Size = int64(len(manifestBytes))
|
||||
} else {
|
||||
if manifestDigest == "" {
|
||||
manifestDigest = *instanceInfo.instanceDigest
|
||||
}
|
||||
}
|
||||
err = l.List.AddInstance(*instanceInfo.instanceDigest, instanceInfo.Size, manifestType, instanceInfo.OS, instanceInfo.Architecture, instanceInfo.OSVersion, instanceInfo.OSFeatures, instanceInfo.Variant, instanceInfo.Features, instanceInfo.Annotations)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error adding instance with digest %q", *instanceInfo.instanceDigest)
|
||||
}
|
||||
if _, ok := l.instances[*instanceInfo.instanceDigest]; !ok {
|
||||
l.instances[*instanceInfo.instanceDigest] = transports.ImageName(ref)
|
||||
}
|
||||
}
|
||||
|
||||
return manifestDigest, nil
|
||||
}
|
||||
|
||||
// Remove filters out any instances in the list which match the specified digest.
|
||||
func (l *list) Remove(instanceDigest digest.Digest) error {
|
||||
err := l.List.Remove(instanceDigest)
|
||||
if err == nil {
|
||||
if _, needToDelete := l.instances[instanceDigest]; needToDelete {
|
||||
delete(l.instances, instanceDigest)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
16
vendor/github.com/containers/buildah/pkg/manifests/errors.go
generated
vendored
Normal file
16
vendor/github.com/containers/buildah/pkg/manifests/errors.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
package manifests
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrDigestNotFound is returned when we look for an image instance
|
||||
// with a particular digest in a list or index, and fail to find it.
|
||||
ErrDigestNotFound = errors.New("no image instance matching the specified digest was found in the list or index")
|
||||
// ErrManifestTypeNotSupported is returned when we attempt to parse a
|
||||
// manifest with a known MIME type as a list or index, or when we attempt
|
||||
// to serialize a list or index to a manifest with a MIME type that we
|
||||
// don't know how to encode.
|
||||
ErrManifestTypeNotSupported = errors.New("manifest type not supported")
|
||||
)
|
493
vendor/github.com/containers/buildah/pkg/manifests/manifests.go
generated
vendored
Normal file
493
vendor/github.com/containers/buildah/pkg/manifests/manifests.go
generated
vendored
Normal file
@ -0,0 +1,493 @@
|
||||
package manifests
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/containers/image/v5/manifest"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
imgspec "github.com/opencontainers/image-spec/specs-go"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// List is a generic interface for manipulating a manifest list or an image
|
||||
// index.
|
||||
type List interface {
|
||||
AddInstance(manifestDigest digest.Digest, manifestSize int64, manifestType, os, architecture, osVersion string, osFeatures []string, variant string, features []string, annotations []string) error
|
||||
Remove(instanceDigest digest.Digest) error
|
||||
|
||||
SetURLs(instanceDigest digest.Digest, urls []string) error
|
||||
URLs(instanceDigest digest.Digest) ([]string, error)
|
||||
|
||||
SetAnnotations(instanceDigest *digest.Digest, annotations map[string]string) error
|
||||
Annotations(instanceDigest *digest.Digest) (map[string]string, error)
|
||||
|
||||
SetOS(instanceDigest digest.Digest, os string) error
|
||||
OS(instanceDigest digest.Digest) (string, error)
|
||||
|
||||
SetArchitecture(instanceDigest digest.Digest, arch string) error
|
||||
Architecture(instanceDigest digest.Digest) (string, error)
|
||||
|
||||
SetOSVersion(instanceDigest digest.Digest, osVersion string) error
|
||||
OSVersion(instanceDigest digest.Digest) (string, error)
|
||||
|
||||
SetVariant(instanceDigest digest.Digest, variant string) error
|
||||
Variant(instanceDigest digest.Digest) (string, error)
|
||||
|
||||
SetFeatures(instanceDigest digest.Digest, features []string) error
|
||||
Features(instanceDigest digest.Digest) ([]string, error)
|
||||
|
||||
SetOSFeatures(instanceDigest digest.Digest, osFeatures []string) error
|
||||
OSFeatures(instanceDigest digest.Digest) ([]string, error)
|
||||
|
||||
Serialize(mimeType string) ([]byte, error)
|
||||
Instances() []digest.Digest
|
||||
OCIv1() *v1.Index
|
||||
Docker() *manifest.Schema2List
|
||||
|
||||
findDocker(instanceDigest digest.Digest) (*manifest.Schema2ManifestDescriptor, error)
|
||||
findOCIv1(instanceDigest digest.Digest) (*v1.Descriptor, error)
|
||||
}
|
||||
|
||||
type list struct {
|
||||
docker manifest.Schema2List
|
||||
oci v1.Index
|
||||
}
|
||||
|
||||
// OCIv1 returns the list as a Docker schema 2 list. The returned structure should NOT be modified.
|
||||
func (l *list) Docker() *manifest.Schema2List {
|
||||
return &l.docker
|
||||
}
|
||||
|
||||
// OCIv1 returns the list as an OCI image index. The returned structure should NOT be modified.
|
||||
func (l *list) OCIv1() *v1.Index {
|
||||
return &l.oci
|
||||
}
|
||||
|
||||
// Create creates a new list.
|
||||
func Create() List {
|
||||
return &list{
|
||||
docker: manifest.Schema2List{
|
||||
SchemaVersion: 2,
|
||||
MediaType: manifest.DockerV2ListMediaType,
|
||||
},
|
||||
oci: v1.Index{
|
||||
Versioned: imgspec.Versioned{SchemaVersion: 2},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// AddInstance adds an entry for the specified manifest digest, with assorted
|
||||
// additional information specified in parameters, to the list or index.
|
||||
func (l *list) AddInstance(manifestDigest digest.Digest, manifestSize int64, manifestType, osName, architecture, osVersion string, osFeatures []string, variant string, features []string, annotations []string) error {
|
||||
if err := l.Remove(manifestDigest); err != nil && !os.IsNotExist(errors.Cause(err)) {
|
||||
return err
|
||||
}
|
||||
|
||||
schema2platform := manifest.Schema2PlatformSpec{
|
||||
Architecture: architecture,
|
||||
OS: osName,
|
||||
OSVersion: osVersion,
|
||||
OSFeatures: osFeatures,
|
||||
Variant: variant,
|
||||
Features: features,
|
||||
}
|
||||
l.docker.Manifests = append(l.docker.Manifests, manifest.Schema2ManifestDescriptor{
|
||||
Schema2Descriptor: manifest.Schema2Descriptor{
|
||||
MediaType: manifestType,
|
||||
Size: manifestSize,
|
||||
Digest: manifestDigest,
|
||||
},
|
||||
Platform: schema2platform,
|
||||
})
|
||||
|
||||
ociv1platform := v1.Platform{
|
||||
Architecture: architecture,
|
||||
OS: osName,
|
||||
OSVersion: osVersion,
|
||||
OSFeatures: osFeatures,
|
||||
Variant: variant,
|
||||
}
|
||||
l.oci.Manifests = append(l.oci.Manifests, v1.Descriptor{
|
||||
MediaType: manifestType,
|
||||
Size: manifestSize,
|
||||
Digest: manifestDigest,
|
||||
Platform: &ociv1platform,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove filters out any instances in the list which match the specified digest.
|
||||
func (l *list) Remove(instanceDigest digest.Digest) error {
|
||||
err := errors.Wrapf(os.ErrNotExist, "no instance matching digest %q found in manifest list", instanceDigest)
|
||||
newDockerManifests := make([]manifest.Schema2ManifestDescriptor, 0, len(l.docker.Manifests))
|
||||
for i := range l.docker.Manifests {
|
||||
if l.docker.Manifests[i].Digest != instanceDigest {
|
||||
newDockerManifests = append(newDockerManifests, l.docker.Manifests[i])
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
l.docker.Manifests = newDockerManifests
|
||||
newOCIv1Manifests := make([]v1.Descriptor, 0, len(l.oci.Manifests))
|
||||
for i := range l.oci.Manifests {
|
||||
if l.oci.Manifests[i].Digest != instanceDigest {
|
||||
newOCIv1Manifests = append(newOCIv1Manifests, l.oci.Manifests[i])
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
l.oci.Manifests = newOCIv1Manifests
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *list) findDocker(instanceDigest digest.Digest) (*manifest.Schema2ManifestDescriptor, error) {
|
||||
for i := range l.docker.Manifests {
|
||||
if l.docker.Manifests[i].Digest == instanceDigest {
|
||||
return &l.docker.Manifests[i], nil
|
||||
}
|
||||
}
|
||||
return nil, errors.Wrapf(ErrDigestNotFound, "no Docker manifest matching digest %q was found in list", instanceDigest.String())
|
||||
}
|
||||
|
||||
func (l *list) findOCIv1(instanceDigest digest.Digest) (*v1.Descriptor, error) {
|
||||
for i := range l.oci.Manifests {
|
||||
if l.oci.Manifests[i].Digest == instanceDigest {
|
||||
return &l.oci.Manifests[i], nil
|
||||
}
|
||||
}
|
||||
return nil, errors.Wrapf(ErrDigestNotFound, "no OCI manifest matching digest %q was found in list", instanceDigest.String())
|
||||
}
|
||||
|
||||
// SetURLs sets the URLs where the manifest might also be found.
|
||||
func (l *list) SetURLs(instanceDigest digest.Digest, urls []string) error {
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
docker, err := l.findDocker(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oci.URLs = append([]string{}, urls...)
|
||||
docker.URLs = append([]string{}, urls...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// URLs retrieves the locations from which this object might possibly be downloaded.
|
||||
func (l *list) URLs(instanceDigest digest.Digest) ([]string, error) {
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append([]string{}, oci.URLs...), nil
|
||||
}
|
||||
|
||||
// SetAnnotations sets annotations on the image index, or on a specific manifest.
|
||||
// The field is specific to the OCI image index format, and is not present in Docker manifest lists.
|
||||
func (l *list) SetAnnotations(instanceDigest *digest.Digest, annotations map[string]string) error {
|
||||
a := &l.oci.Annotations
|
||||
if instanceDigest != nil {
|
||||
oci, err := l.findOCIv1(*instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a = &oci.Annotations
|
||||
}
|
||||
(*a) = make(map[string]string)
|
||||
for k, v := range annotations {
|
||||
(*a)[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Annotations retrieves the annotations which have been set on the image index, or on one instance.
|
||||
// The field is specific to the OCI image index format, and is not present in Docker manifest lists.
|
||||
func (l *list) Annotations(instanceDigest *digest.Digest) (map[string]string, error) {
|
||||
a := l.oci.Annotations
|
||||
if instanceDigest != nil {
|
||||
oci, err := l.findOCIv1(*instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a = oci.Annotations
|
||||
}
|
||||
annotations := make(map[string]string)
|
||||
for k, v := range a {
|
||||
annotations[k] = v
|
||||
}
|
||||
return annotations, nil
|
||||
}
|
||||
|
||||
// SetOS sets the OS field in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) SetOS(instanceDigest digest.Digest, os string) error {
|
||||
docker, err := l.findDocker(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
docker.Platform.OS = os
|
||||
oci.Platform.OS = os
|
||||
return nil
|
||||
}
|
||||
|
||||
// OS retrieves the OS field in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) OS(instanceDigest digest.Digest) (string, error) {
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return oci.Platform.OS, nil
|
||||
}
|
||||
|
||||
// SetArchitecture sets the Architecture field in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) SetArchitecture(instanceDigest digest.Digest, arch string) error {
|
||||
docker, err := l.findDocker(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
docker.Platform.Architecture = arch
|
||||
oci.Platform.Architecture = arch
|
||||
return nil
|
||||
}
|
||||
|
||||
// Architecture retrieves the Architecture field in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) Architecture(instanceDigest digest.Digest) (string, error) {
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return oci.Platform.Architecture, nil
|
||||
}
|
||||
|
||||
// SetOSVersion sets the OSVersion field in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) SetOSVersion(instanceDigest digest.Digest, osVersion string) error {
|
||||
docker, err := l.findDocker(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
docker.Platform.OSVersion = osVersion
|
||||
oci.Platform.OSVersion = osVersion
|
||||
return nil
|
||||
}
|
||||
|
||||
// OSVersion retrieves the OSVersion field in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) OSVersion(instanceDigest digest.Digest) (string, error) {
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return oci.Platform.OSVersion, nil
|
||||
}
|
||||
|
||||
// SetVariant sets the Variant field in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) SetVariant(instanceDigest digest.Digest, variant string) error {
|
||||
docker, err := l.findDocker(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
docker.Platform.Variant = variant
|
||||
oci.Platform.Variant = variant
|
||||
return nil
|
||||
}
|
||||
|
||||
// Variant retrieves the Variant field in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) Variant(instanceDigest digest.Digest) (string, error) {
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return oci.Platform.Variant, nil
|
||||
}
|
||||
|
||||
// SetFeatures sets the features list in the platform information associated with the instance with the specified digest.
|
||||
// The field is specific to the Docker manifest list format, and is not present in OCI's image indexes.
|
||||
func (l *list) SetFeatures(instanceDigest digest.Digest, features []string) error {
|
||||
docker, err := l.findDocker(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
docker.Platform.Features = append([]string{}, features...)
|
||||
// no OCI equivalent
|
||||
return nil
|
||||
}
|
||||
|
||||
// Features retrieves the features list from the platform information associated with the instance with the specified digest.
|
||||
// The field is specific to the Docker manifest list format, and is not present in OCI's image indexes.
|
||||
func (l *list) Features(instanceDigest digest.Digest) ([]string, error) {
|
||||
docker, err := l.findDocker(instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append([]string{}, docker.Platform.Features...), nil
|
||||
}
|
||||
|
||||
// SetOSFeatures sets the OS features list in the platform information associated with the instance with the specified digest.
|
||||
func (l *list) SetOSFeatures(instanceDigest digest.Digest, osFeatures []string) error {
|
||||
docker, err := l.findDocker(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
docker.Platform.OSFeatures = append([]string{}, osFeatures...)
|
||||
oci.Platform.OSFeatures = append([]string{}, osFeatures...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// OSFeatures retrieves the OS features list from the platform information associated with the instance with the specified digest.
|
||||
func (l *list) OSFeatures(instanceDigest digest.Digest) ([]string, error) {
|
||||
oci, err := l.findOCIv1(instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append([]string{}, oci.Platform.OSFeatures...), nil
|
||||
}
|
||||
|
||||
// FromBlob builds a list from an encoded manifest list or image index.
|
||||
func FromBlob(manifestBytes []byte) (List, error) {
|
||||
manifestType := manifest.GuessMIMEType(manifestBytes)
|
||||
list := &list{
|
||||
docker: manifest.Schema2List{
|
||||
SchemaVersion: 2,
|
||||
MediaType: manifest.DockerV2ListMediaType,
|
||||
},
|
||||
oci: v1.Index{
|
||||
Versioned: imgspec.Versioned{SchemaVersion: 2},
|
||||
},
|
||||
}
|
||||
switch manifestType {
|
||||
default:
|
||||
return nil, errors.Wrapf(ErrManifestTypeNotSupported, "unable to load manifest list: unsupported format %q", manifestType)
|
||||
case manifest.DockerV2ListMediaType:
|
||||
if err := json.Unmarshal(manifestBytes, &list.docker); err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to parse Docker manifest list from image")
|
||||
}
|
||||
for _, m := range list.docker.Manifests {
|
||||
list.oci.Manifests = append(list.oci.Manifests, v1.Descriptor{
|
||||
MediaType: m.Schema2Descriptor.MediaType,
|
||||
Size: m.Schema2Descriptor.Size,
|
||||
Digest: m.Schema2Descriptor.Digest,
|
||||
Platform: &v1.Platform{
|
||||
Architecture: m.Platform.Architecture,
|
||||
OS: m.Platform.OS,
|
||||
OSVersion: m.Platform.OSVersion,
|
||||
OSFeatures: m.Platform.OSFeatures,
|
||||
Variant: m.Platform.Variant,
|
||||
},
|
||||
})
|
||||
}
|
||||
case v1.MediaTypeImageIndex:
|
||||
if err := json.Unmarshal(manifestBytes, &list.oci); err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to parse OCIv1 manifest list")
|
||||
}
|
||||
for _, m := range list.oci.Manifests {
|
||||
platform := m.Platform
|
||||
if platform == nil {
|
||||
platform = &v1.Platform{}
|
||||
}
|
||||
list.docker.Manifests = append(list.docker.Manifests, manifest.Schema2ManifestDescriptor{
|
||||
Schema2Descriptor: manifest.Schema2Descriptor{
|
||||
MediaType: m.MediaType,
|
||||
Size: m.Size,
|
||||
Digest: m.Digest,
|
||||
},
|
||||
Platform: manifest.Schema2PlatformSpec{
|
||||
Architecture: platform.Architecture,
|
||||
OS: platform.OS,
|
||||
OSVersion: platform.OSVersion,
|
||||
OSFeatures: platform.OSFeatures,
|
||||
Variant: platform.Variant,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (l *list) preferOCI() bool {
|
||||
// If we have any data that's only in the OCI format, use that.
|
||||
for _, m := range l.oci.Manifests {
|
||||
if len(m.URLs) > 0 {
|
||||
return true
|
||||
}
|
||||
if len(m.Annotations) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// If we have any data that's only in the Docker format, use that.
|
||||
for _, m := range l.docker.Manifests {
|
||||
if len(m.Platform.Features) > 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// If we have no manifests, remember that the Docker format is
|
||||
// explicitly typed, so use that. Otherwise, default to using the OCI
|
||||
// format.
|
||||
return len(l.docker.Manifests) != 0
|
||||
}
|
||||
|
||||
// Serialize encodes the list using the specified format, or by selecting one
|
||||
// which it thinks is appropriate.
|
||||
func (l *list) Serialize(mimeType string) ([]byte, error) {
|
||||
var manifestBytes []byte
|
||||
switch mimeType {
|
||||
case "":
|
||||
if l.preferOCI() {
|
||||
manifest, err := json.Marshal(&l.oci)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error marshalling OCI image index")
|
||||
}
|
||||
manifestBytes = manifest
|
||||
} else {
|
||||
manifest, err := json.Marshal(&l.docker)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error marshalling Docker manifest list")
|
||||
}
|
||||
manifestBytes = manifest
|
||||
}
|
||||
case v1.MediaTypeImageIndex:
|
||||
manifest, err := json.Marshal(&l.oci)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error marshalling OCI image index")
|
||||
}
|
||||
manifestBytes = manifest
|
||||
case manifest.DockerV2ListMediaType:
|
||||
manifest, err := json.Marshal(&l.docker)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error marshalling Docker manifest list")
|
||||
}
|
||||
manifestBytes = manifest
|
||||
default:
|
||||
return nil, errors.Wrapf(ErrManifestTypeNotSupported, "serializing list to type %q not implemented", mimeType)
|
||||
}
|
||||
return manifestBytes, nil
|
||||
}
|
||||
|
||||
// Instances returns the list of image instances mentioned in this list.
|
||||
func (l *list) Instances() []digest.Digest {
|
||||
instances := make([]digest.Digest, 0, len(l.oci.Manifests))
|
||||
for _, instance := range l.oci.Manifests {
|
||||
instances = append(instances, instance.Digest)
|
||||
}
|
||||
return instances
|
||||
}
|
17
vendor/github.com/containers/buildah/pkg/supplemented/errors.go
generated
vendored
Normal file
17
vendor/github.com/containers/buildah/pkg/supplemented/errors.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
package supplemented
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/containers/buildah/pkg/manifests"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrDigestNotFound is returned when we look for an image instance
|
||||
// with a particular digest in a list or index, and fail to find it.
|
||||
ErrDigestNotFound = manifests.ErrDigestNotFound
|
||||
// ErrBlobNotFound is returned when try to figure out which supplemental
|
||||
// image we should ask for a blob with the specified characteristics,
|
||||
// based on the information in each of the supplemental images' manifests.
|
||||
ErrBlobNotFound = errors.New("location of blob could not be determined")
|
||||
)
|
393
vendor/github.com/containers/buildah/pkg/supplemented/supplemented.go
generated
vendored
Normal file
393
vendor/github.com/containers/buildah/pkg/supplemented/supplemented.go
generated
vendored
Normal file
@ -0,0 +1,393 @@
|
||||
package supplemented
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"context"
|
||||
"io"
|
||||
|
||||
cp "github.com/containers/image/v5/copy"
|
||||
"github.com/containers/image/v5/image"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/transports"
|
||||
"github.com/containers/image/v5/types"
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// supplementedImageReference groups multiple references together.
|
||||
type supplementedImageReference struct {
|
||||
types.ImageReference
|
||||
references []types.ImageReference
|
||||
multiple cp.ImageListSelection
|
||||
instances []digest.Digest
|
||||
}
|
||||
|
||||
// supplementedImageSource represents an image, plus all of the blobs of other images.
|
||||
type supplementedImageSource struct {
|
||||
types.ImageSource
|
||||
reference types.ImageReference
|
||||
manifest []byte // The manifest list or image index.
|
||||
manifestType string // The MIME type of the manifest list or image index.
|
||||
sourceDefaultInstances map[types.ImageSource]digest.Digest // The default manifest instances of open ImageSource objects.
|
||||
sourceInstancesByInstance map[digest.Digest]types.ImageSource // A map from manifest instance digests to open ImageSource objects.
|
||||
instancesByBlobDigest map[digest.Digest]digest.Digest // A map from blob digests to manifest instance digests.
|
||||
}
|
||||
|
||||
// Reference groups one reference and some number of additional references
|
||||
// together as a group. The first reference's default instance will be treated
|
||||
// as the default instance of the resulting reference, with the other
|
||||
// references' instances made available as instances for their respective
|
||||
// digests.
|
||||
func Reference(ref types.ImageReference, supplemental []types.ImageReference, multiple cp.ImageListSelection, instances []digest.Digest) types.ImageReference {
|
||||
if len(instances) > 0 {
|
||||
i := make([]digest.Digest, len(instances))
|
||||
copy(i, instances)
|
||||
instances = i
|
||||
}
|
||||
return &supplementedImageReference{
|
||||
ImageReference: ref,
|
||||
references: append([]types.ImageReference{}, supplemental...),
|
||||
multiple: multiple,
|
||||
instances: instances,
|
||||
}
|
||||
}
|
||||
|
||||
// NewImage returns a new higher-level view of the image.
|
||||
func (s *supplementedImageReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
|
||||
src, err := s.NewImageSource(ctx, sys)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error building a new Image using an ImageSource")
|
||||
}
|
||||
return image.FromSource(ctx, sys, src)
|
||||
}
|
||||
|
||||
// NewImageSource opens the referenced images, scans their manifests for
|
||||
// instances, and builds mappings from each blob mentioned in them to their
|
||||
// instances.
|
||||
func (s *supplementedImageReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (iss types.ImageSource, err error) {
|
||||
sources := make(map[digest.Digest]types.ImageSource)
|
||||
defaultInstances := make(map[types.ImageSource]digest.Digest)
|
||||
instances := make(map[digest.Digest]digest.Digest)
|
||||
var sis *supplementedImageSource
|
||||
|
||||
// Open the default instance for reading.
|
||||
top, err := s.ImageReference.NewImageSource(ctx, sys)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error opening %q as image source", transports.ImageName(s.ImageReference))
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if iss != nil {
|
||||
// The composite source has been created. Use its Close method.
|
||||
if err2 := iss.Close(); err2 != nil {
|
||||
logrus.Errorf("error opening image: %v", err2)
|
||||
}
|
||||
} else if top != nil {
|
||||
// The composite source has not been created, but the top was already opened. Close it.
|
||||
if err2 := top.Close(); err2 != nil {
|
||||
logrus.Errorf("error opening image: %v", err2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var addSingle, addMulti func(manifestBytes []byte, manifestType string, src types.ImageSource) error
|
||||
type manifestToRead struct {
|
||||
src types.ImageSource
|
||||
instance *digest.Digest
|
||||
}
|
||||
manifestsToRead := list.New()
|
||||
|
||||
addSingle = func(manifestBytes []byte, manifestType string, src types.ImageSource) error {
|
||||
// Mark this instance as being associated with this ImageSource.
|
||||
manifestDigest, err := manifest.Digest(manifestBytes)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error computing digest over manifest %q", string(manifestBytes))
|
||||
}
|
||||
sources[manifestDigest] = src
|
||||
|
||||
// Parse the manifest as a single image.
|
||||
man, err := manifest.FromBlob(manifestBytes, manifestType)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error parsing manifest %q", string(manifestBytes))
|
||||
}
|
||||
|
||||
// Log the config blob's digest and the blobs of its layers as associated with this manifest.
|
||||
config := man.ConfigInfo()
|
||||
if config.Digest != "" {
|
||||
instances[config.Digest] = manifestDigest
|
||||
logrus.Debugf("blob %q belongs to %q", config.Digest, manifestDigest)
|
||||
}
|
||||
|
||||
layers := man.LayerInfos()
|
||||
for _, layer := range layers {
|
||||
instances[layer.Digest] = manifestDigest
|
||||
logrus.Debugf("layer %q belongs to %q", layer.Digest, manifestDigest)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
addMulti = func(manifestBytes []byte, manifestType string, src types.ImageSource) error {
|
||||
// Mark this instance as being associated with this ImageSource.
|
||||
manifestDigest, err := manifest.Digest(manifestBytes)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error computing manifest digest")
|
||||
}
|
||||
sources[manifestDigest] = src
|
||||
|
||||
// Parse the manifest as a list of images.
|
||||
list, err := manifest.ListFromBlob(manifestBytes, manifestType)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error parsing manifest blob %q as a %q", string(manifestBytes), manifestType)
|
||||
}
|
||||
|
||||
// Figure out which of its instances we want to look at.
|
||||
var chaseInstances []digest.Digest
|
||||
switch s.multiple {
|
||||
case cp.CopySystemImage:
|
||||
instance, err := list.ChooseInstance(sys)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error selecting appropriate instance from list")
|
||||
}
|
||||
chaseInstances = []digest.Digest{instance}
|
||||
case cp.CopySpecificImages:
|
||||
chaseInstances = s.instances
|
||||
case cp.CopyAllImages:
|
||||
chaseInstances = list.Instances()
|
||||
}
|
||||
|
||||
// Queue these manifest instances for reading from this
|
||||
// ImageSource later, if we don't stumble across them somewhere
|
||||
// else first.
|
||||
for _, instanceIterator := range chaseInstances {
|
||||
instance := instanceIterator
|
||||
next := &manifestToRead{
|
||||
src: src,
|
||||
instance: &instance,
|
||||
}
|
||||
if src == top {
|
||||
// Prefer any other source.
|
||||
manifestsToRead.PushBack(next)
|
||||
} else {
|
||||
// Prefer this source over the first ("main") one.
|
||||
manifestsToRead.PushFront(next)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
visitedReferences := make(map[types.ImageReference]struct{})
|
||||
for i, ref := range append([]types.ImageReference{s.ImageReference}, s.references...) {
|
||||
if _, visited := visitedReferences[ref]; visited {
|
||||
continue
|
||||
}
|
||||
visitedReferences[ref] = struct{}{}
|
||||
|
||||
// Open this image for reading.
|
||||
var src types.ImageSource
|
||||
if ref == s.ImageReference {
|
||||
src = top
|
||||
} else {
|
||||
src, err = ref.NewImageSource(ctx, sys)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error opening %q as image source", transports.ImageName(ref))
|
||||
}
|
||||
}
|
||||
|
||||
// Read the default manifest for the image.
|
||||
manifestBytes, manifestType, err := src.GetManifest(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading default manifest from image %q", transports.ImageName(ref))
|
||||
}
|
||||
|
||||
// If this is the first image, mark it as our starting point.
|
||||
if i == 0 {
|
||||
sources[""] = src
|
||||
|
||||
sis = &supplementedImageSource{
|
||||
ImageSource: top,
|
||||
reference: s,
|
||||
manifest: manifestBytes,
|
||||
manifestType: manifestType,
|
||||
sourceDefaultInstances: defaultInstances,
|
||||
sourceInstancesByInstance: sources,
|
||||
instancesByBlobDigest: instances,
|
||||
}
|
||||
iss = sis
|
||||
}
|
||||
|
||||
// Record the digest of the ImageSource's default instance's manifest.
|
||||
manifestDigest, err := manifest.Digest(manifestBytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error computing digest of manifest from image %q", transports.ImageName(ref))
|
||||
}
|
||||
sis.sourceDefaultInstances[src] = manifestDigest
|
||||
|
||||
// If the ImageSource's default manifest is a list, parse each of its instances.
|
||||
if manifest.MIMETypeIsMultiImage(manifestType) {
|
||||
if err = addMulti(manifestBytes, manifestType, src); err != nil {
|
||||
return nil, errors.Wrapf(err, "error adding multi-image %q", transports.ImageName(ref))
|
||||
}
|
||||
} else {
|
||||
if err = addSingle(manifestBytes, manifestType, src); err != nil {
|
||||
return nil, errors.Wrapf(err, "error adding single image %q", transports.ImageName(ref))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the rest of the instances.
|
||||
for manifestsToRead.Front() != nil {
|
||||
front := manifestsToRead.Front()
|
||||
value := front.Value
|
||||
manifestToRead, ok := value.(*manifestToRead)
|
||||
if !ok {
|
||||
panic("bug: wrong type looking for *manifestToRead in list?")
|
||||
}
|
||||
manifestsToRead.Remove(front)
|
||||
|
||||
// If we already read this manifest, no need to read it again.
|
||||
if _, alreadyRead := sources[*manifestToRead.instance]; alreadyRead {
|
||||
continue
|
||||
}
|
||||
|
||||
// Read the instance's manifest.
|
||||
manifestBytes, manifestType, err := manifestToRead.src.GetManifest(ctx, manifestToRead.instance)
|
||||
if err != nil {
|
||||
// if errors.Cause(err) == storage.ErrImageUnknown || os.IsNotExist(errors.Cause(err)) {
|
||||
// Trust that we either don't need it, or that it's in another reference.
|
||||
// continue
|
||||
// }
|
||||
return nil, errors.Wrapf(err, "error reading manifest for instance %q", manifestToRead.instance)
|
||||
}
|
||||
|
||||
if manifest.MIMETypeIsMultiImage(manifestType) {
|
||||
// Add the list's contents.
|
||||
if err = addMulti(manifestBytes, manifestType, manifestToRead.src); err != nil {
|
||||
return nil, errors.Wrapf(err, "error adding single image instance %q", manifestToRead.instance)
|
||||
}
|
||||
} else {
|
||||
// Add the single image's contents.
|
||||
if err = addSingle(manifestBytes, manifestType, manifestToRead.src); err != nil {
|
||||
return nil, errors.Wrapf(err, "error adding single image instance %q", manifestToRead.instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return iss, nil
|
||||
}
|
||||
|
||||
func (s *supplementedImageReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
|
||||
return errors.Errorf("deletion of images not implemented")
|
||||
}
|
||||
|
||||
func (s *supplementedImageSource) Close() error {
|
||||
var returnErr *multierror.Error
|
||||
closed := make(map[types.ImageSource]struct{})
|
||||
for _, sourceInstance := range s.sourceInstancesByInstance {
|
||||
if _, closed := closed[sourceInstance]; closed {
|
||||
continue
|
||||
}
|
||||
if err := sourceInstance.Close(); err != nil {
|
||||
returnErr = multierror.Append(returnErr, err)
|
||||
}
|
||||
closed[sourceInstance] = struct{}{}
|
||||
}
|
||||
return returnErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
func (s *supplementedImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
requestInstanceDigest := instanceDigest
|
||||
if instanceDigest == nil {
|
||||
return s.manifest, s.manifestType, nil
|
||||
}
|
||||
if sourceInstance, ok := s.sourceInstancesByInstance[*instanceDigest]; ok {
|
||||
if *instanceDigest == s.sourceDefaultInstances[sourceInstance] {
|
||||
requestInstanceDigest = nil
|
||||
}
|
||||
return sourceInstance.GetManifest(ctx, requestInstanceDigest)
|
||||
}
|
||||
return nil, "", errors.Wrapf(ErrDigestNotFound, "error getting manifest for digest %q", *instanceDigest)
|
||||
}
|
||||
|
||||
func (s *supplementedImageSource) GetBlob(ctx context.Context, blob types.BlobInfo, bic types.BlobInfoCache) (io.ReadCloser, int64, error) {
|
||||
sourceInstance, ok := s.instancesByBlobDigest[blob.Digest]
|
||||
if !ok {
|
||||
return nil, -1, errors.Wrapf(ErrBlobNotFound, "error blob %q in known instances", blob.Digest)
|
||||
}
|
||||
src, ok := s.sourceInstancesByInstance[sourceInstance]
|
||||
if !ok {
|
||||
return nil, -1, errors.Wrapf(ErrDigestNotFound, "error getting image source for instance %q", sourceInstance)
|
||||
}
|
||||
return src.GetBlob(ctx, blob, bic)
|
||||
}
|
||||
|
||||
func (s *supplementedImageSource) HasThreadSafeGetBlob() bool {
|
||||
checked := make(map[types.ImageSource]struct{})
|
||||
for _, sourceInstance := range s.sourceInstancesByInstance {
|
||||
if _, checked := checked[sourceInstance]; checked {
|
||||
continue
|
||||
}
|
||||
if !sourceInstance.HasThreadSafeGetBlob() {
|
||||
return false
|
||||
}
|
||||
checked[sourceInstance] = struct{}{}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *supplementedImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
var src types.ImageSource
|
||||
requestInstanceDigest := instanceDigest
|
||||
if instanceDigest == nil {
|
||||
if sourceInstance, ok := s.sourceInstancesByInstance[""]; ok {
|
||||
src = sourceInstance
|
||||
}
|
||||
} else {
|
||||
if sourceInstance, ok := s.sourceInstancesByInstance[*instanceDigest]; ok {
|
||||
src = sourceInstance
|
||||
}
|
||||
if *instanceDigest == s.sourceDefaultInstances[src] {
|
||||
requestInstanceDigest = nil
|
||||
}
|
||||
}
|
||||
if src != nil {
|
||||
return src.GetSignatures(ctx, requestInstanceDigest)
|
||||
}
|
||||
return nil, errors.Wrapf(ErrDigestNotFound, "error finding instance for instance digest %q to read signatures", *instanceDigest)
|
||||
}
|
||||
|
||||
func (s *supplementedImageSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) {
|
||||
var src types.ImageSource
|
||||
requestInstanceDigest := instanceDigest
|
||||
if instanceDigest == nil {
|
||||
if sourceInstance, ok := s.sourceInstancesByInstance[""]; ok {
|
||||
src = sourceInstance
|
||||
}
|
||||
} else {
|
||||
if sourceInstance, ok := s.sourceInstancesByInstance[*instanceDigest]; ok {
|
||||
src = sourceInstance
|
||||
}
|
||||
if *instanceDigest == s.sourceDefaultInstances[src] {
|
||||
requestInstanceDigest = nil
|
||||
}
|
||||
}
|
||||
if src != nil {
|
||||
blobInfos, err := src.LayerInfosForCopy(ctx, requestInstanceDigest)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading layer infos for copy from instance %q", instanceDigest)
|
||||
}
|
||||
var manifestDigest digest.Digest
|
||||
if instanceDigest != nil {
|
||||
manifestDigest = *instanceDigest
|
||||
}
|
||||
for _, blobInfo := range blobInfos {
|
||||
s.instancesByBlobDigest[blobInfo.Digest] = manifestDigest
|
||||
}
|
||||
return blobInfos, nil
|
||||
}
|
||||
return nil, errors.Wrapf(ErrDigestNotFound, "error finding instance for instance digest %q to copy layers", *instanceDigest)
|
||||
}
|
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@ -68,13 +68,16 @@ github.com/containers/buildah/bind
|
||||
github.com/containers/buildah/chroot
|
||||
github.com/containers/buildah/docker
|
||||
github.com/containers/buildah/imagebuildah
|
||||
github.com/containers/buildah/manifests
|
||||
github.com/containers/buildah/pkg/blobcache
|
||||
github.com/containers/buildah/pkg/chrootuser
|
||||
github.com/containers/buildah/pkg/cli
|
||||
github.com/containers/buildah/pkg/formats
|
||||
github.com/containers/buildah/pkg/manifests
|
||||
github.com/containers/buildah/pkg/overlay
|
||||
github.com/containers/buildah/pkg/parse
|
||||
github.com/containers/buildah/pkg/secrets
|
||||
github.com/containers/buildah/pkg/supplemented
|
||||
github.com/containers/buildah/pkg/umask
|
||||
github.com/containers/buildah/util
|
||||
# github.com/containers/common v0.4.2
|
||||
|
Reference in New Issue
Block a user