diff options
Diffstat (limited to 'pkg/api/handlers/libpod')
-rw-r--r-- | pkg/api/handlers/libpod/containers.go | 131 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/containers_create.go | 7 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/images.go | 226 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/manifests.go | 6 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/networks.go | 2 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/pods.go | 232 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/swagger.go | 50 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/volumes.go | 9 |
8 files changed, 518 insertions, 145 deletions
diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index cdc34004f..fde72552b 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -1,16 +1,21 @@ package libpod import ( + "io/ioutil" "net/http" + "os" "path/filepath" "sort" "strconv" "time" + "github.com/containers/libpod/pkg/api/handlers/compat" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/domain/entities" "github.com/gorilla/schema" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -325,3 +330,129 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts shared.P } return ps, nil } + +func Checkpoint(w http.ResponseWriter, r *http.Request) { + var targetFile string + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Keep bool `schema:"keep"` + LeaveRunning bool `schema:"leaveRunning"` + TCPEstablished bool `schema:"tcpEstablished"` + Export bool `schema:"export"` + IgnoreRootFS bool `schema:"ignoreRootFS"` + }{ + // override any golang type defaults + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + name := utils.GetName(r) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + ctr, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + if query.Export { + tmpFile, err := ioutil.TempFile("", "checkpoint") + if err != nil { + utils.InternalServerError(w, err) + return + } + defer os.Remove(tmpFile.Name()) + if err := tmpFile.Close(); err != nil { + utils.InternalServerError(w, err) + return + } + targetFile = tmpFile.Name() + } + options := libpod.ContainerCheckpointOptions{ + Keep: query.Keep, + KeepRunning: query.LeaveRunning, + TCPEstablished: query.TCPEstablished, + IgnoreRootfs: query.IgnoreRootFS, + } + if query.Export { + options.TargetFile = targetFile + } + err = ctr.Checkpoint(r.Context(), options) + if err != nil { + utils.InternalServerError(w, err) + return + } + if query.Export { + f, err := os.Open(targetFile) + if err != nil { + utils.InternalServerError(w, err) + return + } + defer f.Close() + utils.WriteResponse(w, http.StatusOK, f) + return + } + utils.WriteResponse(w, http.StatusOK, entities.CheckpointReport{Id: ctr.ID()}) +} + +func Restore(w http.ResponseWriter, r *http.Request) { + var ( + targetFile string + ) + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Keep bool `schema:"keep"` + TCPEstablished bool `schema:"tcpEstablished"` + Import bool `schema:"import"` + Name string `schema:"name"` + IgnoreRootFS bool `schema:"ignoreRootFS"` + IgnoreStaticIP bool `schema:"ignoreStaticIP"` + IgnoreStaticMAC bool `schema:"ignoreStaticMAC"` + }{ + // override any golang type defaults + } + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + name := utils.GetName(r) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + ctr, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + if query.Import { + t, err := ioutil.TempFile("", "restore") + if err != nil { + utils.InternalServerError(w, err) + return + } + defer t.Close() + if err := compat.SaveFromBody(t, r); err != nil { + utils.InternalServerError(w, err) + return + } + targetFile = t.Name() + } + + options := libpod.ContainerCheckpointOptions{ + Keep: query.Keep, + TCPEstablished: query.TCPEstablished, + IgnoreRootfs: query.IgnoreRootFS, + IgnoreStaticIP: query.IgnoreStaticIP, + IgnoreStaticMAC: query.IgnoreStaticMAC, + } + if query.Import { + options.TargetFile = targetFile + options.Name = query.Name + } + err = ctr.Restore(r.Context(), options) + if err != nil { + utils.InternalServerError(w, err) + return + } + utils.WriteResponse(w, http.StatusOK, entities.RestoreReport{Id: ctr.ID()}) +} diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go index ebca41151..38a341a89 100644 --- a/pkg/api/handlers/libpod/containers_create.go +++ b/pkg/api/handlers/libpod/containers_create.go @@ -7,6 +7,7 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/specgen" + "github.com/containers/libpod/pkg/specgen/generate" "github.com/pkg/errors" ) @@ -19,7 +20,11 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) return } - ctr, err := sg.MakeContainer(runtime) + if err := generate.CompleteSpec(r.Context(), runtime, &sg); err != nil { + utils.InternalServerError(w, err) + return + } + ctr, err := generate.MakeContainer(runtime, &sg) if err != nil { utils.InternalServerError(w, err) return diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index f8e666451..850de4598 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -14,15 +14,16 @@ import ( "github.com/containers/image/v5/docker" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/manifest" - "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/image" 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/domain/entities" "github.com/containers/libpod/pkg/util" + utils2 "github.com/containers/libpod/utils" "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -119,12 +120,12 @@ func GetImages(w http.ResponseWriter, r *http.Request) { func PruneImages(w http.ResponseWriter, r *http.Request) { var ( - all bool err error ) runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { + All bool `schema:"all"` Filters map[string][]string `schema:"filters"` }{ // override any golang type defaults @@ -140,7 +141,7 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { if _, found := r.URL.Query()["filters"]; found { dangling := query.Filters["all"] if len(dangling) > 0 { - all, err = strconv.ParseBool(query.Filters["all"][0]) + query.All, err = strconv.ParseBool(query.Filters["all"][0]) if err != nil { utils.InternalServerError(w, err) return @@ -152,7 +153,8 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0])) } } - cids, err := runtime.ImageRuntime().PruneImages(r.Context(), all, libpodFilters) + + cids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, libpodFilters) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return @@ -161,13 +163,16 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { } func ExportImage(w http.ResponseWriter, r *http.Request) { + var ( + output string + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { Compress bool `schema:"compress"` Format string `schema:"format"` }{ - Format: "docker-archive", + Format: define.OCIArchive, } if err := decoder.Decode(&query, r.URL.Query()); err != nil { @@ -175,14 +180,27 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) 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")) + switch query.Format { + case define.OCIArchive, define.V2s2Archive: + tmpfile, err := ioutil.TempFile("", "api.tar") + if err != nil { + utils.Error(w, "unable to create tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) + return + } + output = tmpfile.Name() + if err := tmpfile.Close(); err != nil { + utils.Error(w, "unable to close tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) + return + } + case define.OCIManifestDir, define.V2s2ManifestDir: + tmpdir, err := ioutil.TempDir("", "save") + if err != nil { + utils.Error(w, "unable to create tmpdir", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempdir")) + return + } + output = tmpdir + default: + utils.Error(w, "unknown format", http.StatusInternalServerError, errors.Errorf("unknown format %q", query.Format)) return } name := utils.GetName(r) @@ -192,17 +210,28 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { return } - if err := newImage.Save(r.Context(), name, query.Format, tmpfile.Name(), []string{}, false, query.Compress); err != nil { + if err := newImage.Save(r.Context(), name, query.Format, output, []string{}, false, query.Compress); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) return } - rdr, err := os.Open(tmpfile.Name()) + defer os.RemoveAll(output) + // if dir format, we need to tar it + if query.Format == "oci-dir" || query.Format == "docker-dir" { + rdr, err := utils2.Tar(output) + if err != nil { + utils.InternalServerError(w, err) + return + } + defer rdr.Close() + utils.WriteResponse(w, http.StatusOK, rdr) + return + } + rdr, err := os.Open(output) 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) } @@ -253,7 +282,7 @@ func ImagesLoad(w http.ResponseWriter, r *http.Request) { return } } - utils.WriteResponse(w, http.StatusOK, handlers.LibpodImagesLoadReport{ID: loadedImage}) + utils.WriteResponse(w, http.StatusOK, entities.ImageLoadReport{Name: loadedImage}) } func ImagesImport(w http.ResponseWriter, r *http.Request) { @@ -299,9 +328,13 @@ func ImagesImport(w http.ResponseWriter, r *http.Request) { return } - utils.WriteResponse(w, http.StatusOK, handlers.LibpodImagesImportReport{ID: importedImage}) + utils.WriteResponse(w, http.StatusOK, entities.ImageImportReport{Id: importedImage}) } +// ImagesPull is the v2 libpod endpoint for pulling images. Note that the +// mandatory `reference` must be a reference to a registry (i.e., of docker +// transport or be normalized to one). Other transports are rejected as they +// do not make sense in a remote context. func ImagesPull(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) @@ -326,36 +359,27 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, errors.New("reference parameter cannot be empty")) return } - // Enforce the docker transport. This is just a precaution as some callers - // might accustomed to using the "transport:reference" notation. Using - // another than the "docker://" transport does not really make sense for a - // remote case. For loading tarballs, the load and import endpoints should - // be used. - imageRef, err := alltransports.ParseImageName(query.Reference) - if err == nil && imageRef.Transport().Name() != docker.Transport.Name() { + + imageRef, err := utils.ParseDockerReference(query.Reference) + if err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - errors.Errorf("reference %q must be a docker reference", query.Reference)) + errors.Wrapf(err, "image destination %q is not a docker-transport reference", query.Reference)) return - } else if err != nil { - origErr := err - imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s://%s", docker.Transport.Name(), query.Reference)) - if err != nil { - utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - errors.Wrapf(origErr, "reference %q must be a docker reference", query.Reference)) - return - } } + // Trim the docker-transport prefix. + rawImage := strings.TrimPrefix(query.Reference, fmt.Sprintf("%s://", docker.Transport.Name())) + // all-tags doesn't work with a tagged reference, so let's check early - namedRef, err := reference.Parse(query.Reference) + namedRef, err := reference.Parse(rawImage) if err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - errors.Wrapf(err, "error parsing reference %q", query.Reference)) + errors.Wrapf(err, "error parsing reference %q", rawImage)) return } if _, isTagged := namedRef.(reference.Tagged); isTagged && query.AllTags { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - errors.Errorf("reference %q must not have a tag for all-tags", query.Reference)) + errors.Errorf("reference %q must not have a tag for all-tags", rawImage)) return } @@ -376,7 +400,7 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { OSChoice: query.OverrideOS, ArchitectureChoice: query.OverrideArch, } - if query.TLSVerify { + if _, found := r.URL.Query()["tlsVerify"]; found { dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) } @@ -399,13 +423,19 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { } } + authfile := "" + if sys := runtime.SystemContext(); sys != nil { + dockerRegistryOptions.DockerCertPath = sys.DockerCertPath + authfile = sys.AuthFilePath + } + // Finally pull the images for _, img := range imagesToPull { newImage, err := runtime.ImageRuntime().New( context.Background(), img, "", - "", + authfile, os.Stderr, &dockerRegistryOptions, image.SigningOptions{}, @@ -421,6 +451,94 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, res) } +// PushImage is the handler for the compat http endpoint for pushing images. +func PushImage(w http.ResponseWriter, r *http.Request) { + decoder := r.Context().Value("decoder").(*schema.Decoder) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + query := struct { + Credentials string `schema:"credentials"` + Destination string `schema:"destination"` + TLSVerify bool `schema:"tlsVerify"` + }{ + // 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 + } + + source := strings.TrimSuffix(utils.GetName(r), "/push") // GetName returns the entire path + if _, err := utils.ParseStorageReference(source); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "image source %q is not a containers-storage-transport reference", source)) + return + } + + destination := query.Destination + if destination == "" { + destination = source + } + + if _, err := utils.ParseDockerReference(destination); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "image destination %q is not a docker-transport reference", destination)) + return + } + + newImage, err := runtime.ImageRuntime().NewFromLocal(source) + if err != nil { + utils.ImageNotFound(w, source, errors.Wrapf(err, "Failed to find image %s", source)) + return + } + + var registryCreds *types.DockerAuthConfig + if len(query.Credentials) != 0 { + creds, err := util.ParseRegistryCreds(query.Credentials) + if err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "error parsing credentials %q", query.Credentials)) + return + } + registryCreds = creds + } + + // TODO: the X-Registry-Auth header is not checked yet here nor in any other + // endpoint. Pushing does NOT work with authentication at the moment. + dockerRegistryOptions := &image.DockerRegistryOptions{ + DockerRegistryCreds: registryCreds, + } + authfile := "" + if sys := runtime.SystemContext(); sys != nil { + dockerRegistryOptions.DockerCertPath = sys.DockerCertPath + authfile = sys.AuthFilePath + } + if _, found := r.URL.Query()["tlsVerify"]; found { + dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) + } + + err = newImage.PushImageToHeuristicDestination( + context.Background(), + destination, + "", // manifest type + authfile, + "", // digest file + "", // signature policy + os.Stderr, + false, // force compression + image.SigningOptions{}, + dockerRegistryOptions, + nil, // additional tags + ) + 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, "") +} + func CommitContainer(w http.ResponseWriter, r *http.Request) { var ( destImage string @@ -451,7 +569,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "failed to get runtime config", http.StatusInternalServerError, errors.Wrap(err, "failed to get runtime config")) return } - sc := image2.GetSystemContext(rtc.SignaturePolicyPath, "", false) + sc := image2.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) tag := "latest" options := libpod.ContainerCommitOptions{ Pause: true, @@ -470,7 +588,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { return } options.CommitOptions = buildah.CommitOptions{ - SignaturePolicyPath: rtc.SignaturePolicyPath, + SignaturePolicyPath: rtc.Engine.SignaturePolicyPath, ReportWriter: os.Stderr, SystemContext: sc, PreferredManifestType: mimeType, @@ -501,3 +619,29 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { } utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: commitImage.ID()}) // nolint } + +func UntagImage(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + name := utils.GetName(r) + newImage, err := runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name)) + return + } + tag := "latest" + if len(r.Form.Get("tag")) > 0 { + tag = r.Form.Get("tag") + } + if len(r.Form.Get("repo")) < 1 { + utils.Error(w, "repo tag is required", http.StatusBadRequest, errors.New("repo parameter is required to tag an image")) + return + } + repo := r.Form.Get("repo") + tagName := fmt.Sprintf("%s:%s", repo, tag) + if err := newImage.UntagImage(tagName); err != nil { + utils.Error(w, "failed to untag", http.StatusInternalServerError, err) + return + } + utils.WriteResponse(w, http.StatusCreated, "") +} diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index a3d2caba6..d87ed7eba 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -36,7 +36,7 @@ func ManifestCreate(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false) + sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) manID, err := image.CreateManifestList(runtime.ImageRuntime(), *sc, query.Name, query.Image, query.All) if err != nil { utils.InternalServerError(w, err) @@ -79,7 +79,7 @@ func ManifestAdd(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false) + sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) newID, err := newImage.AddManifest(*sc, manifestInput) if err != nil { utils.InternalServerError(w, err) @@ -149,7 +149,7 @@ func ManifestPush(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false) + sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) opts := manifests.PushOptions{ ImageListSelection: copy2.CopySpecificImages, SystemContext: sc, diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go index e3dbe3b35..e8a92e93e 100644 --- a/pkg/api/handlers/libpod/networks.go +++ b/pkg/api/handlers/libpod/networks.go @@ -18,7 +18,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - configDir := config.CNIConfigDir + configDir := config.Network.NetworkConfigDir if len(configDir) < 1 { configDir = network.CNIConfigDir } diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index 27ec64d89..a890169a1 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -6,12 +6,12 @@ import ( "net/http" "strings" - "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/cmd/podman/shared/parse" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/util" "github.com/gorilla/schema" "github.com/pkg/errors" @@ -20,76 +20,14 @@ import ( func PodCreate(w http.ResponseWriter, r *http.Request) { var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) - options []libpod.PodCreateOption err error ) - labels := make(map[string]string) - input := handlers.PodCreateConfig{} - if err := json.NewDecoder(r.Body).Decode(&input); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + var psg specgen.PodSpecGenerator + if err := json.NewDecoder(r.Body).Decode(&psg); err != nil { + utils.Error(w, "Failed to decode specgen", http.StatusInternalServerError, errors.Wrap(err, "failed to decode specgen")) return } - if len(input.InfraCommand) > 0 || len(input.InfraImage) > 0 { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, - errors.New("infra-command and infra-image are not implemented yet")) - return - } - // TODO long term we should break the following out of adapter and into libpod proper - // so that the cli and api can share the creation of a pod with the same options - if len(input.CGroupParent) > 0 { - options = append(options, libpod.WithPodCgroupParent(input.CGroupParent)) - } - - if len(input.Labels) > 0 { - labels, err = parse.GetAllLabels([]string{}, input.Labels) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - } - - if len(labels) != 0 { - options = append(options, libpod.WithPodLabels(labels)) - } - - if len(input.Name) > 0 { - options = append(options, libpod.WithPodName(input.Name)) - } - - if len(input.Hostname) > 0 { - options = append(options, libpod.WithPodHostname(input.Hostname)) - } - - if input.Infra { - // TODO infra-image and infra-command are not supported in the libpod API yet. Will fix - // when implemented in libpod - options = append(options, libpod.WithInfraContainer()) - sharedNamespaces := shared.DefaultKernelNamespaces - if len(input.Share) > 0 { - sharedNamespaces = input.Share - } - nsOptions, err := shared.GetNamespaceOptions(strings.Split(sharedNamespaces, ",")) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - options = append(options, nsOptions...) - } - - if len(input.Publish) > 0 { - portBindings, err := shared.CreatePortBindings(input.Publish) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - options = append(options, libpod.WithInfraContainerPorts(portBindings)) - - } - // always have containers use pod cgroups - // User Opt out is not yet supported - options = append(options, libpod.WithPodCgroups()) - - pod, err := runtime.NewPod(r.Context(), options...) + pod, err := psg.MakePod(runtime) if err != nil { http_code := http.StatusInternalServerError if errors.Cause(err) == define.ErrPodExists { @@ -102,9 +40,6 @@ func PodCreate(w http.ResponseWriter, r *http.Request) { } func Pods(w http.ResponseWriter, r *http.Request) { - var ( - podInspectData []*libpod.PodInspect - ) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { Filters map[string][]string `schema:"filters"` @@ -118,20 +53,11 @@ func Pods(w http.ResponseWriter, r *http.Request) { } pods, err := utils.GetPods(w, r) - if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - for _, pod := range pods { - data, err := pod.Inspect() - if err != nil { - utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) - return - } - podInspectData = append(podInspectData, data) - } - utils.WriteResponse(w, http.StatusOK, podInspectData) + utils.WriteResponse(w, http.StatusOK, pods) } func PodInspect(w http.ResponseWriter, r *http.Request) { @@ -147,7 +73,10 @@ func PodInspect(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusOK, podData) + report := entities.PodInspectReport{ + PodInspect: podData, + } + utils.WriteResponse(w, http.StatusOK, report) } func PodStop(w http.ResponseWriter, r *http.Request) { @@ -155,6 +84,8 @@ func PodStop(w http.ResponseWriter, r *http.Request) { stopError error runtime = r.Context().Value("runtime").(*libpod.Runtime) decoder = r.Context().Value("decoder").(*schema.Decoder) + responses map[string]error + errs []error ) query := struct { Timeout int `schema:"t"` @@ -185,18 +116,28 @@ func PodStop(w http.ResponseWriter, r *http.Request) { } if query.Timeout > 0 { - _, stopError = pod.StopWithTimeout(r.Context(), false, query.Timeout) + responses, stopError = pod.StopWithTimeout(r.Context(), false, query.Timeout) } else { - _, stopError = pod.Stop(r.Context(), false) + responses, stopError = pod.Stop(r.Context(), false) } if stopError != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusOK, "") + for _, err := range responses { + errs = append(errs, err) + } + report := entities.PodStopReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodStart(w http.ResponseWriter, r *http.Request) { + var ( + errs []error + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) pod, err := runtime.LookupPod(name) @@ -213,11 +154,19 @@ func PodStart(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusNotModified, "") return } - if _, err := pod.Start(r.Context()); err != nil { + responses, err := pod.Start(r.Context()) + if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusOK, "") + for _, err := range responses { + errs = append(errs, err) + } + report := entities.PodStartReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodDelete(w http.ResponseWriter, r *http.Request) { @@ -246,10 +195,16 @@ func PodDelete(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusNoContent, "") + report := entities.PodRmReport{ + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodRestart(w http.ResponseWriter, r *http.Request) { + var ( + errs []error + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) pod, err := runtime.LookupPod(name) @@ -257,12 +212,19 @@ func PodRestart(w http.ResponseWriter, r *http.Request) { utils.PodNotFound(w, name, err) return } - _, err = pod.Restart(r.Context()) + responses, err := pod.Restart(r.Context()) if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusOK, "") + for _, err := range responses { + errs = append(errs, err) + } + report := entities.PodRestartReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodPrune(w http.ResponseWriter, r *http.Request) { @@ -278,6 +240,9 @@ func PodPrune(w http.ResponseWriter, r *http.Request) { } func PodPause(w http.ResponseWriter, r *http.Request) { + var ( + errs []error + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) pod, err := runtime.LookupPod(name) @@ -285,15 +250,25 @@ func PodPause(w http.ResponseWriter, r *http.Request) { utils.PodNotFound(w, name, err) return } - _, err = pod.Pause() + responses, err := pod.Pause() if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusNoContent, "") + for _, v := range responses { + errs = append(errs, v) + } + report := entities.PodPauseReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodUnpause(w http.ResponseWriter, r *http.Request) { + var ( + errs []error + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) pod, err := runtime.LookupPod(name) @@ -301,12 +276,61 @@ func PodUnpause(w http.ResponseWriter, r *http.Request) { utils.PodNotFound(w, name, err) return } - _, err = pod.Unpause() + responses, err := pod.Unpause() if err != nil { - utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + utils.Error(w, "failed to pause pod", http.StatusInternalServerError, err) + return + } + for _, v := range responses { + errs = append(errs, v) + } + report := entities.PodUnpauseReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, &report) +} + +func PodTop(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + + query := struct { + PsArgs string `schema:"ps_args"` + }{ + PsArgs: "", + } + 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 } - utils.WriteResponse(w, http.StatusOK, "") + + name := utils.GetName(r) + pod, err := runtime.LookupPod(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + args := []string{} + if query.PsArgs != "" { + args = append(args, query.PsArgs) + } + output, err := pod.GetPodPidInformation(args) + if err != nil { + utils.InternalServerError(w, err) + return + } + + var body = handlers.PodTopOKBody{} + if len(output) > 0 { + body.Titles = strings.Split(output[0], "\t") + for _, line := range output[1:] { + body.Processes = append(body.Processes, strings.Split(line, "\t")) + } + } + utils.WriteJSON(w, http.StatusOK, body) } func PodKill(w http.ResponseWriter, r *http.Request) { @@ -314,6 +338,7 @@ func PodKill(w http.ResponseWriter, r *http.Request) { runtime = r.Context().Value("runtime").(*libpod.Runtime) decoder = r.Context().Value("decoder").(*schema.Decoder) signal = "SIGKILL" + errs []error ) query := struct { Signal string `schema:"signal"` @@ -356,12 +381,23 @@ func PodKill(w http.ResponseWriter, r *http.Request) { utils.Error(w, msg, http.StatusConflict, errors.Errorf("cannot kill a pod with no running containers: %s", pod.ID())) return } - _, err = pod.Kill(uint(sig)) + + responses, err := pod.Kill(uint(sig)) if err != nil { - utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + utils.Error(w, "failed to kill pod", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusOK, "") + + for _, v := range responses { + if v != nil { + errs = append(errs, v) + } + } + report := &entities.PodKillReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodExists(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go index 149fa10dc..1fad2dd1a 100644 --- a/pkg/api/handlers/libpod/swagger.go +++ b/pkg/api/handlers/libpod/swagger.go @@ -6,6 +6,7 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" ) @@ -26,6 +27,55 @@ type swagInspectManifestResponse struct { Body manifest.List } +// Kill Pod +// swagger:response PodKillReport +type swagKillPodResponse struct { + // in:body + Body entities.PodKillReport +} + +// Pause pod +// swagger:response PodPauseReport +type swagPausePodResponse struct { + // in:body + Body entities.PodPauseReport +} + +// Unpause pod +// swagger:response PodUnpauseReport +type swagUnpausePodResponse struct { + // in:body + Body entities.PodUnpauseReport +} + +// Stop pod +// swagger:response PodStopReport +type swagStopPodResponse struct { + // in:body + Body entities.PodStopReport +} + +// Restart pod +// swagger:response PodRestartReport +type swagRestartPodResponse struct { + // in:body + Body entities.PodRestartReport +} + +// Start pod +// swagger:response PodStartReport +type swagStartPodResponse struct { + // in:body + Body entities.PodStartReport +} + +// Rm pod +// swagger:response PodRmReport +type swagRmPodResponse struct { + // in:body + Body entities.PodRmReport +} + func ServeSwagger(w http.ResponseWriter, r *http.Request) { path := DefaultPodmanSwaggerSpec if p, found := os.LookupEnv("PODMAN_SWAGGER_SPEC"); found { diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go index e61d272f4..5a6fc021e 100644 --- a/pkg/api/handlers/libpod/volumes.go +++ b/pkg/api/handlers/libpod/volumes.go @@ -149,13 +149,20 @@ func ListVolumes(w http.ResponseWriter, r *http.Request) { func PruneVolumes(w http.ResponseWriter, r *http.Request) { var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) + reports []*entities.VolumePruneReport ) pruned, err := runtime.PruneVolumes(r.Context()) if err != nil { utils.InternalServerError(w, err) return } - utils.WriteResponse(w, http.StatusOK, pruned) + for k, v := range pruned { + reports = append(reports, &entities.VolumePruneReport{ + Err: v, + Id: k, + }) + } + utils.WriteResponse(w, http.StatusOK, reports) } func RemoveVolume(w http.ResponseWriter, r *http.Request) { |