Skip to content

Commit

Permalink
Review corrections pass #2
Browse files Browse the repository at this point in the history
Add API review comments to correct documentation and endpoints.  Also, add a libpode prune method to reduce code duplication.  Only used right now for the API but when the remote client is wired, we will switch over there too.

Signed-off-by: Brent Baude <bbaude@redhat.com>
  • Loading branch information
baude committed Jan 23, 2020
1 parent e6cf0ec commit cf7be58
Show file tree
Hide file tree
Showing 12 changed files with 277 additions and 114 deletions.
41 changes: 41 additions & 0 deletions libpod/runtime_ctr.go
Original file line number Diff line number Diff line change
Expand Up @@ -836,3 +836,44 @@ func (r *Runtime) GetLatestContainer() (*Container, error) {
}
return ctrs[lastCreatedIndex], nil
}

// PruneContainers removes stopped and exited containers from localstorage. A set of optional filters
// can be provided to be more granular.
func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) (map[string]int64, map[string]error, error) {
pruneErrors := make(map[string]error)
prunedContainers := make(map[string]int64)
// We add getting the exited and stopped containers via a filter
containerStateFilter := func(c *Container) bool {
if c.PodID() != "" {
return false
}
state, err := c.State()
if err != nil {
logrus.Error(err)
return false
}
if state == define.ContainerStateStopped || state == define.ContainerStateExited {
return true
}
return false
}
filterFuncs = append(filterFuncs, containerStateFilter)
delContainers, err := r.GetContainers(filterFuncs...)
if err != nil {
return nil, nil, err
}
for _, c := range delContainers {
ctr := c
size, err := ctr.RWSize()
if err != nil {
pruneErrors[ctr.ID()] = err
continue
}
err = r.RemoveContainer(context.Background(), ctr, false, false)
pruneErrors[ctr.ID()] = err
if err != nil {
prunedContainers[ctr.ID()] = size
}
}
return prunedContainers, pruneErrors, nil
}
53 changes: 53 additions & 0 deletions pkg/api/handlers/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package handlers

import (
"fmt"
"github.com/docker/docker/api/types"
"net/http"

"github.com/containers/libpod/libpod"
Expand Down Expand Up @@ -192,3 +193,55 @@ func RestartContainer(w http.ResponseWriter, r *http.Request) {
// Success
utils.WriteResponse(w, http.StatusNoContent, "")
}

func PruneContainers(w http.ResponseWriter, r *http.Request) {
var (
delContainers []string
space int64
)
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)

query := struct {
Filters map[string][]string `schema:"filter"`
}{}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}

filterFuncs, err := utils.GenerateFilterFuncsFromMap(runtime, query.Filters)
if err != nil {
utils.InternalServerError(w, err)
return
}
prunedContainers, pruneErrors, err := runtime.PruneContainers(filterFuncs)
if err != nil {
utils.InternalServerError(w, err)
return
}

// Libpod response differs
if utils.IsLibpodRequest(r) {
var response []LibpodContainersPruneReport
for ctrID, size := range prunedContainers {
response = append(response, LibpodContainersPruneReport{ID: ctrID, SpaceReclaimed: size})
}
for ctrID, err := range pruneErrors {
response = append(response, LibpodContainersPruneReport{ID: ctrID, PruneError: err.Error()})
}
utils.WriteResponse(w, http.StatusOK, response)
return
}
for ctrID, size := range prunedContainers {
if pruneErrors[ctrID] == nil {
space += size
delContainers = append(delContainers, ctrID)
}
}
report := types.ContainersPruneReport{
ContainersDeleted: delContainers,
SpaceReclaimed: uint64(space),
}
utils.WriteResponse(w, http.StatusOK, report)
}
80 changes: 29 additions & 51 deletions pkg/api/handlers/generic/containers.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package generic

