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/server | |
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/server')
-rw-r--r-- | pkg/api/server/handler_api.go | 37 | ||||
-rw-r--r-- | pkg/api/server/register_auth.go | 11 | ||||
-rw-r--r-- | pkg/api/server/register_containers.go | 889 | ||||
-rw-r--r-- | pkg/api/server/register_distribution.go | 11 | ||||
-rw-r--r-- | pkg/api/server/register_events.go | 32 | ||||
-rw-r--r-- | pkg/api/server/register_healthcheck.go | 13 | ||||
-rw-r--r-- | pkg/api/server/register_images.go | 663 | ||||
-rw-r--r-- | pkg/api/server/register_info.go | 28 | ||||
-rw-r--r-- | pkg/api/server/register_monitor.go | 11 | ||||
-rw-r--r-- | pkg/api/server/register_ping.go | 17 | ||||
-rw-r--r-- | pkg/api/server/register_plugins.go | 11 | ||||
-rw-r--r-- | pkg/api/server/register_pods.go | 24 | ||||
-rw-r--r-- | pkg/api/server/register_swarm.go | 27 | ||||
-rw-r--r-- | pkg/api/server/register_system.go | 11 | ||||
-rw-r--r-- | pkg/api/server/register_version.go | 12 | ||||
-rw-r--r-- | pkg/api/server/register_volumes.go | 17 | ||||
-rw-r--r-- | pkg/api/server/server.go | 189 |
17 files changed, 2003 insertions, 0 deletions
diff --git a/pkg/api/server/handler_api.go b/pkg/api/server/handler_api.go new file mode 100644 index 000000000..4b93998ee --- /dev/null +++ b/pkg/api/server/handler_api.go @@ -0,0 +1,37 @@ +package server + +import ( + "context" + "net/http" + + log "github.com/sirupsen/logrus" +) + +// APIHandler is a wrapper to enhance HandlerFunc's and remove redundant code +func APIHandler(ctx context.Context, h http.HandlerFunc) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Debugf("APIHandler -- Method: %s URL: %s", r.Method, r.URL.String()) + if err := r.ParseForm(); err != nil { + log.Infof("Failed Request: unable to parse form: %q", err) + } + + // TODO: Use ConnContext when ported to go 1.13 + c := context.WithValue(r.Context(), "decoder", ctx.Value("decoder")) + c = context.WithValue(c, "runtime", ctx.Value("runtime")) + c = context.WithValue(c, "shutdownFunc", ctx.Value("shutdownFunc")) + r = r.WithContext(c) + + h(w, r) + + shutdownFunc := r.Context().Value("shutdownFunc").(func() error) + if err := shutdownFunc(); err != nil { + log.Errorf("Failed to shutdown Server in APIHandler(): %s", err.Error()) + } + }) +} + +// VersionedPath prepends the version parsing code +// any handler may override this default when registering URL(s) +func VersionedPath(p string) string { + return "/v{version:[0-9][0-9.]*}" + p +} diff --git a/pkg/api/server/register_auth.go b/pkg/api/server/register_auth.go new file mode 100644 index 000000000..9f312683d --- /dev/null +++ b/pkg/api/server/register_auth.go @@ -0,0 +1,11 @@ +package server + +import ( + "github.com/containers/libpod/pkg/api/handlers" + "github.com/gorilla/mux" +) + +func (s *APIServer) RegisterAuthHandlers(r *mux.Router) error { + r.Handle(VersionedPath("/auth"), APIHandler(s.Context, handlers.UnsupportedHandler)) + return nil +} diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go new file mode 100644 index 000000000..7d80f2f67 --- /dev/null +++ b/pkg/api/server/register_containers.go @@ -0,0 +1,889 @@ +package server + +import ( + "net/http" + + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/generic" + "github.com/containers/libpod/pkg/api/handlers/libpod" + "github.com/gorilla/mux" +) + +func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { + // swagger:operation POST /containers/create containers createContainer + // + // Create a container + // + // --- + // produces: + // - application/json + // parameters: + // - in: query + // name: name + // type: string + // description: container name + // responses: + // '201': + // schema: + // items: + // "$ref": "#/ctrCreateResponse" + // '400': + // description: bad parameter + // schema: + // "$ref": "#/types/ErrorModel" + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '409': + // description: conflict + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/containers/create"), APIHandler(s.Context, generic.CreateContainer)).Methods(http.MethodPost) + // swagger:operation GET /containers/json containers listContainers + // + // List containers + // + // --- + // produces: + // - application/json + // responses: + // '200': + // schema: + // type: array + // items: + // "$ref": "#/types/Container" + // '400': + // description: bad parameter + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/containers/json"), APIHandler(s.Context, generic.ListContainers)).Methods(http.MethodGet) + // swagger:operation POST /containers/prune containers pruneContainers + // + // Prune unused containers + // + // --- + // parameters: + // - in: query + // name: filters + // type: map[string][]string + // description: something + // produces: + // - application/json + // responses: + // '200': + // schema: + // "$ref": "#/types/ContainerPruneReport" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/containers/prune"), APIHandler(s.Context, generic.PruneContainers)).Methods(http.MethodPost) + // swagger:operation DELETE /containers/{nameOrID} containers removeContainer + // + // Delete container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: force + // type: bool + // description: need something + // - in: query + // name: v + // type: bool + // description: need something + // - in: query + // name: link + // type: bool + // description: not supported + // produces: + // - application/json + // responses: + // '200': + // schema: + // type: array + // items: + // "$ref": "#/types/Container" + // '400': + // description: bad parameter + // schema: + // "$ref": "#/types/ErrorModel" + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '409': + // description: conflict + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/containers/{name:..*}"), APIHandler(s.Context, generic.RemoveContainer)).Methods(http.MethodDelete) + // swagger:operation GET /containers/{nameOrID}/json containers getContainer + // + // Inspect Container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "#/types/ContainerJSON" + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/containers/{name:..*}/json"), APIHandler(s.Context, generic.GetContainer)).Methods(http.MethodGet) + // swagger:operation POST /containers/{nameOrID}/kill containers killContainer + // + // Kill Container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: signal + // type: int + // description: signal to be sent to container + // produces: + // - application/json + // responses: + // '204': + // description: no error + // schema: + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '409': + // description: conflict + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/containers/{name:..*}/kill"), APIHandler(s.Context, generic.KillContainer)).Methods(http.MethodPost) + // swagger:operation GET /containers/{nameOrID}/logs containers LogsFromContainer + // + // Get logs from container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: follow + // type: bool + // description: needs description + // - in: query + // name: stdout + // type: bool + // description: needs description + // - in: query + // name: stderr + // type: bool + // description: needs description + // - in: query + // name: since + // type: string + // description: needs description + // - in: query + // name: until + // type: string + // description: needs description + // - in: query + // name: timestamps + // type: bool + // description: needs description + // - in: query + // name: tail + // type: string + // description: needs description + // produces: + // - application/json + // responses: + // '200': + // description: no error + // schema: + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/containers/{name:..*}/logs"), APIHandler(s.Context, generic.LogsFromContainer)).Methods(http.MethodGet) + // swagger:operation POST /containers/{nameOrID}/pause containers pauseContainer + // + // Pause Container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '204': + // description: no error + // schema: + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/containers/{name:..*}/pause"), APIHandler(s.Context, handlers.PauseContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/containers/{name:..*}/rename"), APIHandler(s.Context, handlers.UnsupportedHandler)).Methods(http.MethodPost) + // swagger:operation POST /containers/{nameOrID}/restart containers restartContainer + // + // Restart Container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: t + // type: int + // description: timeout before sending kill signal to container + // produces: + // - application/json + // responses: + // '204': + // description: no error + // schema: + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/containers/{name:..*}/restart"), APIHandler(s.Context, handlers.RestartContainer)).Methods(http.MethodPost) + // swagger:operation POST /containers/{nameOrID}/start containers startContainer + // + // Start a container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: detachKeys + // type: string + // description: needs description + // produces: + // - application/json + // responses: + // '204': + // description: no error + // '304': + // description: container already started + // schema: + // "$ref": "#/types/ErrorModel" + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/containers/{name:..*}/start"), APIHandler(s.Context, handlers.StartContainer)).Methods(http.MethodPost) + // swagger:operation GET /containers/{nameOrID}/stats containers statsContainer + // + // Get stats for a contrainer + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: stream + // type: bool + // description: needs description + // produces: + // - application/json + // responses: + // '200': + // description: no error + // schema: + // "ref": "#/handler/stats" + // '304': + // description: container already started + // schema: + // "$ref": "#/types/ErrorModel" + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/containers/{name:..*}/stats"), APIHandler(s.Context, generic.StatsContainer)).Methods(http.MethodGet) + // swagger:operation POST /containers/{nameOrID}/stop containers stopContainer + // + // Stop a container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: t + // type: int + // description: number of seconds to wait before killing container + // produces: + // - application/json + // responses: + // '204': + // description: no error + // '304': + // description: container already stopped + // schema: + // "$ref": "#/types/ErrorModel" + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/containers/{name:..*}/stop"), APIHandler(s.Context, handlers.StopContainer)).Methods(http.MethodPost) + // swagger:operation GET /containers/{nameOrID}/top containers topContainer + // + // List processes running inside a container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: ps_args + // type: string + // description: arguments to pass to ps such as aux + // produces: + // - application/json + // responses: + // '200': + // description: no error + // schema: + // "ref": "#/types/ContainerTopBody" + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/containers/{name:..*}/top"), APIHandler(s.Context, handlers.TopContainer)).Methods(http.MethodGet) + // swagger:operation POST /containers/{nameOrID}/unpause containers unpauseContainer + // + // Unpause Container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '204': + // description: no error + // schema: + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/containers/{name:..*}/unpause"), APIHandler(s.Context, handlers.UnpauseContainer)).Methods(http.MethodPost) + // swagger:operation POST /containers/{nameOrID}/wait containers waitContainer + // + // Wait on a container to exit + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: condition + // type: string + // description: Wait until the container reaches the given condition + // produces: + // - application/json + // responses: + // '204': + // description: no error + // schema: + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/containers/{name:..*}/wait"), APIHandler(s.Context, generic.WaitContainer)).Methods(http.MethodPost) + + /* + libpod endpoints + */ + + r.HandleFunc(VersionedPath("/libpod/containers/create"), APIHandler(s.Context, libpod.CreateContainer)).Methods(http.MethodPost) + // swagger:operation GET /libpod/containers/json containers listContainers + // + // List containers + // + // --- + // produces: + // - application/json + // responses: + // '200': + // schema: + // type: array + // items: + // "$ref": "#/shared/GetPsContainerOutput" + // '400': + // description: bad parameter + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/libpod/containers/json"), APIHandler(s.Context, libpod.ListContainers)).Methods(http.MethodGet) + // swagger:operation POST /libpod/containers/prune containers pruneContainers + // + // Prune unused containers + // + // --- + // parameters: + // - in: query + // name: force + // type: bool + // description: something + // - in: query + // name: filters + // type: map[string][]string + // description: something + // produces: + // - application/json + // responses: + // '200': + // schema: + // "$ref": "#/types/ContainerPruneReport" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/libpod/containers/prune"), APIHandler(s.Context, libpod.PruneContainers)).Methods(http.MethodPost) + // swagger:operation GET /libpod/containers/showmounted containers showMounterContainers + // + // Show mounted containers + // + // --- + // produces: + // - application/json + // responses: + // '200': + // schema: + // "$ref": "TBD" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/libpod/containers/showmounted"), APIHandler(s.Context, libpod.ShowMountedContainers)).Methods(http.MethodGet) + // swagger:operation DELETE /libpod/containers/json containers removeContainer + // + // Delete container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: force + // type: bool + // description: need something + // - in: query + // name: v + // type: bool + // description: need something + // produces: + // - application/json + // responses: + // '200': + // schema: + // type: array + // items: + // "$ref": "#/types/Container" + // '400': + // description: bad parameter + // schema: + // "$ref": "#/types/ErrorModel" + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '409': + // description: conflict + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}"), APIHandler(s.Context, libpod.RemoveContainer)).Methods(http.MethodDelete) + // swagger:operation GET /libpod/containers/{nameOrID}/json containers getContainer + // + // Inspect Container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: size + // type: bool + // description: display filesystem usage + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "#InspectContainerData" + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/json"), APIHandler(s.Context, libpod.GetContainer)).Methods(http.MethodGet) + // swagger:operation POST /libpod/containers/{nameOrID}/kill containers killContainer + // + // Kill Container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: signal + // type: int + // default: 15 + // description: signal to be sent to container + // produces: + // - application/json + // responses: + // '204': + // description: no error + // schema: + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '409': + // description: conflict + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/kill"), APIHandler(s.Context, libpod.KillContainer)).Methods(http.MethodGet) + // swagger:operation GET /libpod/containers/{nameOrID}/mount containers mountContainer + // + // Mount a container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "string" + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/mount"), APIHandler(s.Context, libpod.LogsFromContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/logs"), APIHandler(s.Context, libpod.LogsFromContainer)).Methods(http.MethodGet) + // swagger:operation POST /libpod/containers/{nameOrID}/pause containers pauseContainer + // + // Pause Container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '204': + // description: no error + // schema: + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/pause"), APIHandler(s.Context, handlers.PauseContainer)).Methods(http.MethodPost) + // swagger:operation POST /libpod/containers/{nameOrID}/restart containers restartContainer + // + // Restart Container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: t + // type: int + // description: timeout before sending kill signal to container + // produces: + // - application/json + // responses: + // '204': + // description: no error + // schema: + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/restart"), APIHandler(s.Context, handlers.RestartContainer)).Methods(http.MethodPost) + // swagger:operation POST /libpod/containers/{nameOrID}/start containers startContainer + // + // Start a container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: detachKeys + // type: string + // description: needs description + // produces: + // - application/json + // responses: + // '204': + // description: no error + // '304': + // description: container already started + // schema: + // "$ref": "#/types/ErrorModel" + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/start"), APIHandler(s.Context, handlers.StartContainer)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/stats"), APIHandler(s.Context, libpod.StatsContainer)).Methods(http.MethodGet) + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/top"), APIHandler(s.Context, handlers.TopContainer)).Methods(http.MethodGet) + // swagger:operation POST /libpod/containers/{nameOrID}/unpause containers unpauseContainer + // + // Unpause Container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '204': + // description: no error + // schema: + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/unpause"), APIHandler(s.Context, handlers.UnpauseContainer)).Methods(http.MethodPost) + // swagger:operation POST /libpod/containers/{nameOrID}/wait containers waitContainer + // + // Wait on a container to exit + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: condition + // type: string + // description: Wait until the container reaches the given condition + // produces: + // - application/json + // responses: + // '204': + // description: no error + // schema: + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/wait"), APIHandler(s.Context, libpod.WaitContainer)).Methods(http.MethodPost) + // swagger:operation POST /libpod/containers/{nameOrID}/exists containers containerExists + // + // Check if container exists + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '204': + // description: no error + // schema: + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/exists"), APIHandler(s.Context, libpod.ContainerExists)).Methods(http.MethodGet) + // swagger:operation POST /libpod/containers/{nameOrID}/stop containers stopContainer + // + // Stop a container + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: t + // type: int + // description: number of seconds to wait before killing container + // produces: + // - application/json + // responses: + // '204': + // description: no error + // '304': + // description: container already stopped + // schema: + // "$ref": "#/types/ErrorModel" + // '404': + // description: no such container + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/stop"), APIHandler(s.Context, handlers.StopContainer)).Methods(http.MethodPost) + return nil +} diff --git a/pkg/api/server/register_distribution.go b/pkg/api/server/register_distribution.go new file mode 100644 index 000000000..23820b4a7 --- /dev/null +++ b/pkg/api/server/register_distribution.go @@ -0,0 +1,11 @@ +package server + +import ( + "github.com/containers/libpod/pkg/api/handlers" + "github.com/gorilla/mux" +) + +func (s *APIServer) RegisterDistributionHandlers(r *mux.Router) error { + r.HandleFunc(VersionedPath("/distribution/{name:..*}/json"), handlers.UnsupportedHandler) + return nil +} diff --git a/pkg/api/server/register_events.go b/pkg/api/server/register_events.go new file mode 100644 index 000000000..d764fdbb4 --- /dev/null +++ b/pkg/api/server/register_events.go @@ -0,0 +1,32 @@ +package server + +import ( + "github.com/containers/libpod/pkg/api/handlers" + "github.com/gorilla/mux" +) + +func (s *APIServer) RegisterEventsHandlers(r *mux.Router) error { + // swagger:operation GET /events system getEvents + // --- + // summary: Returns events filtered on query parameters + // produces: + // - application/json + // parameters: + // - name: since + // in: query + // description: start streaming events from this time + // - name: until + // in: query + // description: stop streaming events later than this + // - name: filters + // in: query + // description: JSON encoded map[string][]string of constraints + // responses: + // "200": + // description: OK + // "500": + // description: Failed + // "$ref": "#/types/errorModel" + r.Handle(VersionedPath("/events"), APIHandler(s.Context, handlers.GetEvents)) + return nil +} diff --git a/pkg/api/server/register_healthcheck.go b/pkg/api/server/register_healthcheck.go new file mode 100644 index 000000000..e4cc145d5 --- /dev/null +++ b/pkg/api/server/register_healthcheck.go @@ -0,0 +1,13 @@ +package server + +import ( + "net/http" + + "github.com/containers/libpod/pkg/api/handlers/libpod" + "github.com/gorilla/mux" +) + +func (s *APIServer) registerHealthCheckHandlers(r *mux.Router) error { + r.Handle(VersionedPath("/libpod/containers/{name:..*}/runhealthcheck"), APIHandler(s.Context, libpod.RunHealthCheck)).Methods(http.MethodGet) + return nil +} diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go new file mode 100644 index 000000000..4ad4409df --- /dev/null +++ b/pkg/api/server/register_images.go @@ -0,0 +1,663 @@ +package server + +import ( + "net/http" + + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/generic" + "github.com/containers/libpod/pkg/api/handlers/libpod" + "github.com/gorilla/mux" +) + +func (s *APIServer) registerImagesHandlers(r *mux.Router) error { + // swagger:operation POST /images/create images createImage + // + // Create an image from an image + // + // --- + // produces: + // - application/json + // parameters: + // - in: query + // name: fromImage + // type: string + // description: needs description + // - in: query + // name: tag + // type: string + // description: needs description + // responses: + // '200': + // schema: + // items: + // "$ref": "TBD" + // '404': + // description: repo or image does not exist + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/images/create"), APIHandler(s.Context, generic.CreateImageFromImage)).Methods(http.MethodPost).Queries("fromImage", "{fromImage}") + // swagger:operation POST /images/create images createImage + // + // Create an image from Source + // + // --- + // produces: + // - application/json + // parameters: + // - in: query + // name: fromSrc + // type: string + // description: needs description + // - in: query + // name: changes + // type: TBD + // description: needs description + // responses: + // '200': + // schema: + // items: + // "$ref": "TBD" + // '404': + // description: repo or image does not exist + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/images/create"), APIHandler(s.Context, generic.CreateImageFromSrc)).Methods(http.MethodPost).Queries("fromSrc", "{fromSrc}") + // swagger:operation GET /images/json images listImages + // + // List Images + // + // --- + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "#/types/ImageSummary" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/images/json"), APIHandler(s.Context, generic.GetImages)).Methods(http.MethodGet) + // swagger:operation POST /images/load images loadImage + // + // Import image + // + // --- + // parameters: + // - in: query + // name: quiet + // type: bool + // description: not supported + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "#/types/ImageSummary" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/images/load"), APIHandler(s.Context, handlers.LoadImage)).Methods(http.MethodPost) + // swagger:operation POST /images/prune images pruneImages + // + // Prune unused images + // + // --- + // parameters: + // - in: query + // name: filters + // type: map[string][]string + // description: not supported + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "#/ImageDeleteResponse" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/images/prune"), APIHandler(s.Context, generic.PruneImages)).Methods(http.MethodPost) + // swagger:operation GET /images/search images searchImages + // + // Search images + // + // --- + // parameters: + // - in: query + // name: term + // type: string + // description: term to search + // - in: query + // name: limit + // type: int + // description: maximum number of results + // - in: query + // name: filters + // type: map[string][]string + // description: TBD + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "#/images.SearchResult" + // description: no error + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/images/search"), APIHandler(s.Context, handlers.SearchImages)).Methods(http.MethodGet) + // swagger:operation DELETE /images/{nameOrID} images removeImage + // + // Remove Image + // + // --- + // parameters: + // - in: query + // name: force + // type: bool + // description: remove the image even if used by containers or has other tags + // - in: query + // name: noprune + // type: bool + // description: not supported + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "TBD" + // description: no error + // '404': + // description: no such image + // schema: + // "$ref": "#/types/ErrorModel" + // '409': + // description: conflict + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/images/{name:..*}"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete) + // swagger:operation GET /images/{nameOrID}/get images exportImage + // + // Export an image + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "TBD" + // description: no error + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/images/{name:..*}/get"), APIHandler(s.Context, generic.ExportImage)).Methods(http.MethodGet) + // swagger:operation GET /images/{nameOrID}/history images imageHistory + // + // History of an image + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "#/types/HistoryResponse" + // '404': + // description: no such image + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/images/{name:..*}/history"), APIHandler(s.Context, handlers.HistoryImage)).Methods(http.MethodGet) + // swagger:operation GET /images/{nameOrID}/json images inspectImage + // + // Inspect an image + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "#/types/imageInspect" + // '404': + // description: no such image + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/images/{name:..*}/json"), APIHandler(s.Context, generic.GetImage)) + // swagger:operation POST /images/{nameOrID}/tag images tagImage + // + // Tag an image + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: repo + // type: string + // description: the repository to tag in + // - in: query + // name: tag + // type: string + // description: the name of the new tag + // produces: + // - application/json + // responses: + // '201': + // description: no error + // '400': + // description: bad parameter + // schema: + // "$ref": "#/types/ErrorModel" + // '404': + // description: no such image + // schema: + // "$ref": "#/types/ErrorModel" + // '409': + // description: conflict + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/images/{name:..*}/tag"), APIHandler(s.Context, handlers.TagImage)).Methods(http.MethodPost) + // swagger:operation POST /commit/ commit commitContainer + // + // Create a new image from a container + // + // --- + // parameters: + // - in: query + // name: container + // type: string + // description: the name or ID of a container + // - in: query + // name: repo + // type: string + // description: the repository name for the created image + // - in: query + // name: tag + // type: string + // description: tag name for the created image + // - in: query + // name: comment + // type: string + // description: commit message + // - in: query + // name: author + // type: string + // description: author of the image + // - in: query + // name: pause + // type: bool + // description: pause the container before committing it + // - in: query + // name: changes + // type: string + // description: instructions to apply while committing in Dockerfile format + // produces: + // - application/json + // responses: + // '201': + // description: no error + // '404': + // description: no such image + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/commit"), APIHandler(s.Context, generic.CommitContainer)).Methods(http.MethodPost) + + /* + libpod endpoints + */ + + // swagger:operation POST /libpod/images/{nameOrID}/exists images imageExists + // + // Check if image exists in local store + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // parameters: + // - in: query + // name: fromImage + // type: string + // description: needs description + // - in: query + // name: tag + // type: string + // description: needs description + // responses: + // '204': + // description: image exists + // '404': + // description: no such image + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/libpod/images/{name:..*}/exists"), APIHandler(s.Context, libpod.ImageExists)) + r.Handle(VersionedPath("/libpod/images/{name:..*}/tree"), APIHandler(s.Context, libpod.ImageTree)) + // swagger:operation GET /libpod/images/{nameOrID}/history images imageHistory + // + // History of an image + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "#/types/HistoryResponse" + // '404': + // description: no such image + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/libpod/images/history"), APIHandler(s.Context, handlers.HistoryImage)).Methods(http.MethodGet) + // swagger:operation GET /libpod/images/json images listImages + // + // List Images + // + // --- + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "#/types/ImageSummary" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/libpod/images/json"), APIHandler(s.Context, libpod.GetImages)).Methods(http.MethodGet) + // swagger:operation POST /libpod/images/load images loadImage + // + // Import image + // + // --- + // parameters: + // - in: query + // name: quiet + // type: bool + // description: not supported + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "#/types/ImageSummary" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/libpod/images/load"), APIHandler(s.Context, handlers.LoadImage)).Methods(http.MethodPost) + // swagger:operation POST /libpod/images/prune images pruneImages + // + // Prune unused images + // + // --- + // parameters: + // - in: query + // name: filters + // type: map[string][]string + // description: image filters + // - in: query + // name: all + // type: bool + // description: prune all images + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "#/ImageDeleteResponse" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/libpod/images/prune"), APIHandler(s.Context, libpod.PruneImages)).Methods(http.MethodPost) + // swagger:operation GET /libpod/images/search images searchImages + // + // Search images + // + // --- + // parameters: + // - in: query + // name: term + // type: string + // description: term to search + // - in: query + // name: limit + // type: int + // description: maximum number of results + // - in: query + // name: filters + // type: map[string][]string + // description: TBD + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "#/images.SearchResult" + // description: no error + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/libpod/images/search"), APIHandler(s.Context, handlers.SearchImages)).Methods(http.MethodGet) + // swagger:operation DELETE /libpod/images/{nameOrID} images removeImage + // + // Remove Image + // + // --- + // parameters: + // - in: query + // name: force + // type: bool + // description: remove the image even if used by containers or has other tags + // - in: query + // name: noprune + // type: bool + // description: not supported + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "TBD" + // description: no error + // '404': + // description: no such image + // schema: + // "$ref": "#/types/ErrorModel" + // '409': + // description: conflict + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/libpod/images/{name:..*}"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete) + // swagger:operation GET /libpod/images/{nameOrID}/get images exportImage + // + // Export an image + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: format + // type: string + // description: format for exported image + // - in: query + // name: compress + // type: bool + // description: use compression on image + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "TBD" + // description: no error + // '404': + // description: no such image + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/libpod/images/{name:..*}/get"), APIHandler(s.Context, libpod.ExportImage)).Methods(http.MethodGet) + // swagger:operation GET /libpod/images/{nameOrID}/json images inspectImage + // + // Inspect an image + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // "$ref": "#/inspect/ImageData" + // '404': + // description: no such image + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/libpod/images/{name:..*}/json"), APIHandler(s.Context, libpod.GetImage)) + // swagger:operation POST /libpod/images/{nameOrID}/tag images tagImage + // + // Tag an image + // + // --- + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: repo + // type: string + // description: the repository to tag in + // - in: query + // name: tag + // type: string + // description: the name of the new tag + // produces: + // - application/json + // responses: + // '201': + // description: no error + // '400': + // description: bad parameter + // schema: + // "$ref": "#/types/ErrorModel" + // '404': + // description: no such image + // schema: + // "$ref": "#/types/ErrorModel" + // '409': + // description: conflict + // schema: + // "$ref": "#/types/ErrorModel" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/libpod/images/{name:..*}/tag"), APIHandler(s.Context, handlers.TagImage)).Methods(http.MethodPost) + + r.Handle(VersionedPath("/build"), APIHandler(s.Context, handlers.BuildImage)).Methods(http.MethodPost) + return nil +} diff --git a/pkg/api/server/register_info.go b/pkg/api/server/register_info.go new file mode 100644 index 000000000..797158553 --- /dev/null +++ b/pkg/api/server/register_info.go @@ -0,0 +1,28 @@ +package server + +import ( + "net/http" + + "github.com/containers/libpod/pkg/api/handlers/generic" + "github.com/gorilla/mux" +) + +func (s *APIServer) registerInfoHandlers(r *mux.Router) error { + // swagger:operation GET /info libpod getInfo + // + // Returns information on the system and libpod configuration + // + // --- + // produces: + // - application/json + // responses: + // '200': + // schema: + // "$ref": "#/types/Info" + // '500': + // description: unexpected error + // schema: + // "$ref": "#/types/ErrorModel" + r.Handle(VersionedPath("/info"), APIHandler(s.Context, generic.GetInfo)).Methods(http.MethodGet) + return nil +} diff --git a/pkg/api/server/register_monitor.go b/pkg/api/server/register_monitor.go new file mode 100644 index 000000000..e6c235419 --- /dev/null +++ b/pkg/api/server/register_monitor.go @@ -0,0 +1,11 @@ +package server + +import ( + "github.com/containers/libpod/pkg/api/handlers" + "github.com/gorilla/mux" +) + +func (s *APIServer) RegisterMonitorHandlers(r *mux.Router) error { + r.Handle(VersionedPath("/monitor"), APIHandler(s.Context, handlers.UnsupportedHandler)) + return nil +} diff --git a/pkg/api/server/register_ping.go b/pkg/api/server/register_ping.go new file mode 100644 index 000000000..4956f9822 --- /dev/null +++ b/pkg/api/server/register_ping.go @@ -0,0 +1,17 @@ +package server + +import ( + "net/http" + + "github.com/containers/libpod/pkg/api/handlers/generic" + "github.com/gorilla/mux" +) + +func (s *APIServer) registerPingHandlers(r *mux.Router) error { + r.Handle("/_ping", APIHandler(s.Context, generic.PingGET)).Methods(http.MethodGet) + r.Handle("/_ping", APIHandler(s.Context, generic.PingHEAD)).Methods("HEAD") + + // libpod + r.Handle("/libpod/_ping", APIHandler(s.Context, generic.PingGET)).Methods(http.MethodGet) + return nil +} diff --git a/pkg/api/server/register_plugins.go b/pkg/api/server/register_plugins.go new file mode 100644 index 000000000..7fd6b9c4c --- /dev/null +++ b/pkg/api/server/register_plugins.go @@ -0,0 +1,11 @@ +package server + +import ( + "github.com/containers/libpod/pkg/api/handlers" + "github.com/gorilla/mux" +) + +func (s *APIServer) RegisterPluginsHandlers(r *mux.Router) error { + r.Handle(VersionedPath("/plugins"), APIHandler(s.Context, handlers.UnsupportedHandler)) + return nil +} diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go new file mode 100644 index 000000000..b5915da98 --- /dev/null +++ b/pkg/api/server/register_pods.go @@ -0,0 +1,24 @@ +package server + +import ( + "net/http" + + "github.com/containers/libpod/pkg/api/handlers/libpod" + "github.com/gorilla/mux" +) + +func (s *APIServer) registerPodsHandlers(r *mux.Router) error { + r.Handle(VersionedPath("/libpod/pods/json"), APIHandler(s.Context, libpod.Pods)).Methods(http.MethodGet) + r.Handle(VersionedPath("/libpod/pods/create"), APIHandler(s.Context, libpod.PodCreate)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/pods/prune"), APIHandler(s.Context, libpod.PodPrune)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/pods/{name:..*}"), APIHandler(s.Context, libpod.PodDelete)).Methods(http.MethodDelete) + r.Handle(VersionedPath("/libpod/pods/{name:..*}"), APIHandler(s.Context, libpod.PodInspect)).Methods(http.MethodGet) + r.Handle(VersionedPath("/libpod/pods/{name:..*}/exists"), APIHandler(s.Context, libpod.PodExists)).Methods(http.MethodGet) + r.Handle(VersionedPath("/libpod/pods/{name:..*}/kill"), APIHandler(s.Context, libpod.PodKill)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/pods/{name:..*}/pause"), APIHandler(s.Context, libpod.PodPause)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/pods/{name:..*}/restart"), APIHandler(s.Context, libpod.PodRestart)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/pods/{name:..*}/start"), APIHandler(s.Context, libpod.PodStart)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/pods/{name:..*}/stop"), APIHandler(s.Context, libpod.PodStop)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/pods/{name:..*}/unpause"), APIHandler(s.Context, libpod.PodUnpause)).Methods(http.MethodPost) + return nil +} diff --git a/pkg/api/server/register_swarm.go b/pkg/api/server/register_swarm.go new file mode 100644 index 000000000..61c0b2d83 --- /dev/null +++ b/pkg/api/server/register_swarm.go @@ -0,0 +1,27 @@ +package server + +import ( + "errors" + "github.com/containers/libpod/pkg/api/handlers/utils" + "net/http" + + "github.com/gorilla/mux" + "github.com/sirupsen/logrus" +) + +func (s *APIServer) RegisterSwarmHandlers(r *mux.Router) error { + r.PathPrefix("/v{version:[0-9.]+}/configs/").HandlerFunc(noSwarm) + r.PathPrefix("/v{version:[0-9.]+}/nodes/").HandlerFunc(noSwarm) + r.PathPrefix("/v{version:[0-9.]+}/secrets/").HandlerFunc(noSwarm) + r.PathPrefix("/v{version:[0-9.]+}/services/").HandlerFunc(noSwarm) + r.PathPrefix("/v{version:[0-9.]+}/swarm/").HandlerFunc(noSwarm) + r.PathPrefix("/v{version:[0-9.]+}/tasks/").HandlerFunc(noSwarm) + return nil +} + +// noSwarm returns http.StatusServiceUnavailable rather than something like http.StatusInternalServerError, +// this allows the client to decide if they still can talk to us +func noSwarm(w http.ResponseWriter, r *http.Request) { + logrus.Errorf("%s is not a podman supported service", r.URL.String()) + utils.Error(w, "node is not part of a swarm", http.StatusServiceUnavailable, errors.New("Podman does not support service: "+r.URL.String())) +} diff --git a/pkg/api/server/register_system.go b/pkg/api/server/register_system.go new file mode 100644 index 000000000..f0eaeffd2 --- /dev/null +++ b/pkg/api/server/register_system.go @@ -0,0 +1,11 @@ +package server + +import ( + "github.com/containers/libpod/pkg/api/handlers/generic" + "github.com/gorilla/mux" +) + +func (s *APIServer) registerSystemHandlers(r *mux.Router) error { + r.Handle(VersionedPath("/system/df"), APIHandler(s.Context, generic.GetDiskUsage)) + return nil +} diff --git a/pkg/api/server/register_version.go b/pkg/api/server/register_version.go new file mode 100644 index 000000000..94216b1b6 --- /dev/null +++ b/pkg/api/server/register_version.go @@ -0,0 +1,12 @@ +package server + +import ( + "github.com/containers/libpod/pkg/api/handlers/generic" + "github.com/gorilla/mux" +) + +func (s *APIServer) registerVersionHandlers(r *mux.Router) error { + r.Handle("/version", APIHandler(s.Context, generic.VersionHandler)) + r.Handle(VersionedPath("/version"), APIHandler(s.Context, generic.VersionHandler)) + return nil +} diff --git a/pkg/api/server/register_volumes.go b/pkg/api/server/register_volumes.go new file mode 100644 index 000000000..3a9cf8c2d --- /dev/null +++ b/pkg/api/server/register_volumes.go @@ -0,0 +1,17 @@ +package server + +import ( + "net/http" + + "github.com/containers/libpod/pkg/api/handlers/libpod" + "github.com/gorilla/mux" +) + +func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { + r.Handle("/libpod/volumes/create", APIHandler(s.Context, libpod.CreateVolume)).Methods(http.MethodPost) + r.Handle("/libpod/volumes/json", APIHandler(s.Context, libpod.ListVolumes)).Methods(http.MethodGet) + r.Handle("/libpod/volumes/prune", APIHandler(s.Context, libpod.PruneVolumes)).Methods(http.MethodPost) + r.Handle("/libpod/volumes/{name:..*}/json", APIHandler(s.Context, libpod.InspectVolume)).Methods(http.MethodGet) + r.Handle("/libpod/volumes/{name:..*}", APIHandler(s.Context, libpod.RemoveVolume)).Methods(http.MethodDelete) + return nil +} diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go new file mode 100644 index 000000000..717c7a876 --- /dev/null +++ b/pkg/api/server/server.go @@ -0,0 +1,189 @@ +// Package serviceapi Provides a Container compatible interface. +// +// This documentation describes the HTTP LibPod interface +// +// Schemes: http, https +// Host: podman.io +// BasePath: / +// Version: 0.0.1 +// License: Apache-2.0 https://opensource.org/licenses/Apache-2.0 +// Contact: Podman <podman@lists.podman.io> https://podman.io/community/ +// +// Consumes: +// - application/json +// - application/x-tar +// +// Produces: +// - application/json +// - text/plain +// - text/html +// +// tags: +// - name: "Containers" +// description: manage containers +// - name: "Images" +// description: manage images +// - name: "System" +// description: manage system resources +// +// swagger:meta +package server + +import ( + "context" + "net" + "net/http" + "os" + "os/signal" + "strings" + "time" + + "github.com/containers/libpod/libpod" + "github.com/coreos/go-systemd/activation" + "github.com/gorilla/mux" + "github.com/gorilla/schema" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" +) + +type APIServer struct { + http.Server // Where the HTTP work happens + *schema.Decoder // Decoder for Query parameters to structs + context.Context // Context for graceful server shutdown + *libpod.Runtime // Where the real work happens + net.Listener // mux for routing HTTP API calls to libpod routines + context.CancelFunc // Stop APIServer + *time.Timer // Hold timer for sliding window + time.Duration // Duration of client access sliding window +} + +// NewServer will create and configure a new API HTTP server +func NewServer(runtime *libpod.Runtime) (*APIServer, error) { + listeners, err := activation.Listeners() + if err != nil { + return nil, errors.Wrap(err, "Cannot retrieve file descriptors from systemd") + } + if len(listeners) != 1 { + return nil, errors.Errorf("Wrong number of file descriptors from systemd for socket activation (%d != 1)", len(listeners)) + } + + quit := make(chan os.Signal, 1) + signal.Notify(quit) + + router := mux.NewRouter() + + server := APIServer{ + Server: http.Server{ + Handler: router, + ReadHeaderTimeout: 20 * time.Second, + ReadTimeout: 20 * time.Second, + WriteTimeout: 2 * time.Minute, + }, + Decoder: schema.NewDecoder(), + Context: nil, + Runtime: runtime, + Listener: listeners[0], + CancelFunc: nil, + Duration: 300 * time.Second, + } + server.Timer = time.AfterFunc(server.Duration, func() { + if err := server.Shutdown(); err != nil { + log.Errorf("unable to shutdown server: %q", err) + } + }) + + ctx, cancelFn := context.WithCancel(context.Background()) + + // TODO: Use ConnContext when ported to go 1.13 + ctx = context.WithValue(ctx, "decoder", server.Decoder) + ctx = context.WithValue(ctx, "runtime", runtime) + ctx = context.WithValue(ctx, "shutdownFunc", server.Shutdown) + server.Context = ctx + + server.CancelFunc = cancelFn + server.Decoder.IgnoreUnknownKeys(true) + + router.NotFoundHandler = http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + // We can track user errors... + log.Infof("Failed Request: (%d:%s) for %s:'%s'", http.StatusNotFound, http.StatusText(http.StatusNotFound), r.Method, r.URL.String()) + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + }, + ) + + for _, fn := range []func(*mux.Router) error{ + server.RegisterAuthHandlers, + server.RegisterContainersHandlers, + server.RegisterDistributionHandlers, + server.registerHealthCheckHandlers, + server.registerImagesHandlers, + server.registerInfoHandlers, + server.RegisterMonitorHandlers, + server.registerPingHandlers, + server.RegisterPluginsHandlers, + server.registerPodsHandlers, + server.RegisterSwarmHandlers, + server.registerSystemHandlers, + server.registerVersionHandlers, + server.registerVolumeHandlers, + } { + if err := fn(router); err != nil { + return nil, err + } + } + + if log.IsLevelEnabled(log.DebugLevel) { + router.Walk(func(route *mux.Route, r *mux.Router, ancestors []*mux.Route) error { // nolint + path, err := route.GetPathTemplate() + if err != nil { + path = "" + } + methods, err := route.GetMethods() + if err != nil { + methods = []string{} + } + log.Debugf("Methods: %s Path: %s", strings.Join(methods, ", "), path) + return nil + }) + } + + return &server, nil +} + +// Serve starts responding to HTTP requests +func (s *APIServer) Serve() error { + defer s.CancelFunc() + + err := s.Server.Serve(s.Listener) + if err != nil && err != http.ErrServerClosed { + return errors.Wrap(err, "Failed to start APIServer") + } + + return nil +} + +// Shutdown is a clean shutdown waiting on existing clients +func (s *APIServer) Shutdown() error { + // We're still in the sliding service window + if s.Timer.Stop() { + s.Timer.Reset(s.Duration) + return nil + } + + // We've been idle for the service window, really shutdown + go func() { + err := s.Server.Shutdown(s.Context) + if err != nil && err != context.Canceled { + log.Errorf("Failed to cleanly shutdown APIServer: %s", err.Error()) + } + }() + + // Wait for graceful shutdown vs. just killing connections and dropping data + <-s.Context.Done() + return nil +} + +// Close immediately stops responding to clients and exits +func (s *APIServer) Close() error { + return s.Server.Close() +} |