diff options
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 | 718 | ||||
-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 | 568 | ||||
-rw-r--r-- | pkg/api/server/register_info.go | 24 | ||||
-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 | 239 | ||||
-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 | 77 | ||||
-rw-r--r-- | pkg/api/server/server.go | 189 | ||||
-rw-r--r-- | pkg/api/server/swagger.go | 138 |
18 files changed, 2146 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..711aecc84 --- /dev/null +++ b/pkg/api/server/register_containers.go @@ -0,0 +1,718 @@ +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 + // --- + // summary: Create a container + // produces: + // - application/json + // parameters: + // - in: query + // name: name + // type: string + // description: container name + // responses: + // '201': + // $ref: "#/responses/ContainerCreateResponse" + // '400': + // "$ref": "#/responses/BadParamError" + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '409': + // "$ref": "#/responses/ConflictError" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/create"), APIHandler(s.Context, generic.CreateContainer)).Methods(http.MethodPost) + // swagger:operation GET /containers/json containers listContainers + // --- + // summary: List containers + // description: Returns a list of containers + // parameters: + // - in: query + // name: filters + // type: string + // description: | + // Returns a list of containers. + // - ancestor=(<image-name>[:<tag>], <image id>, or <image@digest>) + // - before=(<container id> or <container name>) + // - expose=(<port>[/<proto>]|<startport-endport>/[<proto>]) + // - exited=<int> containers with exit code of <int> + // - health=(starting|healthy|unhealthy|none) + // - id=<ID> a container's ID + // - is-task=(true|false) + // - label=key or label="key=value" of a container label + // - name=<name> a container's name + // - network=(<network id> or <network name>) + // - publish=(<port>[/<proto>]|<startport-endport>/[<proto>]) + // - since=(<container id> or <container name>) + // - status=(created|restarting|running|removing|paused|exited|dead) + // - volume=(<volume name> or <mount point destination>) + // produces: + // - application/json + // responses: + // '200': + // "$ref": "#/responses/DocsListContainer" + // '400': + // "$ref": "#/responses/BadParamError" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/json"), APIHandler(s.Context, generic.ListContainers)).Methods(http.MethodGet) + // swagger:operation POST /containers/prune containers pruneContainers + // --- + // summary: Delete stopped containers + // description: Remove containers not in use + // parameters: + // - in: query + // name: filters + // type: string + // description: | + // Filters to process on the prune list, encoded as JSON (a `map[string][]string`). Available filters: + // - `until=<timestamp>` Prune containers created before this timestamp. The `<timestamp>` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon machine’s time. + // - `label` (`label=<key>`, `label=<key>=<value>`, `label!=<key>`, or `label!=<key>=<value>`) Prune containers with (or without, in case `label!=...` is used) the specified labels. + // produces: + // - application/json + // responses: + // '200': + // "$ref": "#/responses/DocsContainerPruneReport" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/prune"), APIHandler(s.Context, generic.PruneContainers)).Methods(http.MethodPost) + // swagger:operation DELETE /containers/{nameOrID} containers removeContainer + // --- + // summary: Remove a container + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: force + // type: bool + // default: false + // description: If the container is running, kill it before removing it. + // - in: query + // name: v + // type: bool + // default: false + // description: Remove the volumes associated with the container. + // - in: query + // name: link + // type: bool + // description: not supported + // produces: + // - application/json + // responses: + // '204': + // description: no error + // '400': + // "$ref": "#/responses/BadParamError" + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '409': + // "$ref": "#/responses/ConflictError" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/{name:..*}"), APIHandler(s.Context, generic.RemoveContainer)).Methods(http.MethodDelete) + // swagger:operation GET /containers/{nameOrID}/json containers getContainer + // --- + // summary: Inspect container + // description: Return low-level information about a container. + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or id of the container + // - in: query + // name: size + // type: bool + // default: false + // description: include the size of the container + // produces: + // - application/json + // responses: + // '200': + // "$ref": "#/responses/DocsContainerInspectResponse" + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/{name:..*}/json"), APIHandler(s.Context, generic.GetContainer)).Methods(http.MethodGet) + // swagger:operation post /containers/{nameOrID}/kill containers killcontainer + // --- + // summary: Kill container + // description: Signal to send to the container as an integer or string (e.g. SIGINT) + // 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 + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '409': + // "$ref": "#/responses/ConflictError" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/{name:..*}/kill"), APIHandler(s.Context, generic.KillContainer)).Methods(http.MethodPost) + // swagger:operation GET /containers/{nameOrID}/logs containers LogsFromContainer + // --- + // summary: Get container logs + // description: Get stdout and stderr logs from a container. + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: follow + // type: bool + // description: Keep connection after returning logs. + // - in: query + // name: stdout + // type: bool + // description: not supported + // - in: query + // name: stderr + // type: bool + // description: not supported? + // - in: query + // name: since + // type: string + // description: Only return logs since this time, as a UNIX timestamp + // - in: query + // name: until + // type: string + // description: Only return logs before this time, as a UNIX timestamp + // - in: query + // name: timestamps + // type: bool + // default: false + // description: Add timestamps to every log line + // - in: query + // name: tail + // type: string + // description: Only return this number of log lines from the end of the logs + // default: all + // produces: + // - application/json + // responses: + // '200': + // description: logs returned as a stream in response body. + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/{name:..*}/logs"), APIHandler(s.Context, generic.LogsFromContainer)).Methods(http.MethodGet) + // swagger:operation POST /containers/{nameOrID}/pause containers pauseContainer + // --- + // summary: Pause container + // description: Use the cgroups freezer to suspend all processes in a container. + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '204': + // description: no error + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + 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 + // --- + // summary: 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 + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/{name:..*}/restart"), APIHandler(s.Context, handlers.RestartContainer)).Methods(http.MethodPost) + // swagger:operation POST /containers/{nameOrID}/start containers startContainer + // --- + // summary: 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': + // "$ref": "#/responses/ContainerAlreadyStartedError" + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/{name:..*}/start"), APIHandler(s.Context, handlers.StartContainer)).Methods(http.MethodPost) + // swagger:operation GET /containers/{nameOrID}/stats containers statsContainer + // --- + // summary: Get stats for a container + // description: This returns a live stream of a container’s resource usage statistics. + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // - in: query + // name: stream + // type: bool + // default: true + // description: Stream the output + // produces: + // - application/json + // responses: + // '200': + // description: no error + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/{name:..*}/stats"), APIHandler(s.Context, generic.StatsContainer)).Methods(http.MethodGet) + // swagger:operation POST /containers/{nameOrID}/stop containers stopContainer + // --- + // summary: 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': + // "$ref": "#/responses/ContainerAlreadyStoppedError" + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/{name:..*}/stop"), APIHandler(s.Context, handlers.StopContainer)).Methods(http.MethodPost) + // swagger:operation GET /containers/{nameOrID}/top containers topContainer + // --- + // summary: 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': + // "ref": "#/responses/DockerTopResponse" + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/{name:..*}/top"), APIHandler(s.Context, handlers.TopContainer)).Methods(http.MethodGet) + // swagger:operation POST /containers/{nameOrID}/unpause containers unpauseContainer + // --- + // summary: Unpause container + // description: Resume a paused container + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '204': + // description: no error + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/{name:..*}/unpause"), APIHandler(s.Context, handlers.UnpauseContainer)).Methods(http.MethodPost) + // swagger:operation POST /containers/{nameOrID}/wait containers waitContainer + // --- + // summary: Wait on a container to exit + // description: Block until a container stops, then returns the exit code. + // 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: + // '200': + // $ref: "#/responses/ContainerWaitResponse" + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + 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 libpodListContainers + // --- + // summary: List containers + // description: Returns a list of containers + // produces: + // - application/json + // responses: + // '200': + // schema: + // type: array + // items: + // "$ref": "#/responses/LibpodListContainersResponse" + // '400': + // "$ref": "#/responses/BadParamError" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/json"), APIHandler(s.Context, libpod.ListContainers)).Methods(http.MethodGet) + // swagger:operation POST /libpod/containers/prune containers libpodPruneContainers + // --- + // summary: Prune unused containers + // description: Remove stopped and exited containers + // parameters: + // - in: query + // name: force + // type: bool + // description: something + // - in: query + // name: filters + // type: string + // description: | + // Filters to process on the prune list, encoded as JSON (a `map[string][]string`). Available filters: + // - `until=<timestamp>` Prune containers created before this timestamp. The `<timestamp>` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon machine’s time. + // - `label` (`label=<key>`, `label=<key>=<value>`, `label!=<key>`, or `label!=<key>=<value>`) Prune containers with (or without, in case `label!=...` is used) the specified labels. + // produces: + // - application/json + // responses: + // '200': + // description: to be determined + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/prune"), APIHandler(s.Context, libpod.PruneContainers)).Methods(http.MethodPost) + // swagger:operation GET /libpod/containers/showmounted containers showMounterContainers + // --- + // summary: Show mounted containers + // description: Lists all mounted containers mount points + // produces: + // - application/json + // responses: + // '200': + // description: mounted containers + // schema: + // type: object + // additionalProperties: + // type: string + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/showmounted"), APIHandler(s.Context, libpod.ShowMountedContainers)).Methods(http.MethodGet) + // swagger:operation DELETE /libpod/containers/json containers libpodRemoveContainer + // --- + // summary: 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: delete volumes + // produces: + // - application/json + // responses: + // '204': + // description: no error + // '400': + // "$ref": "#/responses/BadParamError" + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '409': + // "$ref": "#/responses/ConflictError" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}"), APIHandler(s.Context, libpod.RemoveContainer)).Methods(http.MethodDelete) + // swagger:operation GET /libpod/containers/{nameOrID}/json containers libpodGetContainer + // --- + // summary: Inspect container + // description: Return low-level information about a 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': + // "$ref": "#/responses/LibpodInspectContainerResponse" + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/json"), APIHandler(s.Context, libpod.GetContainer)).Methods(http.MethodGet) + // swagger:operation POST /libpod/containers/{nameOrID}/kill containers libpodKillContainer + // --- + // summary: Kill container + // description: send a signal to a container, defaults to killing the 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 + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '409': + // "$ref": "#/responses/ConflictError" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/kill"), APIHandler(s.Context, libpod.KillContainer)).Methods(http.MethodGet) + // swagger:operation GET /libpod/containers/{nameOrID}/mount containers mountContainer + // --- + // summary: Mount a container + // description: Mount a container to the filesystem + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '200': + // description: mounted container + // schema: + // description: id + // type: string + // example: 3c784de79b791b4ebd3ac55e511f97fedc042328499554937a3f8bfd9c1a2cb8 + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/mount"), APIHandler(s.Context, libpod.MountContainer)).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 libpodPauseContainer + // --- + // summary: Pause a container + // description: Use the cgroups freezer to suspend all processes in a container. + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '204': + // description: no error + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/pause"), APIHandler(s.Context, handlers.PauseContainer)).Methods(http.MethodPost) + // swagger:operation POST /libpod/containers/{nameOrID}/restart containers libpodRestartContainer + // --- + // summary: Restart a 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 + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/restart"), APIHandler(s.Context, handlers.RestartContainer)).Methods(http.MethodPost) + // swagger:operation POST /libpod/containers/{nameOrID}/start containers libpodStartContainer + // --- + // summary: 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': + // "$ref": "#/responses/ContainerAlreadyStartedError" + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + 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 libpodUnpauseContainer + // --- + // summary: 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 + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/unpause"), APIHandler(s.Context, handlers.UnpauseContainer)).Methods(http.MethodPost) + // swagger:operation POST /libpod/containers/{nameOrID}/wait containers libpodWaitContainer + // --- + // summary: 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 + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/wait"), APIHandler(s.Context, libpod.WaitContainer)).Methods(http.MethodPost) + // swagger:operation POST /libpod/containers/{nameOrID}/exists containers containerExists + // --- + // summary: Check if container exists + // description: Quick way to determine if a container exists by name or ID + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '204': + // description: container exists + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/exists"), APIHandler(s.Context, libpod.ContainerExists)).Methods(http.MethodGet) + // swagger:operation POST /libpod/containers/{nameOrID}/stop containers libpodStopContainer + // --- + // summary: 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': + // "$ref": "#/responses/ContainerAlreadyStoppedError" + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + 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..56cf96de1 --- /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": "#/responses/InternalError" + 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..916534fc7 --- /dev/null +++ b/pkg/api/server/register_images.go @@ -0,0 +1,568 @@ +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 compat_images createImage + // + // --- + // summary: Create an image from an image + // description: Create an image by either pulling it from a registry or importing it. + // 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: "to be determined" + // '404': + // description: repo or image does not exist + // schema: + // $ref: "#/responses/InternalError" + // '500': + // description: unexpected error + // schema: + // $ref: '#/responses/GenericError' + r.Handle(VersionedPath("/images/create"), APIHandler(s.Context, generic.CreateImageFromImage)).Methods(http.MethodPost).Queries("fromImage", "{fromImage}") + // swagger:operation POST /images/create compat_images createImage + // --- + // summary: Create an image from Source + // description: Create an image by either pulling it from a registry or importing it. + // produces: + // - application/json + // parameters: + // - in: query + // name: fromSrc + // type: string + // description: needs description + // - in: query + // name: changes + // type: to be determined + // description: needs description + // responses: + // '200': + // schema: + // items: + // $ref: "to be determined" + // '404': + // description: repo or image does not exist + // schema: + // $ref: "#/responses/InternalError" + // '500': + // description: unexpected error + // schema: + // $ref: '#/responses/GenericError' + r.Handle(VersionedPath("/images/create"), APIHandler(s.Context, generic.CreateImageFromSrc)).Methods(http.MethodPost).Queries("fromSrc", "{fromSrc}") + // swagger:operation GET /images/json compat_images listImages + // --- + // summary: List Images + // description: Returns a list of images on the server. Note that it uses a different, smaller representation of an image than inspecting a single image. + // produces: + // - application/json + // responses: + // '200': + // schema: + // type: array + // items: + // schema: + // $ref: "#/responses/DockerImageSummary" + // '500': + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/images/json"), APIHandler(s.Context, generic.GetImages)).Methods(http.MethodGet) + // swagger:operation POST /images/load compat_images loadImage + // + // --- + // summary: Import image + // description: Load a set of images and tags into a repository. + // parameters: + // - in: query + // name: quiet + // type: bool + // description: not supported + // - in: body + // description: tarball of container image + // type: string + // format: binary + // produces: + // - application/json + // responses: + // '200': + // description: no error + // '500': + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/images/load"), APIHandler(s.Context, handlers.LoadImage)).Methods(http.MethodPost) + // swagger:operation POST /images/prune compat_images pruneImages + // --- + // summary: Prune unused images + // description: Remove images from local storage that are not being used by a container + // parameters: + // - in: query + // name: filters + // type: string + // description: | + // filters to apply to image pruning, encoded as JSON (map[string][]string). Available filters: + // - `dangling=<boolean>` When set to `true` (or `1`), prune only + // unused *and* untagged images. When set to `false` + // (or `0`), all unused images are pruned. + // - `until=<string>` Prune images created before this timestamp. The `<timestamp>` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon machine’s time. + // - `label` (`label=<key>`, `label=<key>=<value>`, `label!=<key>`, or `label!=<key>=<value>`) Prune images with (or without, in case `label!=...` is used) the specified labels. + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // $ref: "#/responses/DocsImageDeleteResponse" + // '500': + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/images/prune"), APIHandler(s.Context, generic.PruneImages)).Methods(http.MethodPost) + // swagger:operation GET /images/search compat_images searchImages + // --- + // summary: Search images + // description: Search registries for an image + // 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: string + // description: | + // A JSON encoded value of the filters (a `map[string][]string`) to process on the images list. Available filters: + // - `is-automated=(true|false)` + // - `is-official=(true|false)` + // - `stars=<number>` Matches images that has at least 'number' stars. + // produces: + // - application/json + // responses: + // '200': + // $ref: "#/responses/DocsSearchResponse" + // '500': + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/images/search"), APIHandler(s.Context, handlers.SearchImages)).Methods(http.MethodGet) + // swagger:operation DELETE /images/{nameOrID} compat_images removeImage + // --- + // summary: Remove Image + // description: Delete an image from local storage + // 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. will be logged as an invalid parameter if enabled + // produces: + // - application/json + // responses: + // '200': + // $ref: "#/responses/DocsImageDeleteResponse" + // '400': + // $ref: '#/responses/BadParamError' + // '409': + // $ref: '#/responses/ConflictError' + // '500': + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/images/{name:..*}"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete) + // swagger:operation GET /images/{nameOrID}/get compat_images exportImage + // --- + // summary: Export an image + // description: Export an image in tarball format + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '200': + // description: no error + // schema: + // type: string + // format: binary + // '500': + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/images/{name:..*}/get"), APIHandler(s.Context, generic.ExportImage)).Methods(http.MethodGet) + // swagger:operation GET /images/{nameOrID}/history compat_images imageHistory + // --- + // summary: History of an image + // description: Return parent layers of an image. + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '200': + // $ref: "#/responses/DocsHistory" + // '404': + // $ref: "#/responses/NoSuchImage" + // '500': + // $ref: "#/responses/InternalError" + r.Handle(VersionedPath("/images/{name:..*}/history"), APIHandler(s.Context, handlers.HistoryImage)).Methods(http.MethodGet) + // swagger:operation GET /images/{nameOrID}/json compat_images inspectImage + // --- + // summary: Inspect an image + // description: Return low-level information about an image. + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '200': + // $ref: "#/responses/DocsImageInspect" + // '404': + // $ref: "#/responses/NoSuchImage" + // '500': + // $ref: "#/responses/InternalError" + r.Handle(VersionedPath("/images/{name:..*}/json"), APIHandler(s.Context, generic.GetImage)) + // swagger:operation POST /images/{nameOrID}/tag compat_images tagImage + // --- + // summary: Tag an image + // description: Tag an image so that it becomes part of a repository. + // 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: + // $ref: '#/responses/BadParamError' + // 404: + // $ref: '#/responses/NoSuchImage' + // 409: + // $ref: '#/responses/ConflictError' + // 500: + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/images/{name:..*}/tag"), APIHandler(s.Context, handlers.TagImage)).Methods(http.MethodPost) + // swagger:operation POST /commit/ compat_commit commitContainer + // --- + // summary: 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': + // $ref: '#/responses/NoSuchImage' + // '500': + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/commit"), APIHandler(s.Context, generic.CommitContainer)).Methods(http.MethodPost) + + /* + libpod endpoints + */ + + // swagger:operation POST /libpod/images/{nameOrID}/exists images libpodImageExists + // --- + // summary: Image exists + // description: 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 + // responses: + // '204': + // description: image exists + // '404': + // $ref: '#/responses/NoSuchImage' + // '500': + // $ref: '#/responses/InternalError' + 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 libpodImageHistory + // --- + // summary: History of an image + // description: Return parent layers 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: "#/responses/HistoryResponse" + // '404': + // $ref: '#/responses/NoSuchImage' + // '500': + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/libpod/images/history"), APIHandler(s.Context, handlers.HistoryImage)).Methods(http.MethodGet) + // swagger:operation GET /libpod/images/json images libpodListImages + // --- + // summary: List Images + // description: Returns a list of images on the server + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // $ref: "#/responses/DockerImageSummary" + // '500': + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/libpod/images/json"), APIHandler(s.Context, libpod.GetImages)).Methods(http.MethodGet) + // swagger:operation POST /libpod/images/load images libpodLoadImage + // --- + // summary: Import image + // description: Load a set of images and tags into a repository. + // parameters: + // - in: query + // name: quiet + // type: bool + // description: not supported + // - in: body + // description: tarball of container image + // type: string + // format: binary + // produces: + // - application/json + // responses: + // '200': + // description: no error + // '500': + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/libpod/images/load"), APIHandler(s.Context, handlers.LoadImage)).Methods(http.MethodPost) + // swagger:operation POST /libpod/images/prune images libpodPruneImages + // --- + // summary: Prune unused images + // description: Remove images that are not being used by a container + // parameters: + // - in: query + // name: filters + // type: string + // description: | + // filters to apply to image pruning, encoded as JSON (map[string][]string). Available filters: + // - `dangling=<boolean>` When set to `true` (or `1`), prune only + // unused *and* untagged images. When set to `false` + // (or `0`), all unused images are pruned. + // - `until=<string>` Prune images created before this timestamp. The `<timestamp>` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon machine’s time. + // - `label` (`label=<key>`, `label=<key>=<value>`, `label!=<key>`, or `label!=<key>=<value>`) Prune images with (or without, in case `label!=...` is used) the specified labels. + // - in: query + // name: all + // type: bool + // description: prune all images + // produces: + // - application/json + // responses: + // '200': + // items: + // $ref: "#/responses/DocsImageDeleteResponse" + // '500': + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/libpod/images/prune"), APIHandler(s.Context, libpod.PruneImages)).Methods(http.MethodPost) + // swagger:operation GET /libpod/images/search images libpodSearchImages + // --- + // summary: Search images + // description: Search registries for 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: string + // description: | + // A JSON encoded value of the filters (a `map[string][]string`) to process on the images list. Available filters: + // - `is-automated=(true|false)` + // - `is-official=(true|false)` + // - `stars=<number>` Matches images that has at least 'number' stars. + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // $ref: "#/responses/DocsSearchResponse" + // '500': + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/libpod/images/search"), APIHandler(s.Context, handlers.SearchImages)).Methods(http.MethodGet) + // swagger:operation DELETE /libpod/images/{nameOrID} images libpodRemoveImage + // --- + // summary: Remove Image + // description: Delete an image from local store + // parameters: + // - in: query + // name: force + // type: bool + // description: remove the image even if used by containers or has other tags + // produces: + // - application/json + // responses: + // '200': + // schema: + // items: + // $ref: "#/responses/DocsIageDeleteResponse" + // '400': + // $ref: "#/responses/BadParamError" + // '404': + // $ref: '#/responses/NoSuchImage' + // '409': + // $ref: '#/responses/ConflictError' + // '500': + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/libpod/images/{name:..*}"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete) + // swagger:operation GET /libpod/images/{nameOrID}/get images libpoodExportImage + // --- + // summary: Export an image + // description: Export an image as a tarball + // 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': + // description: no error + // schema: + // type: string + // format: binary + // '404': + // $ref: '#/responses/NoSuchImage' + // '500': + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/libpod/images/{name:..*}/get"), APIHandler(s.Context, libpod.ExportImage)).Methods(http.MethodGet) + // swagger:operation GET /libpod/images/{nameOrID}/json images libpodInspectImage + // --- + // summary: Inspect an image + // description: Obtain low-level information about an image + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // '200': + // $ref: "#/responses/DocsLibpodInspectImageResponse" + // '404': + // $ref: '#/responses/NoSuchImage' + // '500': + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/libpod/images/{name:..*}/json"), APIHandler(s.Context, libpod.GetImage)) + // swagger:operation POST /libpod/images/{nameOrID}/tag images libpodTagImage + // --- + // summary: Tag an image + // description: Tag an image so that it becomes part of a repository. + // 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': + // $ref: '#/responses/BadParamError' + // '404': + // $ref: '#/responses/NoSuchImage' + // '409': + // $ref: '#/responses/ConflictError' + // '500': + // $ref: '#/responses/InternalError' + 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..a7fb18721 --- /dev/null +++ b/pkg/api/server/register_info.go @@ -0,0 +1,24 @@ +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 libpodGetInfo + // --- + // summary: Get info + // description: Returns information on the system and libpod configuration + // produces: + // - application/json + // responses: + // '200': + // description: to be determined + // '500': + // "$ref": "#/responses/InternalError" + 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..5069326b6 --- /dev/null +++ b/pkg/api/server/register_pods.go @@ -0,0 +1,239 @@ +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 { + // swagger:operation GET /libpod/pods/json pods ListPods + // --- + // summary: List pods + // produces: + // - application/json + // parameters: + // - in: query + // name: filters + // descriptions: needs description and plumbing for filters + // responses: + // '200': + // properties: + // items: + // $ref: "#/responses/ListPodsResponse" + // type: array + // '400': + // $ref: "#/responses/BadParamError" + // '500': + // $ref: "#/responses/InternalError" + 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) + // swagger:operation POST /libpod/pods/prune pods PrunePods + // --- + // summary: Prune unused pods + // parameters: + // - in: query + // name: force + // description: force delete + // type: bool + // default: false + // produces: + // - application/json + // responses: + // '204': + // description: no error + // '400': + // $ref: "#/responses/BadParamError" + // '500': + // $ref: "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/pods/prune"), APIHandler(s.Context, libpod.PodPrune)).Methods(http.MethodPost) + // swagger:operation DELETE /libpod/pods/{nameOrID} pods removePod + // --- + // summary: Remove pod + // produces: + // - application/json + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the pod + // - in: query + // name: force + // type: bool + // description: force delete + // responses: + // '204': + // description: no error + // '400': + // $ref: "#/responses/BadParamError" + // '404': + // $ref: "#/responses/NoSuchPod" + // '500': + // $ref: "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/pods/{name:..*}"), APIHandler(s.Context, libpod.PodDelete)).Methods(http.MethodDelete) + // swagger:operation GET /libpod/pods/{nameOrID}/json pods inspectPod + // --- + // summary: Inspect pod + // produces: + // - application/json + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the pod + // responses: + // '200': + // $ref: "#/responses/InspectPodResponse" + // '404': + // $ref: "#/responses/NoSuchPod" + // '500': + // $ref: "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/pods/{name:..*}/json"), APIHandler(s.Context, libpod.PodInspect)).Methods(http.MethodGet) + // swagger:operation GET /libpod/pods/{nameOrID}/exists pods podExists + // --- + // summary: Pod exists + // description: Check if a pod exists by name or ID + // produces: + // - application/json + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the pod + // responses: + // '204': + // description: pod exists + // '404': + // $ref: "#/responses/NoSuchPod" + // '500': + // $ref: "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/pods/{name:..*}/exists"), APIHandler(s.Context, libpod.PodExists)).Methods(http.MethodGet) + // swagger:operation POST /libpod/pods/{nameOrID}/kill pods killPod + // --- + // summary: Kill a pod + // produces: + // - application/json + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the pod + // - in: query + // name: signal + // type: int + // description: signal to be sent to pod + // responses: + // '204': + // description: no error + // '400': + // $ref: "#/responses/BadParamError" + // '404': + // $ref: "#/responses/NoSuchPod" + // '409': + // $ref: "#/responses/ConflictError" + // '500': + // $ref: "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/pods/{name:..*}/kill"), APIHandler(s.Context, libpod.PodKill)).Methods(http.MethodPost) + // swagger:operation POST /libpod/pods/{nameOrID}/pause pods pausePod + // --- + // summary: Pause a pod + // produces: + // - application/json + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the pod + // responses: + // '204': + // description: no error + // '404': + // $ref: "#/responses/NoSuchPod" + // '500': + // $ref: "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/pods/{name:..*}/pause"), APIHandler(s.Context, libpod.PodPause)).Methods(http.MethodPost) + // swagger:operation POST /libpod/pods/{nameOrID}/restart pods restartPod + // --- + // summary: Restart a pod + // produces: + // - application/json + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the pod + // responses: + // '204': + // description: no error + // '404': + // $ref: "#/responses/NoSuchPod" + // '500': + // $ref: "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/pods/{name:..*}/restart"), APIHandler(s.Context, libpod.PodRestart)).Methods(http.MethodPost) + // swagger:operation POST /libpod/pods/{nameOrID}/start pods startPod + // --- + // summary: Start a pod + // produces: + // - application/json + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the pod + // responses: + // '204': + // description: no error + // '304': + // $ref: "#/responses/PodAlreadyStartedError" + // '404': + // $ref: "#/responses/NoSuchPod" + // '500': + // $ref: "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/pods/{name:..*}/start"), APIHandler(s.Context, libpod.PodStart)).Methods(http.MethodPost) + // swagger:operation POST /libpod/pods/{nameOrID}/stop pods stopPod + // --- + // summary: Stop a pod + // produces: + // - application/json + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the pod + // - in: query + // name: t + // type: int + // description: timeout + // responses: + // '204': + // description: no error + // '304': + // $ref: "#/responses/PodAlreadyStoppedError" + // '400': + // $ref: "#/responses/BadParamError" + // '404': + // $ref: "#/responses/NoSuchPod" + // '500': + // $ref: "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/pods/{name:..*}/stop"), APIHandler(s.Context, libpod.PodStop)).Methods(http.MethodPost) + // swagger:operation POST /libpod/pods/{nameOrID}/unpause pods unpausePod + // --- + // summary: Unpause a pod + // produces: + // - application/json + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the pod + // responses: + // '204': + // description: no error + // '404': + // $ref: "#/responses/NoSuchPod" + // '500': + // $ref: "#/responses/InternalError" + 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..63d8acfde --- /dev/null +++ b/pkg/api/server/register_swarm.go @@ -0,0 +1,27 @@ +package server + +import ( + "errors" + "net/http" + + "github.com/containers/libpod/pkg/api/handlers/utils" + "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..34138cfbf --- /dev/null +++ b/pkg/api/server/register_volumes.go @@ -0,0 +1,77 @@ +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 { + // swagger:operation POST /libpod/volumes/create volumes createVolume + // --- + // summary: Create a volume + // produces: + // - application/json + // responses: + // '200': + // description: tbd + // '500': + // "$ref": "#/responses/InternalError" + 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) + // swagger:operation POST /volumes/prune volumes pruneVolumes + // --- + // summary: Prune volumes + // produces: + // - application/json + // responses: + // '204': + // description: no error + // '500': + // "$ref": "#/responses/InternalError" + r.Handle("/libpod/volumes/prune", APIHandler(s.Context, libpod.PruneVolumes)).Methods(http.MethodPost) + // swagger:operation GET /volumes/{nameOrID}/json volumes inspectVolume + // --- + // summary: Inspect volume + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the volume + // produces: + // - application/json + // responses: + // '200': + // "$ref": "#/responses/InspectVolumeResponse" + // '404': + // "$ref": "#/responses/NoSuchVolume" + // '500': + // "$ref": "#/responses/InternalError" + r.Handle("/libpod/volumes/{name:..*}/json", APIHandler(s.Context, libpod.InspectVolume)).Methods(http.MethodGet) + // swagger:operation DELETE /volumes/{nameOrID} volumes removeVolume + // --- + // summary: Remove volume + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the volume + // - in: query + // name: force + // type: bool + // description: force removal + // produces: + // - application/json + // responses: + // '204': + // description: no error + // '400': + // "$ref": "#/responses/BadParamError" + // '404': + // "$ref": "#/responses/NoSuchVolume" + // '500': + // "$ref": "#/responses/InternalError" + 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() +} diff --git a/pkg/api/server/swagger.go b/pkg/api/server/swagger.go new file mode 100644 index 000000000..95129e637 --- /dev/null +++ b/pkg/api/server/swagger.go @@ -0,0 +1,138 @@ +package server + +import ( + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" +) + +// No such image +// swagger:response NoSuchImage +type swagErrNoSuchImage struct { + // in:body + Body struct { + utils.ErrorModel + } +} + +// No such container +// swagger:response NoSuchContainer +type swagErrNoSuchContainer struct { + // in:body + Body struct { + utils.ErrorModel + } +} + +// No such volume +// swagger:response NoSuchVolume +type swagErrNoSuchVolume struct { + // in:body + Body struct { + utils.ErrorModel + } +} + +// No such pod +// swagger:response NoSuchPod +type swagErrNoSuchPod struct { + // in:body + Body struct { + utils.ErrorModel + } +} + +// Internal error +// swagger:response InternalError +type swagInternalError struct { + // in:body + Body struct { + utils.ErrorModel + } +} + +// Generic error +// swagger:response GenericError +type swagGenericError struct { + // in:body + Body struct { + utils.ErrorModel + } +} + +// Conflict error +// swagger:response ConflictError +type swagConflictError struct { + // in:body + Body struct { + utils.ErrorModel + } +} + +// Bad parameter +// swagger:response BadParamError +type swagBadParamError struct { + // in:body + Body struct { + utils.ErrorModel + } +} + +// Container already started +// swagger:response ContainerAlreadyStartedError +type swagContainerAlreadyStartedError struct { + // in:body + Body struct { + utils.ErrorModel + } +} + +// Container already stopped +// swagger:response ContainerAlreadyStoppedError +type swagContainerAlreadyStopped struct { + // in:body + Body struct { + utils.ErrorModel + } +} + +// Pod already started +// swagger:response PodAlreadyStartedError +type swagPodAlreadyStartedError struct { + // in:body + Body struct { + utils.ErrorModel + } +} + +// Pod already stopped +// swagger:response PodAlreadyStoppedError +type swagPodAlreadyStopped struct { + // in:body + Body struct { + utils.ErrorModel + } +} + +// Image summary +// swagger:response DockerImageSummary +type swagImageSummary struct { + // in:body + Body struct { + handlers.ImageSummary + } +} + +// List Containers +// swagger:response DocsListContainer +type swagListContainers struct { + // in:body + Body struct { + // This causes go-swagger to crash + //handlers.Container + } +} + +// To be determined +// swagger:response tbd +type swagTBD struct { +} |