mirror of
https://github.com/containers/podman.git
synced 2026-10-27 04:09:19 -04:00
Refactor manifest list operations
* Update method/function signatures use the manifest list name and
images associated with the operation explicitly, in general
func f(ctx context.Context, manifestListName string,
ImageNames []string, options *fOptions)
* Leverage gorilla/mux Subrouters to support API v3.x and v4.x for
manifests
* Make manifest API endpoints more RESTful
* Add PUT /manifest/{id} to update existing manifests
* Add manifests.Annotate to go bindings, uncommented unit test
* Add DELETE /manifest/{Id} to remove existing manifest list, use
PUT /manifest/{id} to remove images from a list
* Deprecated POST /manifest/{id}/add and /manifest/{id}/remove, use
PUT /manifest/{id} instead
* Corrected swagger godoc and updated to cover API changes
* Update podman manifest commands to use registry.Context()
* Expose utils.GetVar() to obtain query parameters by name
* Unexpose server.registerSwaggerHandlers, not sure why this was ever
exposed.
* Refactored code to use http.Header instead of map[string]string when
operating on HTTP headers.
* Add API-Version header support in bindings to allow calling explicate
versions of the API. Header is _NOT_ forwarded to the API service.
Signed-off-by: Jhon Honce <jhonce@redhat.com>
This commit is contained in:
1 parent
ec2b213ab6
commit
8a7e70919f
32 files changed
+1056
-408
No files matched your search
@@ -26,14 +26,14 @@ type manifestAddOptsWrapper struct {
|
||||
var (
|
||||
manifestAddOpts = manifestAddOptsWrapper{}
|
||||
addCmd = &cobra.Command{
|
||||
Use: "add [options] LIST LIST",
|
||||
Use: "add [options] LIST IMAGE [IMAGE...]",
|
||||
Short: "Add images to a manifest list or image index",
|
||||
Long: "Adds an image to a manifest list or image index.",
|
||||
RunE: add,
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
ValidArgsFunction: common.AutocompleteImages,
|
||||
Example: `podman manifest add mylist:v1.11 image:v1.11-amd64
|
||||
podman manifest add mylist:v1.11 transport:imageName`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -93,10 +93,6 @@ func add(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// FIXME: (@vrothberg) this interface confuses me a lot. Why are they
|
||||
// not two arguments?
|
||||
manifestAddOpts.Images = []string{args[1], args[0]}
|
||||
|
||||
if manifestAddOpts.CredentialsCLI != "" {
|
||||
creds, err := util.ParseRegistryCreds(manifestAddOpts.CredentialsCLI)
|
||||
if err != nil {
|
||||
@@ -114,10 +110,10 @@ func add(cmd *cobra.Command, args []string) error {
|
||||
manifestAddOpts.SkipTLSVerify = types.NewOptionalBool(!manifestAddOpts.TLSVerifyCLI)
|
||||
}
|
||||
|
||||
listID, err := registry.ImageEngine().ManifestAdd(context.Background(), manifestAddOpts.ManifestAddOptions)
|
||||
listID, err := registry.ImageEngine().ManifestAdd(context.Background(), args[0], args[1:], manifestAddOpts.ManifestAddOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%s\n", listID)
|
||||
fmt.Println(listID)
|
||||
return nil
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
package manifest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/common/pkg/completion"
|
||||
"github.com/containers/podman/v3/cmd/podman/common"
|
||||
"github.com/containers/podman/v3/cmd/podman/registry"
|
||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -20,8 +18,8 @@ var (
|
||||
Short: "Add or update information about an entry in a manifest list or image index",
|
||||
Long: "Adds or updates information about an entry in a manifest list or image index.",
|
||||
RunE: annotate,
|
||||
Example: `podman manifest annotate --annotation left=right mylist:v1.11 image:v1.11-amd64`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
Example: `podman manifest annotate --annotation left=right mylist:v1.11 image:v1.11-amd64`,
|
||||
ValidArgsFunction: common.AutocompleteImages,
|
||||
}
|
||||
)
|
||||
@@ -63,18 +61,10 @@ func init() {
|
||||
}
|
||||
|
||||
func annotate(cmd *cobra.Command, args []string) error {
|
||||
listImageSpec := args[0]
|
||||
instanceSpec := args[1]
|
||||
if listImageSpec == "" {
|
||||
return errors.Errorf(`invalid image name "%s"`, listImageSpec)
|
||||
}
|
||||
if instanceSpec == "" {
|
||||
return errors.Errorf(`invalid image digest "%s"`, instanceSpec)
|
||||
}
|
||||
updatedListID, err := registry.ImageEngine().ManifestAnnotate(context.Background(), args, manifestAnnotateOpts)
|
||||
id, err := registry.ImageEngine().ManifestAnnotate(registry.Context(), args[0], args[1], manifestAnnotateOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%s\n", updatedListID)
|
||||
fmt.Println(id)
|
||||
return nil
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package manifest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/podman/v3/cmd/podman/common"
|
||||
@@ -36,7 +35,7 @@ func init() {
|
||||
}
|
||||
|
||||
func create(cmd *cobra.Command, args []string) error {
|
||||
imageID, err := registry.ImageEngine().ManifestCreate(context.Background(), args[:1], args[1:], manifestCreateOpts)
|
||||
imageID, err := registry.ImageEngine().ManifestCreate(registry.Context(), args[0], args[1:], manifestCreateOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package manifest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/podman/v3/cmd/podman/common"
|
||||
@@ -29,10 +28,10 @@ func init() {
|
||||
}
|
||||
|
||||
func inspect(cmd *cobra.Command, args []string) error {
|
||||
buf, err := registry.ImageEngine().ManifestInspect(context.Background(), args[0])
|
||||
buf, err := registry.ImageEngine().ManifestInspect(registry.Context(), args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%s\n", buf)
|
||||
fmt.Println(string(buf))
|
||||
return nil
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package manifest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/podman/v3/cmd/podman/common"
|
||||
@@ -16,9 +15,9 @@ var (
|
||||
Short: "Remove an entry from a manifest list or image index",
|
||||
Long: "Removes an image from a manifest list or image index.",
|
||||
RunE: remove,
|
||||
Args: cobra.ExactArgs(2),
|
||||
ValidArgsFunction: common.AutocompleteImages,
|
||||
Example: `podman manifest remove mylist:v1.11 sha256:15352d97781ffdf357bf3459c037be3efac4133dc9070c2dce7eca7c05c3e736`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -30,18 +29,10 @@ func init() {
|
||||
}
|
||||
|
||||
func remove(cmd *cobra.Command, args []string) error {
|
||||
listImageSpec := args[0]
|
||||
instanceSpec := args[1]
|
||||
if listImageSpec == "" {
|
||||
return errors.Errorf(`invalid image name "%s"`, listImageSpec)
|
||||
}
|
||||
if instanceSpec == "" {
|
||||
return errors.Errorf(`invalid image digest "%s"`, instanceSpec)
|
||||
}
|
||||
updatedListID, err := registry.ImageEngine().ManifestRemove(context.Background(), args)
|
||||
updatedListID, err := registry.ImageEngine().ManifestRemoveDigest(registry.Context(), args[0], args[1])
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error removing from manifest list %s", listImageSpec)
|
||||
return errors.Wrapf(err, "error removing from manifest list %s", args[0])
|
||||
}
|
||||
fmt.Printf("%s\n", updatedListID)
|
||||
fmt.Println(updatedListID)
|
||||
return nil
|
||||
}
|
||||
@@ -12,13 +12,13 @@ import (
|
||||
|
||||
var (
|
||||
rmCmd = &cobra.Command{
|
||||
Use: "rm LIST",
|
||||
Use: "rm LIST [LIST...]",
|
||||
Short: "Remove manifest list or image index from local storage",
|
||||
Long: "Remove manifest list or image index from local storage.",
|
||||
RunE: rm,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
ValidArgsFunction: common.AutocompleteImages,
|
||||
Example: `podman manifest rm mylist:v1.11`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,11 @@ package libpod
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/docker/reference"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
@@ -15,6 +19,8 @@ import (
|
||||
"github.com/containers/podman/v3/pkg/auth"
|
||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||
"github.com/containers/podman/v3/pkg/domain/infra/abi"
|
||||
"github.com/containers/podman/v3/pkg/errorhandling"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/schema"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
@@ -24,40 +30,93 @@ func ManifestCreate(w http.ResponseWriter, r *http.Request) {
|
||||
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
||||
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
|
||||
query := struct {
|
||||
Name []string `schema:"name"`
|
||||
Image []string `schema:"image"`
|
||||
All bool `schema:"all"`
|
||||
Name string `schema:"name"`
|
||||
Images []string `schema:"images"`
|
||||
All bool `schema:"all"`
|
||||
}{
|
||||
// Add defaults here once needed.
|
||||
}
|
||||
|
||||
// Support 3.x API calls, alias image to images
|
||||
if image, ok := r.URL.Query()["image"]; ok {
|
||||
query.Images = image
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// TODO: (jhonce) When c/image is refactored the roadmap calls for this check to be pushed into that library.
|
||||
for _, n := range query.Name {
|
||||
if _, err := reference.ParseNormalizedNamed(n); err != nil {
|
||||
// Support 4.x API calls, map query parameter to path
|
||||
if name, ok := mux.Vars(r)["name"]; ok {
|
||||
n, err := url.QueryUnescape(name)
|
||||
if err != nil {
|
||||
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
|
||||
errors.Wrapf(err, "invalid image name %s", n))
|
||||
errors.Wrapf(err, "failed to parse name parameter %q", name))
|
||||
return
|
||||
}
|
||||
query.Name = n
|
||||
}
|
||||
|
||||
if _, err := reference.ParseNormalizedNamed(query.Name); err != nil {
|
||||
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
|
||||
errors.Wrapf(err, "invalid image name %s", query.Name))
|
||||
return
|
||||
}
|
||||
|
||||
imageEngine := abi.ImageEngine{Libpod: runtime}
|
||||
|
||||
createOptions := entities.ManifestCreateOptions{All: query.All}
|
||||
manID, err := imageEngine.ManifestCreate(r.Context(), query.Name, query.Image, createOptions)
|
||||
manID, err := imageEngine.ManifestCreate(r.Context(), query.Name, query.Images, createOptions)
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: manID})
|
||||
|
||||
status := http.StatusOK
|
||||
if _, err := utils.SupportedVersion(r, "< 4.0.0"); err == utils.ErrVersionNotSupported {
|
||||
status = http.StatusCreated
|
||||
}
|
||||
|
||||
buffer, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Treat \r\n as empty body
|
||||
if len(buffer) < 3 {
|
||||
utils.WriteResponse(w, status, handlers.IDResponse{ID: manID})
|
||||
return
|
||||
}
|
||||
|
||||
body := new(entities.ManifestModifyOptions)
|
||||
if err := json.Unmarshal(buffer, body); err != nil {
|
||||
utils.InternalServerError(w, errors.Wrap(err, "Decode()"))
|
||||
return
|
||||
}
|
||||
|
||||
// gather all images for manifest list
|
||||
var images []string
|
||||
if len(query.Images) > 0 {
|
||||
images = append(query.Images)
|
||||
}
|
||||
if len(body.Images) > 0 {
|
||||
images = append(body.Images)
|
||||
}
|
||||
|
||||
id, err := imageEngine.ManifestAdd(r.Context(), query.Name, images, body.ManifestAddOptions)
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteResponse(w, status, handlers.IDResponse{ID: id})
|
||||
}
|
||||
|
||||
// ExistsManifest check if a manifest list exists
|
||||
func ExistsManifest(w http.ResponseWriter, r *http.Request) {
|
||||
// ManifestExists return true if manifest list exists.
|
||||
func ManifestExists(w http.ResponseWriter, r *http.Request) {
|
||||
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
||||
name := utils.GetName(r)
|
||||
|
||||
@@ -94,10 +153,18 @@ func ManifestInspect(w http.ResponseWriter, r *http.Request) {
|
||||
utils.WriteResponse(w, http.StatusOK, schema2List)
|
||||
}
|
||||
|
||||
// ManifestAdd remove digest from manifest list
|
||||
//
|
||||
// Deprecated: As of 4.0.0 use ManifestModify instead
|
||||
func ManifestAdd(w http.ResponseWriter, r *http.Request) {
|
||||
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
||||
var addOptions entities.ManifestAddOptions
|
||||
if err := json.NewDecoder(r.Body).Decode(&addOptions); err != nil {
|
||||
|
||||
// Wrapper to support 3.x with 4.x libpod
|
||||
query := struct {
|
||||
entities.ManifestAddOptions
|
||||
Images []string
|
||||
}{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&query); err != nil {
|
||||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
|
||||
return
|
||||
}
|
||||
@@ -108,15 +175,8 @@ func ManifestAdd(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME: we really need to clean up the manifest API. Swagger states
|
||||
// the arguments were strings not string slices. The use of string
|
||||
// slices, mixing lists and images is incredibly confusing.
|
||||
if len(addOptions.Images) == 1 {
|
||||
addOptions.Images = append(addOptions.Images, name)
|
||||
}
|
||||
|
||||
imageEngine := abi.ImageEngine{Libpod: runtime}
|
||||
newID, err := imageEngine.ManifestAdd(r.Context(), addOptions)
|
||||
newID, err := imageEngine.ManifestAdd(r.Context(), name, query.Images, query.ManifestAddOptions)
|
||||
if err != nil {
|
||||
utils.InternalServerError(w, err)
|
||||
return
|
||||
@@ -124,7 +184,10 @@ func ManifestAdd(w http.ResponseWriter, r *http.Request) {
|
||||
utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: newID})
|
||||
}
|
||||
|
||||
func ManifestRemove(w http.ResponseWriter, r *http.Request) {
|
||||
// ManifestRemoveDigest remove digest from manifest list
|
||||
//
|
||||
// Deprecated: As of 4.0.0 use ManifestModify instead
|
||||
func ManifestRemoveDigest(w http.ResponseWriter, r *http.Request) {
|
||||
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
||||
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
|
||||
query := struct {
|
||||
@@ -155,7 +218,10 @@ func ManifestRemove(w http.ResponseWriter, r *http.Request) {
|
||||
utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: manifestList.ID()})
|
||||
}
|
||||
|
||||
func ManifestPush(w http.ResponseWriter, r *http.Request) {
|
||||
// ManifestPushV3 push image to registry
|
||||
//
|
||||
// Deprecated: As of 4.0.0 use ManifestPush instead
|
||||
func ManifestPushV3(w http.ResponseWriter, r *http.Request) {
|
||||
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
||||
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
|
||||
query := struct {
|
||||
@@ -207,3 +273,156 @@ func ManifestPush(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
utils.WriteResponse(w, http.StatusOK, digest)
|
||||
}
|
||||
|
||||
// ManifestPush push image to registry
|
||||
//
|
||||
// As of 4.0.0
|
||||
func ManifestPush(w http.ResponseWriter, r *http.Request) {
|
||||
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
||||
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
|
||||
|
||||
query := struct {
|
||||
All bool `schema:"all"`
|
||||
TLSVerify bool `schema:"tlsVerify"`
|
||||
}{
|
||||
// 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
|
||||
}
|
||||
|
||||
destination := utils.GetVar(r, "destination")
|
||||
if err := utils.IsRegistryReference(destination); err != nil {
|
||||
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
authconf, authfile, err := auth.GetCredentials(r)
|
||||
if err != nil {
|
||||
utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse registry header for %s", r.URL.String()))
|
||||
return
|
||||
}
|
||||
defer auth.RemoveAuthfile(authfile)
|
||||
var username, password string
|
||||
if authconf != nil {
|
||||
username = authconf.Username
|
||||
password = authconf.Password
|
||||
}
|
||||
options := entities.ImagePushOptions{
|
||||
Authfile: authfile,
|
||||
Username: username,
|
||||
Password: password,
|
||||
All: query.All,
|
||||
}
|
||||
if sys := runtime.SystemContext(); sys != nil {
|
||||
options.CertDir = sys.DockerCertPath
|
||||
}
|
||||
if _, found := r.URL.Query()["tlsVerify"]; found {
|
||||
options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
|
||||
}
|
||||
|
||||
imageEngine := abi.ImageEngine{Libpod: runtime}
|
||||
source := utils.GetName(r)
|
||||
digest, err := imageEngine.ManifestPush(context.Background(), source, destination, options)
|
||||
if err != nil {
|
||||
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "error pushing image %q", destination))
|
||||
return
|
||||
}
|
||||
utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: digest})
|
||||
}
|
||||
|
||||
// ManifestModify efficiently updates the named manifest list
|
||||
func ManifestModify(w http.ResponseWriter, r *http.Request) {
|
||||
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
||||
imageEngine := abi.ImageEngine{Libpod: runtime}
|
||||
|
||||
body := new(entities.ManifestModifyOptions)
|
||||
if err := json.NewDecoder(r.Body).Decode(body); err != nil {
|
||||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
|
||||
return
|
||||
}
|
||||
|
||||
name := utils.GetName(r)
|
||||
if _, err := runtime.LibimageRuntime().LookupManifestList(name); err != nil {
|
||||
utils.Error(w, "Something went wrong.", http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
|
||||
var report entities.ManifestModifyReport
|
||||
switch {
|
||||
case strings.EqualFold("update", body.Operation):
|
||||
id, err := imageEngine.ManifestAdd(r.Context(), name, body.Images, body.ManifestAddOptions)
|
||||
if err != nil {
|
||||
report.Errors = append(report.Errors, err)
|
||||
break
|
||||
}
|
||||
report = entities.ManifestModifyReport{
|
||||
ID: id,
|
||||
Images: body.Images,
|
||||
}
|
||||
case strings.EqualFold("remove", body.Operation):
|
||||
for _, image := range body.Images {
|
||||
id, err := imageEngine.ManifestRemoveDigest(r.Context(), name, image)
|
||||
if err != nil {
|
||||
report.Errors = append(report.Errors, err)
|
||||
continue
|
||||
}
|
||||
report.ID = id
|
||||
report.Images = append(report.Images, image)
|
||||
}
|
||||
case strings.EqualFold("annotate", body.Operation):
|
||||
options := entities.ManifestAnnotateOptions{
|
||||
Annotation: body.Annotation,
|
||||
Arch: body.Arch,
|
||||
Features: body.Features,
|
||||
OS: body.OS,
|
||||
OSFeatures: body.OSFeatures,
|
||||
OSVersion: body.OSVersion,
|
||||
Variant: body.Variant,
|
||||
}
|
||||
for _, image := range body.Images {
|
||||
id, err := imageEngine.ManifestAnnotate(r.Context(), name, image, options)
|
||||
if err != nil {
|
||||
report.Errors = append(report.Errors, err)
|
||||
continue
|
||||
}
|
||||
report.ID = id
|
||||
report.Images = append(report.Images, image)
|
||||
}
|
||||
default:
|
||||
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
|
||||
fmt.Errorf("illegal operation %q for %q", body.Operation, r.URL.String()))
|
||||
return
|
||||
}
|
||||
|
||||
statusCode := http.StatusOK
|
||||
switch {
|
||||
case len(report.Errors) > 0 && len(report.Images) > 0:
|
||||
statusCode = http.StatusConflict
|
||||
case len(report.Errors) > 0:
|
||||
statusCode = http.StatusInternalServerError
|
||||
}
|
||||
utils.WriteResponse(w, statusCode, report)
|
||||
}
|
||||
|
||||
// ManifestDelete removes a manifest list from storage
|
||||
func ManifestDelete(w http.ResponseWriter, r *http.Request) {
|
||||
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
|
||||
imageEngine := abi.ImageEngine{Libpod: runtime}
|
||||
|
||||
name := utils.GetName(r)
|
||||
if _, err := runtime.LibimageRuntime().LookupManifestList(name); err != nil {
|
||||
utils.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
|
||||
results, errs := imageEngine.ManifestRm(r.Context(), []string{name})
|
||||
errsString := errorhandling.ErrorsToStrings(errs)
|
||||
report := handlers.LibpodImagesRemoveReport{
|
||||
ImageRemoveReport: *results,
|
||||
Errors: errsString,
|
||||
}
|
||||
utils.WriteResponse(w, http.StatusOK, report)
|
||||
}
|
||||
@@ -174,7 +174,7 @@ func FilterMapToString(filters map[string][]string) (string, error) {
|
||||
return string(f), nil
|
||||
}
|
||||
|
||||
func getVar(r *http.Request, k string) string {
|
||||
func GetVar(r *http.Request, k string) string {
|
||||
val := mux.Vars(r)[k]
|
||||
safeVal, err := url.PathUnescape(val)
|
||||
if err != nil {
|
||||
@@ -186,5 +186,5 @@ func getVar(r *http.Request, k string) string {
|
||||
|
||||
// GetName extracts the name from the mux
|
||||
func GetName(r *http.Request) string {
|
||||
return getVar(r, "name")
|
||||
return GetVar(r, "name")
|
||||
}
|
||||
@@ -8,7 +8,9 @@ import (
|
||||
)
|
||||
|
||||
func (s *APIServer) registerManifestHandlers(r *mux.Router) error {
|
||||
// swagger:operation POST /libpod/manifests/create manifests ManifestCreateLibpod
|
||||
v3 := r.PathPrefix("/v{version:[0-3][0-9A-Za-z.-]*}/libpod/manifests").Subrouter()
|
||||
v4 := r.PathPrefix("/v{version:[4-9][0-9A-Za-z.-]*}/libpod/manifests").Subrouter()
|
||||
// swagger:operation POST /libpod/manifests manifests ManifestCreateLibpod
|
||||
// ---
|
||||
// summary: Create
|
||||
// description: Create a manifest list
|
||||
@@ -18,18 +20,30 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error {
|
||||
// - in: query
|
||||
// name: name
|
||||
// type: string
|
||||
// description: manifest list name
|
||||
// description: manifest list or index name to create
|
||||
// required: true
|
||||
// - in: query
|
||||
// name: image
|
||||
// name: images
|
||||
// type: string
|
||||
// description: name of the image
|
||||
// required: true
|
||||
// description: |
|
||||
// One or more names of an image or a manifest list. Repeat parameter as needed.
|
||||
//
|
||||
// Support for multiple images, as of version 4.0.0
|
||||
// Alias of `image` is support for compatibility with < 4.0.0
|
||||
// Response status code is 200 with < 4.0.0 for compatibility
|
||||
// - in: query
|
||||
// name: all
|
||||
// type: boolean
|
||||
// description: add all contents if given list
|
||||
// - in: body
|
||||
// name: options
|
||||
// description: options for new manifest
|
||||
// required: false
|
||||
// schema:
|
||||
// $ref: "#/definitions/ManifestModifyOptions"
|
||||
// responses:
|
||||
// 200:
|
||||
// 201:
|
||||
// schema:
|
||||
// $ref: "#/definitions/IDResponse"
|
||||
// 400:
|
||||
@@ -38,17 +52,21 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error {
|
||||
// $ref: "#/responses/NoSuchImage"
|
||||
// 500:
|
||||
// $ref: "#/responses/InternalError"
|
||||
r.Handle(VersionedPath("/libpod/manifests/create"), s.APIHandler(libpod.ManifestCreate)).Methods(http.MethodPost)
|
||||
v3.Handle("/create", s.APIHandler(libpod.ManifestCreate)).Methods(http.MethodPost)
|
||||
v4.Handle("/{name:.*}", s.APIHandler(libpod.ManifestCreate)).Methods(http.MethodPost)
|
||||
// swagger:operation GET /libpod/manifests/{name}/exists manifests ManifestExistsLibpod
|
||||
// ---
|
||||
// summary: Exists
|
||||
// description: Check if manifest list exists
|
||||
// description: |
|
||||
// Check if manifest list exists
|
||||
//
|
||||
// Note: There is no contract that the manifest list will exist for a follow-on operation
|
||||
// parameters:
|
||||
// - in: path
|
||||
// name: name
|
||||
// type: string
|
||||
// required: true
|
||||
// description: the name of the manifest list
|
||||
// description: the name or ID of the manifest list
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
@@ -58,11 +76,38 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error {
|
||||
// $ref: '#/responses/NoSuchManifest'
|
||||
// 500:
|
||||
// $ref: '#/responses/InternalError'
|
||||
r.Handle(VersionedPath("/libpod/manifests/{name}/exists"), s.APIHandler(libpod.ExistsManifest)).Methods(http.MethodGet)
|
||||
v3.Handle("/{name:.*}/exists", s.APIHandler(libpod.ManifestExists)).Methods(http.MethodGet)
|
||||
v4.Handle("/{name:.*}/exists", s.APIHandler(libpod.ManifestExists)).Methods(http.MethodGet)
|
||||
// swagger:operation GET /libpod/manifests/{name}/json manifests ManifestInspectLibpod
|
||||
// ---
|
||||
// summary: Inspect
|
||||
// description: Display a manifest list
|
||||
// description: Display attributes of given manifest list
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - in: path
|
||||
// name: name
|
||||
// type: string
|
||||
// required: true
|
||||
// description: the name or ID of the manifest list
|
||||
// responses:
|
||||
// 200:
|
||||
// $ref: "#/responses/InspectManifest"
|
||||
// 404:
|
||||
// $ref: "#/responses/NoSuchManifest"
|
||||
// 500:
|
||||
// $ref: "#/responses/InternalError"
|
||||
v3.Handle("/{name:.*}/json", s.APIHandler(libpod.ManifestInspect)).Methods(http.MethodGet)
|
||||
v4.Handle("/{name:.*}/json", s.APIHandler(libpod.ManifestInspect)).Methods(http.MethodGet)
|
||||
// swagger:operation PUT /libpod/manifests/{name} manifests ManifestModifyLibpod
|
||||
// ---
|
||||
// summary: Modify manifest list
|
||||
// description: |
|
||||
// Add/Remove an image(s) to a manifest list
|
||||
//
|
||||
// Note: operations are not atomic when multiple Images are provided.
|
||||
//
|
||||
// As of v4.0.0
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
@@ -71,18 +116,34 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error {
|
||||
// type: string
|
||||
// required: true
|
||||
// description: the name or ID of the manifest
|
||||
// - in: body
|
||||
// name: options
|
||||
// description: options for mutating a manifest
|
||||
// required: true
|
||||
// schema:
|
||||
// $ref: "#/definitions/ManifestModifyOptions"
|
||||
// responses:
|
||||
// 200:
|
||||
// $ref: "#/responses/InspectManifest"
|
||||
// schema:
|
||||
// $ref: "#/definitions/ManifestModifyReport"
|
||||
// 404:
|
||||
// $ref: "#/responses/NoSuchManifest"
|
||||
// 400:
|
||||
// $ref: "#/responses/BadParamError"
|
||||
// 409:
|
||||
// description: Operation had partial success, both Images and Errors may have members
|
||||
// schema:
|
||||
// $ref: "#/definitions/ManifestModifyReport"
|
||||
// 500:
|
||||
// $ref: "#/responses/InternalError"
|
||||
r.Handle(VersionedPath("/libpod/manifests/{name:.*}/json"), s.APIHandler(libpod.ManifestInspect)).Methods(http.MethodGet)
|
||||
v4.Handle("/{name:.*}", s.APIHandler(libpod.ManifestModify)).Methods(http.MethodPut)
|
||||
// swagger:operation POST /libpod/manifests/{name}/add manifests ManifestAddLibpod
|
||||
// ---
|
||||
// summary: Add image
|
||||
// description: Add an image to a manifest list
|
||||
// description: |
|
||||
// Add an image to a manifest list
|
||||
//
|
||||
// Deprecated: As of 4.0.0 use ManifestModifyLibpod instead
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
@@ -95,7 +156,7 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error {
|
||||
// name: options
|
||||
// description: options for creating a manifest
|
||||
// schema:
|
||||
// $ref: "#/definitions/ManifestAddOpts"
|
||||
// $ref: "#/definitions/ManifestAddOptions"
|
||||
// responses:
|
||||
// 200:
|
||||
// schema:
|
||||
@@ -106,11 +167,14 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error {
|
||||
// $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 ManifestDeleteLibpod
|
||||
v3.Handle("/{name:.*}/add", s.APIHandler(libpod.ManifestAdd)).Methods(http.MethodPost)
|
||||
// swagger:operation DELETE /libpod/manifests/{name} manifests ManifestDeleteV3Libpod
|
||||
// ---
|
||||
// summary: Remove
|
||||
// description: Remove an image from a manifest list
|
||||
// summary: Remove image from a manifest list
|
||||
// description: |
|
||||
// Remove an image from a manifest list
|
||||
//
|
||||
// Deprecated: As of 4.0.0 use ManifestModifyLibpod instead
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
@@ -133,11 +197,37 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error {
|
||||
// $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 ManifestPushLibpod
|
||||
v3.Handle("/{name:.*}", s.APIHandler(libpod.ManifestRemoveDigest)).Methods(http.MethodDelete)
|
||||
// swagger:operation DELETE /libpod/manifests/{name} manifests ManifestDeleteLibpod
|
||||
// ---
|
||||
// summary: Push
|
||||
// description: Push a manifest list or image index to a registry
|
||||
// summary: Delete manifest list
|
||||
// description: |
|
||||
// Delete named manifest list
|
||||
//
|
||||
// As of v4.0.0
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - in: path
|
||||
// name: name
|
||||
// type: string
|
||||
// required: true
|
||||
// description: The name or ID of the list to be deleted
|
||||
// responses:
|
||||
// 200:
|
||||
// $ref: "#/responses/DocsLibpodImagesRemoveResponse"
|
||||
// 404:
|
||||
// $ref: "#/responses/NoSuchManifest"
|
||||
// 500:
|
||||
// $ref: "#/responses/InternalError"
|
||||
v4.Handle("/{name:.*}", s.APIHandler(libpod.ManifestDelete)).Methods(http.MethodDelete)
|
||||
// swagger:operation POST /libpod/manifests/{name}/push manifests ManifestPushV3Libpod
|
||||
// ---
|
||||
// summary: Push manifest to registry
|
||||
// description: |
|
||||
// Push a manifest list or image index to a registry
|
||||
//
|
||||
// Deprecated: As of 4.0.0 use ManifestPushLibpod instead
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
@@ -165,6 +255,47 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error {
|
||||
// $ref: "#/responses/NoSuchManifest"
|
||||
// 500:
|
||||
// $ref: "#/responses/InternalError"
|
||||
r.Handle(VersionedPath("/libpod/manifests/{name}/push"), s.APIHandler(libpod.ManifestPush)).Methods(http.MethodPost)
|
||||
v3.Handle("/{name}/push", s.APIHandler(libpod.ManifestPushV3)).Methods(http.MethodPost)
|
||||
// swagger:operation POST /libpod/manifests/{name}/registry/{destination} manifests ManifestPushLibpod
|
||||
// ---
|
||||
// summary: Push manifest list to registry
|
||||
// description: |
|
||||
// Push a manifest list or image index to the named registry
|
||||
//
|
||||
// As of v4.0.0
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - in: path
|
||||
// name: name
|
||||
// type: string
|
||||
// required: true
|
||||
// description: the name or ID of the manifest list
|
||||
// - in: path
|
||||
// name: destination
|
||||
// type: string
|
||||
// required: true
|
||||
// description: the registry for the manifest list
|
||||
// - in: query
|
||||
// name: all
|
||||
// description: push all images
|
||||
// type: boolean
|
||||
// default: false
|
||||
// - in: query
|
||||
// name: tlsVerify
|
||||
// type: boolean
|
||||
// default: false
|
||||
// description: skip TLS verification for registries
|
||||
// responses:
|
||||
// 200:
|
||||
// schema:
|
||||
// $ref: "#/definitions/IDResponse"
|
||||
// 400:
|
||||
// $ref: "#/responses/BadParamError"
|
||||
// 404:
|
||||
// $ref: "#/responses/NoSuchManifest"
|
||||
// 500:
|
||||
// $ref: "#/responses/InternalError"
|
||||
v4.Handle("/{name:.*}/registry/{destination:.*}", s.APIHandler(libpod.ManifestPush)).Methods(http.MethodPost)
|
||||
return nil
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// RegisterSwaggerHandlers maps the swagger endpoint for the server
|
||||
func (s *APIServer) RegisterSwaggerHandlers(r *mux.Router) error {
|
||||
// registerSwaggerHandlers maps the swagger endpoint for the server
|
||||
func (s *APIServer) registerSwaggerHandlers(r *mux.Router) error {
|
||||
// This handler does _*NOT*_ provide an UI rather just a swagger spec that an UI could render
|
||||
r.HandleFunc(VersionedPath("/libpod/swagger"), s.APIHandler(libpod.ServeSwagger)).Methods(http.MethodGet)
|
||||
return nil
|
||||
|
||||
@@ -151,7 +151,7 @@ func newServer(runtime *libpod.Runtime, listener *net.Listener, opts entities.Se
|
||||
server.registerPluginsHandlers,
|
||||
server.registerPodsHandlers,
|
||||
server.registerSecretHandlers,
|
||||
server.RegisterSwaggerHandlers,
|
||||
server.registerSwaggerHandlers,
|
||||
server.registerSwarmHandlers,
|
||||
server.registerSystemHandlers,
|
||||
server.registerVersionHandlers,
|
||||
|
||||
+5
-5
@@ -140,7 +140,7 @@ func getAuthCredentials(headers []string) (*types.DockerAuthConfig, map[string]t
|
||||
|
||||
// MakeXRegistryConfigHeader returns a map with the "X-Registry-Config" header set, which can
|
||||
// conveniently be used in the http stack.
|
||||
func MakeXRegistryConfigHeader(sys *types.SystemContext, username, password string) (map[string]string, error) {
|
||||
func MakeXRegistryConfigHeader(sys *types.SystemContext, username, password string) (http.Header, error) {
|
||||
if sys == nil {
|
||||
sys = &types.SystemContext{}
|
||||
}
|
||||
@@ -163,18 +163,18 @@ func MakeXRegistryConfigHeader(sys *types.SystemContext, username, password stri
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]string{xRegistryConfigHeader: content}, nil
|
||||
return http.Header{xRegistryConfigHeader: []string{content}}, nil
|
||||
}
|
||||
|
||||
// MakeXRegistryAuthHeader returns a map with the "X-Registry-Auth" header set, which can
|
||||
// conveniently be used in the http stack.
|
||||
func MakeXRegistryAuthHeader(sys *types.SystemContext, username, password string) (map[string]string, error) {
|
||||
func MakeXRegistryAuthHeader(sys *types.SystemContext, username, password string) (http.Header, error) {
|
||||
if username != "" {
|
||||
content, err := encodeSingleAuthConfig(types.DockerAuthConfig{Username: username, Password: password})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]string{xRegistryAuthHeader: content}, nil
|
||||
return http.Header{xRegistryAuthHeader: []string{content}}, nil
|
||||
}
|
||||
|
||||
if sys == nil {
|
||||
@@ -188,7 +188,7 @@ func MakeXRegistryAuthHeader(sys *types.SystemContext, username, password string
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]string{xRegistryAuthHeader: content}, nil
|
||||
return http.Header{xRegistryAuthHeader: []string{content}}, nil
|
||||
}
|
||||
|
||||
// RemoveAuthfile is a convenience function that is meant to be called in a
|
||||
|
||||
@@ -85,8 +85,8 @@ func TestMakeXRegistryConfigHeaderGetCredentialsRoundtrip(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
req, err := http.NewRequest(http.MethodPost, "/", nil)
|
||||
require.NoError(t, err, tc.name)
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
for _, v := range headers.Values(xRegistryConfigHeader) {
|
||||
req.Header.Add(xRegistryConfigHeader, v)
|
||||
}
|
||||
|
||||
override, resPath, err := GetCredentials(req)
|
||||
@@ -137,8 +137,8 @@ func TestMakeXRegistryAuthHeaderGetCredentialsRoundtrip(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
req, err := http.NewRequest(http.MethodPost, "/", nil)
|
||||
require.NoError(t, err, tc.name)
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
for _, v := range headers.Values(xRegistryAuthHeader) {
|
||||
req.Header.Set(xRegistryAuthHeader, v)
|
||||
}
|
||||
|
||||
override, resPath, err := GetCredentials(req)
|
||||
@@ -219,7 +219,7 @@ func TestMakeXRegistryConfigHeader(t *testing.T) {
|
||||
require.Len(t, res, 1, tc.name)
|
||||
header, ok := res[xRegistryConfigHeader]
|
||||
require.True(t, ok, tc.name)
|
||||
decodedHeader, err := base64.URLEncoding.DecodeString(header)
|
||||
decodedHeader, err := base64.URLEncoding.DecodeString(header[0])
|
||||
require.NoError(t, err, tc.name)
|
||||
// Don't test for a specific JSON representation, just for the expected contents.
|
||||
expected := map[string]interface{}{}
|
||||
@@ -282,7 +282,7 @@ func TestMakeXRegistryAuthHeader(t *testing.T) {
|
||||
require.Len(t, res, 1, tc.name)
|
||||
header, ok := res[xRegistryAuthHeader]
|
||||
require.True(t, ok, tc.name)
|
||||
decodedHeader, err := base64.URLEncoding.DecodeString(header)
|
||||
decodedHeader, err := base64.URLEncoding.DecodeString(header[0])
|
||||
require.NoError(t, err, tc.name)
|
||||
// Don't test for a specific JSON representation, just for the expected contents.
|
||||
expected := map[string]interface{}{}
|
||||
|
||||
+48
-37
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/blang/semver"
|
||||
"github.com/containers/podman/v3/pkg/terminal"
|
||||
"github.com/containers/podman/v3/version"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/ssh"
|
||||
@@ -35,16 +34,24 @@ type Connection struct {
|
||||
type valueKey string
|
||||
|
||||
const (
|
||||
clientKey = valueKey("Client")
|
||||
clientKey = valueKey("Client")
|
||||
versionKey = valueKey("ServiceVersion")
|
||||
)
|
||||
|
||||
// GetClient from context build by NewConnection()
|
||||
func GetClient(ctx context.Context) (*Connection, error) {
|
||||
c, ok := ctx.Value(clientKey).(*Connection)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("ClientKey not set in context")
|
||||
if c, ok := ctx.Value(clientKey).(*Connection); ok {
|
||||
return c, nil
|
||||
}
|
||||
return c, nil
|
||||
return nil, errors.Errorf("%s not set in context", clientKey)
|
||||
}
|
||||
|
||||
// ServiceVersion from context build by NewConnection()
|
||||
func ServiceVersion(ctx context.Context) *semver.Version {
|
||||
if v, ok := ctx.Value(versionKey).(*semver.Version); ok {
|
||||
return v
|
||||
}
|
||||
return new(semver.Version)
|
||||
}
|
||||
|
||||
// JoinURL elements with '/'
|
||||
@@ -52,6 +59,7 @@ func JoinURL(elements ...string) string {
|
||||
return "/" + strings.Join(elements, "/")
|
||||
}
|
||||
|
||||
// NewConnection creates a new service connection without an identity
|
||||
func NewConnection(ctx context.Context, uri string) (context.Context, error) {
|
||||
return NewConnectionWithIdentity(ctx, uri, "")
|
||||
}
|
||||
@@ -116,9 +124,11 @@ func NewConnectionWithIdentity(ctx context.Context, uri string, identity string)
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, clientKey, &connection)
|
||||
if err := pingNewConnection(ctx); err != nil {
|
||||
serviceVersion, err := pingNewConnection(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to connect to Podman socket")
|
||||
}
|
||||
ctx = context.WithValue(ctx, versionKey, serviceVersion)
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
@@ -139,15 +149,15 @@ func tcpClient(_url *url.URL) Connection {
|
||||
|
||||
// pingNewConnection pings to make sure the RESTFUL service is up
|
||||
// and running. it should only be used when initializing a connection
|
||||
func pingNewConnection(ctx context.Context) error {
|
||||
func pingNewConnection(ctx context.Context) (*semver.Version, error) {
|
||||
client, err := GetClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
// the ping endpoint sits at / in this case
|
||||
response, err := client.DoRequest(ctx, nil, http.MethodGet, "/_ping", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
@@ -155,23 +165,23 @@ func pingNewConnection(ctx context.Context) error {
|
||||
versionHdr := response.Header.Get("Libpod-API-Version")
|
||||
if versionHdr == "" {
|
||||
logrus.Info("Service did not provide Libpod-API-Version Header")
|
||||
return nil
|
||||
return new(semver.Version), nil
|
||||
}
|
||||
versionSrv, err := semver.ParseTolerant(versionHdr)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch version.APIVersion[version.Libpod][version.MinimalAPI].Compare(versionSrv) {
|
||||
case -1, 0:
|
||||
// Server's job when Client version is equal or older
|
||||
return nil
|
||||
return &versionSrv, nil
|
||||
case 1:
|
||||
return errors.Errorf("server API version is too old. Client %q server %q",
|
||||
return nil, errors.Errorf("server API version is too old. Client %q server %q",
|
||||
version.APIVersion[version.Libpod][version.MinimalAPI].String(), versionSrv.String())
|
||||
}
|
||||
}
|
||||
return errors.Errorf("ping response was %d", response.StatusCode)
|
||||
return nil, errors.Errorf("ping response was %d", response.StatusCode)
|
||||
}
|
||||
|
||||
func sshClient(_url *url.URL, secure bool, passPhrase string, identity string) (Connection, error) {
|
||||
@@ -306,26 +316,29 @@ func unixClient(_url *url.URL) Connection {
|
||||
}
|
||||
|
||||
// DoRequest assembles the http request and returns the response
|
||||
func (c *Connection) DoRequest(ctx context.Context, httpBody io.Reader, httpMethod, endpoint string, queryParams url.Values, header map[string]string, pathValues ...string) (*APIResponse, error) {
|
||||
func (c *Connection) DoRequest(ctx context.Context, httpBody io.Reader, httpMethod, endpoint string, queryParams url.Values, headers http.Header, pathValues ...string) (*APIResponse, error) {
|
||||
var (
|
||||
err error
|
||||
response *http.Response
|
||||
)
|
||||
|
||||
params := make([]interface{}, len(pathValues)+3)
|
||||
params := make([]interface{}, len(pathValues)+1)
|
||||
|
||||
if v := headers.Values("API-Version"); len(v) > 0 {
|
||||
params[0] = v[0]
|
||||
} else {
|
||||
// Including the semver suffices breaks older services... so do not include them
|
||||
v := version.APIVersion[version.Libpod][version.CurrentAPI]
|
||||
params[0] = fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||
}
|
||||
|
||||
// Including the semver suffices breaks older services... so do not include them
|
||||
v := version.APIVersion[version.Libpod][version.CurrentAPI]
|
||||
params[0] = v.Major
|
||||
params[1] = v.Minor
|
||||
params[2] = v.Patch
|
||||
for i, pv := range pathValues {
|
||||
// url.URL lacks the semantics for escaping embedded path parameters... so we manually
|
||||
// escape each one and assume the caller included the correct formatting in "endpoint"
|
||||
params[i+3] = url.PathEscape(pv)
|
||||
params[i+1] = url.PathEscape(pv)
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("http://d/v%d.%d.%d/libpod"+endpoint, params...)
|
||||
uri := fmt.Sprintf("http://d/v%s/libpod"+endpoint, params...)
|
||||
logrus.Debugf("DoRequest Method: %s URI: %v", httpMethod, uri)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, httpMethod, uri, httpBody)
|
||||
@@ -335,9 +348,17 @@ func (c *Connection) DoRequest(ctx context.Context, httpBody io.Reader, httpMeth
|
||||
if len(queryParams) > 0 {
|
||||
req.URL.RawQuery = queryParams.Encode()
|
||||
}
|
||||
for key, val := range header {
|
||||
req.Header.Set(key, val)
|
||||
|
||||
for key, val := range headers {
|
||||
if key == "API-Version" {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, v := range val {
|
||||
req.Header.Add(key, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Give the Do three chances in the case of a comm/service hiccup
|
||||
for i := 1; i <= 3; i++ {
|
||||
response, err = c.Client.Do(req) // nolint
|
||||
@@ -349,7 +370,7 @@ func (c *Connection) DoRequest(ctx context.Context, httpBody io.Reader, httpMeth
|
||||
return &APIResponse{response, req}, err
|
||||
}
|
||||
|
||||
// Get raw Transport.DialContext from client
|
||||
// GetDialer returns raw Transport.DialContext from client
|
||||
func (c *Connection) GetDialer(ctx context.Context) (net.Conn, error) {
|
||||
client := c.Client
|
||||
transport := client.Transport.(*http.Transport)
|
||||
@@ -360,16 +381,6 @@ func (c *Connection) GetDialer(ctx context.Context) (net.Conn, error) {
|
||||
return nil, errors.New("Unable to get dial context")
|
||||
}
|
||||
|
||||
// FiltersToString converts our typical filter format of a
|
||||
// map[string][]string to a query/html safe string.
|
||||
func FiltersToString(filters map[string][]string) (string, error) {
|
||||
lowerCaseKeys := make(map[string][]string)
|
||||
for k, v := range filters {
|
||||
lowerCaseKeys[strings.ToLower(k)] = v
|
||||
}
|
||||
return jsoniter.MarshalToString(lowerCaseKeys)
|
||||
}
|
||||
|
||||
// IsInformational returns true if the response code is 1xx
|
||||
func (h *APIResponse) IsInformational() bool {
|
||||
return h.Response.StatusCode/100 == 1
|
||||
|
||||
@@ -108,9 +108,9 @@ func Attach(ctx context.Context, nameOrID string, stdin io.Reader, stdout io.Wri
|
||||
}()
|
||||
}
|
||||
|
||||
headers := make(map[string]string)
|
||||
headers["Connection"] = "Upgrade"
|
||||
headers["Upgrade"] = "tcp"
|
||||
headers := make(http.Header)
|
||||
headers.Add("Connection", "Upgrade")
|
||||
headers.Add("Upgrade", "tcp")
|
||||
|
||||
var socket net.Conn
|
||||
socketSet := false
|
||||
@@ -157,7 +157,7 @@ func Attach(ctx context.Context, nameOrID string, stdin io.Reader, stdout io.Wri
|
||||
}
|
||||
|
||||
stdoutChan := make(chan error)
|
||||
stdinChan := make(chan error, 1) //stdin channel should not block
|
||||
stdinChan := make(chan error, 1) // stdin channel should not block
|
||||
|
||||
if isSet.stdin {
|
||||
go func() {
|
||||
|
||||
@@ -25,7 +25,7 @@ func (h APIResponse) Process(unmarshalInto interface{}) error {
|
||||
return h.ProcessWithError(unmarshalInto, &errorhandling.ErrorModel{})
|
||||
}
|
||||
|
||||
// Process drains the response body, and processes the HTTP status code
|
||||
// ProcessWithError drains the response body, and processes the HTTP status code
|
||||
// Note: Closing the response.Body is left to the caller
|
||||
func (h APIResponse) ProcessWithError(unmarshalInto interface{}, unmarshalErrorInto interface{}) error {
|
||||
data, err := ioutil.ReadAll(h.Response.Body)
|
||||
|
||||
@@ -300,7 +300,7 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
|
||||
}
|
||||
|
||||
var (
|
||||
headers map[string]string
|
||||
headers http.Header
|
||||
err error
|
||||
)
|
||||
if options.SystemContext != nil && options.SystemContext.DockerAuthConfig != nil {
|
||||
@@ -421,7 +421,7 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//add tmp file to context dir
|
||||
// add tmp file to context dir
|
||||
tarContent = append(tarContent, tmpSecretFile.Name())
|
||||
|
||||
modifiedSrc := fmt.Sprintf("src=%s", filepath.Base(tmpSecretFile.Name()))
|
||||
@@ -634,7 +634,7 @@ func nTar(excludes []string, sources ...string) (io.ReadCloser, error) {
|
||||
if lerr := tw.WriteHeader(hdr); lerr != nil {
|
||||
return lerr
|
||||
}
|
||||
} //skip other than file,folder and symlinks
|
||||
} // skip other than file,folder and symlinks
|
||||
return nil
|
||||
})
|
||||
merr = multierror.Append(merr, err)
|
||||
|
||||
@@ -104,3 +104,11 @@ func ToParams(o interface{}) (url.Values, error) {
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func MapToArrayString(data map[string]string) []string {
|
||||
l := make([]string, 0)
|
||||
for k, v := range data {
|
||||
l = append(l, k+"="+v)
|
||||
}
|
||||
return l
|
||||
}
|
||||
@@ -3,15 +3,18 @@ package manifests
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/podman/v3/pkg/api/handlers"
|
||||
"github.com/containers/podman/v3/pkg/bindings"
|
||||
"github.com/containers/podman/v3/pkg/bindings/images"
|
||||
"github.com/containers/podman/v3/version"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
@@ -19,7 +22,7 @@ import (
|
||||
// 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, options *CreateOptions) (string, error) {
|
||||
func Create(ctx context.Context, name string, images []string, options *CreateOptions) (string, error) {
|
||||
var idr handlers.IDResponse
|
||||
if options == nil {
|
||||
options = new(CreateOptions)
|
||||
@@ -28,21 +31,19 @@ func Create(ctx context.Context, names, images []string, options *CreateOptions)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(names) < 1 {
|
||||
if len(name) < 1 {
|
||||
return "", errors.New("creating a manifest requires at least one name argument")
|
||||
}
|
||||
params, err := options.ToParams()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, name := range names {
|
||||
params.Add("name", name)
|
||||
}
|
||||
|
||||
for _, i := range images {
|
||||
params.Add("image", i)
|
||||
params.Add("images", i)
|
||||
}
|
||||
|
||||
response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/manifests/create", params, nil)
|
||||
response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/manifests/%s", params, nil, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -67,70 +68,96 @@ func Exists(ctx context.Context, name string, options *ExistsOptions) (bool, err
|
||||
}
|
||||
|
||||
// Inspect returns a manifest list for a given name.
|
||||
func Inspect(ctx context.Context, name string, options *InspectOptions) (*manifest.Schema2List, error) {
|
||||
var list manifest.Schema2List
|
||||
if options == nil {
|
||||
options = new(InspectOptions)
|
||||
}
|
||||
_ = options
|
||||
func Inspect(ctx context.Context, name string, _ *InspectOptions) (*manifest.Schema2List, error) {
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/manifests/%s/json", nil, nil, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var list manifest.Schema2List
|
||||
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 *AddOptions) (string, error) {
|
||||
var idr handlers.IDResponse
|
||||
if options == nil {
|
||||
options = new(AddOptions)
|
||||
}
|
||||
|
||||
if bindings.ServiceVersion(ctx).GTE(semver.MustParse("4.0.0")) {
|
||||
optionsv4 := ModifyOptions{
|
||||
All: options.All,
|
||||
Annotations: options.Annotation,
|
||||
Arch: options.Arch,
|
||||
Features: options.Features,
|
||||
Images: options.Images,
|
||||
OS: options.OS,
|
||||
OSFeatures: nil,
|
||||
OSVersion: options.OSVersion,
|
||||
Variant: options.Variant,
|
||||
}
|
||||
optionsv4.WithOperation("update")
|
||||
return Modify(ctx, name, options.Images, &optionsv4)
|
||||
}
|
||||
|
||||
// API Version < 4.0.0
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
optionsString, err := jsoniter.MarshalToString(options)
|
||||
opts, err := jsoniter.MarshalToString(options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
stringReader := strings.NewReader(optionsString)
|
||||
response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/manifests/%s/add", nil, nil, name)
|
||||
reader := strings.NewReader(opts)
|
||||
|
||||
headers := make(http.Header)
|
||||
v := version.APIVersion[version.Libpod][version.MinimalAPI]
|
||||
headers.Add("API-Version",
|
||||
fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch))
|
||||
response, err := conn.DoRequest(ctx, reader, http.MethodPost, "/manifests/%s/add", nil, headers, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var idr handlers.IDResponse
|
||||
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, options *RemoveOptions) (string, error) {
|
||||
var idr handlers.IDResponse
|
||||
if options == nil {
|
||||
options = new(RemoveOptions)
|
||||
func Remove(ctx context.Context, name, digest string, _ *RemoveOptions) (string, error) {
|
||||
if bindings.ServiceVersion(ctx).GTE(semver.MustParse("4.0.0")) {
|
||||
optionsv4 := new(ModifyOptions).WithOperation("remove")
|
||||
return Modify(ctx, name, []string{digest}, optionsv4)
|
||||
}
|
||||
_ = options
|
||||
|
||||
// API Version < 4.0.0
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
headers := http.Header{}
|
||||
headers.Add("API-Version", "3.4.0")
|
||||
|
||||
params := url.Values{}
|
||||
params.Set("digest", digest)
|
||||
response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/manifests/%s", params, nil, name)
|
||||
response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/manifests/%s", params, headers, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var idr handlers.IDResponse
|
||||
return idr.ID, response.Process(&idr)
|
||||
}
|
||||
|
||||
@@ -151,19 +178,26 @@ func Push(ctx context.Context, name, destination string, options *images.PushOpt
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
params, err := options.ToParams()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// SkipTLSVerify is special. We need to delete the param added by
|
||||
// toparams and change the key and flip the bool
|
||||
// ToParams() and change the key and flip the bool
|
||||
if options.SkipTLSVerify != nil {
|
||||
params.Del("SkipTLSVerify")
|
||||
params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify()))
|
||||
}
|
||||
params.Set("image", name)
|
||||
params.Set("destination", destination)
|
||||
response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/manifests/%s/push", params, nil, name)
|
||||
|
||||
var response *bindings.APIResponse
|
||||
if bindings.ServiceVersion(ctx).GTE(semver.MustParse("4.0.0")) {
|
||||
response, err = conn.DoRequest(ctx, nil, http.MethodPost, "/manifests/%s/registry/%s", params, nil, name, destination)
|
||||
} else {
|
||||
params.Set("image", name)
|
||||
params.Set("destination", destination)
|
||||
response, err = conn.DoRequest(ctx, nil, http.MethodPost, "/manifests/%s/push", params, nil, name)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -172,25 +206,37 @@ func Push(ctx context.Context, name, destination string, options *images.PushOpt
|
||||
return idr.ID, err
|
||||
}
|
||||
|
||||
// There is NO annotate endpoint. this binding could never work
|
||||
// Annotate updates the image configuration of a given manifest list
|
||||
//func Annotate(ctx context.Context, name, digest string, options image.ManifestAnnotateOpts) (string, error) {
|
||||
// var idr handlers.IDResponse
|
||||
// conn, err := bindings.GetClient(ctx)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// params := url.Values{}
|
||||
// params.Set("digest", digest)
|
||||
// optionsString, err := jsoniter.MarshalToString(options)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// stringReader := strings.NewReader(optionsString)
|
||||
// response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/manifests/%s/annotate", params, name)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// defer response.Body.Close()
|
||||
// return idr.ID, response.Process(&idr)
|
||||
//}
|
||||
// Modify modifies the given manifest list using options and the optional list of images
|
||||
func Modify(ctx context.Context, name string, images []string, options *ModifyOptions) (string, error) {
|
||||
if options == nil || *options.Operation == "" {
|
||||
return "", errors.New(`the field ModifyOptions.Operation must be set to either "update" or "remove"`)
|
||||
}
|
||||
options.WithImages(images)
|
||||
|
||||
conn, err := bindings.GetClient(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
opts, err := jsoniter.MarshalToString(options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
reader := strings.NewReader(opts)
|
||||
|
||||
response, err := conn.DoRequest(ctx, reader, http.MethodPut, "/manifests/%s", nil, nil, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var idr handlers.IDResponse
|
||||
return idr.ID, response.Process(&idr)
|
||||
}
|
||||
|
||||
// Annotate modifies the given manifest list using options and the optional list of images
|
||||
//
|
||||
// As of 4.0.0
|
||||
func Annotate(ctx context.Context, name string, images []string, options *ModifyOptions) (string, error) {
|
||||
options.WithOperation("annotate")
|
||||
return Modify(ctx, name, images, options)
|
||||
}
|
||||
@@ -18,7 +18,7 @@ type ExistsOptions struct {
|
||||
}
|
||||
|
||||
//go:generate go run ../generator/generator.go AddOptions
|
||||
// AddOptions are optional options for adding manifests
|
||||
// AddOptions are optional options for adding manifest lists
|
||||
type AddOptions struct {
|
||||
All *bool
|
||||
Annotation map[string]string
|
||||
@@ -31,6 +31,24 @@ type AddOptions struct {
|
||||
}
|
||||
|
||||
//go:generate go run ../generator/generator.go RemoveOptions
|
||||
// RemoveOptions are optional options for removing manifests
|
||||
// RemoveOptions are optional options for removing manifest lists
|
||||
type RemoveOptions struct {
|
||||
}
|
||||
|
||||
//go:generate go run ../generator/generator.go ModifyOptions
|
||||
// ModifyOptions are optional options for modifying manifest lists
|
||||
type ModifyOptions struct {
|
||||
// Operation values are "update", "remove" and "annotate". This allows the service to
|
||||
// efficiently perform each update on a manifest list.
|
||||
Operation *string
|
||||
All *bool // All when true, operate on all images in a manifest list that may be included in Images
|
||||
Annotations map[string]string // Annotations to add to manifest list
|
||||
Arch *string // Arch overrides the architecture for the image
|
||||
Features []string // Feature list for the image
|
||||
Images []string // Images is an optional list of images to add/remove to/from manifest list depending on operation
|
||||
OS *string // OS overrides the operating system for the image
|
||||
OSFeatures []string // OS features for the image
|
||||
OSVersion *string // OSVersion overrides the operating system for the image
|
||||
Variant *string // Variant overrides the operating system variant for the image
|
||||
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
package manifests
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/containers/podman/v3/pkg/bindings/internal/util"
|
||||
)
|
||||
|
||||
// Changed returns true if named field has been set
|
||||
func (o *ModifyOptions) Changed(fieldName string) bool {
|
||||
return util.Changed(o, fieldName)
|
||||
}
|
||||
|
||||
// ToParams formats struct fields to be passed to API service
|
||||
func (o *ModifyOptions) ToParams() (url.Values, error) {
|
||||
return util.ToParams(o)
|
||||
}
|
||||
|
||||
// WithOperation set field Operation to given value
|
||||
func (o *ModifyOptions) WithOperation(value string) *ModifyOptions {
|
||||
o.Operation = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetOperation returns value of field Operation
|
||||
func (o *ModifyOptions) GetOperation() string {
|
||||
if o.Operation == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.Operation
|
||||
}
|
||||
|
||||
// WithAll set all when true, operate on all images in a manifest list that may be included in Images
|
||||
func (o *ModifyOptions) WithAll(value bool) *ModifyOptions {
|
||||
o.All = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetAll returns value of all when true, operate on all images in a manifest list that may be included in Images
|
||||
func (o *ModifyOptions) GetAll() bool {
|
||||
if o.All == nil {
|
||||
var z bool
|
||||
return z
|
||||
}
|
||||
return *o.All
|
||||
}
|
||||
|
||||
// WithAnnotations set annotations to add to manifest list
|
||||
func (o *ModifyOptions) WithAnnotations(value map[string]string) *ModifyOptions {
|
||||
o.Annotations = value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetAnnotations returns value of annotations to add to manifest list
|
||||
func (o *ModifyOptions) GetAnnotations() map[string]string {
|
||||
if o.Annotations == nil {
|
||||
var z map[string]string
|
||||
return z
|
||||
}
|
||||
return o.Annotations
|
||||
}
|
||||
|
||||
// WithArch set arch overrides the architecture for the image
|
||||
func (o *ModifyOptions) WithArch(value string) *ModifyOptions {
|
||||
o.Arch = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetArch returns value of arch overrides the architecture for the image
|
||||
func (o *ModifyOptions) GetArch() string {
|
||||
if o.Arch == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.Arch
|
||||
}
|
||||
|
||||
// WithFeatures set feature list for the image
|
||||
func (o *ModifyOptions) WithFeatures(value []string) *ModifyOptions {
|
||||
o.Features = value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetFeatures returns value of feature list for the image
|
||||
func (o *ModifyOptions) GetFeatures() []string {
|
||||
if o.Features == nil {
|
||||
var z []string
|
||||
return z
|
||||
}
|
||||
return o.Features
|
||||
}
|
||||
|
||||
// WithImages set images is an optional list of images to add/remove to/from manifest list depending on operation
|
||||
func (o *ModifyOptions) WithImages(value []string) *ModifyOptions {
|
||||
o.Images = value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetImages returns value of images is an optional list of images to add/remove to/from manifest list depending on operation
|
||||
func (o *ModifyOptions) GetImages() []string {
|
||||
if o.Images == nil {
|
||||
var z []string
|
||||
return z
|
||||
}
|
||||
return o.Images
|
||||
}
|
||||
|
||||
// WithOS set oS overrides the operating system for the image
|
||||
func (o *ModifyOptions) WithOS(value string) *ModifyOptions {
|
||||
o.OS = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetOS returns value of oS overrides the operating system for the image
|
||||
func (o *ModifyOptions) GetOS() string {
|
||||
if o.OS == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.OS
|
||||
}
|
||||
|
||||
// WithOSFeatures set oS features for the image
|
||||
func (o *ModifyOptions) WithOSFeatures(value []string) *ModifyOptions {
|
||||
o.OSFeatures = value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetOSFeatures returns value of oS features for the image
|
||||
func (o *ModifyOptions) GetOSFeatures() []string {
|
||||
if o.OSFeatures == nil {
|
||||
var z []string
|
||||
return z
|
||||
}
|
||||
return o.OSFeatures
|
||||
}
|
||||
|
||||
// WithOSVersion set oSVersion overrides the operating system for the image
|
||||
func (o *ModifyOptions) WithOSVersion(value string) *ModifyOptions {
|
||||
o.OSVersion = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetOSVersion returns value of oSVersion overrides the operating system for the image
|
||||
func (o *ModifyOptions) GetOSVersion() string {
|
||||
if o.OSVersion == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.OSVersion
|
||||
}
|
||||
|
||||
// WithVariant set variant overrides the operating system variant for the image
|
||||
func (o *ModifyOptions) WithVariant(value string) *ModifyOptions {
|
||||
o.Variant = &value
|
||||
return o
|
||||
}
|
||||
|
||||
// GetVariant returns value of variant overrides the operating system variant for the image
|
||||
func (o *ModifyOptions) GetVariant() string {
|
||||
if o.Variant == nil {
|
||||
var z string
|
||||
return z
|
||||
}
|
||||
return *o.Variant
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/onsi/gomega/gexec"
|
||||
)
|
||||
|
||||
var _ = Describe("Podman containers ", func() {
|
||||
var _ = Describe("podman manifest", func() {
|
||||
var (
|
||||
bt *bindingTest
|
||||
s *gexec.Session
|
||||
@@ -24,7 +24,8 @@ var _ = Describe("Podman containers ", func() {
|
||||
s = bt.startAPIService()
|
||||
time.Sleep(1 * time.Second)
|
||||
err := bt.NewConnection()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
@@ -32,17 +33,19 @@ var _ = Describe("Podman containers ", func() {
|
||||
bt.cleanup()
|
||||
})
|
||||
|
||||
It("create manifest", func() {
|
||||
It("create", func() {
|
||||
// create manifest list without images
|
||||
id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
id, err := manifests.Create(bt.conn, "quay.io/libpod/foobar:latest", []string{}, nil)
|
||||
Expect(err).ToNot(HaveOccurred(), err)
|
||||
list, err := manifests.Inspect(bt.conn, id, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
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())
|
||||
_, err = manifests.Create(bt.conn, "quay.io/libpod/foobar:latest", nil, nil)
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
code, _ := bindings.CheckResponseCode(err)
|
||||
Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
|
||||
|
||||
@@ -50,96 +53,113 @@ var _ = Describe("Podman containers ", func() {
|
||||
Expect(len(errs)).To(BeZero())
|
||||
|
||||
// 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())
|
||||
id, err = manifests.Create(bt.conn, "quay.io/libpod/foobar:latest", []string{alpine.name}, nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
list, err = manifests.Inspect(bt.conn, id, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(len(list.Manifests)).To(BeNumerically("==", 1))
|
||||
})
|
||||
|
||||
It("inspect bogus manifest", func() {
|
||||
It("inspect", func() {
|
||||
_, err := manifests.Inspect(bt.conn, "larry", nil)
|
||||
Expect(err).ToNot(BeNil())
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
code, _ := bindings.CheckResponseCode(err)
|
||||
Expect(code).To(BeNumerically("==", http.StatusNotFound))
|
||||
})
|
||||
|
||||
It("add manifest", func() {
|
||||
It("add", func() {
|
||||
// add to bogus should 404
|
||||
_, err := manifests.Add(bt.conn, "foobar", nil)
|
||||
Expect(err).ToNot(BeNil())
|
||||
code, _ := bindings.CheckResponseCode(err)
|
||||
Expect(code).To(BeNumerically("==", http.StatusNotFound))
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
code, _ := bindings.CheckResponseCode(err)
|
||||
Expect(code).To(BeNumerically("==", http.StatusNotFound), err.Error())
|
||||
|
||||
id, err := manifests.Create(bt.conn, "quay.io/libpod/foobar:latest", []string{}, nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil)
|
||||
Expect(err).To(BeNil())
|
||||
options := new(manifests.AddOptions).WithImages([]string{alpine.name})
|
||||
_, err = manifests.Add(bt.conn, id, options)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
list, err := manifests.Inspect(bt.conn, id, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(len(list.Manifests)).To(BeNumerically("==", 1))
|
||||
|
||||
// add bogus name to existing list should fail
|
||||
options.WithImages([]string{"larry"})
|
||||
_, err = manifests.Add(bt.conn, id, options)
|
||||
Expect(err).ToNot(BeNil())
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
code, _ = bindings.CheckResponseCode(err)
|
||||
Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
|
||||
})
|
||||
|
||||
It("remove manifest", func() {
|
||||
It("remove digest", func() {
|
||||
// removal on bogus manifest list should be 404
|
||||
_, err := manifests.Remove(bt.conn, "larry", "1234", nil)
|
||||
Expect(err).ToNot(BeNil())
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
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())
|
||||
id, err := manifests.Create(bt.conn, "quay.io/libpod/foobar:latest", []string{alpine.name}, nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
data, err := manifests.Inspect(bt.conn, id, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
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", nil)
|
||||
Expect(err).ToNot(BeNil())
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
code, _ = bindings.CheckResponseCode(err)
|
||||
Expect(code).To(BeNumerically("==", http.StatusBadRequest))
|
||||
|
||||
digest := data.Manifests[0].Digest.String()
|
||||
_, err = manifests.Remove(bt.conn, id, digest, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// removal on good manifest with good digest should work
|
||||
data, err = manifests.Inspect(bt.conn, id, nil)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(len(data.Manifests)).To(BeZero())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(data.Manifests).Should(BeEmpty())
|
||||
})
|
||||
|
||||
// There is NO annotate endpoint, this could never work.:w
|
||||
It("annotate", func() {
|
||||
id, err := manifests.Create(bt.conn, "quay.io/libpod/foobar:latest", []string{}, nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
//It("annotate manifest", func() {
|
||||
// id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil)
|
||||
// Expect(err).To(BeNil())
|
||||
// opts := image.ManifestAddOpts{Images: []string{"docker.io/library/alpine:latest"}}
|
||||
//
|
||||
// _, err = manifests.Add(bt.conn, id, opts)
|
||||
// Expect(err).To(BeNil())
|
||||
// data, err := manifests.Inspect(bt.conn, id)
|
||||
// Expect(err).To(BeNil())
|
||||
// Expect(len(data.Manifests)).To(BeNumerically("==", 1))
|
||||
// digest := data.Manifests[0].Digest.String()
|
||||
// annoOpts := image.ManifestAnnotateOpts{OS: "foo"}
|
||||
// _, err = manifests.Annotate(bt.conn, id, digest, annoOpts)
|
||||
// Expect(err).To(BeNil())
|
||||
// list, err := manifests.Inspect(bt.conn, id)
|
||||
// Expect(err).To(BeNil())
|
||||
// Expect(len(list.Manifests)).To(BeNumerically("==", 1))
|
||||
// Expect(list.Manifests[0].Platform.OS).To(Equal("foo"))
|
||||
//})
|
||||
opts := manifests.AddOptions{Images: []string{"quay.io/libpod/alpine:latest"}}
|
||||
|
||||
_, err = manifests.Add(bt.conn, id, &opts)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
data, err := manifests.Inspect(bt.conn, id, nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(len(data.Manifests)).To(BeNumerically("==", 1))
|
||||
|
||||
digest := data.Manifests[0].Digest.String()
|
||||
annoOpts := new(manifests.ModifyOptions).WithOS("foo")
|
||||
_, err = manifests.Annotate(bt.conn, id, []string{digest}, annoOpts)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
list, err := manifests.Inspect(bt.conn, id, nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(len(list.Manifests)).To(BeNumerically("==", 1))
|
||||
Expect(list.Manifests[0].Platform.OS).To(Equal("foo"))
|
||||
})
|
||||
|
||||
It("push manifest", func() {
|
||||
Skip("TODO")
|
||||
Skip("TODO: implement test for manifest push to registry")
|
||||
})
|
||||
})
|
||||
@@ -31,12 +31,12 @@ type ImageEngine interface {
|
||||
Tree(ctx context.Context, nameOrID string, options ImageTreeOptions) (*ImageTreeReport, error)
|
||||
Unmount(ctx context.Context, images []string, options ImageUnmountOptions) ([]*ImageUnmountReport, error)
|
||||
Untag(ctx context.Context, nameOrID string, tags []string, options ImageUntagOptions) error
|
||||
ManifestCreate(ctx context.Context, names, images []string, opts ManifestCreateOptions) (string, error)
|
||||
ManifestCreate(ctx context.Context, name string, images []string, opts ManifestCreateOptions) (string, error)
|
||||
ManifestExists(ctx context.Context, name string) (*BoolReport, error)
|
||||
ManifestInspect(ctx context.Context, name string) ([]byte, error)
|
||||
ManifestAdd(ctx context.Context, opts ManifestAddOptions) (string, error)
|
||||
ManifestAnnotate(ctx context.Context, names []string, opts ManifestAnnotateOptions) (string, error)
|
||||
ManifestRemove(ctx context.Context, names []string) (string, error)
|
||||
ManifestAdd(ctx context.Context, listName string, imageNames []string, opts ManifestAddOptions) (string, error)
|
||||
ManifestAnnotate(ctx context.Context, names, image string, opts ManifestAnnotateOptions) (string, error)
|
||||
ManifestRemoveDigest(ctx context.Context, names, image string) (string, error)
|
||||
ManifestRm(ctx context.Context, names []string) (*ImageRemoveReport, []error)
|
||||
ManifestPush(ctx context.Context, name, destination string, imagePushOpts ImagePushOptions) (string, error)
|
||||
Sign(ctx context.Context, names []string, options SignOptions) (*SignReport, error)
|
||||
|
||||
@@ -94,7 +94,7 @@ type ImageRemoveOptions struct {
|
||||
LookupManifest bool
|
||||
}
|
||||
|
||||
// ImageRemoveResponse is the response for removing one or more image(s) from storage
|
||||
// ImageRemoveReport is the response for removing one or more image(s) from storage
|
||||
// and images what was untagged vs actually removed.
|
||||
type ImageRemoveReport struct {
|
||||
// Deleted images.
|
||||
|
||||
@@ -2,35 +2,79 @@ package entities
|
||||
|
||||
import "github.com/containers/image/v5/types"
|
||||
|
||||
// TODO: add comments to *all* types and fields.
|
||||
|
||||
// ManifestCreateOptions provides model for creating manifest
|
||||
type ManifestCreateOptions struct {
|
||||
All bool `schema:"all"`
|
||||
}
|
||||
|
||||
// swagger:model ManifestAddOpts
|
||||
// ManifestAddOptions provides model for adding digests to manifest list
|
||||
//
|
||||
// swagger:model
|
||||
type ManifestAddOptions struct {
|
||||
All bool `json:"all" schema:"all"`
|
||||
Annotation []string `json:"annotation" schema:"annotation"`
|
||||
Arch string `json:"arch" schema:"arch"`
|
||||
Authfile string `json:"-" schema:"-"`
|
||||
CertDir string `json:"-" schema:"-"`
|
||||
Features []string `json:"features" schema:"features"`
|
||||
Images []string `json:"images" schema:"images"`
|
||||
OS string `json:"os" schema:"os"`
|
||||
OSVersion string `json:"os_version" schema:"os_version"`
|
||||
Password string `json:"-" schema:"-"`
|
||||
ManifestAnnotateOptions
|
||||
// True when operating on a list to include all images
|
||||
All bool `json:"all" schema:"all"`
|
||||
// authfile to use when pushing manifest list
|
||||
Authfile string `json:"-" schema:"-"`
|
||||
// Home directory for certificates when pushing a manifest list
|
||||
CertDir string `json:"-" schema:"-"`
|
||||
// Password to authenticate to registry when pushing manifest list
|
||||
Password string `json:"-" schema:"-"`
|
||||
// Should TLS registry certificate be verified?
|
||||
SkipTLSVerify types.OptionalBool `json:"-" schema:"-"`
|
||||
Username string `json:"-" schema:"-"`
|
||||
Variant string `json:"variant" schema:"variant"`
|
||||
// Username to authenticate to registry when pushing manifest list
|
||||
Username string `json:"-" schema:"-"`
|
||||
// Images is an optional list of images to add to manifest list
|
||||
Images []string `json:"images" schema:"images"`
|
||||
}
|
||||
|
||||
// ManifestAnnotateOptions provides model for annotating manifest list
|
||||
type ManifestAnnotateOptions struct {
|
||||
Annotation []string `json:"annotation"`
|
||||
Arch string `json:"arch" schema:"arch"`
|
||||
Features []string `json:"features" schema:"features"`
|
||||
OS string `json:"os" schema:"os"`
|
||||
// Annotation to add to manifest list
|
||||
Annotation []string `json:"annotation" schema:"annotation"`
|
||||
// Arch overrides the architecture for the image
|
||||
Arch string `json:"arch" schema:"arch"`
|
||||
// Feature list for the image
|
||||
Features []string `json:"features" schema:"features"`
|
||||
// OS overrides the operating system for the image
|
||||
OS string `json:"os" schema:"os"`
|
||||
// OS features for the image
|
||||
OSFeatures []string `json:"os_features" schema:"os_features"`
|
||||
OSVersion string `json:"os_version" schema:"os_version"`
|
||||
Variant string `json:"variant" schema:"variant"`
|
||||
// OSVersion overrides the operating system for the image
|
||||
OSVersion string `json:"os_version" schema:"os_version"`
|
||||
// Variant for the image
|
||||
Variant string `json:"variant" schema:"variant"`
|
||||
}
|
||||
|
||||
// ManifestModifyOptions provides the model for mutating a manifest
|
||||
//
|
||||
// swagger 2.0 does not support oneOf for schema validation.
|
||||
//
|
||||
// Operation "update" uses all fields.
|
||||
// Operation "remove" uses fields: Operation and Images
|
||||
// Operation "annotate" uses fields: Operation and Annotations
|
||||
//
|
||||
// swagger:model
|
||||
type ManifestModifyOptions struct {
|
||||
Operation string `json:"operation" schema:"operation"` // Valid values: update, remove, annotate
|
||||
ManifestAddOptions
|
||||
ManifestRemoveOptions
|
||||
}
|
||||
|
||||
// ManifestRemoveOptions provides the model for removing digests from a manifest
|
||||
//
|
||||
// swagger:model
|
||||
type ManifestRemoveOptions struct {
|
||||
}
|
||||
|
||||
// ManifestModifyReport provides the model for removed digests and changed manifest
|
||||
//
|
||||
// swagger:model
|
||||
type ManifestModifyReport struct {
|
||||
// Manifest List ID
|
||||
ID string `json:"Id"`
|
||||
// Images to removed from manifest list, otherwise not provided.
|
||||
Images []string `json:"images,omitempty" schema:"images"`
|
||||
// Errors associated with operation
|
||||
Errors []error `json:"errors,omitempty"`
|
||||
}
|
||||
@@ -22,13 +22,10 @@ import (
|
||||
)
|
||||
|
||||
// ManifestCreate implements logic for creating manifest lists via ImageEngine
|
||||
func (ir *ImageEngine) ManifestCreate(ctx context.Context, names []string, images []string, opts entities.ManifestCreateOptions) (string, error) {
|
||||
// FIXME: change the interface of manifest create `names []string` ->
|
||||
// `name string`.
|
||||
if len(names) == 0 {
|
||||
func (ir *ImageEngine) ManifestCreate(ctx context.Context, name string, images []string, opts entities.ManifestCreateOptions) (string, error) {
|
||||
if len(name) == 0 {
|
||||
return "", errors.New("no name specified for creating a manifest list")
|
||||
}
|
||||
name := names[0]
|
||||
|
||||
manifestList, err := ir.Libpod.LibimageRuntime().CreateManifestList(name)
|
||||
if err != nil {
|
||||
@@ -175,18 +172,12 @@ func (ir *ImageEngine) remoteManifestInspect(ctx context.Context, name string) (
|
||||
}
|
||||
|
||||
// ManifestAdd adds images to the manifest list
|
||||
func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAddOptions) (string, error) {
|
||||
// FIXME: the name options below are *mandatory* arguments and should
|
||||
// be reflected as such in the signature.
|
||||
|
||||
if len(opts.Images) < 2 {
|
||||
return "", errors.New("manifest add requires two images")
|
||||
func (ir *ImageEngine) ManifestAdd(ctx context.Context, name string, images []string, opts entities.ManifestAddOptions) (string, error) {
|
||||
if len(images) < 1 {
|
||||
return "", errors.New("manifest add requires at least one image")
|
||||
}
|
||||
|
||||
imageName := opts.Images[0]
|
||||
listName := opts.Images[1]
|
||||
|
||||
manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(listName)
|
||||
manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -200,53 +191,46 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAd
|
||||
Password: opts.Password,
|
||||
}
|
||||
|
||||
instanceDigest, err := manifestList.Add(ctx, imageName, addOptions)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
annotateOptions := &libimage.ManifestListAnnotateOptions{
|
||||
Architecture: opts.Arch,
|
||||
Features: opts.Features,
|
||||
OS: opts.OS,
|
||||
OSVersion: opts.OSVersion,
|
||||
Variant: opts.Variant,
|
||||
}
|
||||
if len(opts.Annotation) != 0 {
|
||||
annotations := make(map[string]string)
|
||||
for _, annotationSpec := range opts.Annotation {
|
||||
spec := strings.SplitN(annotationSpec, "=", 2)
|
||||
if len(spec) != 2 {
|
||||
return "", errors.Errorf("no value given for annotation %q", spec[0])
|
||||
}
|
||||
annotations[spec[0]] = spec[1]
|
||||
for _, image := range images {
|
||||
instanceDigest, err := manifestList.Add(ctx, image, addOptions)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
annotateOptions.Annotations = annotations
|
||||
}
|
||||
|
||||
if err := manifestList.AnnotateInstance(instanceDigest, annotateOptions); err != nil {
|
||||
return "", err
|
||||
}
|
||||
annotateOptions := &libimage.ManifestListAnnotateOptions{
|
||||
Architecture: opts.Arch,
|
||||
Features: opts.Features,
|
||||
OS: opts.OS,
|
||||
OSVersion: opts.OSVersion,
|
||||
Variant: opts.Variant,
|
||||
}
|
||||
if len(opts.Annotation) != 0 {
|
||||
annotations := make(map[string]string)
|
||||
for _, annotationSpec := range opts.Annotation {
|
||||
spec := strings.SplitN(annotationSpec, "=", 2)
|
||||
if len(spec) != 2 {
|
||||
return "", errors.Errorf("no value given for annotation %q", spec[0])
|
||||
}
|
||||
annotations[spec[0]] = spec[1]
|
||||
}
|
||||
annotateOptions.Annotations = annotations
|
||||
}
|
||||
|
||||
if err := manifestList.AnnotateInstance(instanceDigest, annotateOptions); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return manifestList.ID(), nil
|
||||
}
|
||||
|
||||
// ManifestAnnotate updates an entry of the manifest list
|
||||
func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opts entities.ManifestAnnotateOptions) (string, error) {
|
||||
// FIXME: the `names` are *mandatory* arguments and should be
|
||||
// reflected as such in the signature.
|
||||
|
||||
if len(names) < 2 {
|
||||
return "", errors.New("manifest annotate requires two names")
|
||||
}
|
||||
|
||||
listName := names[0]
|
||||
instanceDigest, err := digest.Parse(names[1])
|
||||
func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, name, image string, opts entities.ManifestAnnotateOptions) (string, error) {
|
||||
instanceDigest, err := digest.Parse(image)
|
||||
if err != nil {
|
||||
return "", errors.Errorf(`invalid image digest "%s": %v`, names[1], err)
|
||||
return "", errors.Errorf(`invalid image digest "%s": %v`, image, err)
|
||||
}
|
||||
|
||||
manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(listName)
|
||||
manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -277,22 +261,14 @@ func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opt
|
||||
return manifestList.ID(), nil
|
||||
}
|
||||
|
||||
// ManifestRemove removes specified digest from the specified manifest list
|
||||
func (ir *ImageEngine) ManifestRemove(ctx context.Context, names []string) (string, error) {
|
||||
// FIXME: the `names` are *mandatory* arguments and should be
|
||||
// reflected as such in the signature.
|
||||
|
||||
if len(names) < 2 {
|
||||
return "", errors.New("manifest remove requires two names")
|
||||
}
|
||||
|
||||
listName := names[0]
|
||||
instanceDigest, err := digest.Parse(names[1])
|
||||
// ManifestRemoveDigest removes specified digest from the specified manifest list
|
||||
func (ir *ImageEngine) ManifestRemoveDigest(ctx context.Context, name, image string) (string, error) {
|
||||
instanceDigest, err := digest.Parse(image)
|
||||
if err != nil {
|
||||
return "", errors.Errorf(`invalid image digest "%s": %v`, names[1], err)
|
||||
return "", errors.Errorf(`invalid image digest "%s": %v`, image, err)
|
||||
}
|
||||
|
||||
manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(listName)
|
||||
manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func NewContainerEngine(facts *entities.PodmanConfig) (entities.ContainerEngine,
|
||||
return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
|
||||
}
|
||||
|
||||
// NewContainerEngine factory provides a libpod runtime for image-related operations
|
||||
// NewImageEngine factory provides a libpod runtime for image-related operations
|
||||
func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error) {
|
||||
switch facts.EngineMode {
|
||||
case entities.ABIMode:
|
||||
|
||||
@@ -7,16 +7,16 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/types"
|
||||
images "github.com/containers/podman/v3/pkg/bindings/images"
|
||||
"github.com/containers/podman/v3/pkg/bindings/images"
|
||||
"github.com/containers/podman/v3/pkg/bindings/manifests"
|
||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ManifestCreate implements manifest create via ImageEngine
|
||||
func (ir *ImageEngine) ManifestCreate(ctx context.Context, names, images []string, opts entities.ManifestCreateOptions) (string, error) {
|
||||
func (ir *ImageEngine) ManifestCreate(ctx context.Context, name string, images []string, opts entities.ManifestCreateOptions) (string, error) {
|
||||
options := new(manifests.CreateOptions).WithAll(opts.All)
|
||||
imageID, err := manifests.Create(ir.ClientCtx, names, images, options)
|
||||
imageID, err := manifests.Create(ir.ClientCtx, name, images, options)
|
||||
if err != nil {
|
||||
return imageID, errors.Wrapf(err, "error creating manifest")
|
||||
}
|
||||
@@ -33,7 +33,7 @@ func (ir *ImageEngine) ManifestExists(ctx context.Context, name string) (*entiti
|
||||
}
|
||||
|
||||
// ManifestInspect returns contents of manifest list with given name
|
||||
func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte, error) {
|
||||
func (ir *ImageEngine) ManifestInspect(_ context.Context, name string) ([]byte, error) {
|
||||
list, err := manifests.Inspect(ir.ClientCtx, name, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error getting content of manifest list or image %s", name)
|
||||
@@ -47,9 +47,9 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte
|
||||
}
|
||||
|
||||
// ManifestAdd adds images to the manifest list
|
||||
func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAddOptions) (string, error) {
|
||||
func (ir *ImageEngine) ManifestAdd(_ context.Context, name string, imageNames []string, opts entities.ManifestAddOptions) (string, error) {
|
||||
options := new(manifests.AddOptions).WithAll(opts.All).WithArch(opts.Arch).WithVariant(opts.Variant)
|
||||
options.WithFeatures(opts.Features).WithImages(opts.Images).WithOS(opts.OS).WithOSVersion(opts.OSVersion)
|
||||
options.WithFeatures(opts.Features).WithImages(imageNames).WithOS(opts.OS).WithOSVersion(opts.OSVersion)
|
||||
if len(opts.Annotation) != 0 {
|
||||
annotations := make(map[string]string)
|
||||
for _, annotationSpec := range opts.Annotation {
|
||||
@@ -62,25 +62,25 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAd
|
||||
options.WithAnnotation(annotations)
|
||||
}
|
||||
|
||||
listID, err := manifests.Add(ir.ClientCtx, opts.Images[1], options)
|
||||
id, err := manifests.Add(ir.ClientCtx, name, options)
|
||||
if err != nil {
|
||||
return listID, errors.Wrapf(err, "error adding to manifest list %s", opts.Images[1])
|
||||
return id, errors.Wrapf(err, "error adding to manifest list %s", name)
|
||||
}
|
||||
return listID, nil
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// ManifestAnnotate updates an entry of the manifest list
|
||||
func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opts entities.ManifestAnnotateOptions) (string, error) {
|
||||
func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, name, images string, opts entities.ManifestAnnotateOptions) (string, error) {
|
||||
return "", errors.New("not implemented")
|
||||
}
|
||||
|
||||
// ManifestRemove removes the digest from manifest list
|
||||
func (ir *ImageEngine) ManifestRemove(ctx context.Context, names []string) (string, error) {
|
||||
updatedListID, err := manifests.Remove(ir.ClientCtx, names[0], names[1], nil)
|
||||
// ManifestRemoveDigest removes the digest from manifest list
|
||||
func (ir *ImageEngine) ManifestRemoveDigest(ctx context.Context, name string, image string) (string, error) {
|
||||
updatedListID, err := manifests.Remove(ir.ClientCtx, name, image, nil)
|
||||
if err != nil {
|
||||
return updatedListID, errors.Wrapf(err, "error removing from manifest %s", names[0])
|
||||
return updatedListID, errors.Wrapf(err, "error removing from manifest %s", name)
|
||||
}
|
||||
return fmt.Sprintf("%s :%s\n", updatedListID, names[1]), nil
|
||||
return fmt.Sprintf("%s :%s\n", updatedListID, image), nil
|
||||
}
|
||||
|
||||
// ManifestRm removes the specified manifest list from storage
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# -*- sh -*-
|
||||
#
|
||||
# Tests for manifest list endpoints
|
||||
|
||||
t POST /v3.4.0/libpod/manifests/create?name=abc 200 \
|
||||
.Id~[0-9a-f]\\{64\\}
|
||||
id_abc=$(jq -r '.Id' <<<"$output")
|
||||
|
||||
t POST /v4.0.0/libpod/manifests/xyz 201 \
|
||||
.Id~[0-9a-f]\\{64\\}
|
||||
echo xyz $output
|
||||
id_xyz=$(jq -r '.Id' <<<"$output")
|
||||
|
||||
t GET /v3.4.0/libpod/manifests/$id_abc/exists 204
|
||||
t GET /v4.0.0/libpod/manifests/$id_xyz/exists 204
|
||||
|
||||
# /v3.x cannot delete a manifest list
|
||||
t DELETE /v4.0.0/libpod/manifests/$id_abc 200
|
||||
t DELETE /v4.0.0/libpod/manifests/$id_xyz 200
|
||||
@@ -364,7 +364,9 @@ function start_service() {
|
||||
echo $WORKDIR
|
||||
# Some tests use shortnames; force registry override to work around
|
||||
# docker.io throttling.
|
||||
env CONTAINERS_REGISTRIES_CONF=$TESTS_DIR/../registries.conf $PODMAN_BIN \
|
||||
# FIXME esm revisit pulling expected images re: shortnames caused tests to fail
|
||||
# env CONTAINERS_REGISTRIES_CONF=$TESTS_DIR/../registries.conf
|
||||
$PODMAN_BIN \
|
||||
--root $WORKDIR/server_root --syslog=true \
|
||||
system service \
|
||||
--time 15 \
|
||||
@@ -497,7 +499,7 @@ function wait_for_port() {
|
||||
############
|
||||
function podman() {
|
||||
echo "\$ $PODMAN_BIN $*" >>$WORKDIR/output.log
|
||||
env CONTAINERS_REGISTRIES_CONF=$TESTS_DIR/../registries.conf \
|
||||
# env CONTAINERS_REGISTRIES_CONF=$TESTS_DIR/../registries.conf \
|
||||
$PODMAN_BIN --root $WORKDIR/server_root "$@" >>$WORKDIR/output.log 2>&1
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ func (p *PodmanTestIntegration) StartRemoteService() {
|
||||
|
||||
args := []string{}
|
||||
if _, found := os.LookupEnv("DEBUG_SERVICE"); found {
|
||||
args = append(args, "--log-level", "debug")
|
||||
args = append(args, "--log-level", "trace")
|
||||
}
|
||||
remoteSocket := p.RemoteSocket
|
||||
args = append(args, "system", "service", "--time", "0", remoteSocket)
|
||||
@@ -151,7 +151,7 @@ func (p *PodmanTestIntegration) StopRemoteService() {
|
||||
}
|
||||
}
|
||||
|
||||
//MakeOptions assembles all the podman main options
|
||||
// MakeOptions assembles all the podman main options
|
||||
func getRemoteOptions(p *PodmanTestIntegration, args []string) []string {
|
||||
podmanOptions := strings.Split(fmt.Sprintf("--root %s --runroot %s --runtime %s --conmon %s --cni-config-dir %s --cgroup-manager %s",
|
||||
p.Root, p.RunRoot, p.OCIRuntime, p.ConmonBinary, p.CNIConfigDir, p.CgroupManager), " ")
|
||||
|
||||
+57
-46
@@ -44,19 +44,19 @@ var _ = Describe("Podman manifest", func() {
|
||||
processTestResult(f)
|
||||
|
||||
})
|
||||
It("podman manifest create", func() {
|
||||
It("create w/o image", func() {
|
||||
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
})
|
||||
|
||||
It("podman manifest create", func() {
|
||||
It("create w/ image", func() {
|
||||
session := podmanTest.Podman([]string{"manifest", "create", "foo", imageList})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
})
|
||||
|
||||
It("podman manifest inspect", func() {
|
||||
It("inspect", func() {
|
||||
session := podmanTest.Podman([]string{"manifest", "inspect", BB})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
@@ -71,29 +71,27 @@ var _ = Describe("Podman manifest", func() {
|
||||
Expect(session).Should(Exit(0))
|
||||
})
|
||||
|
||||
It("podman manifest add", func() {
|
||||
It("add w/ inspect", func() {
|
||||
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
session = podmanTest.Podman([]string{"manifest", "add", "--arch=arm64", "foo", imageListInstance})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
})
|
||||
id := strings.TrimSpace(string(session.Out.Contents()))
|
||||
|
||||
It("podman manifest add one", func() {
|
||||
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
|
||||
session = podmanTest.Podman([]string{"manifest", "inspect", id})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
session = podmanTest.Podman([]string{"manifest", "add", "--arch=arm64", "foo", imageListInstance})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
session = podmanTest.Podman([]string{"manifest", "inspect", "foo"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
Expect(session.OutputToString()).To(ContainSubstring(imageListARM64InstanceDigest))
|
||||
})
|
||||
|
||||
It("podman manifest tag", func() {
|
||||
It("tag", func() {
|
||||
session := podmanTest.Podman([]string{"manifest", "create", "foobar"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
@@ -112,7 +110,7 @@ var _ = Describe("Podman manifest", func() {
|
||||
Expect(session2.OutputToString()).To(Equal(session.OutputToString()))
|
||||
})
|
||||
|
||||
It("podman manifest add --all", func() {
|
||||
It(" add --all", func() {
|
||||
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
@@ -122,14 +120,17 @@ var _ = Describe("Podman manifest", func() {
|
||||
session = podmanTest.Podman([]string{"manifest", "inspect", "foo"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
Expect(session.OutputToString()).To(ContainSubstring(imageListAMD64InstanceDigest))
|
||||
Expect(session.OutputToString()).To(ContainSubstring(imageListARMInstanceDigest))
|
||||
Expect(session.OutputToString()).To(ContainSubstring(imageListARM64InstanceDigest))
|
||||
Expect(session.OutputToString()).To(ContainSubstring(imageListPPC64LEInstanceDigest))
|
||||
Expect(session.OutputToString()).To(ContainSubstring(imageListS390XInstanceDigest))
|
||||
Expect(session.OutputToString()).To(
|
||||
And(
|
||||
ContainSubstring(imageListAMD64InstanceDigest),
|
||||
ContainSubstring(imageListARMInstanceDigest),
|
||||
ContainSubstring(imageListARM64InstanceDigest),
|
||||
ContainSubstring(imageListPPC64LEInstanceDigest),
|
||||
ContainSubstring(imageListS390XInstanceDigest),
|
||||
))
|
||||
})
|
||||
|
||||
It("podman manifest add --os", func() {
|
||||
It("add --os", func() {
|
||||
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
@@ -142,7 +143,7 @@ var _ = Describe("Podman manifest", func() {
|
||||
Expect(session.OutputToString()).To(ContainSubstring(`"os": "bar"`))
|
||||
})
|
||||
|
||||
It("podman manifest annotate", func() {
|
||||
It("annotate", func() {
|
||||
SkipIfRemote("Not supporting annotate on remote connections")
|
||||
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
@@ -159,7 +160,7 @@ var _ = Describe("Podman manifest", func() {
|
||||
Expect(session.OutputToString()).To(ContainSubstring(`"architecture": "bar"`))
|
||||
})
|
||||
|
||||
It("podman manifest remove", func() {
|
||||
It("remove digest", func() {
|
||||
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
@@ -176,14 +177,18 @@ var _ = Describe("Podman manifest", func() {
|
||||
session = podmanTest.Podman([]string{"manifest", "inspect", "foo"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
Expect(session.OutputToString()).To(ContainSubstring(imageListAMD64InstanceDigest))
|
||||
Expect(session.OutputToString()).To(ContainSubstring(imageListARMInstanceDigest))
|
||||
Expect(session.OutputToString()).To(ContainSubstring(imageListPPC64LEInstanceDigest))
|
||||
Expect(session.OutputToString()).To(ContainSubstring(imageListS390XInstanceDigest))
|
||||
Expect(session.OutputToString()).To(Not(ContainSubstring(imageListARM64InstanceDigest)))
|
||||
Expect(session.OutputToString()).To(
|
||||
And(
|
||||
ContainSubstring(imageListAMD64InstanceDigest),
|
||||
ContainSubstring(imageListARMInstanceDigest),
|
||||
ContainSubstring(imageListPPC64LEInstanceDigest),
|
||||
ContainSubstring(imageListS390XInstanceDigest),
|
||||
Not(
|
||||
ContainSubstring(imageListARM64InstanceDigest)),
|
||||
))
|
||||
})
|
||||
|
||||
It("podman manifest remove not-found", func() {
|
||||
It("remove not-found", func() {
|
||||
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
@@ -199,7 +204,7 @@ var _ = Describe("Podman manifest", func() {
|
||||
Expect(session).Should(Exit(0))
|
||||
})
|
||||
|
||||
It("podman manifest push", func() {
|
||||
It("push --all", func() {
|
||||
SkipIfRemote("manifest push to dir not supported in remote mode")
|
||||
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
@@ -216,20 +221,24 @@ var _ = Describe("Podman manifest", func() {
|
||||
session = podmanTest.Podman([]string{"manifest", "push", "--all", "foo", "dir:" + dest})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
files, err := filepath.Glob(dest + string(os.PathSeparator) + "*")
|
||||
Expect(err).To(BeNil())
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
check := SystemExec("sha256sum", files)
|
||||
check.WaitWithDefaultTimeout()
|
||||
Expect(check).Should(Exit(0))
|
||||
prefix := "sha256:"
|
||||
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListAMD64InstanceDigest, prefix)))
|
||||
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListARMInstanceDigest, prefix)))
|
||||
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListPPC64LEInstanceDigest, prefix)))
|
||||
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListS390XInstanceDigest, prefix)))
|
||||
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListARM64InstanceDigest, prefix)))
|
||||
Expect(check.OutputToString()).To(
|
||||
And(
|
||||
ContainSubstring(strings.TrimPrefix(imageListAMD64InstanceDigest, prefix)),
|
||||
ContainSubstring(strings.TrimPrefix(imageListARMInstanceDigest, prefix)),
|
||||
ContainSubstring(strings.TrimPrefix(imageListPPC64LEInstanceDigest, prefix)),
|
||||
ContainSubstring(strings.TrimPrefix(imageListS390XInstanceDigest, prefix)),
|
||||
ContainSubstring(strings.TrimPrefix(imageListARM64InstanceDigest, prefix)),
|
||||
))
|
||||
})
|
||||
|
||||
It("podman push --all", func() {
|
||||
It("push", func() {
|
||||
SkipIfRemote("manifest push to dir not supported in remote mode")
|
||||
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
@@ -251,15 +260,19 @@ var _ = Describe("Podman manifest", func() {
|
||||
check := SystemExec("sha256sum", files)
|
||||
check.WaitWithDefaultTimeout()
|
||||
Expect(check).Should(Exit(0))
|
||||
|
||||
prefix := "sha256:"
|
||||
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListAMD64InstanceDigest, prefix)))
|
||||
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListARMInstanceDigest, prefix)))
|
||||
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListPPC64LEInstanceDigest, prefix)))
|
||||
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListS390XInstanceDigest, prefix)))
|
||||
Expect(check.OutputToString()).To(ContainSubstring(strings.TrimPrefix(imageListARM64InstanceDigest, prefix)))
|
||||
Expect(check.OutputToString()).To(
|
||||
And(
|
||||
ContainSubstring(strings.TrimPrefix(imageListAMD64InstanceDigest, prefix)),
|
||||
ContainSubstring(strings.TrimPrefix(imageListARMInstanceDigest, prefix)),
|
||||
ContainSubstring(strings.TrimPrefix(imageListPPC64LEInstanceDigest, prefix)),
|
||||
ContainSubstring(strings.TrimPrefix(imageListS390XInstanceDigest, prefix)),
|
||||
ContainSubstring(strings.TrimPrefix(imageListARM64InstanceDigest, prefix)),
|
||||
))
|
||||
})
|
||||
|
||||
It("podman manifest push --rm", func() {
|
||||
It("push --rm", func() {
|
||||
SkipIfRemote("remote does not support --rm")
|
||||
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
@@ -285,7 +298,7 @@ var _ = Describe("Podman manifest", func() {
|
||||
Expect(session).To(ExitWithError())
|
||||
})
|
||||
|
||||
It("podman manifest exists", func() {
|
||||
It("exists", func() {
|
||||
manifestList := "manifest-list"
|
||||
session := podmanTest.Podman([]string{"manifest", "create", manifestList})
|
||||
session.WaitWithDefaultTimeout()
|
||||
@@ -300,7 +313,7 @@ var _ = Describe("Podman manifest", func() {
|
||||
Expect(session).Should(Exit(1))
|
||||
})
|
||||
|
||||
It("podman manifest rm should not remove referenced images", func() {
|
||||
It("rm should not remove referenced images", func() {
|
||||
manifestList := "manifestlist"
|
||||
imageName := "quay.io/libpod/busybox"
|
||||
|
||||
@@ -320,11 +333,9 @@ var _ = Describe("Podman manifest", func() {
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
//image should still show up
|
||||
session = podmanTest.Podman([]string{"images"})
|
||||
// image should still show up
|
||||
session = podmanTest.Podman([]string{"image", "exists", imageName})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.OutputToString()).To(ContainSubstring(imageName))
|
||||
Expect(session).Should(Exit(0))
|
||||
})
|
||||
|
||||
})
|
||||
Reference in new issue
Block a user