import (
"context"
"encoding/binary"
"fmt"
"net/http"
Expand All @@ -11,12 +10,10 @@ import (
"time"

"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/logs"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/util"
"github.com/docker/docker/api/types"
"github.com/gorilla/mux"
"github.com/gorilla/schema"
"github.com/pkg/errors"
Expand Down Expand Up @@ -47,14 +44,40 @@ func RemoveContainer(w http.ResponseWriter, r *http.Request) {
}

func ListContainers(w http.ResponseWriter, r *http.Request) {
var (
containers []*libpod.Container
err error
)
runtime := r.Context().Value("runtime").(*libpod.Runtime)

containers, err := runtime.GetAllContainers()
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
All bool `schema:"all"`
Limit int `schema:"limit"`
Size bool `schema:"size"`
Filters map[string][]string `schema:"filters"`
}{
// override any golang type defaults
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
if query.All {
containers, err = runtime.GetAllContainers()
} else {
containers, err = runtime.GetRunningContainers()
}
if err != nil {
utils.InternalServerError(w, err)
return
}

if _, found := mux.Vars(r)["limit"]; found {
last := query.Limit
if len(containers) > last {
containers = containers[len(containers)-last:]
}
}
// TODO filters still need to be applied
infoData, err := runtime.Info()
if err != nil {
utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain system info"))
Expand Down Expand Up @@ -125,51 +148,6 @@ func WaitContainer(w http.ResponseWriter, r *http.Request) {
})
}

func PruneContainers(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)

containers, err := runtime.GetAllContainers()
if err != nil {
utils.InternalServerError(w, err)
return
}

deletedContainers := []string{}
var spaceReclaimed uint64
for _, ctnr := range containers {
// Only remove stopped or exit'ed containers.
state, err := ctnr.State()
if err != nil {
utils.InternalServerError(w, err)
return
}
switch state {
case define.ContainerStateStopped, define.ContainerStateExited:
default:
continue
}

deletedContainers = append(deletedContainers, ctnr.ID())
cSize, err := ctnr.RootFsSize()
if err != nil {
utils.InternalServerError(w, err)
return
}
spaceReclaimed += uint64(cSize)

err = runtime.RemoveContainer(context.Background(), ctnr, false, false)
if err != nil && !(errors.Cause(err) == define.ErrCtrRemoved) {
utils.InternalServerError(w, err)
return
}
}
report := types.ContainersPruneReport{
ContainersDeleted: deletedContainers,
SpaceReclaimed: spaceReclaimed,
}
utils.WriteResponse(w, http.StatusOK, report)
}

func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value("decoder").(*schema.Decoder)
runtime := r.Context().Value("runtime").(*libpod.Runtime)
Expand Down
4 changes: 3 additions & 1 deletion pkg/api/handlers/generic/containers_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
return
}

if len(input.HostConfig.Links) > 0 {
utils.Error(w, utils.ErrLinkNotSupport.Error(), http.StatusBadRequest, errors.Wrapf(utils.ErrLinkNotSupport, "bad parameter"))
}
newImage, err := runtime.ImageRuntime().NewFromLocal(input.Image)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "NewFromLocal()"))
Expand Down
42 changes: 25 additions & 17 deletions pkg/api/handlers/libpod/containers.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package libpod

