diff options
Diffstat (limited to 'pkg/api/handlers')
-rw-r--r-- | pkg/api/handlers/compat/images.go | 54 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/images.go | 43 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/manifests.go | 265 | ||||
-rw-r--r-- | pkg/api/handlers/types.go | 40 | ||||
-rw-r--r-- | pkg/api/handlers/utils/handler.go | 4 | ||||
-rw-r--r-- | pkg/api/handlers/utils/images.go | 40 |
6 files changed, 285 insertions, 161 deletions
diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index c1cc99da4..f08a7ee41 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -11,6 +11,7 @@ import ( "github.com/containers/buildah" "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" + "github.com/containers/common/pkg/filters" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" @@ -404,25 +405,52 @@ func GetImage(w http.ResponseWriter, r *http.Request) { } func GetImages(w http.ResponseWriter, r *http.Request) { - images, err := utils.GetImages(w, r) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images")) + decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) + query := struct { + All bool + Digests bool + Filter string // Docker 1.24 compatibility + }{ + // This is where you can override the golang default value for one of fields + } + + 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 + } + if _, found := r.URL.Query()["digests"]; found && query.Digests { + utils.UnSupportedParameter("digests") return } - summaries := make([]*entities.ImageSummary, 0, len(images)) - for _, img := range images { - // If the image is a manifest list, extract as much as we can. - if isML, _ := img.IsManifestList(r.Context()); isML { - continue + filterList, err := filters.FiltersFromRequest(r) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) + return + } + if !utils.IsLibpodRequest(r) { + if len(query.Filter) > 0 { // Docker 1.24 compatibility + filterList = append(filterList, "reference="+query.Filter) } + filterList = append(filterList, "manifest=false") + } - is, err := handlers.ImageToImageSummary(img) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed transform image summaries")) - return + imageEngine := abi.ImageEngine{Libpod: runtime} + + listOptions := entities.ImageListOptions{All: query.All, Filter: filterList} + summaries, err := imageEngine.List(r.Context(), listOptions) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) + return + } + + if !utils.IsLibpodRequest(r) { + // docker adds sha256: in front of the ID + for _, s := range summaries { + s.ID = "sha256:" + s.ID } - summaries = append(summaries, is) } utils.WriteResponse(w, http.StatusOK, summaries) } diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 6e23845f0..d3dbf3023 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -12,7 +12,6 @@ import ( "github.com/containers/buildah" "github.com/containers/common/libimage" - "github.com/containers/common/pkg/filters" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" @@ -103,48 +102,6 @@ func GetImage(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, inspect) } -func GetImages(w http.ResponseWriter, r *http.Request) { - decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) - runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) - query := struct { - All bool - Digests bool - Filter string // Docker 1.24 compatibility - }{ - // This is where you can override the golang default value for one of fields - } - - 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 - } - if _, found := r.URL.Query()["digests"]; found && query.Digests { - utils.UnSupportedParameter("digests") - return - } - - filterList, err := filters.FiltersFromRequest(r) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - if !utils.IsLibpodRequest(r) && len(query.Filter) > 0 { // Docker 1.24 compatibility - filterList = append(filterList, "reference="+query.Filter) - } - - imageEngine := abi.ImageEngine{Libpod: runtime} - - listOptions := entities.ImageListOptions{All: query.All, Filter: filterList} - summaries, err := imageEngine.List(r.Context(), listOptions) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - - utils.WriteResponse(w, http.StatusOK, summaries) -} - func PruneImages(w http.ResponseWriter, r *http.Request) { var ( err error 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/types.go b/pkg/api/handlers/types.go index 1733476cc..d3a592bdf 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -184,46 +184,6 @@ type ExecStartConfig struct { Width uint16 `json:"w"` } -func ImageToImageSummary(l *libimage.Image) (*entities.ImageSummary, error) { - options := &libimage.InspectOptions{WithParent: true, WithSize: true} - imageData, err := l.Inspect(context.TODO(), options) - if err != nil { - return nil, errors.Wrapf(err, "failed to obtain summary for image %s", l.ID()) - } - - containers, err := l.Containers() - if err != nil { - return nil, errors.Wrapf(err, "failed to obtain Containers for image %s", l.ID()) - } - containerCount := len(containers) - - isDangling, err := l.IsDangling(context.TODO()) - if err != nil { - return nil, errors.Wrapf(err, "failed to check if image %s is dangling", l.ID()) - } - - is := entities.ImageSummary{ - // docker adds sha256: in front of the ID - ID: "sha256:" + l.ID(), - ParentId: imageData.Parent, - RepoTags: imageData.RepoTags, - RepoDigests: imageData.RepoDigests, - Created: l.Created().Unix(), - Size: imageData.Size, - SharedSize: 0, - VirtualSize: imageData.VirtualSize, - Labels: imageData.Labels, - Containers: containerCount, - ReadOnly: l.IsReadOnly(), - Dangling: isDangling, - Names: l.Names(), - Digest: string(imageData.Digest), - ConfigDigest: "", // TODO: libpod/image didn't set it but libimage should - History: imageData.NamesHistory, - } - return &is, nil -} - func ImageDataToImageInspect(ctx context.Context, l *libimage.Image) (*ImageInspect, error) { options := &libimage.InspectOptions{WithParent: true, WithSize: true} info, err := l.Inspect(context.Background(), options) 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") } diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index 3f3f48193..639de9915 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/containers/common/libimage" - "github.com/containers/common/pkg/filters" "github.com/containers/image/v5/docker" storageTransport "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports/alltransports" @@ -16,7 +15,6 @@ import ( "github.com/containers/podman/v3/pkg/util" "github.com/containers/storage" "github.com/docker/distribution/reference" - "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -91,44 +89,6 @@ func ParseStorageReference(name string) (types.ImageReference, error) { return imageRef, nil } -// GetImages is a common function used to get images for libpod and other compatibility -// mechanisms -func GetImages(w http.ResponseWriter, r *http.Request) ([]*libimage.Image, error) { - decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) - runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) - query := struct { - All bool - Digests bool - Filter string // Docker 1.24 compatibility - }{ - // This is where you can override the golang default value for one of fields - } - - if err := decoder.Decode(&query, r.URL.Query()); err != nil { - return nil, err - } - if _, found := r.URL.Query()["digests"]; found && query.Digests { - UnSupportedParameter("digests") - } - - filterList, err := filters.FiltersFromRequest(r) - if err != nil { - return nil, err - } - if !IsLibpodRequest(r) && len(query.Filter) > 0 { // Docker 1.24 compatibility - filterList = append(filterList, "reference="+query.Filter) - } - - if !query.All { - // Filter intermediate images unless we want to list *all*. - // NOTE: it's a positive filter, so `intermediate=false` means - // to display non-intermediate images. - filterList = append(filterList, "intermediate=false") - } - listOptions := &libimage.ListImagesOptions{Filters: filterList} - return runtime.LibimageRuntime().ListImages(r.Context(), nil, listOptions) -} - func GetImage(r *http.Request, name string) (*libimage.Image, error) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) image, _, err := runtime.LibimageRuntime().LookupImage(name, nil) |