diff options
Diffstat (limited to 'pkg')
36 files changed, 934 insertions, 543 deletions
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 8b21d6b94..f66999ffa 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -27,7 +27,6 @@ import ( "github.com/containers/libpod/libpod/logs" "github.com/containers/libpod/pkg/adapter/shortcuts" "github.com/containers/libpod/pkg/systemdgen" - "github.com/containers/psgo" "github.com/containers/storage" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -913,71 +912,7 @@ func (r *LocalRuntime) Top(cli *cliconfig.TopValues) ([]string, error) { return nil, errors.Wrapf(err, "unable to lookup requested container") } - output, psgoErr := container.Top(descriptors) - if psgoErr == nil { - return output, nil - } - - // If we encountered an ErrUnknownDescriptor error, fallback to executing - // ps(1). This ensures backwards compatibility to users depending on ps(1) - // and makes sure we're ~compatible with docker. - if errors.Cause(psgoErr) != psgo.ErrUnknownDescriptor { - return nil, psgoErr - } - - output, err = r.execPS(container, descriptors) - if err != nil { - // Note: return psgoErr to guide users into using the AIX descriptors - // instead of using ps(1). - return nil, psgoErr - } - - // Trick: filter the ps command from the output instead of - // checking/requiring PIDs in the output. - filtered := []string{} - cmd := strings.Join(descriptors, " ") - for _, line := range output { - if !strings.Contains(line, cmd) { - filtered = append(filtered, line) - } - } - - return filtered, nil -} - -func (r *LocalRuntime) execPS(c *libpod.Container, args []string) ([]string, error) { - rPipe, wPipe, err := os.Pipe() - if err != nil { - return nil, err - } - defer wPipe.Close() - defer rPipe.Close() - - streams := new(libpod.AttachStreams) - streams.OutputStream = wPipe - streams.ErrorStream = wPipe - streams.InputStream = bufio.NewReader(os.Stdin) - streams.AttachOutput = true - streams.AttachError = true - streams.AttachInput = true - - psOutput := []string{} - go func() { - scanner := bufio.NewScanner(rPipe) - for scanner.Scan() { - psOutput = append(psOutput, scanner.Text()) - } - }() - - cmd := append([]string{"ps"}, args...) - ec, err := c.Exec(false, false, map[string]string{}, cmd, "", "", streams, 0, nil, "") - if err != nil { - return nil, err - } else if ec != 0 { - return nil, errors.Errorf("Runtime failed with exit status: %d and output: %s", ec, strings.Join(psOutput, " ")) - } - - return psOutput, nil + return container.Top(descriptors) } // ExecContainer executes a command in the container diff --git a/pkg/api/Makefile b/pkg/api/Makefile new file mode 100644 index 000000000..5fb4e7da5 --- /dev/null +++ b/pkg/api/Makefile @@ -0,0 +1,3 @@ +swagger: + swagger generate spec -o swagger.yaml -w ./ + cat tags.yaml >> swagger.yaml diff --git a/pkg/api/handlers/containers_top.go b/pkg/api/handlers/containers_top.go index bab559da1..6b7688eb0 100644 --- a/pkg/api/handlers/containers_top.go +++ b/pkg/api/handlers/containers_top.go @@ -5,7 +5,6 @@ import ( "strings" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/gorilla/mux" "github.com/gorilla/schema" @@ -16,10 +15,14 @@ func TopContainer(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) + defaultValue := "-ef" + if utils.IsLibpodRequest(r) { + defaultValue = "" + } query := struct { PsArgs string `schema:"ps_args"` }{ - PsArgs: "-ef", + PsArgs: defaultValue, } if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, @@ -28,23 +31,13 @@ func TopContainer(w http.ResponseWriter, r *http.Request) { } name := mux.Vars(r)["name"] - ctnr, err := runtime.LookupContainer(name) + c, err := runtime.LookupContainer(name) if err != nil { utils.ContainerNotFound(w, name, err) return } - state, err := ctnr.State() - if err != nil { - utils.InternalServerError(w, err) - return - } - if state != define.ContainerStateRunning { - utils.ContainerNotRunning(w, name, errors.Errorf("Container %s must be running to perform top operation", name)) - return - } - - output, err := ctnr.Top([]string{}) + output, err := c.Top([]string{query.PsArgs}) if err != nil { utils.InternalServerError(w, err) return diff --git a/pkg/api/handlers/generic/config.go b/pkg/api/handlers/generic/config.go new file mode 100644 index 000000000..f715d25eb --- /dev/null +++ b/pkg/api/handlers/generic/config.go @@ -0,0 +1,9 @@ +package generic + +// ContainerCreateResponse is the response struct for creating a container +type ContainerCreateResponse struct { + // ID of the container created + Id string `json:"Id"` + // Warnings during container creation + Warnings []string `json:"Warnings"` +} diff --git a/pkg/api/handlers/generic/containers_create.go b/pkg/api/handlers/generic/containers_create.go index ef5337abd..f98872690 100644 --- a/pkg/api/handlers/generic/containers_create.go +++ b/pkg/api/handlers/generic/containers_create.go @@ -71,11 +71,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { return } - type ctrCreateResponse struct { - Id string `json:"Id"` - Warnings []string `json:"Warnings"` - } - response := ctrCreateResponse{ + response := ContainerCreateResponse{ Id: ctr.ID(), Warnings: []string{}} diff --git a/pkg/api/handlers/generic/containers_stats.go b/pkg/api/handlers/generic/containers_stats.go index 0c4efc1df..e33d37606 100644 --- a/pkg/api/handlers/generic/containers_stats.go +++ b/pkg/api/handlers/generic/containers_stats.go @@ -43,28 +43,26 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) { return } + // If the container isn't running, then let's not bother and return + // immediately. state, err := ctnr.State() if err != nil { utils.InternalServerError(w, err) return } if state != define.ContainerStateRunning && !query.Stream { - utils.WriteJSON(w, http.StatusOK, &handlers.Stats{StatsJSON: docker.StatsJSON{ - Name: ctnr.Name(), - ID: ctnr.ID(), - }}) + utils.InternalServerError(w, define.ErrCtrStateInvalid) return } - var preRead time.Time - var preCPUStats docker.CPUStats - stats, err := ctnr.GetContainerStats(&libpod.ContainerStats{}) if err != nil { utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain Container %s stats", name)) return } + var preRead time.Time + var preCPUStats docker.CPUStats if query.Stream { preRead = time.Now() preCPUStats = docker.CPUStats{ @@ -78,25 +76,44 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) { OnlineCPUs: 0, ThrottlingData: docker.ThrottlingData{}, } - time.Sleep(DefaultStatsPeriod) } - cgroupPath, _ := ctnr.CGroupPath() - cgroup, _ := cgroups.Load(cgroupPath) - for ok := true; ok; ok = query.Stream { - state, _ := ctnr.State() - if state != define.ContainerStateRunning { - time.Sleep(10 * time.Second) - continue + // Container stats + stats, err := ctnr.GetContainerStats(stats) + if err != nil { + utils.InternalServerError(w, err) + return + } + inspect, err := ctnr.Inspect(false) + if err != nil { + utils.InternalServerError(w, err) + return + } + // Cgroup stats + cgroupPath, err := ctnr.CGroupPath() + if err != nil { + utils.InternalServerError(w, err) + return + } + cgroup, err := cgroups.Load(cgroupPath) + if err != nil { + utils.InternalServerError(w, err) + return + } + cgroupStat, err := cgroup.Stat() + if err != nil { + utils.InternalServerError(w, err) + return } - stats, _ := ctnr.GetContainerStats(stats) - cgroupStat, _ := cgroup.Stat() - inspect, _ := ctnr.Inspect(false) - + // FIXME: network inspection does not yet work entirely net := make(map[string]docker.NetworkStats) - net[inspect.NetworkSettings.EndpointID] = docker.NetworkStats{ + networkName := inspect.NetworkSettings.EndpointID + if networkName == "" { + networkName = "network" + } + net[networkName] = docker.NetworkStats{ RxBytes: stats.NetInput, RxPackets: 0, RxErrors: 0, @@ -127,13 +144,6 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) { IoTimeRecursive: nil, SectorsRecursive: nil, }, - NumProcs: 0, - StorageStats: docker.StorageStats{ - ReadCountNormalized: 0, - ReadSizeBytes: 0, - WriteCountNormalized: 0, - WriteSizeBytes: 0, - }, CPUStats: docker.CPUStats{ CPUUsage: docker.CPUUsage{ TotalUsage: cgroupStat.CPU.Usage.Total, @@ -174,17 +184,21 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) { preRead = s.Read bits, err := json.Marshal(s.CPUStats) if err != nil { - logrus.Errorf("unable to marshal cpu stats: %q", err) + logrus.Errorf("Unable to marshal cpu stats: %q", err) } if err := json.Unmarshal(bits, &preCPUStats); err != nil { - logrus.Errorf("unable to unmarshal previous stats: %q", err) + logrus.Errorf("Unable to unmarshal previous stats: %q", err) + } + + // Only sleep when we're streaming. + if query.Stream { + time.Sleep(DefaultStatsPeriod) } - time.Sleep(DefaultStatsPeriod) } } func toBlkioStatEntry(entries []cgroups.BlkIOEntry) []docker.BlkioStatEntry { - results := make([]docker.BlkioStatEntry, 0, len(entries)) + results := make([]docker.BlkioStatEntry, len(entries)) for i, e := range entries { bits, err := json.Marshal(e) if err != nil { diff --git a/pkg/api/handlers/generic/images.go b/pkg/api/handlers/generic/images.go index 8029ee861..395f64064 100644 --- a/pkg/api/handlers/generic/images.go +++ b/pkg/api/handlers/generic/images.go @@ -350,7 +350,7 @@ func GetImages(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images")) return } - var summaries = make([]*handlers.ImageSummary, len(images)) + var summaries = make([]*handlers.ImageSummary, len(images)+1) for j, img := range images { is, err := handlers.ImageToImageSummary(img) if err != nil { diff --git a/pkg/api/handlers/generic/swagger.go b/pkg/api/handlers/generic/swagger.go new file mode 100644 index 000000000..27e1fc18d --- /dev/null +++ b/pkg/api/handlers/generic/swagger.go @@ -0,0 +1,23 @@ +package generic + +// Create container +// swagger:response ContainerCreateResponse +type swagCtrCreateResponse struct { + // in:body + Body struct { + ContainerCreateResponse + } +} + +// Wait container +// swagger:response ContainerWaitResponse +type swagCtrWaitResponse struct { + // in:body + Body struct { + // container exit code + StatusCode int + Error struct { + Message string + } + } +} diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index bfb028b1b..388be24b6 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -30,11 +30,6 @@ func ContainerExists(w http.ResponseWriter, r *http.Request) { } func RemoveContainer(w http.ResponseWriter, r *http.Request) { - // 204 no error - // 400 bad param - // 404 no such container - // 409 conflict - // 500 internal error decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { Force bool `schema:"force"` @@ -143,9 +138,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) { // timestamps // tail string } -func StatsContainer(w http.ResponseWriter, r *http.Request) { - //stream -} + func CreateContainer(w http.ResponseWriter, r *http.Request) { } diff --git a/pkg/api/handlers/swagger.go b/pkg/api/handlers/swagger.go index b677a5a0b..c845c8195 100644 --- a/pkg/api/handlers/swagger.go +++ b/pkg/api/handlers/swagger.go @@ -57,9 +57,7 @@ type swagLibpodInspectImageResponse struct { // swagger:response DocsContainerPruneReport type swagContainerPruneReport struct { // in: body - Body struct { - ContainersPruneReport - } + Body []ContainersPruneReport } // Inspect container @@ -84,9 +82,7 @@ type swagDockerTopResponse struct { // swagger:response LibpodListContainersResponse type swagLibpodListContainersResponse struct { // in:body - Body struct { - shared.PsContainerOutput - } + Body []shared.PsContainerOutput } // Inspect container diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 9edbbdccc..2526a3317 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -135,7 +135,6 @@ type Stats struct { type ContainerTopOKBody struct { dockerContainer.ContainerTopOKBody - ID string `json:"Id"` } type PodCreateConfig struct { diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go index 8c2110f97..2fd9bffba 100644 --- a/pkg/api/handlers/utils/handler.go +++ b/pkg/api/handlers/utils/handler.go @@ -6,10 +6,18 @@ import ( "io" "net/http" "os" + "strings" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) +// IsLibpodRequest returns true if the request related to a libpod endpoint +// (e.g., /v2/libpod/...). +func IsLibpodRequest(r *http.Request) bool { + split := strings.Split(r.URL.String(), "/") + return len(split) >= 3 && split[2] == "libpod" +} + // WriteResponse encodes the given value as JSON or string and renders it for http client func WriteResponse(w http.ResponseWriter, code int, value interface{}) { switch v := value.(type) { @@ -18,14 +26,14 @@ func WriteResponse(w http.ResponseWriter, code int, value interface{}) { w.WriteHeader(code) if _, err := fmt.Fprintln(w, v); err != nil { - log.Errorf("unable to send string response: %q", err) + logrus.Errorf("unable to send string response: %q", err) } case *os.File: w.Header().Set("Content-Type", "application/octet; charset=us-ascii") w.WriteHeader(code) if _, err := io.Copy(w, v); err != nil { - log.Errorf("unable to copy to response: %q", err) + logrus.Errorf("unable to copy to response: %q", err) } default: WriteJSON(w, code, value) @@ -33,12 +41,13 @@ func WriteResponse(w http.ResponseWriter, code int, value interface{}) { } func WriteJSON(w http.ResponseWriter, code int, value interface{}) { + // FIXME: we don't need to write the header in all/some circumstances. w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) coder := json.NewEncoder(w) coder.SetEscapeHTML(true) if err := coder.Encode(value); err != nil { - log.Errorf("unable to write json: %q", err) + logrus.Errorf("unable to write json: %q", err) } } diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index af1881624..b275fa4d1 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -10,58 +10,80 @@ import ( ) func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { - // swagger:operation POST /containers/create containers createContainer - // - // Create a container - // + // swagger:operation POST /containers/create compat containerCreate // --- - // produces: - // - application/json + // summary: Create a container + // tags: + // - containers (compat) + // 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 compat listContainers + // --- + // tags: + // - containers (compat) + // summary: List containers + // description: Returns a list of containers // parameters: // - in: query - // name: name + // name: filters // type: string - // description: container name - // responses: - // '201': - // description: tbd - // '400': - // "$ref": "#/responses/BadParamError" - // '404': - // "$ref": "#/responses/NoSuchContainer" - // '409': - // "$ref": "#/types/ConflictError" - // '500': - // "$ref": "#/types/InternalError" - r.HandleFunc(VersionedPath("/containers/create"), APIHandler(s.Context, generic.CreateContainer)).Methods(http.MethodPost) - // swagger:operation GET /containers/json containers listContainers - // - // List containers - // - // --- + // 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': - // schema: - // type: array - // items: - // "$ref": "#/responses/Container" + // "$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 - // - // Prune unused containers - // + // swagger:operation POST /containers/prune compat pruneContainers // --- + // tags: + // - containers (compat) + // summary: Delete stopped containers + // description: Remove containers not in use // parameters: // - in: query // name: filters - // type: map[string][]string - // description: something + // 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: @@ -70,11 +92,11 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '500': // "$ref": "#/responses/InternalError" r.HandleFunc(VersionedPath("/containers/prune"), APIHandler(s.Context, generic.PruneContainers)).Methods(http.MethodPost) - // swagger:operation DELETE /containers/{nameOrID} containers removeContainer - // - // Delete container - // + // swagger:operation DELETE /containers/{nameOrID} compat removeContainer // --- + // tags: + // - containers (compat) + // summary: Remove a container // parameters: // - in: path // name: nameOrID @@ -83,11 +105,13 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // - in: query // name: force // type: bool - // description: need something + // default: false + // description: If the container is running, kill it before removing it. // - in: query // name: v // type: bool - // description: need something + // default: false + // description: Remove the volumes associated with the container. // - in: query // name: link // type: bool @@ -106,16 +130,22 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '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 - // - // Inspect Container - // + // swagger:operation GET /containers/{nameOrID}/json compat getContainer // --- + // tags: + // - containers (compat) + // 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 + // 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: @@ -126,11 +156,12 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '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 - // - // Kill Container - // + // swagger:operation post /containers/{nameOrID}/kill compat killcontainer // --- + // tags: + // - containers (compat) + // summary: Kill container + // description: Signal to send to the container as an integer or string (e.g. SIGINT) // parameters: // - in: path // name: nameOrID @@ -152,11 +183,12 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '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 - // - // Get logs from container - // + // swagger:operation GET /containers/{nameOrID}/logs compat LogsFromContainer // --- + // tags: + // - containers (compat) + // summary: Get container logs + // description: Get stdout and stderr logs from a container. // parameters: // - in: path // name: nameOrID @@ -165,46 +197,49 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // - in: query // name: follow // type: bool - // description: needs description + // description: Keep connection after returning logs. // - in: query // name: stdout // type: bool - // description: needs description + // description: not supported // - in: query // name: stderr // type: bool - // description: needs description + // description: not supported? // - in: query // name: since // type: string - // description: needs description + // description: Only return logs since this time, as a UNIX timestamp // - in: query // name: until // type: string - // description: needs description + // description: Only return logs before this time, as a UNIX timestamp // - in: query // name: timestamps // type: bool - // description: needs description + // default: false + // description: Add timestamps to every log line // - in: query // name: tail // type: string - // description: needs description + // description: Only return this number of log lines from the end of the logs + // default: all // produces: // - application/json // responses: // '200': - // description: tbd + // 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 - // - // Pause Container - // + // swagger:operation POST /containers/{nameOrID}/pause compat pauseContainer // --- + // tags: + // - containers (compat) + // summary: Pause container + // description: Use the cgroups freezer to suspend all processes in a container. // parameters: // - in: path // name: nameOrID @@ -221,11 +256,11 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // "$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 - // - // Restart Container - // + // swagger:operation POST /containers/{nameOrID}/restart compat restartContainer // --- + // tags: + // - containers (compat) + // summary: Restart container // parameters: // - in: path // name: nameOrID @@ -245,11 +280,11 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '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 - // - // Start a container - // + // swagger:operation POST /containers/{nameOrID}/start compat startContainer // --- + // tags: + // - containers (compat) + // summary: Start a container // parameters: // - in: path // name: nameOrID @@ -271,11 +306,12 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '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 - // - // Get stats for a container - // + // swagger:operation GET /containers/{nameOrID}/stats compat statsContainer // --- + // tags: + // - containers (compat) + // summary: Get stats for a container + // description: This returns a live stream of a container’s resource usage statistics. // parameters: // - in: path // name: nameOrID @@ -284,24 +320,23 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // - in: query // name: stream // type: bool - // description: needs description + // default: true + // description: Stream the output // produces: // - application/json // responses: - // '204': - // description: tbd - // '304': - // "$ref": "#/responses/ContainerAlreadyStartedError" + // '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 - // - // Stop a container - // + // swagger:operation POST /containers/{nameOrID}/stop compat stopContainer // --- + // tags: + // - containers (compat) + // summary: Stop a container // parameters: // - in: path // name: nameOrID @@ -323,11 +358,11 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '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 - // - // List processes running inside a container - // + // swagger:operation GET /containers/{nameOrID}/top compat topContainer // --- + // tags: + // - containers (compat) + // summary: List processes running inside a container // parameters: // - in: path // name: nameOrID @@ -336,7 +371,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // - in: query // name: ps_args // type: string - // description: arguments to pass to ps such as aux + // description: arguments to pass to ps such as aux. Requires ps(1) to be installed in the container if no ps(1) compatible AIX descriptors are used. // produces: // - application/json // responses: @@ -347,11 +382,12 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '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 - // - // Unpause Container - // + // swagger:operation POST /containers/{nameOrID}/unpause compat unpauseContainer // --- + // tags: + // - containers (compat) + // summary: Unpause container + // description: Resume a paused container // parameters: // - in: path // name: nameOrID @@ -362,17 +398,17 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // responses: // '204': // description: no error - // schema: // '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 - // - // Wait on a container to exit - // + // swagger:operation POST /containers/{nameOrID}/wait compat waitContainer // --- + // tags: + // - containers (compat) + // summary: Wait on a container to exit + // description: Block until a container stops, then returns the exit code. // parameters: // - in: path // name: nameOrID @@ -385,8 +421,8 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // produces: // - application/json // responses: - // '204': - // description: no error + // '200': + // $ref: "#/responses/ContainerWaitResponse" // '404': // "$ref": "#/responses/NoSuchContainer" // '500': @@ -398,29 +434,29 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { */ r.HandleFunc(VersionedPath("/libpod/containers/create"), APIHandler(s.Context, libpod.CreateContainer)).Methods(http.MethodPost) - // swagger:operation GET /libpod/containers/json containers listContainers - // - // List containers - // + // swagger:operation GET /libpod/containers/json libpod libpodListContainers // --- + // tags: + // - containers + // 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 pruneContainers - // - // Prune unused containers - // + // swagger:operation POST /libpod/containers/prune libpod libpodPruneContainers // --- + // tags: + // - containers + // summary: Prune unused containers + // description: Remove stopped and exited containers // parameters: // - in: query // name: force @@ -428,21 +464,25 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // description: something // - in: query // name: filters - // type: map[string][]string - // description: something + // 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: TBD + // 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 - // - // Show mounted containers - // + // swagger:operation GET /libpod/containers/showmounted libpod showMounterContainers // --- + // tags: + // - containers + // summary: Show mounted containers + // description: Lists all mounted containers mount points // produces: // - application/json // responses: @@ -455,11 +495,11 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '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 removeContainer - // - // Delete container - // + // swagger:operation DELETE /libpod/containers/json libpod libpodRemoveContainer // --- + // tags: + // - containers + // summary: Delete container // parameters: // - in: path // name: nameOrID @@ -472,7 +512,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // - in: query // name: v // type: bool - // description: need something + // description: delete volumes // produces: // - application/json // responses: @@ -487,11 +527,12 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '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 getContainer - // - // Inspect Container - // + // swagger:operation GET /libpod/containers/{nameOrID}/json libpod libpodGetContainer // --- + // tags: + // - containers + // summary: Inspect container + // description: Return low-level information about a container. // parameters: // - in: path // name: nameOrID @@ -511,11 +552,12 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '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 killContainer - // - // Kill Container - // + // swagger:operation POST /libpod/containers/{nameOrID}/kill libpod libpodKillContainer // --- + // tags: + // - containers + // summary: Kill container + // description: send a signal to a container, defaults to killing the container // parameters: // - in: path // name: nameOrID @@ -538,11 +580,12 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '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 - // - // Mount a container - // + // swagger:operation GET /libpod/containers/{nameOrID}/mount libpod mountContainer // --- + // tags: + // - containers + // summary: Mount a container + // description: Mount a container to the filesystem // parameters: // - in: path // name: nameOrID @@ -563,11 +606,12 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // "$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 pauseContainer - // - // Pause Container - // + // swagger:operation POST /libpod/containers/{nameOrID}/pause libpod libpodPauseContainer // --- + // tags: + // - containers + // summary: Pause a container + // description: Use the cgroups freezer to suspend all processes in a container. // parameters: // - in: path // name: nameOrID @@ -583,11 +627,11 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '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 restartContainer - // - // Restart Container - // + // swagger:operation POST /libpod/containers/{nameOrID}/restart libpod libpodRestartContainer // --- + // tags: + // - containers + // summary: Restart a container // parameters: // - in: path // name: nameOrID @@ -607,11 +651,11 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '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 startContainer - // - // Start a container - // + // swagger:operation POST /libpod/containers/{nameOrID}/start libpod libpodStartContainer // --- + // tags: + // - containers + // summary: Start a container // parameters: // - in: path // name: nameOrID @@ -633,11 +677,35 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '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 unpauseContainer + // swagger:operation GET /libpod/containers/{nameOrID}/stats libpod statsContainer + // --- + // tags: + // - containers + // 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("/libpod/containers/{name:..*}/stats"), APIHandler(s.Context, generic.StatsContainer)).Methods(http.MethodGet) + // swagger:operation GET /libpod/containers/{nameOrID}/top containers topContainer // - // Unpause Container + // List processes running inside a container. Note // // --- // parameters: @@ -645,6 +713,35 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // name: nameOrID // required: true // description: the name or ID of the container + // - in: query + // name: stream + // type: bool + // default: true + // description: Stream the output + // name: ps_args + // type: string + // description: arguments to pass to ps such as aux. Requires ps(1) to be installed in the container if no ps(1) compatible AIX descriptors are used. + // produces: + // - application/json + // responses: + // '200': + // description: no error + // "ref": "#/responses/DockerTopResponse" + // '404': + // "$ref": "#/responses/NoSuchContainer" + // '500': + // "$ref": "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name:..*}/top"), APIHandler(s.Context, handlers.TopContainer)).Methods(http.MethodGet) + // swagger:operation POST /libpod/containers/{nameOrID}/unpause libpod libpodUnpauseContainer + // --- + // tags: + // - containers + // summary: Unpause Container + // parameters: + // - in: path + // name: nameOrID + // required: true + // description: the name or ID of the container // produces: // - application/json // responses: @@ -655,11 +752,11 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '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 waitContainer - // - // Wait on a container to exit - // + // swagger:operation POST /libpod/containers/{nameOrID}/wait libpod libpodWaitContainer // --- + // tags: + // - containers + // summary: Wait on a container to exit // parameters: // - in: path // name: nameOrID @@ -679,11 +776,12 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '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 - // - // Check if container exists - // + // swagger:operation POST /libpod/containers/{nameOrID}/exists libpod containerExists // --- + // tags: + // - containers + // summary: Check if container exists + // description: Quick way to determine if a container exists by name or ID // parameters: // - in: path // name: nameOrID @@ -699,11 +797,11 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // '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 stopContainer - // - // Stop a container - // + // swagger:operation POST /libpod/containers/{nameOrID}/stop libpod libpodStopContainer // --- + // tags: + // - containers + // summary: Stop a container // parameters: // - in: path // name: nameOrID diff --git a/pkg/api/server/register_events.go b/pkg/api/server/register_events.go index d764fdbb4..56cf96de1 100644 --- a/pkg/api/server/register_events.go +++ b/pkg/api/server/register_events.go @@ -26,7 +26,7 @@ func (s *APIServer) RegisterEventsHandlers(r *mux.Router) error { // description: OK // "500": // description: Failed - // "$ref": "#/types/errorModel" + // "$ref": "#/responses/InternalError" r.Handle(VersionedPath("/events"), APIHandler(s.Context, handlers.GetEvents)) return nil } diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index 488427f3c..cd42afe71 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -10,11 +10,13 @@ import ( ) func (s *APIServer) registerImagesHandlers(r *mux.Router) error { - // swagger:operation POST /images/create images createImage - // - // Create an image from an image + // swagger:operation POST /images/create compat createImage // // --- + // tags: + // - images (compat) + // 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: @@ -30,21 +32,22 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // '200': // schema: // items: - // $ref: "TBD" + // $ref: "to be determined" // '404': // description: repo or image does not exist // schema: - // $ref: "#/responses/generalError" + // $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 images createImage - // - // Create an image from Source - // + // swagger:operation POST /images/create compat createImage // --- + // tags: + // - images (compat) + // summary: Create an image from Source + // description: Create an image by either pulling it from a registry or importing it. // produces: // - application/json // parameters: @@ -54,27 +57,28 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: needs description // - in: query // name: changes - // type: TBD + // type: to be determined // description: needs description // responses: // '200': // schema: // items: - // $ref: "TBD" + // $ref: "to be determined" // '404': // description: repo or image does not exist // schema: - // $ref: "#/responses/generalError" + // $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 images listImages - // - // List Images - // + // swagger:operation GET /images/json compat listImages // --- + // tags: + // - images (compat) + // 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: @@ -87,34 +91,47 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // '500': // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/images/json"), APIHandler(s.Context, generic.GetImages)).Methods(http.MethodGet) - // swagger:operation POST /images/load images loadImage - // - // Import image + // swagger:operation POST /images/load compat loadImage // // --- + // tags: + // - images (compat) + // 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: TBD + // 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 images pruneImages - // - // Prune unused images - // + // swagger:operation POST /images/prune compat pruneImages // --- + // tags: + // - images (compat) + // summary: Prune unused images + // description: Remove images from local storage that are not being used by a container // parameters: // - in: query // name: filters - // type: map[string][]string - // description: not supported + // 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: @@ -125,11 +142,12 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // '500': // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/images/prune"), APIHandler(s.Context, generic.PruneImages)).Methods(http.MethodPost) - // swagger:operation GET /images/search images searchImages - // - // Search images - // + // swagger:operation GET /images/search compat searchImages // --- + // tags: + // - images (compat) + // summary: Search images + // description: Search registries for an image // parameters: // - in: query // name: term @@ -141,8 +159,12 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: maximum number of results // - in: query // name: filters - // type: map[string][]string - // description: TBD + // 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: @@ -151,11 +173,12 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // '500': // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/images/search"), APIHandler(s.Context, handlers.SearchImages)).Methods(http.MethodGet) - // swagger:operation DELETE /images/{nameOrID} images removeImage - // - // Remove Image - // + // swagger:operation DELETE /images/{nameOrID} compat removeImage // --- + // tags: + // - images (compat) + // summary: Remove Image + // description: Delete an image from local storage // parameters: // - in: query // name: force @@ -164,24 +187,25 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // - in: query // name: noprune // type: bool - // description: not supported + // description: not supported. will be logged as an invalid parameter if enabled // produces: // - application/json // responses: // '200': // $ref: "#/responses/DocsImageDeleteResponse" - // '404': + // '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 images exportImage - // - // Export an image - // + // swagger:operation GET /images/{nameOrID}/get compat exportImage // --- + // tags: + // - images (compat) + // summary: Export an image + // description: Export an image in tarball format // parameters: // - in: path // name: nameOrID @@ -191,17 +215,19 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // - application/json // responses: // '200': + // description: no error // schema: - // $ref: "TBD" - // description: TBD + // 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 images imageHistory - // - // History of an image - // + // swagger:operation GET /images/{nameOrID}/history compat imageHistory // --- + // tags: + // - images (compat) + // summary: History of an image + // description: Return parent layers of an image. // parameters: // - in: path // name: nameOrID @@ -217,11 +243,12 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // '500': // $ref: "#/responses/InternalError" 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 - // + // swagger:operation GET /images/{nameOrID}/json compat inspectImage // --- + // tags: + // - images (compat) + // summary: Inspect an image + // description: Return low-level information about an image. // parameters: // - in: path // name: nameOrID @@ -237,11 +264,12 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // '500': // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/images/{name:..*}/json"), APIHandler(s.Context, generic.GetImage)) - // swagger:operation POST /images/{nameOrID}/tag images tagImage - // - // Tag an image - // + // swagger:operation POST /images/{nameOrID}/tag compat tagImage // --- + // tags: + // - images (compat) + // summary: Tag an image + // description: Tag an image so that it becomes part of a repository. // parameters: // - in: path // name: nameOrID @@ -269,11 +297,11 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // 500: // $ref: '#/responses/InternalError' 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 - // + // swagger:operation POST /commit/ compat commitContainer // --- + // tags: + // - commit (compat) + // summary: Create a new image from a container // parameters: // - in: query // name: container @@ -318,11 +346,12 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { libpod endpoints */ - // swagger:operation POST /libpod/images/{nameOrID}/exists images imageExists - // - // Check if image exists in local store - // + // swagger:operation POST /libpod/images/{nameOrID}/exists libpod libpodImageExists // --- + // tags: + // - images + // summary: Image exists + // description: Check if image exists in local store // parameters: // - in: path // name: nameOrID @@ -330,15 +359,6 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // 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 @@ -348,11 +368,12 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // $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 imageHistory - // - // History of an image - // + // swagger:operation GET /libpod/images/{nameOrID}/history libpod libpodImageHistory // --- + // tags: + // - images + // summary: History of an image + // description: Return parent layers of an image. // parameters: // - in: path // name: nameOrID @@ -370,49 +391,60 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // '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 listImages - // - // List Images - // + // swagger:operation GET /libpod/images/json libpod libpodListImages // --- + // tags: + // - images + // 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 loadImage - // - // Import image - // + // swagger:operation POST /libpod/images/load libpod libpodLoadImage // --- + // tags: + // - images + // 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: TBD + // 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 pruneImages - // - // Prune unused images - // + // swagger:operation POST /libpod/images/prune libpod libpodPruneImages // --- + // tags: + // - images + // summary: Prune unused images + // description: Remove images that are not being used by a container // parameters: // - in: query // name: filters - // type: map[string][]string - // description: image 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 @@ -421,17 +453,17 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // - application/json // responses: // '200': - // schema: // 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 searchImages - // - // Search images - // + // swagger:operation GET /libpod/images/search libpod libpodSearchImages // --- + // tags: + // - images + // summary: Search images + // description: Search registries for images // parameters: // - in: query // name: term @@ -443,8 +475,12 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: maximum number of results // - in: query // name: filters - // type: map[string][]string - // description: TBD + // 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: @@ -455,20 +491,17 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // '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 removeImage - // - // Remove Image - // + // swagger:operation DELETE /libpod/images/{nameOrID} libpod libpodRemoveImage // --- + // tags: + // - images + // 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 - // - in: query - // name: noprune - // type: bool - // description: not supported // produces: // - application/json // responses: @@ -476,6 +509,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // schema: // items: // $ref: "#/responses/DocsIageDeleteResponse" + // '400': + // $ref: "#/responses/BadParamError" // '404': // $ref: '#/responses/NoSuchImage' // '409': @@ -483,11 +518,12 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // '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 exportImage - // - // Export an image - // + // swagger:operation GET /libpod/images/{nameOrID}/get libpod libpoodExportImage // --- + // tags: + // - images + // summary: Export an image + // description: Export an image as a tarball // parameters: // - in: path // name: nameOrID @@ -505,17 +541,21 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // - application/json // responses: // '200': - // description: TBD + // 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 inspectImage - // - // Inspect an image - // + // swagger:operation GET /libpod/images/{nameOrID}/json libpod libpodInspectImage // --- + // tags: + // - images + // summary: Inspect an image + // description: Obtain low-level information about an image // parameters: // - in: path // name: nameOrID @@ -525,19 +565,18 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // - application/json // responses: // '200': - // schema: - // items: // $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 tagImage - // - // Tag an image - // + // swagger:operation POST /libpod/images/{nameOrID}/tag libpod libpodTagImage // --- + // tags: + // - images + // summary: Tag an image + // description: Tag an image so that it becomes part of a repository. // parameters: // - in: path // name: nameOrID diff --git a/pkg/api/server/register_info.go b/pkg/api/server/register_info.go index 797158553..a7fb18721 100644 --- a/pkg/api/server/register_info.go +++ b/pkg/api/server/register_info.go @@ -8,21 +8,17 @@ import ( ) func (s *APIServer) registerInfoHandlers(r *mux.Router) error { - // swagger:operation GET /info libpod getInfo - // - // Returns information on the system and libpod configuration - // + // swagger:operation GET /info libpod libpodGetInfo // --- + // summary: Get info + // description: Returns information on the system and libpod configuration // produces: // - application/json // responses: // '200': - // schema: - // "$ref": "#/types/Info" + // description: to be determined // '500': - // description: unexpected error - // schema: - // "$ref": "#/types/ErrorModel" + // "$ref": "#/responses/InternalError" r.Handle(VersionedPath("/info"), APIHandler(s.Context, generic.GetInfo)).Methods(http.MethodGet) return nil } diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go index c2e10277e..5069326b6 100644 --- a/pkg/api/server/register_pods.go +++ b/pkg/api/server/register_pods.go @@ -9,10 +9,8 @@ import ( func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // swagger:operation GET /libpod/pods/json pods ListPods - // - // List Pods - // // --- + // summary: List pods // produces: // - application/json // parameters: @@ -21,23 +19,25 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // descriptions: needs description and plumbing for filters // responses: // '200': - // $ref: "#/responses/ListPodsResponse" + // properties: + // items: + // $ref: "#/responses/ListPodsResponse" + // type: array // '400': // $ref: "#/responses/BadParamError" // '500': - // $ref: "#responses/InternalError" + // $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 - // - // Prune unused pods - // // --- + // summary: Prune unused pods // parameters: // - in: query // name: force // description: force delete // type: bool + // default: false // produces: // - application/json // responses: @@ -46,13 +46,11 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '400': // $ref: "#/responses/BadParamError" // '500': - // $ref: "#responses/InternalError" + // $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 - // - // Remove Pod - // // --- + // summary: Remove pod // produces: // - application/json // parameters: @@ -72,13 +70,11 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '404': // $ref: "#/responses/NoSuchPod" // '500': - // $ref: "#responses/InternalError" + // $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 - // - // Inspect Pod - // // --- + // summary: Inspect pod // produces: // - application/json // parameters: @@ -92,13 +88,12 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '404': // $ref: "#/responses/NoSuchPod" // '500': - // $ref: "#responses/InternalError" + // $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 - // - // Inspect Pod - // // --- + // summary: Pod exists + // description: Check if a pod exists by name or ID // produces: // - application/json // parameters: @@ -112,13 +107,11 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '404': // $ref: "#/responses/NoSuchPod" // '500': - // $ref: "#responses/InternalError" + // $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 - // - // Kill a pod - // // --- + // summary: Kill a pod // produces: // - application/json // parameters: @@ -140,13 +133,11 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '409': // $ref: "#/responses/ConflictError" // '500': - // $ref: "#responses/InternalError" + // $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 - // - // Pause a pod - // // --- + // summary: Pause a pod // produces: // - application/json // parameters: @@ -160,13 +151,11 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '404': // $ref: "#/responses/NoSuchPod" // '500': - // $ref: "#responses/InternalError" + // $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 - // - // Restart a pod - // // --- + // summary: Restart a pod // produces: // - application/json // parameters: @@ -180,13 +169,11 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '404': // $ref: "#/responses/NoSuchPod" // '500': - // $ref: "#responses/InternalError" + // $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 - // - // Start a pod - // // --- + // summary: Start a pod // produces: // - application/json // parameters: @@ -202,13 +189,11 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '404': // $ref: "#/responses/NoSuchPod" // '500': - // $ref: "#responses/InternalError" + // $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 - // - // Stop a pod - // // --- + // summary: Stop a pod // produces: // - application/json // parameters: @@ -230,13 +215,11 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '404': // $ref: "#/responses/NoSuchPod" // '500': - // $ref: "#responses/InternalError" + // $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 - // - // Unpause a pod - // // --- + // summary: Unpause a pod // produces: // - application/json // parameters: @@ -250,7 +233,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // '404': // $ref: "#/responses/NoSuchPod" // '500': - // $ref: "#responses/InternalError" + // $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_volumes.go b/pkg/api/server/register_volumes.go index 8fe5a67e4..34138cfbf 100644 --- a/pkg/api/server/register_volumes.go +++ b/pkg/api/server/register_volumes.go @@ -9,10 +9,8 @@ import ( func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // swagger:operation POST /libpod/volumes/create volumes createVolume - // - // Create a volume - // // --- + // summary: Create a volume // produces: // - application/json // responses: @@ -23,10 +21,8 @@ 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) // swagger:operation POST /volumes/prune volumes pruneVolumes - // - // Prune volumes - // // --- + // summary: Prune volumes // produces: // - application/json // responses: @@ -36,10 +32,8 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // "$ref": "#/responses/InternalError" r.Handle("/libpod/volumes/prune", APIHandler(s.Context, libpod.PruneVolumes)).Methods(http.MethodPost) // swagger:operation GET /volumes/{nameOrID}/json volumes inspectVolume - // - // Inspect volume - // // --- + // summary: Inspect volume // parameters: // - in: path // name: nameOrID @@ -56,10 +50,8 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // "$ref": "#/responses/InternalError" r.Handle("/libpod/volumes/{name:..*}/json", APIHandler(s.Context, libpod.InspectVolume)).Methods(http.MethodGet) // swagger:operation DELETE /volumes/{nameOrID} volumes removeVolume - // - // Inspect volume - // // --- + // summary: Remove volume // parameters: // - in: path // name: nameOrID diff --git a/pkg/api/server/swagger.go b/pkg/api/server/swagger.go index 0eb57ebab..dbf499ce7 100644 --- a/pkg/api/server/swagger.go +++ b/pkg/api/server/swagger.go @@ -117,9 +117,7 @@ type swagPodAlreadyStopped struct { // swagger:response DockerImageSummary type swagImageSummary struct { // in:body - Body struct { - handlers.ImageSummary - } + Body []handlers.ImageSummary } // List Containers @@ -128,6 +126,11 @@ type swagListContainers struct { // in:body Body struct { // This causes go-swagger to crash - //handlers.Container + // handlers.Container } } + +// To be determined +// swagger:response tbd +type swagTBD struct { +} diff --git a/pkg/api/tags.yaml b/pkg/api/tags.yaml new file mode 100644 index 000000000..ad0de656f --- /dev/null +++ b/pkg/api/tags.yaml @@ -0,0 +1,13 @@ +tags: + - name: containers + description: Actions related to containers + - name: images + description: Actions related to images + - name: pods + description: Actions related to pods + - name: volumes + description: Actions related to volumes + - name: containers (compat) + description: Actions related to containers for the compatibility endpoints + - name: images (compat) + description: Actions related to images for the compatibility endpoints diff --git a/pkg/apparmor/apparmor_linux_test.go b/pkg/apparmor/apparmor_linux_test.go index e94293d87..3ff6e18bc 100644 --- a/pkg/apparmor/apparmor_linux_test.go +++ b/pkg/apparmor/apparmor_linux_test.go @@ -134,7 +134,7 @@ func TestDefaultContent(t *testing.T) { if _, err := os.Stat(aapath); err != nil { t.Skip("AppArmor isn't available in this environment") } - if err := DefaultContent(profile); err != nil { + if _, err := DefaultContent(profile); err != nil { t.Fatalf("Couldn't retrieve default AppArmor profile content '%s': %v", profile, err) } } diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go new file mode 100644 index 000000000..551a63c62 --- /dev/null +++ b/pkg/bindings/connection.go @@ -0,0 +1,62 @@ +package bindings + +import ( + "fmt" + "io" + "net/http" +) + +const ( + defaultConnection string = "http://localhost:8080/v1.24/libpod" + pingConnection string = "http://localhost:8080/_ping" +) + +type APIResponse struct { + *http.Response + Request *http.Request +} + +type Connection struct { + url string + client *http.Client +} + +func NewConnection(url string) (Connection, error) { + if len(url) < 1 { + url = defaultConnection + } + newConn := Connection{ + url: url, + client: &http.Client{}, + } + response, err := http.Get(pingConnection) + if err != nil { + return newConn, err + } + if err := response.Body.Close(); err != nil { + return newConn, err + } + return newConn, err +} + +func (c Connection) makeEndpoint(u string) string { + return fmt.Sprintf("%s%s", defaultConnection, u) +} + +func (c Connection) newRequest(httpMethod, endpoint string, httpBody io.Reader, params map[string]string) (*APIResponse, error) { + e := c.makeEndpoint(endpoint) + req, err := http.NewRequest(httpMethod, e, httpBody) + if err != nil { + return nil, err + } + if len(params) > 0 { + // if more desirable we could use url to form the encoded endpoint with params + r := req.URL.Query() + for k, v := range params { + r.Add(k, v) + } + req.URL.RawQuery = r.Encode() + } + response, err := c.client.Do(req) // nolint + return &APIResponse{response, req}, err +} diff --git a/pkg/bindings/containers.go b/pkg/bindings/containers.go index cd0b09767..01f68f970 100644 --- a/pkg/bindings/containers.go +++ b/pkg/bindings/containers.go @@ -109,12 +109,14 @@ func (c Connection) UnpauseContainer(nameOrID string) error { } func (c Connection) WaitContainer(nameOrID string) error { - _, err := http.Post(c.makeEndpoint(fmt.Sprintf("containers/%s/wait", nameOrID)), "application/json", nil) + // TODO when returns are ironed out, we can should use the newRequest approach + _, err := http.Post(c.makeEndpoint(fmt.Sprintf("containers/%s/wait", nameOrID)), "application/json", nil) // nolint return err } func (c Connection) ContainerExists(nameOrID string) (bool, error) { - response, err := http.Get(c.makeEndpoint(fmt.Sprintf("/containers/%s/exists", nameOrID))) + response, err := http.Get(c.makeEndpoint(fmt.Sprintf("/containers/%s/exists", nameOrID))) // nolint + defer closeResponseBody(response) if err != nil { return false, err } diff --git a/pkg/bindings/errors.go b/pkg/bindings/errors.go new file mode 100644 index 000000000..9a02925a3 --- /dev/null +++ b/pkg/bindings/errors.go @@ -0,0 +1,46 @@ +package bindings + +import ( + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +var ( + ErrNotImplemented = errors.New("function not implemented") +) + +func handleError(data []byte) error { + e := utils.ErrorModel{} + if err := json.Unmarshal(data, &e); err != nil { + return err + } + return e +} + +func (a APIResponse) Process(unmarshalInto interface{}) error { + data, err := ioutil.ReadAll(a.Response.Body) + if err != nil { + return errors.Wrap(err, "unable to process API response") + } + if a.Response.StatusCode == http.StatusOK { + if unmarshalInto != nil { + return json.Unmarshal(data, unmarshalInto) + } + return nil + } + // TODO should we add a debug here with the response code? + return handleError(data) +} + +func closeResponseBody(r *http.Response) { + if r != nil { + if err := r.Body.Close(); err != nil { + logrus.Error(errors.Wrap(err, "unable to close response body")) + } + } +} diff --git a/pkg/bindings/images.go b/pkg/bindings/images.go new file mode 100644 index 000000000..3abc8c372 --- /dev/null +++ b/pkg/bindings/images.go @@ -0,0 +1,111 @@ +package bindings + +import ( + "fmt" + "io" + "net/http" + "strconv" + + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/inspect" +) + +func (c Connection) ImageExists(nameOrID string) (bool, error) { + response, err := http.Get(c.makeEndpoint(fmt.Sprintf("/images/%s/exists", nameOrID))) // nolint + defer closeResponseBody(response) + if err != nil { + return false, err + } + if response.StatusCode == http.StatusOK { + return true, nil + } + return false, nil +} + +func (c Connection) ListImages() ([]handlers.ImageSummary, error) { + imageSummary := []handlers.ImageSummary{} + response, err := c.newRequest(http.MethodGet, "/images/json", nil, nil) + if err != nil { + return imageSummary, err + } + return imageSummary, response.Process(&imageSummary) +} + +func (c Connection) GetImage(nameOrID string) (*inspect.ImageData, error) { + inspectedData := inspect.ImageData{} + response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/images/%s/json", nameOrID), nil, nil) + if err != nil { + return &inspectedData, err + } + return &inspectedData, response.Process(&inspectedData) +} + +func (c Connection) ImageTree(nameOrId string) error { + return ErrNotImplemented +} + +func (c Connection) ImageHistory(nameOrID string) ([]handlers.HistoryResponse, error) { + history := []handlers.HistoryResponse{} + response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/images/%s/history", nameOrID), nil, nil) + if err != nil { + return history, err + } + return history, response.Process(&history) +} + +func (c Connection) LoadImage(r io.Reader) error { + // TODO this still needs error handling added + _, err := http.Post(c.makeEndpoint("/images/loads"), "application/json", r) //nolint + return err +} + +func (c Connection) RemoveImage(nameOrID string, force bool) ([]map[string]string, error) { + deletes := []map[string]string{} + params := make(map[string]string) + params["force"] = strconv.FormatBool(force) + response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/images/%s", nameOrID), nil, params) + if err != nil { + return nil, err + } + return deletes, response.Process(&deletes) +} + +func (c Connection) ExportImage(nameOrID string, w io.Writer, format string, compress bool) error { + params := make(map[string]string) + params["format"] = format + params["compress"] = strconv.FormatBool(compress) + response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/images/%s/get", nameOrID), nil, params) + if err != nil { + return err + } + if err := response.Process(nil); err != nil { + return err + } + _, err = io.Copy(w, response.Body) + return err +} + +func (c Connection) PruneImages(all bool, filters []string) ([]string, error) { + var ( + deleted []string + ) + params := make(map[string]string) + // FIXME How do we do []strings? + //params["filters"] = format + response, err := c.newRequest(http.MethodPost, "/images/prune", nil, params) + if err != nil { + return deleted, err + } + return deleted, response.Process(nil) +} + +func (c Connection) TagImage(nameOrID string) error { + var () + response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/images/%s/tag", nameOrID), nil, nil) + if err != nil { + return err + } + return response.Process(nil) +} + +func (c Connection) BuildImage(nameOrId string) {} diff --git a/pkg/bindings/pods.go b/pkg/bindings/pods.go index eac9d2ef5..704d71477 100644 --- a/pkg/bindings/pods.go +++ b/pkg/bindings/pods.go @@ -14,7 +14,8 @@ func (c Connection) CreatePod() error { } func (c Connection) PodExists(nameOrID string) (bool, error) { - response, err := http.Get(c.makeEndpoint(fmt.Sprintf("/pods/%s/exists", nameOrID))) + response, err := http.Get(c.makeEndpoint(fmt.Sprintf("/pods/%s/exists", nameOrID))) // nolint + defer closeResponseBody(response) if err != nil { return false, err } diff --git a/pkg/bindings/volumes.go b/pkg/bindings/volumes.go index 27e6f9efa..219f924e7 100644 --- a/pkg/bindings/volumes.go +++ b/pkg/bindings/volumes.go @@ -52,7 +52,7 @@ func (c Connection) PruneVolumes() ([]string, error) { func (c Connection) RemoveVolume(nameOrID string, force bool) error { params := make(map[string]string) params["force"] = strconv.FormatBool(force) - response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/volumes/prune", nameOrID), nil, params) + response, err := c.newRequest(http.MethodPost, "/volumes/prune", nil, params) if err != nil { return err } diff --git a/pkg/hooks/1.0.0/when_test.go b/pkg/hooks/1.0.0/when_test.go index a749063ff..94b0c3830 100644 --- a/pkg/hooks/1.0.0/when_test.go +++ b/pkg/hooks/1.0.0/when_test.go @@ -10,7 +10,8 @@ import ( func TestNoMatch(t *testing.T) { config := &rspec.Spec{} - for _, or := range []bool{true, false} { + for _, o := range []bool{true, false} { + or := o t.Run(fmt.Sprintf("or %t", or), func(t *testing.T) { when := When{Or: or} match, err := when.Match(config, map[string]string{}, false) @@ -27,9 +28,12 @@ func TestAlways(t *testing.T) { processStruct := &rspec.Process{ Args: []string{"/bin/sh", "a", "b"}, } - for _, always := range []bool{true, false} { - for _, or := range []bool{true, false} { - for _, process := range []*rspec.Process{processStruct, nil} { + for _, a := range []bool{true, false} { + always := a + for _, o := range []bool{true, false} { + or := o + for _, p := range []*rspec.Process{processStruct, nil} { + process := p t.Run(fmt.Sprintf("always %t, or %t, has process %t", always, or, process != nil), func(t *testing.T) { config.Process = process when := When{Always: &always, Or: or} @@ -48,7 +52,8 @@ func TestHasBindMountsAnd(t *testing.T) { hasBindMounts := true when := When{HasBindMounts: &hasBindMounts} config := &rspec.Spec{} - for _, containerHasBindMounts := range []bool{false, true} { + for _, b := range []bool{false, true} { + containerHasBindMounts := b t.Run(fmt.Sprintf("%t", containerHasBindMounts), func(t *testing.T) { match, err := when.Match(config, map[string]string{}, containerHasBindMounts) if err != nil { @@ -63,7 +68,8 @@ func TestHasBindMountsOr(t *testing.T) { hasBindMounts := true when := When{HasBindMounts: &hasBindMounts, Or: true} config := &rspec.Spec{} - for _, containerHasBindMounts := range []bool{false, true} { + for _, b := range []bool{false, true} { + containerHasBindMounts := b t.Run(fmt.Sprintf("%t", containerHasBindMounts), func(t *testing.T) { match, err := when.Match(config, map[string]string{}, containerHasBindMounts) if err != nil { @@ -82,7 +88,7 @@ func TestAnnotations(t *testing.T) { }, } config := &rspec.Spec{} - for _, test := range []struct { + for _, tt := range []struct { name string annotations map[string]string or bool @@ -131,6 +137,7 @@ func TestAnnotations(t *testing.T) { match: false, }, } { + test := tt t.Run(test.name, func(t *testing.T) { when.Or = test.or match, err := when.Match(config, test.annotations, false) @@ -149,7 +156,7 @@ func TestCommands(t *testing.T) { }, } config := &rspec.Spec{} - for _, test := range []struct { + for _, tt := range []struct { name string process *rspec.Process match bool @@ -173,6 +180,7 @@ func TestCommands(t *testing.T) { match: false, }, } { + test := tt t.Run(test.name, func(t *testing.T) { config.Process = test.process match, err := when.Match(config, map[string]string{}, false) @@ -209,7 +217,7 @@ func TestHasBindMountsAndCommands(t *testing.T) { }, } config := &rspec.Spec{Process: &rspec.Process{}} - for _, test := range []struct { + for _, tt := range []struct { name string command string hasBindMounts bool @@ -273,6 +281,7 @@ func TestHasBindMountsAndCommands(t *testing.T) { match: false, }, } { + test := tt t.Run(test.name, func(t *testing.T) { config.Process.Args = []string{test.command} when.Or = test.or @@ -287,7 +296,7 @@ func TestHasBindMountsAndCommands(t *testing.T) { func TestInvalidRegexp(t *testing.T) { config := &rspec.Spec{Process: &rspec.Process{Args: []string{"/bin/sh"}}} - for _, test := range []struct { + for _, tt := range []struct { name string when When expected string @@ -308,6 +317,7 @@ func TestInvalidRegexp(t *testing.T) { expected: "^command: error parsing regexp: .*", }, } { + test := tt t.Run(test.name, func(t *testing.T) { _, err := test.when.Match(config, map[string]string{"a": "b"}, false) if err == nil { diff --git a/pkg/hooks/exec/exec_test.go b/pkg/hooks/exec/exec_test.go index 7aac315cb..1e105373d 100644 --- a/pkg/hooks/exec/exec_test.go +++ b/pkg/hooks/exec/exec_test.go @@ -94,7 +94,7 @@ func TestRunEnvironment(t *testing.T) { Path: path, Args: []string{"sh", "-c", "env"}, } - for _, test := range []struct { + for _, tt := range []struct { name string env []string expected map[string]string @@ -120,6 +120,7 @@ func TestRunEnvironment(t *testing.T) { }, }, } { + test := tt t.Run(test.name, func(t *testing.T) { var stderr, stdout bytes.Buffer hook.Env = test.env @@ -147,7 +148,7 @@ func TestRunCancel(t *testing.T) { Args: []string{"sh", "-c", "echo waiting; sleep 2; echo done"}, } one := 1 - for _, test := range []struct { + for _, tt := range []struct { name string contextTimeout time.Duration hookTimeout *int @@ -174,6 +175,7 @@ func TestRunCancel(t *testing.T) { expectedRunError: context.DeadlineExceeded, }, } { + test := tt t.Run(test.name, func(t *testing.T) { ctx := context.Background() var stderr, stdout bytes.Buffer diff --git a/pkg/hooks/exec/runtimeconfigfilter_test.go b/pkg/hooks/exec/runtimeconfigfilter_test.go index 52d590d14..48dd2f998 100644 --- a/pkg/hooks/exec/runtimeconfigfilter_test.go +++ b/pkg/hooks/exec/runtimeconfigfilter_test.go @@ -25,9 +25,9 @@ func pointerFileMode(value os.FileMode) *os.FileMode { } func TestRuntimeConfigFilter(t *testing.T) { - unexpectedEndOfJSONInput := json.Unmarshal([]byte("{\n"), nil) + unexpectedEndOfJSONInput := json.Unmarshal([]byte("{\n"), nil) //nolint - for _, test := range []struct { + for _, tt := range []struct { name string contextTimeout time.Duration hooks []spec.Hook @@ -244,6 +244,7 @@ func TestRuntimeConfigFilter(t *testing.T) { expectedRunError: unexpectedEndOfJSONInput, }, } { + test := tt t.Run(test.name, func(t *testing.T) { ctx := context.Background() if test.contextTimeout > 0 { diff --git a/pkg/network/network_test.go b/pkg/network/network_test.go index dbffc33ad..1969e792c 100644 --- a/pkg/network/network_test.go +++ b/pkg/network/network_test.go @@ -25,9 +25,10 @@ func Test_networkIntersect(t *testing.T) { {"Two 24s", args{n1: parseCIDR("192.168.1.0/24"), n2: parseCIDR("192.168.2.0/24")}, false}, } for _, tt := range tests { + test := tt t.Run(tt.name, func(t *testing.T) { - if got := networkIntersect(tt.args.n1, tt.args.n2); got != tt.want { - t.Errorf("networkIntersect() = %v, want %v", got, tt.want) + if got := networkIntersect(test.args.n1, test.args.n2); got != test.want { + t.Errorf("networkIntersect() = %v, want %v", got, test.want) } }) } diff --git a/pkg/network/subnet_test.go b/pkg/network/subnet_test.go index 6ecfd2d17..917c3be88 100644 --- a/pkg/network/subnet_test.go +++ b/pkg/network/subnet_test.go @@ -20,14 +20,15 @@ func TestNextSubnet(t *testing.T) { {"class c", args{subnet: parseCIDR("192.168.1.0/24")}, parseCIDR("192.168.2.0/24"), false}, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := NextSubnet(tt.args.subnet) - if (err != nil) != tt.wantErr { - t.Errorf("NextSubnet() error = %v, wantErr %v", err, tt.wantErr) + test := tt + t.Run(test.name, func(t *testing.T) { + got, err := NextSubnet(test.args.subnet) + if (err != nil) != test.wantErr { + t.Errorf("NextSubnet() error = %v, wantErr %v", err, test.wantErr) return } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NextSubnet() got = %v, want %v", got, tt.want) + if !reflect.DeepEqual(got, test.want) { + t.Errorf("NextSubnet() got = %v, want %v", got, test.want) } }) } diff --git a/pkg/spec/config_linux_cgo.go b/pkg/spec/config_linux_cgo.go index c47156456..ae83c9d52 100644 --- a/pkg/spec/config_linux_cgo.go +++ b/pkg/spec/config_linux_cgo.go @@ -8,13 +8,24 @@ import ( spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" seccomp "github.com/seccomp/containers-golang" + "github.com/sirupsen/logrus" ) func getSeccompConfig(config *SecurityConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { var seccompConfig *spec.LinuxSeccomp var err error + if config.SeccompPolicy == SeccompPolicyImage && config.SeccompProfileFromImage != "" { + logrus.Debug("Loading seccomp profile from the security config") + seccompConfig, err = seccomp.LoadProfile(config.SeccompProfileFromImage, configSpec) + if err != nil { + return nil, errors.Wrap(err, "loading seccomp profile failed") + } + return seccompConfig, nil + } + if config.SeccompProfilePath != "" { + logrus.Debugf("Loading seccomp profile from %q", config.SeccompProfilePath) seccompProfile, err := ioutil.ReadFile(config.SeccompProfilePath) if err != nil { return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", config.SeccompProfilePath) @@ -24,6 +35,7 @@ func getSeccompConfig(config *SecurityConfig, configSpec *spec.Spec) (*spec.Linu return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath) } } else { + logrus.Debug("Loading default seccomp profile") seccompConfig, err = seccomp.GetDefaultProfile(configSpec) if err != nil { return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath) diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 6d058229b..fb222083b 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -2,6 +2,7 @@ package createconfig import ( "os" + "sort" "strconv" "strings" "syscall" @@ -106,19 +107,63 @@ type NetworkConfig struct { PublishAll bool //publish-all } +// SeccompPolicy determines which seccomp profile gets applied to the container. +type SeccompPolicy int + +const ( + // SeccompPolicyDefault - if set use SecurityConfig.SeccompProfilePath, + // otherwise use the default profile. The SeccompProfilePath might be + // explicitly set by the user. + SeccompPolicyDefault SeccompPolicy = iota + // SeccompPolicyImage - if set use SecurityConfig.SeccompProfileFromImage, + // otherwise follow SeccompPolicyDefault. + SeccompPolicyImage +) + +// Map for easy lookups of supported policies. +var supportedSeccompPolicies = map[string]SeccompPolicy{ + "": SeccompPolicyDefault, + "default": SeccompPolicyDefault, + "image": SeccompPolicyImage, +} + +// LookupSeccompPolicy looksup the corresponding SeccompPolicy for the specified +// string. If none is found, an errors is returned including the list of +// supported policies. +// Note that an empty string resolved to SeccompPolicyDefault. +func LookupSeccompPolicy(s string) (SeccompPolicy, error) { + policy, exists := supportedSeccompPolicies[s] + if exists { + return policy, nil + } + + // Sort the keys first as maps are non-deterministic. + keys := []string{} + for k := range supportedSeccompPolicies { + if k != "" { + keys = append(keys, k) + } + } + sort.Strings(keys) + + return -1, errors.Errorf("invalid seccomp policy %q: valid policies are %+q", s, keys) +} + // SecurityConfig configures the security features for the container type SecurityConfig struct { - CapAdd []string // cap-add - CapDrop []string // cap-drop - LabelOpts []string //SecurityOpts - NoNewPrivs bool //SecurityOpts - ApparmorProfile string //SecurityOpts - SeccompProfilePath string //SecurityOpts - SecurityOpts []string - Privileged bool //privileged - ReadOnlyRootfs bool //read-only - ReadOnlyTmpfs bool //read-only-tmpfs - Sysctl map[string]string //sysctl + CapAdd []string // cap-add + CapDrop []string // cap-drop + LabelOpts []string //SecurityOpts + NoNewPrivs bool //SecurityOpts + ApparmorProfile string //SecurityOpts + SeccompProfilePath string //SecurityOpts + SeccompProfileFromImage string // seccomp profile from the container image + SeccompPolicy SeccompPolicy + SecurityOpts []string + Privileged bool //privileged + ReadOnlyRootfs bool //read-only + ReadOnlyTmpfs bool //read-only-tmpfs + Sysctl map[string]string //sysctl } // CreateConfig is a pre OCI spec structure. It represents user input from varlink or the CLI diff --git a/pkg/systemdgen/systemdgen_test.go b/pkg/systemdgen/systemdgen_test.go index e1da7e8e0..3894a0205 100644 --- a/pkg/systemdgen/systemdgen_test.go +++ b/pkg/systemdgen/systemdgen_test.go @@ -24,9 +24,10 @@ func TestValidateRestartPolicy(t *testing.T) { {"failblank", ContainerInfo{restart: ""}, true}, } for _, tt := range tests { + test := tt t.Run(tt.name, func(t *testing.T) { - if err := validateRestartPolicy(tt.ContainerInfo.restart); (err != nil) != tt.wantErr { - t.Errorf("ValidateRestartPolicy() error = %v, wantErr %v", err, tt.wantErr) + if err := validateRestartPolicy(test.ContainerInfo.restart); (err != nil) != test.wantErr { + t.Errorf("ValidateRestartPolicy() error = %v, wantErr %v", err, test.wantErr) } }) } @@ -221,18 +222,19 @@ WantedBy=multi-user.target` }, } for _, tt := range tests { + test := tt t.Run(tt.name, func(t *testing.T) { opts := Options{ Files: false, - New: tt.info.New, + New: test.info.New, } - got, err := CreateContainerSystemdUnit(&tt.info, opts) - if (err != nil) != tt.wantErr { - t.Errorf("CreateContainerSystemdUnit() error = \n%v, wantErr \n%v", err, tt.wantErr) + got, err := CreateContainerSystemdUnit(&test.info, opts) + if (err != nil) != test.wantErr { + t.Errorf("CreateContainerSystemdUnit() error = \n%v, wantErr \n%v", err, test.wantErr) return } - if got != tt.want { - t.Errorf("CreateContainerSystemdUnit() = \n%v\n---------> want\n%v", got, tt.want) + if got != test.want { + t.Errorf("CreateContainerSystemdUnit() = \n%v\n---------> want\n%v", got, test.want) } }) } diff --git a/pkg/tracing/tracing.go b/pkg/tracing/tracing.go index d028ddf8f..5be24faaa 100644 --- a/pkg/tracing/tracing.go +++ b/pkg/tracing/tracing.go @@ -12,6 +12,7 @@ import ( // Init returns an instance of Jaeger Tracer that samples 100% of traces and logs all spans to stdout. func Init(service string) (opentracing.Tracer, io.Closer) { cfg := &config.Configuration{ + ServiceName: service, Sampler: &config.SamplerConfig{ Type: "const", Param: 1, @@ -20,7 +21,7 @@ func Init(service string) (opentracing.Tracer, io.Closer) { LogSpans: true, }, } - tracer, closer, err := cfg.New(service, config.Logger(jaeger.StdLogger)) + tracer, closer, err := cfg.NewTracer(config.Logger(jaeger.StdLogger)) if err != nil { panic(fmt.Sprintf("ERROR: cannot init Jaeger: %v\n", err)) } |