import (
"fmt"
"net/http"
"strconv"

"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
Expand Down Expand Up @@ -46,12 +48,18 @@ func RemoveContainer(w http.ResponseWriter, r *http.Request) {
utils.RemoveContainer(w, r, query.Force, query.Vols)
}
func ListContainers(w http.ResponseWriter, r *http.Request) {
var (
filters []string
)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
Filter []string `schema:"filter"`
Last int `schema:"last"`
Size bool `schema:"size"`
Sync bool `schema:"sync"`
All bool `schema:"all"`
Filter map[string][]string `schema:"filter"`
Last int `schema:"last"`
Namespace bool `schema:"namespace"`
Pod bool `schema:"pod"`
Size bool `schema:"size"`
Sync bool `schema:"sync"`
}{
// override any golang type defaults
}
Expand All @@ -63,15 +71,22 @@ func ListContainers(w http.ResponseWriter, r *http.Request) {
}
runtime := r.Context().Value("runtime").(*libpod.Runtime)
opts := shared.PsOptions{
All: true,
All: query.All,
Last: query.Last,
Size: query.Size,
Sort: "",
Namespace: true,
Namespace: query.Namespace,
Pod: query.Pod,
Sync: query.Sync,
}

pss, err := shared.GetPsContainerOutput(runtime, opts, query.Filter, 2)
if len(query.Filter) > 0 {
for k, v := range query.Filter {
for _, val := range v {
filters = append(filters, fmt.Sprintf("%s=%s", k, val))
}
}
}
pss, err := shared.GetPsContainerOutput(runtime, opts, filters, 2)
if err != nil {
utils.InternalServerError(w, err)
}
Expand Down Expand Up @@ -117,19 +132,12 @@ func KillContainer(w http.ResponseWriter, r *http.Request) {
}

func WaitContainer(w http.ResponseWriter, r *http.Request) {
_, err := utils.WaitContainer(w, r)
exitCode, err := utils.WaitContainer(w, r)
if err != nil {
utils.InternalServerError(w, err)
return
}
utils.WriteResponse(w, http.StatusNoContent, "")
}

func PruneContainers(w http.ResponseWriter, r *http.Request) {
// TODO Needs rebase to get filers; Also would be handy to define
// an actual libpod container prune method.
// force
// filters
utils.WriteResponse(w, http.StatusOK, strconv.Itoa(int(exitCode)))
}

func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
Expand Down
7 changes: 7 additions & 0 deletions pkg/api/handlers/swagger.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ type swagContainerPruneReport struct {
Body []ContainersPruneReport
}

// Prune containers
// swagger:response DocsLibpodPruneResponse
type swagLibpodContainerPruneReport struct {
// in: body
Body []LibpodContainersPruneReport
}

// Inspect container
// swagger:response DocsContainerInspectResponse
type swagContainerInspectResponse struct {
Expand Down
6 changes: 6 additions & 0 deletions pkg/api/handlers/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ type ContainersPruneReport struct {
docker.ContainersPruneReport
}

type LibpodContainersPruneReport struct {
ID string `json:"id"`
SpaceReclaimed int64 `json:"space"`
PruneError string `json:"error"`
}

type Info struct {
docker.Info
BuildahVersion string
Expand Down
21 changes: 20 additions & 1 deletion pkg/api/handlers/utils/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"syscall"
"time"

"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/gorilla/mux"
Expand Down Expand Up @@ -83,7 +84,7 @@ func WaitContainer(w http.ResponseWriter, r *http.Request) (int32, error) {
}

if len(query.Condition) > 0 {
return 0, errors.Errorf("the condition parameter is not supported")
UnSupportedParameter("condition")
}

name := mux.Vars(r)["name"]
Expand All @@ -101,3 +102,21 @@ func WaitContainer(w http.ResponseWriter, r *http.Request) (int32, error) {
}
return con.Wait()
}

// GenerateFilterFuncsFromMap is used to generate un-executed functions that can be used to filter
// containers. It is specifically designed for the RESTFUL API input.
func GenerateFilterFuncsFromMap(r *libpod.Runtime, filters map[string][]string) ([]libpod.ContainerFilter, error) {
var (
filterFuncs []libpod.ContainerFilter
)
for k, v := range filters {
for _, val := range v {
f, err := shared.GenerateContainerFilterFuncs(k, val, r)
if err != nil {
return filterFuncs, err
}
filterFuncs = append(filterFuncs, f)
}
}
return filterFuncs, nil
}
8 changes: 8 additions & 0 deletions pkg/api/handlers/utils/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,11 @@ func WriteJSON(w http.ResponseWriter, code int, value interface{}) {
logrus.Errorf("unable to write json: %q", err)
}
}

func FilterMapToString(filters map[string][]string) (string, error) {
f, err := json.Marshal(filters)
if err != nil {
return "", err
}
return string(f), nil
}
Loading

0 comments on commit cf7be58

Please sign in to comment.