aboutsummaryrefslogtreecommitdiff
path: root/pkg/api/handlers
diff options
context:
space:
mode:
authorJhon Honce <jhonce@redhat.com>2021-12-06 16:45:58 -0700
committerJhon Honce <jhonce@redhat.com>2022-01-14 16:13:35 -0700
commit8a7e70919f4bab0757523ae97c170396cb13c83d (patch)
tree0ec2b5aa4e3c1e6574e606a0e7db3638fdeda578 /pkg/api/handlers
parentec2b213ab611cb197e86c45d03fb10af667ad95c (diff)
downloadpodman-8a7e70919f4bab0757523ae97c170396cb13c83d.tar.gz
podman-8a7e70919f4bab0757523ae97c170396cb13c83d.tar.bz2
podman-8a7e70919f4bab0757523ae97c170396cb13c83d.zip
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>
Diffstat (limited to 'pkg/api/handlers')
-rw-r--r--pkg/api/handlers/libpod/manifests.go265
-rw-r--r--pkg/api/handlers/utils/handler.go4
2 files changed, 244 insertions, 25 deletions
diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go
index eb0b6827f..ef0839d1f 100644
--- a/pkg/api/handlers/libpod/manifests.go
+++ b/pkg/api/handlers/libpod/manifests.go
@@ -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
+ }
+
+ 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, http.StatusOK, handlers.IDResponse{ID: manID})
+
+ 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)
+}
diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go
index 96b7a957c..ee83755a1 100644
--- a/pkg/api/handlers/utils/handler.go
+++ b/pkg/api/handlers/utils/handler.go
@@ -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")
}