mirror of
https://github.com/containers/podman.git
synced 2025-06-11 10:25:41 +08:00

* Add converter for URL query parameters of type map[string][]string * Add converter for URL query parameters of type time.Time * Added function to allocate and configure schema.Decoder for API use * Updated API handlers to leverage new converters, and correct handler code for filter type An encoding example for a client using filters: v := map[string][]string{ "dangling": {"true"}, } payload, err := jsoniter.MarshalToString(v) if err != nil { panic(err) } payload = "?filters=" + url.QueryEscape(payload) Signed-off-by: Jhon Honce <jhonce@redhat.com>
363 lines
12 KiB
Go
363 lines
12 KiB
Go
package generic
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"io/ioutil"
|
||
"net/http"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"github.com/containers/buildah"
|
||
"github.com/containers/image/v5/manifest"
|
||
"github.com/containers/libpod/libpod"
|
||
image2 "github.com/containers/libpod/libpod/image"
|
||
"github.com/containers/libpod/pkg/api/handlers"
|
||
"github.com/containers/libpod/pkg/api/handlers/utils"
|
||
"github.com/containers/libpod/pkg/util"
|
||
"github.com/containers/storage"
|
||
"github.com/docker/docker/api/types"
|
||
"github.com/gorilla/mux"
|
||
"github.com/gorilla/schema"
|
||
"github.com/pkg/errors"
|
||
"github.com/sirupsen/logrus"
|
||
)
|
||
|
||
func ExportImage(w http.ResponseWriter, r *http.Request) {
|
||
// 200 ok
|
||
// 500 server
|
||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||
|
||
name := mux.Vars(r)["name"]
|
||
newImage, err := runtime.ImageRuntime().NewFromLocal(name)
|
||
if err != nil {
|
||
utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name))
|
||
return
|
||
}
|
||
tmpfile, err := ioutil.TempFile("", "api.tar")
|
||
if err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
|
||
return
|
||
}
|
||
if err := tmpfile.Close(); err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
|
||
return
|
||
}
|
||
if err := newImage.Save(r.Context(), name, "docker-archive", tmpfile.Name(), []string{}, false, false); err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to save image"))
|
||
return
|
||
}
|
||
rdr, err := os.Open(tmpfile.Name())
|
||
if err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile"))
|
||
return
|
||
}
|
||
defer rdr.Close()
|
||
defer os.Remove(tmpfile.Name())
|
||
utils.WriteResponse(w, http.StatusOK, rdr)
|
||
}
|
||
|
||
func PruneImages(w http.ResponseWriter, r *http.Request) {
|
||
// 200 no error
|
||
// 500 internal
|
||
var (
|
||
dangling = true
|
||
err error
|
||
)
|
||
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||
|
||
query := struct {
|
||
Filters map[string][]string `schema:"filters"`
|
||
}{
|
||
// 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, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
|
||
return
|
||
}
|
||
|
||
// until ts is not supported on podman prune
|
||
if v, found := query.Filters["until"]; found {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "until=%s is not supported yet", v))
|
||
return
|
||
}
|
||
// labels are not supported on podman prune
|
||
if _, found := query.Filters["since"]; found {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "labelis not supported yet"))
|
||
return
|
||
}
|
||
|
||
if v, found := query.Filters["dangling"]; found {
|
||
dangling, err = strconv.ParseBool(v[0])
|
||
if err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "processing dangling filter"))
|
||
return
|
||
}
|
||
}
|
||
|
||
idr := []types.ImageDeleteResponseItem{}
|
||
//
|
||
// This code needs to be migrated to libpod to work correctly. I could not
|
||
// work my around the information docker needs with the existing prune in libpod.
|
||
//
|
||
pruneImages, err := runtime.ImageRuntime().GetPruneImages(!dangling, []image2.ImageFilter{})
|
||
if err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to get images to prune"))
|
||
return
|
||
}
|
||
for _, p := range pruneImages {
|
||
repotags, err := p.RepoTags()
|
||
if err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to get repotags for image"))
|
||
return
|
||
}
|
||
if err := p.Remove(r.Context(), true); err != nil {
|
||
if errors.Cause(err) == storage.ErrImageUsedByContainer {
|
||
logrus.Warnf("Failed to prune image %s as it is in use: %v", p.ID(), err)
|
||
continue
|
||
}
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to prune image"))
|
||
return
|
||
}
|
||
// newimageevent is not export therefore we cannot record the event. this will be fixed
|
||
// when the prune is fixed in libpod
|
||
// defer p.newImageEvent(events.Prune)
|
||
response := types.ImageDeleteResponseItem{
|
||
Deleted: fmt.Sprintf("sha256:%s", p.ID()), // I ack this is not ideal
|
||
}
|
||
if len(repotags) > 0 {
|
||
response.Untagged = repotags[0]
|
||
}
|
||
idr = append(idr, response)
|
||
}
|
||
ipr := types.ImagesPruneReport{
|
||
ImagesDeleted: idr,
|
||
SpaceReclaimed: 1, // TODO we cannot supply this right now
|
||
}
|
||
utils.WriteResponse(w, http.StatusOK, handlers.ImagesPruneReport{ImagesPruneReport: ipr})
|
||
}
|
||
|
||
func CommitContainer(w http.ResponseWriter, r *http.Request) {
|
||
var (
|
||
destImage string
|
||
)
|
||
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||
|
||
query := struct {
|
||
author string
|
||
changes string
|
||
comment string
|
||
container string
|
||
//fromSrc string # fromSrc is currently unused
|
||
pause bool
|
||
repo string
|
||
tag string
|
||
}{
|
||
// 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, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
|
||
return
|
||
}
|
||
rtc, err := runtime.GetConfig()
|
||
if err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
|
||
return
|
||
}
|
||
sc := image2.GetSystemContext(rtc.SignaturePolicyPath, "", false)
|
||
tag := "latest"
|
||
options := libpod.ContainerCommitOptions{
|
||
Pause: true,
|
||
}
|
||
options.CommitOptions = buildah.CommitOptions{
|
||
SignaturePolicyPath: rtc.SignaturePolicyPath,
|
||
ReportWriter: os.Stderr,
|
||
SystemContext: sc,
|
||
PreferredManifestType: manifest.DockerV2Schema2MediaType,
|
||
}
|
||
|
||
input := handlers.CreateContainerConfig{}
|
||
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
|
||
return
|
||
}
|
||
|
||
if len(query.tag) > 0 {
|
||
tag = query.tag
|
||
}
|
||
options.Message = query.comment
|
||
options.Author = query.author
|
||
options.Pause = query.pause
|
||
options.Changes = strings.Fields(query.changes)
|
||
ctr, err := runtime.LookupContainer(query.container)
|
||
if err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusNotFound, err)
|
||
return
|
||
}
|
||
|
||
// I know mitr hates this ... but doing for now
|
||
if len(query.repo) > 1 {
|
||
destImage = fmt.Sprintf("%s:%s", query.repo, tag)
|
||
}
|
||
|
||
commitImage, err := ctr.Commit(r.Context(), destImage, options)
|
||
if err != nil && !strings.Contains(err.Error(), "is not running") {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "CommitFailure"))
|
||
return
|
||
}
|
||
utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: commitImage.ID()}) // nolint
|
||
}
|
||
|
||
func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) {
|
||
// 200 no error
|
||
// 404 repo does not exist or no read access
|
||
// 500 internal
|
||
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||
|
||
query := struct {
|
||
fromSrc string
|
||
changes []string
|
||
}{
|
||
// 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, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
|
||
return
|
||
}
|
||
// fromSrc – Source to import. The value may be a URL from which the image can be retrieved or - to read the image from the request body. This parameter may only be used when importing an image.
|
||
source := query.fromSrc
|
||
if source == "-" {
|
||
f, err := ioutil.TempFile("", "api_load.tar")
|
||
if err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile"))
|
||
return
|
||
}
|
||
source = f.Name()
|
||
if err := handlers.SaveFromBody(f, r); err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file"))
|
||
}
|
||
}
|
||
iid, err := runtime.Import(r.Context(), source, "", query.changes, "", false)
|
||
if err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball"))
|
||
return
|
||
}
|
||
tmpfile, err := ioutil.TempFile("", "fromsrc.tar")
|
||
if err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
|
||
return
|
||
}
|
||
if err := tmpfile.Close(); err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile"))
|
||
return
|
||
}
|
||
// Success
|
||
utils.WriteResponse(w, http.StatusOK, struct {
|
||
Status string `json:"status"`
|
||
Progress string `json:"progress"`
|
||
ProgressDetail map[string]string `json:"progressDetail"`
|
||
Id string `json:"id"`
|
||
}{
|
||
Status: iid,
|
||
ProgressDetail: map[string]string{},
|
||
Id: iid,
|
||
})
|
||
|
||
}
|
||
|
||
func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
|
||
// 200 no error
|
||
// 404 repo does not exist or no read access
|
||
// 500 internal
|
||
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||
|
||
query := struct {
|
||
fromImage string
|
||
tag string
|
||
}{
|
||
// 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, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
|
||
return
|
||
}
|
||
|
||
/*
|
||
fromImage – Name of the image to pull. The name may include a tag or digest. This parameter may only be used when pulling an image. The pull is cancelled if the HTTP connection is closed.
|
||
repo – Repository name given to an image when it is imported. The repo may include a tag. This parameter may only be used when importing an image.
|
||
tag – Tag or digest. If empty when pulling an image, this causes all tags for the given image to be pulled.
|
||
*/
|
||
fromImage := query.fromImage
|
||
if len(query.tag) < 1 {
|
||
fromImage = fmt.Sprintf("%s:%s", fromImage, query.tag)
|
||
}
|
||
|
||
// TODO
|
||
// We are eating the output right now because we haven't talked about how to deal with multiple responses yet
|
||
img, err := runtime.ImageRuntime().New(r.Context(), fromImage, "", "", nil, &image2.DockerRegistryOptions{}, image2.SigningOptions{}, nil, util.PullImageMissing)
|
||
if err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
|
||
return
|
||
}
|
||
|
||
// Success
|
||
utils.WriteResponse(w, http.StatusOK, struct {
|
||
Status string `json:"status"`
|
||
Error string `json:"error"`
|
||
Progress string `json:"progress"`
|
||
ProgressDetail map[string]string `json:"progressDetail"`
|
||
Id string `json:"id"`
|
||
}{
|
||
Status: fmt.Sprintf("pulling image (%s) from %s", img.Tag, strings.Join(img.Names(), ", ")),
|
||
ProgressDetail: map[string]string{},
|
||
Id: img.ID(),
|
||
})
|
||
}
|
||
|
||
func GetImage(w http.ResponseWriter, r *http.Request) {
|
||
// 200 no error
|
||
// 404 no such
|
||
// 500 internal
|
||
name := mux.Vars(r)["name"]
|
||
newImage, err := handlers.GetImage(r, name)
|
||
if err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name))
|
||
return
|
||
}
|
||
inspect, err := handlers.ImageDataToImageInspect(r.Context(), newImage)
|
||
if err != nil {
|
||
utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "Failed to convert ImageData to ImageInspect '%s'", inspect.ID))
|
||
return
|
||
}
|
||
utils.WriteResponse(w, http.StatusOK, inspect)
|
||
}
|
||
|
||
func GetImages(w http.ResponseWriter, r *http.Request) {
|
||
// 200 ok
|
||
// 500 internal
|
||
images, err := utils.GetImages(w, r)
|
||
if err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images"))
|
||
return
|
||
}
|
||
var summaries = make([]*handlers.ImageSummary, len(images)+1)
|
||
for j, img := range images {
|
||
is, err := handlers.ImageToImageSummary(img)
|
||
if err != nil {
|
||
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed transform image summaries"))
|
||
return
|
||
}
|
||
summaries[j] = is
|
||
}
|
||
utils.WriteResponse(w, http.StatusOK, summaries)
|
||
}
|