diff options
author | Jhon Honce <jhonce@redhat.com> | 2019-11-01 13:03:34 -0700 |
---|---|---|
committer | baude <bbaude@redhat.com> | 2020-01-10 09:41:39 -0600 |
commit | d924494f561bb878a2b3a7ce438d87ecb934b5fb (patch) | |
tree | 29983e7411c8108e74e4286b90a535a114dee755 /pkg/api/handlers/libpod | |
parent | 6ed88e047579bd2d1eac99a6089cc617f0c4773d (diff) | |
download | podman-d924494f561bb878a2b3a7ce438d87ecb934b5fb.tar.gz podman-d924494f561bb878a2b3a7ce438d87ecb934b5fb.tar.bz2 podman-d924494f561bb878a2b3a7ce438d87ecb934b5fb.zip |
Initial commit on compatible API
Signed-off-by: Jhon Honce <jhonce@redhat.com>
Create service command
Use cd cmd/service && go build .
$ systemd-socket-activate -l 8081 cmd/service/service &
$ curl http://localhost:8081/v1.24/images/json
Signed-off-by: Jhon Honce <jhonce@redhat.com>
Correct Makefile
Signed-off-by: Jhon Honce <jhonce@redhat.com>
Two more stragglers
Signed-off-by: Jhon Honce <jhonce@redhat.com>
Report errors back as http headers
Signed-off-by: Jhon Honce <jhonce@redhat.com>
Split out handlers, updated output
Output aligned to docker structures
Signed-off-by: Jhon Honce <jhonce@redhat.com>
Refactored routing, added more endpoints and types
* Encapsulated all the routing information in the handler_* files.
* Added more serviceapi/types, including podman additions. See Info
Signed-off-by: Jhon Honce <jhonce@redhat.com>
Cleaned up code, implemented info content
* Move Content-Type check into serviceHandler
* Custom 404 handler showing the url, mostly for debugging
* Refactored images: better method names and explicit http codes
* Added content to /info
* Added podman fields to Info struct
* Added Container struct
Signed-off-by: Jhon Honce <jhonce@redhat.com>
Add a bunch of endpoints
containers: stop, pause, unpause, wait, rm
images: tag, rmi, create (pull only)
Signed-off-by: baude <bbaude@redhat.com>
Add even more handlers
* Add serviceapi/Error() to improve error handling
* Better support for API return payloads
* Renamed unimplemented to unsupported these are generic endpoints
we don't intend to ever support. Swarm broken out since it uses
different HTTP codes to signal that the node is not in a swarm.
* Added more types
* API Version broken out so it can be validated in the future
Signed-off-by: Jhon Honce <jhonce@redhat.com>
Refactor to introduce ServiceWriter
Signed-off-by: Jhon Honce <jhonce@redhat.com>
populate pods endpoints
/libpod/pods/..
exists, kill, pause, prune, restart, remove, start, stop, unpause
Signed-off-by: baude <bbaude@redhat.com>
Add components to Version, fix Error body
Signed-off-by: Jhon Honce <jhonce@redhat.com>
Add images pull output, fix swarm routes
* docker-py tests/integration/api_client_test.py pass 100%
* docker-py tests/integration/api_image_test.py pass 4/16
+ Test failures include services podman does not support
Signed-off-by: Jhon Honce <jhonce@redhat.com>
pods endpoint submission 2
add create and others; only top and stats is left.
Signed-off-by: baude <bbaude@redhat.com>
Update pull image to work from empty registry
Signed-off-by: Jhon Honce <jhonce@redhat.com>
pod create and container create
first pass at pod and container create. the container create does not
quite work yet but it is very close. pod create needs a partial
rewrite. also broken off the DELETE (rm/rmi) to specific handler funcs.
Signed-off-by: baude <bbaude@redhat.com>
Add docker-py demos, GET .../containers/json
* Update serviceapi/types to reflect libpod not podman
* Refactored removeImage() to provide non-streaming return
Signed-off-by: Jhon Honce <jhonce@redhat.com>
create container part2
finished minimal config needed for create container. started demo.py
for upcoming talk
Signed-off-by: baude <bbaude@redhat.com>
Stop server after honoring request
* Remove casting for method calls
* Improve WriteResponse()
* Update Container API type to match docker API
Signed-off-by: Jhon Honce <jhonce@redhat.com>
fix namespace assumptions
cleaned up namespace issues with libpod.
Signed-off-by: baude <bbaude@redhat.com>
wip
Signed-off-by: baude <bbaude@redhat.com>
Add sliding window when shutting down server
* Added a Timeout rather than closing down service on each call
* Added gorilla/schema dependency for Decode'ing query parameters
* Improved error handling
* Container logs returned and multiplexed for stdout and stderr
* .../containers/{name}/logs?stdout=True&stderr=True
* Container stats
* .../containers/{name}/stats
Signed-off-by: Jhon Honce <jhonce@redhat.com>
Improve error handling
* Add check for at least one std stream required for /containers/{id}/logs
* Add check for state in /containers/{id}/top
* Fill in more fields for /info
* Fixed error checking in service start code
Signed-off-by: Jhon Honce <jhonce@redhat.com>
get rest of image tests for pass
Signed-off-by: baude <bbaude@redhat.com>
linting our content
Signed-off-by: baude <bbaude@redhat.com>
more linting
Signed-off-by: baude <bbaude@redhat.com>
more linting
Signed-off-by: baude <bbaude@redhat.com>
pruning
Signed-off-by: baude <bbaude@redhat.com>
[CI:DOCS]apiv2 pods
migrate from using args in the url to using a json struct in body for
pod create.
Signed-off-by: baude <bbaude@redhat.com>
fix handler_images prune
prune's api changed slightly to deal with filters.
Signed-off-by: baude <bbaude@redhat.com>
[CI:DOCS]enabled base container create tests
enabling the base container create tests which allow us to get more into
the stop, kill, etc tests. many new tests now pass.
Signed-off-by: baude <bbaude@redhat.com>
serviceapi errors: append error message to API message
I dearly hope this is not breaking any other tests but debugging
"Internal Server Error" is not helpful to any user. In case, it
breaks tests, we can rever the commit - that's why it's a small one.
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
serviceAPI: add containers/prune endpoint
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
add `service` make target
Also remove the non-functional sub-Makefile.
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
add make targets for testing the service
* `sudo make run-service` for running the service.
* `DOCKERPY_TEST="tests/integration/api_container_test.py::ListContainersTest" \
make run-docker-py-tests`
for running a specific tests. Run all tests by leaving the env
variable empty.
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
Split handlers and server packages
The files were split to help contain bloat. The api/server package will
contain all code related to the functioning of the server while
api/handlers will have all the code related to implementing the end
points.
api/server/register_* will contain the methods for registering
endpoints. Additionally, they will have the comments for generating the
swagger spec file.
See api/handlers/version.go for a small example handler,
api/handlers/containers.go contains much more complex handlers.
Signed-off-by: Jhon Honce <jhonce@redhat.com>
[CI:DOCS]enabled more tests
Signed-off-by: baude <bbaude@redhat.com>
[CI:DOCS]libpod endpoints
small refactor for libpod inclusion and began adding endpoints.
Signed-off-by: baude <bbaude@redhat.com>
Implement /build and /events
* Include crypto libraries for future ssh work
Signed-off-by: Jhon Honce <jhonce@redhat.com>
[CI:DOCS]more image implementations
convert from using for to query structs among other changes including
new endpoints.
Signed-off-by: baude <bbaude@redhat.com>
[CI:DOCS]add bindings for golang
Signed-off-by: baude <bbaude@redhat.com>
[CI:DOCS]add volume endpoints for libpod
create, inspect, ls, prune, and rm
Signed-off-by: baude <bbaude@redhat.com>
[CI:DOCS]apiv2 healthcheck enablement
wire up container healthchecks for the api.
Signed-off-by: baude <bbaude@redhat.com>
[CI:DOCS]Add mount endpoints
via the api, allow ability to mount a container and list container
mounts.
Signed-off-by: baude <bbaude@redhat.com>
[CI:DOCS]Add search endpoint
add search endpoint with golang bindings
Signed-off-by: baude <bbaude@redhat.com>
[CI:DOCS]more apiv2 development
misc population of methods, etc
Signed-off-by: baude <bbaude@redhat.com>
rebase cleanup and epoch reset
Signed-off-by: baude <bbaude@redhat.com>
[CI:DOCS]add more network endpoints
also, add some initial error handling and convenience functions for
standard endpoints.
Signed-off-by: baude <bbaude@redhat.com>
[CI:DOCS]use helper funcs for bindings
use the methods developed to make writing bindings less duplicative and
easier to use.
Signed-off-by: baude <bbaude@redhat.com>
[CI:DOCS]add return info for prereview
begin to add return info and status codes for errors so that we can
review the apiv2
Signed-off-by: baude <bbaude@redhat.com>
[CI:DOCS]first pass at adding swagger docs for api
Signed-off-by: baude <bbaude@redhat.com>
Diffstat (limited to 'pkg/api/handlers/libpod')
-rw-r--r-- | pkg/api/handlers/libpod/containers.go | 186 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/healthcheck.go | 25 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/images.go | 165 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/pods.go | 465 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/volumes.go | 174 |
5 files changed, 1015 insertions, 0 deletions
diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go new file mode 100644 index 000000000..bfb028b1b --- /dev/null +++ b/pkg/api/handlers/libpod/containers.go @@ -0,0 +1,186 @@ +package libpod + +import ( + "net/http" + + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/gorilla/mux" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +func StopContainer(w http.ResponseWriter, r *http.Request) { + handlers.StopContainer(w, r) +} + +func ContainerExists(w http.ResponseWriter, r *http.Request) { + // 404 no such container + // 200 ok + runtime := r.Context().Value("runtime").(*libpod.Runtime) + name := mux.Vars(r)["name"] + _, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + utils.WriteResponse(w, http.StatusNoContent, "") +} + +func RemoveContainer(w http.ResponseWriter, r *http.Request) { + // 204 no error + // 400 bad param + // 404 no such container + // 409 conflict + // 500 internal error + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Force bool `schema:"force"` + Vols bool `schema:"v"` + }{ + // 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 + } + utils.RemoveContainer(w, r, query.Force, query.Vols) +} +func ListContainers(w http.ResponseWriter, r *http.Request) { + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Filter []string `schema:"filter"` + Last int `schema:"last"` + Size bool `schema:"size"` + Sync bool `schema:"sync"` + }{ + // 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 + } + runtime := r.Context().Value("runtime").(*libpod.Runtime) + opts := shared.PsOptions{ + All: true, + Last: query.Last, + Size: query.Size, + Sort: "", + Namespace: true, + Sync: query.Sync, + } + + pss, err := shared.GetPsContainerOutput(runtime, opts, query.Filter, 2) + if err != nil { + utils.InternalServerError(w, err) + } + utils.WriteResponse(w, http.StatusOK, pss) +} + +func GetContainer(w http.ResponseWriter, r *http.Request) { + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Size bool `schema:"size"` + }{ + // 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 + } + runtime := r.Context().Value("runtime").(*libpod.Runtime) + name := mux.Vars(r)["name"] + container, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + data, err := container.Inspect(query.Size) + if err != nil { + utils.InternalServerError(w, err) + return + } + utils.WriteResponse(w, http.StatusOK, data) +} + +func KillContainer(w http.ResponseWriter, r *http.Request) { + // /{version}/containers/(name)/kill + _, err := utils.KillContainer(w, r) + if err != nil { + return + } + // Success + utils.WriteResponse(w, http.StatusNoContent, "") +} + +func WaitContainer(w http.ResponseWriter, r *http.Request) { + _, err := utils.WaitContainer(w, r) + if err != nil { + utils.InternalServerError(w, err) + return + } + utils.WriteResponse(w, http.StatusNoContent, "") +} + +func PruneContainers(w http.ResponseWriter, r *http.Request) { + // TODO Needs rebase to get filers; Also would be handy to define + // an actual libpod container prune method. + // force + // filters +} + +func LogsFromContainer(w http.ResponseWriter, r *http.Request) { + // follow + // since + // timestamps + // tail string +} +func StatsContainer(w http.ResponseWriter, r *http.Request) { + //stream +} +func CreateContainer(w http.ResponseWriter, r *http.Request) { + +} + +func MountContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + name := mux.Vars(r)["name"] + conn, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + m, err := conn.Mount() + if err != nil { + utils.InternalServerError(w, err) + } + utils.WriteResponse(w, http.StatusOK, m) +} + +func ShowMountedContainers(w http.ResponseWriter, r *http.Request) { + response := make(map[string]string) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + conns, err := runtime.GetAllContainers() + if err != nil { + utils.InternalServerError(w, err) + } + for _, conn := range conns { + mounted, mountPoint, err := conn.Mounted() + if err != nil { + utils.InternalServerError(w, err) + } + if !mounted { + continue + } + response[conn.ID()] = mountPoint + } + utils.WriteResponse(w, http.StatusOK, response) +} diff --git a/pkg/api/handlers/libpod/healthcheck.go b/pkg/api/handlers/libpod/healthcheck.go new file mode 100644 index 000000000..0d7bf3ea7 --- /dev/null +++ b/pkg/api/handlers/libpod/healthcheck.go @@ -0,0 +1,25 @@ +package libpod + +import ( + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/gorilla/mux" +) + +func RunHealthCheck(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 404 no such + // 500 internal + runtime := r.Context().Value("runtime").(*libpod.Runtime) + name := mux.Vars(r)["name"] + status, err := runtime.HealthCheck(name) + if err != nil { + if status == libpod.HealthCheckContainerNotFound { + utils.ContainerNotFound(w, name, err) + } + utils.InternalServerError(w, err) + } + utils.WriteResponse(w, http.StatusOK, status) +} diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go new file mode 100644 index 000000000..0d4e220a8 --- /dev/null +++ b/pkg/api/handlers/libpod/images.go @@ -0,0 +1,165 @@ +package libpod + +import ( + "io/ioutil" + "net/http" + "os" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/gorilla/mux" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +// Commit +// author string +// "container" +// repo string +// tag string +// message +// pause bool +// changes []string + +// create + +func ImageExists(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 404 no such + // 500 internal + runtime := r.Context().Value("runtime").(*libpod.Runtime) + name := mux.Vars(r)["name"] + + _, err := runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "Failed to find image %s", name)) + return + } + utils.WriteResponse(w, http.StatusNoContent, "") +} + +func ImageTree(w http.ResponseWriter, r *http.Request) { + // tree is a bit of a mess ... logic is in adapter and therefore not callable from here. needs rework + + //name := mux.Vars(r)["name"] + //_, layerInfoMap, _, err := s.Runtime.Tree(name) + //if err != nil { + // Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to find image information for %q", name)) + // return + //} + // it is not clear to me how to deal with this given all the processing of the image + // is in main. we need to discuss how that really should be and return something useful. + handlers.UnsupportedHandler(w, r) +} + +func GetImage(w http.ResponseWriter, r *http.Request) { + 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 := newImage.Inspect(r.Context()) + if err != nil { + utils.Error(w, "Server error", http.StatusInternalServerError, errors.Wrapf(err, "failed in inspect image %s", inspect.ID)) + return + } + utils.WriteResponse(w, http.StatusOK, inspect) + +} +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")) + return + } + var summaries = make([]*handlers.ImageSummary, len(images)) + 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 + } + // libpod has additional fields that we need to populate. + is.CreatedTime = img.Created() + is.ReadOnly = img.IsReadOnly() + summaries[j] = is + } + utils.WriteResponse(w, http.StatusOK, summaries) +} + +func PruneImages(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 500 internal + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + All bool `schema:"all"` + Filters []string `schema:"filters"` + }{ + // 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 + } + cids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, query.Filters) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) + return + } + utils.WriteResponse(w, http.StatusOK, cids) +} + +func ExportImage(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Compress bool `schema:"compress"` + Format string `schema:"format"` + }{ + // 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 + } + + if len(query.Format) < 1 { + utils.InternalServerError(w, errors.New("format parameter cannot be empty.")) + 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 + } + name := mux.Vars(r)["name"] + newImage, err := runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + utils.ImageNotFound(w, name, err) + return + } + if err := newImage.Save(r.Context(), name, query.Format, tmpfile.Name(), []string{}, false, query.Compress); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) + 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) +} diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go new file mode 100644 index 000000000..cde1fcd48 --- /dev/null +++ b/pkg/api/handlers/libpod/pods.go @@ -0,0 +1,465 @@ +package libpod + +import ( + "encoding/json" + "fmt" + "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/gorilla/mux" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +func PodCreate(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 500 internal + 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()")) + 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 { + if err := parse.ReadKVStrings(labels, []string{}, input.Labels); 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...) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) + return + } + utils.WriteResponse(w, http.StatusCreated, handlers.IDResponse{ID: pod.CgroupParent()}) +} + +func Pods(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 500 internal + var ( + runtime = r.Context().Value("runtime").(*libpod.Runtime) + podInspectData []*libpod.PodInspect + ) + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + filters []string `schema:"filters"` + }{ + // 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 + } + if len(query.filters) > 0 { + utils.Error(w, "filters are not implemented yet", http.StatusInternalServerError, define.ErrNotImplemented) + return + } + pods, err := runtime.GetAllPods() + 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) +} + +func PodInspect(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 404 no such + // 500 internal + runtime := r.Context().Value("runtime").(*libpod.Runtime) + name := mux.Vars(r)["name"] + pod, err := runtime.LookupPod(name) + if err != nil { + utils.PodNotFound(w, name, err) + return + } + podData, err := pod.Inspect() + if err != nil { + utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + return + } + utils.WriteResponse(w, http.StatusOK, podData) +} + +func PodStop(w http.ResponseWriter, r *http.Request) { + // 200 + // 304 not modified + // 404 no such + // 500 internal + var ( + stopError error + runtime = r.Context().Value("runtime").(*libpod.Runtime) + decoder = r.Context().Value("decoder").(*schema.Decoder) + ) + query := struct { + timeout int `schema:"t"` + }{ + // 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 + } + allContainersStopped := true + name := mux.Vars(r)["name"] + pod, err := runtime.LookupPod(name) + if err != nil { + utils.PodNotFound(w, name, err) + return + } + + // TODO we need to implement a pod.State/Status in libpod internal so libpod api + // users dont have to run through all containers. + podContainers, err := pod.AllContainers() + if err != nil { + utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + return + } + + for _, con := range podContainers { + containerState, err := con.State() + if err != nil { + utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + return + } + if containerState == define.ContainerStateRunning { + allContainersStopped = false + break + } + } + if allContainersStopped { + alreadyStopped := errors.Errorf("pod %s is already stopped", pod.ID()) + utils.Error(w, "Something went wrong", http.StatusNotModified, alreadyStopped) + return + } + + if query.timeout > 0 { + _, stopError = pod.StopWithTimeout(r.Context(), false, query.timeout) + } else { + _, stopError = pod.Stop(r.Context(), false) + } + if stopError != nil { + utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + return + } + utils.WriteResponse(w, http.StatusOK, "") +} + +func PodStart(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 304 no modified + // 404 no such + // 500 internal + runtime := r.Context().Value("runtime").(*libpod.Runtime) + allContainersRunning := true + name := mux.Vars(r)["name"] + pod, err := runtime.LookupPod(name) + if err != nil { + utils.PodNotFound(w, name, err) + return + } + + // TODO we need to implement a pod.State/Status in libpod internal so libpod api + // users dont have to run through all containers. + podContainers, err := pod.AllContainers() + if err != nil { + utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + return + } + + for _, con := range podContainers { + containerState, err := con.State() + if err != nil { + utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + return + } + if containerState != define.ContainerStateRunning { + allContainersRunning = false + break + } + } + if allContainersRunning { + alreadyRunning := errors.Errorf("pod %s is already running", pod.ID()) + utils.Error(w, "Something went wrong", http.StatusNotModified, alreadyRunning) + return + } + if _, err := pod.Start(r.Context()); err != nil { + utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + return + } + utils.WriteResponse(w, http.StatusOK, "") +} + +func PodDelete(w http.ResponseWriter, r *http.Request) { + // 200 + // 404 no such + // 500 internal + var ( + runtime = r.Context().Value("runtime").(*libpod.Runtime) + decoder = r.Context().Value("decoder").(*schema.Decoder) + ) + query := struct { + force bool `schema:"force"` + }{ + // 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 := mux.Vars(r)["name"] + pod, err := runtime.LookupPod(name) + if err != nil { + utils.PodNotFound(w, name, err) + return + } + if err := runtime.RemovePod(r.Context(), pod, true, query.force); err != nil { + utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + return + } + utils.WriteResponse(w, http.StatusOK, "") +} + +func PodRestart(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 404 no such + // 500 internal + runtime := r.Context().Value("runtime").(*libpod.Runtime) + name := mux.Vars(r)["name"] + pod, err := runtime.LookupPod(name) + if err != nil { + utils.PodNotFound(w, name, err) + return + } + _, err = pod.Restart(r.Context()) + if err != nil { + utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + return + } + utils.WriteResponse(w, http.StatusOK, "") +} + +func PodPrune(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 500 internal + var ( + err error + pods []*libpod.Pod + runtime = r.Context().Value("runtime").(*libpod.Runtime) + decoder = r.Context().Value("decoder").(*schema.Decoder) + ) + query := struct { + force bool `schema:"force"` + }{ + // 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 + } + + if query.force { + pods, err = runtime.GetAllPods() + if err != nil { + utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + return + } + } else { + // TODO We need to make a libpod.PruneVolumes or this code will be a mess. Volumes + // already does this right. It will also help clean this code path up with less + // conditionals. We do this when we integrate with libpod again. + utils.Error(w, "not implemented", http.StatusInternalServerError, errors.New("not implemented")) + return + } + for _, p := range pods { + if err := runtime.RemovePod(r.Context(), p, true, query.force); err != nil { + utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + return + } + } + utils.WriteResponse(w, http.StatusOK, "") +} + +func PodPause(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 404 no such + // 500 internal + runtime := r.Context().Value("runtime").(*libpod.Runtime) + name := mux.Vars(r)["name"] + pod, err := runtime.LookupPod(name) + if err != nil { + utils.PodNotFound(w, name, err) + return + } + _, err = pod.Pause() + if err != nil { + utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + return + } + utils.WriteResponse(w, http.StatusOK, "") +} + +func PodUnpause(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 404 no such + // 500 internal + runtime := r.Context().Value("runtime").(*libpod.Runtime) + name := mux.Vars(r)["name"] + pod, err := runtime.LookupPod(name) + if err != nil { + utils.PodNotFound(w, name, err) + return + } + _, err = pod.Unpause() + if err != nil { + utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + return + } + utils.WriteResponse(w, http.StatusOK, "") +} + +func PodKill(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 404 no such + // 409 has conflict + // 500 internal + var ( + runtime = r.Context().Value("runtime").(*libpod.Runtime) + decoder = r.Context().Value("decoder").(*schema.Decoder) + ) + query := struct { + signal int `schema:"signal"` + }{ + // 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 := mux.Vars(r)["name"] + pod, err := runtime.LookupPod(name) + if err != nil { + utils.PodNotFound(w, name, err) + return + } + podStates, err := pod.Status() + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) + return + } + hasRunning := false + for _, s := range podStates { + if s == define.ContainerStateRunning { + hasRunning = true + break + } + } + if !hasRunning { + msg := fmt.Sprintf("Container %s is not running", pod.ID()) + utils.Error(w, msg, http.StatusConflict, errors.Errorf("cannot kill a pod with no running containers: %s", pod.ID())) + return + } + // TODO How do we differentiate if a signal was sent vs accepting the pod/container default? + _, err = pod.Kill(uint(query.signal)) + if err != nil { + utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + return + } + utils.WriteResponse(w, http.StatusOK, "") +} + +func PodExists(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 404 no such + // 500 internal (needs work) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + name := mux.Vars(r)["name"] + _, err := runtime.LookupPod(name) + if err != nil { + utils.PodNotFound(w, name, err) + return + } + utils.WriteResponse(w, http.StatusOK, "") +} diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go new file mode 100644 index 000000000..ece59a4b6 --- /dev/null +++ b/pkg/api/handlers/libpod/volumes.go @@ -0,0 +1,174 @@ +package libpod + +import ( + "encoding/json" + "net/http" + + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/gorilla/mux" + "github.com/gorilla/schema" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" +) + +func CreateVolume(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 500 internal + var ( + volumeOptions []libpod.VolumeCreateOption + runtime = r.Context().Value("runtime").(*libpod.Runtime) + decoder = r.Context().Value("decoder").(*schema.Decoder) + ) + query := struct { + }{ + // override any golang type defaults + } + input := handlers.VolumeCreateConfig{} + 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 + } + + // decode params from body + 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(input.Name) > 0 { + volumeOptions = append(volumeOptions, libpod.WithVolumeName(input.Name)) + } + if len(input.Driver) > 0 { + volumeOptions = append(volumeOptions, libpod.WithVolumeDriver(input.Driver)) + } + if len(input.Label) > 0 { + volumeOptions = append(volumeOptions, libpod.WithVolumeLabels(input.Label)) + } + if len(input.Opts) > 0 { + parsedOptions, err := shared.ParseVolumeOptions(input.Opts) + if err != nil { + utils.InternalServerError(w, err) + } + volumeOptions = append(volumeOptions, parsedOptions...) + } + vol, err := runtime.NewVolume(r.Context(), volumeOptions...) + if err != nil { + utils.InternalServerError(w, err) + } + utils.WriteResponse(w, http.StatusOK, vol.Name()) +} + +func InspectVolume(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 404 no such + // 500 internal + var ( + runtime = r.Context().Value("runtime").(*libpod.Runtime) + decoder = r.Context().Value("decoder").(*schema.Decoder) + ) + query := struct { + }{ + // 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 := mux.Vars(r)["name"] + vol, err := runtime.GetVolume(name) + if err != nil { + utils.VolumeNotFound(w, name, err) + } + inspect, err := vol.Inspect() + if err != nil { + utils.InternalServerError(w, err) + } + utils.WriteResponse(w, http.StatusOK, inspect) +} + +func ListVolumes(w http.ResponseWriter, r *http.Request) { + //var ( + // runtime = r.Context().Value("runtime").(*libpod.Runtime) + // decoder = r.Context().Value("decoder").(*schema.Decoder) + //) + //query := struct { + // Filter string `json:"filter"` + //}{ + // // 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 + //} + /* + This is all in main in cmd and needs to be extracted from there first. + */ + +} + +func PruneVolumes(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 500 internal + var ( + runtime = r.Context().Value("runtime").(*libpod.Runtime) + decoder = r.Context().Value("decoder").(*schema.Decoder) + ) + query := struct { + }{ + // 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 + } + pruned, errs := runtime.PruneVolumes(r.Context()) + if errs != nil { + if len(errs) > 1 { + for _, err := range errs { + log.Infof("Request Failed(%s): %s", http.StatusText(http.StatusInternalServerError), err.Error()) + } + } + utils.InternalServerError(w, errs[len(errs)-1]) + } + utils.WriteResponse(w, http.StatusOK, pruned) +} + +func RemoveVolume(w http.ResponseWriter, r *http.Request) { + // 200 ok + // 404 no such + // 500 internal + var ( + runtime = r.Context().Value("runtime").(*libpod.Runtime) + decoder = r.Context().Value("decoder").(*schema.Decoder) + ) + query := struct { + Force bool `schema:"force"` + }{ + // 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 := mux.Vars(r)["name"] + vol, err := runtime.LookupVolume(name) + if err != nil { + utils.VolumeNotFound(w, name, err) + } + if err := runtime.RemoveVolume(r.Context(), vol, query.Force); err != nil { + utils.InternalServerError(w, err) + } + utils.WriteResponse(w, http.StatusOK, "") +} |