diff options
31 files changed, 434 insertions, 179 deletions
diff --git a/cmd/podman/common.go b/cmd/podman/common.go index f3aff1d49..46feae90d 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -12,7 +12,7 @@ import ( "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/sysinfo" - "github.com/fatih/camelcase" + "github.com/containers/libpod/pkg/util/camelcase" jsoniter "github.com/json-iterator/go" "github.com/pkg/errors" "github.com/spf13/cobra" diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go index 15d6bddbb..2f637694b 100644 --- a/cmd/podman/shared/create.go +++ b/cmd/podman/shared/create.go @@ -31,9 +31,9 @@ import ( "github.com/sirupsen/logrus" ) -// seccompAnnotationKey is the key of the image annotation embedding a seccomp +// seccompLabelKey is the key of the image annotation embedding a seccomp // profile. -const seccompAnnotationKey = "io.containers.seccomp.profile" +const seccompLabelKey = "io.containers.seccomp.profile" func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.Runtime) (*libpod.Container, *cc.CreateConfig, error) { var ( @@ -709,7 +709,7 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod. // SECCOMP if data != nil { - if value, exists := data.Annotations[seccompAnnotationKey]; exists { + if value, exists := labels[seccompLabelKey]; exists { secConfig.SeccompProfileFromImage = value } } diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index 7f0c2260c..0e641f3a3 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -679,7 +679,7 @@ of the container is assumed to be managed externally. **--seccomp-policy**=*policy* -Specify the policy to select the seccomp profile. If set to *image*, Podman will look for a "io.podman.seccomp.profile" annotation in the container image and use its value as a seccomp profile. Otherwise, Podman will follow the *default* policy by applying the default profile unless specified otherwise via *--security-opt seccomp* as described below. +Specify the policy to select the seccomp profile. If set to *image*, Podman will look for a "io.podman.seccomp.profile" label in the container-image config and use its value as a seccomp profile. Otherwise, Podman will follow the *default* policy by applying the default profile unless specified otherwise via *--security-opt seccomp* as described below. Note that this feature is experimental and may change in the future. diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 2b1452b53..512a382a6 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -700,7 +700,7 @@ Note: On `SELinux` systems, the rootfs needs the correct label, which is by defa **--seccomp-policy**=*policy* -Specify the policy to select the seccomp profile. If set to *image*, Podman will look for a "io.podman.seccomp.profile" annotation in the container image and use its value as a seccomp profile. Otherwise, Podman will follow the *default* policy by applying the default profile unless specified otherwise via *--security-opt seccomp* as described below. +Specify the policy to select the seccomp profile. If set to *image*, Podman will look for a "io.podman.seccomp.profile" label in the container-image config and use its value as a seccomp profile. Otherwise, Podman will follow the *default* policy by applying the default profile unless specified otherwise via *--security-opt seccomp* as described below. Note that this feature is experimental and may change in the future. @@ -29,7 +29,7 @@ require ( github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect github.com/elazarl/goproxy/ext v0.0.0-20190911111923-ecfe977594f1 // indirect github.com/etcd-io/bbolt v1.3.3 - github.com/fatih/camelcase v1.0.0 + github.com/fatih/camelcase v1.0.0 // indirect github.com/fsnotify/fsnotify v1.4.7 github.com/ghodss/yaml v1.0.0 github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index de7cfd3b8..e8952967d 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -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 +} diff --git a/pkg/api/handlers/containers.go b/pkg/api/handlers/containers.go index 6b09321a0..b5c78ce53 100644 --- a/pkg/api/handlers/containers.go +++ b/pkg/api/handlers/containers.go @@ -2,6 +2,7 @@ package handlers import ( "fmt" + "github.com/docker/docker/api/types" "net/http" "github.com/containers/libpod/libpod" @@ -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) +} diff --git a/pkg/api/handlers/decoder.go b/pkg/api/handlers/decoder.go new file mode 100644 index 000000000..890d77ecc --- /dev/null +++ b/pkg/api/handlers/decoder.go @@ -0,0 +1,91 @@ +package handlers + +import ( + "encoding/json" + "reflect" + "time" + + "github.com/gorilla/schema" + "github.com/sirupsen/logrus" +) + +// NewAPIDecoder returns a configured schema.Decoder +func NewAPIDecoder() *schema.Decoder { + _ = ParseDateTime + + d := schema.NewDecoder() + d.IgnoreUnknownKeys(true) + d.RegisterConverter(map[string][]string{}, convertUrlValuesString) + d.RegisterConverter(time.Time{}, convertTimeString) + return d +} + +// On client: +// v := map[string][]string{ +// "dangling": {"true"}, +// } +// +// payload, err := jsoniter.MarshalToString(v) +// if err != nil { +// panic(err) +// } +// payload = url.QueryEscape(payload) +func convertUrlValuesString(query string) reflect.Value { + f := map[string][]string{} + + err := json.Unmarshal([]byte(query), &f) + if err != nil { + logrus.Infof("convertUrlValuesString: Failed to Unmarshal %s: %s", query, err.Error()) + } + + return reflect.ValueOf(f) +} + +// isZero() can be used to determine if parsing failed. +func convertTimeString(query string) reflect.Value { + var ( + err error + t time.Time + ) + + for _, f := range []string{ + time.UnixDate, + time.ANSIC, + time.RFC1123, + time.RFC1123Z, + time.RFC3339, + time.RFC3339Nano, + time.RFC822, + time.RFC822Z, + time.RFC850, + time.RubyDate, + time.Stamp, + time.StampMicro, + time.StampMilli, + time.StampNano, + } { + t, err = time.Parse(f, query) + if err == nil { + return reflect.ValueOf(t) + } + + if _, isParseError := err.(*time.ParseError); isParseError { + // Try next format + continue + } else { + break + } + } + + // We've exhausted all formats, or something bad happened + if err != nil { + logrus.Infof("convertTimeString: Failed to parse %s: %s", query, err.Error()) + } + return reflect.ValueOf(time.Time{}) +} + +// ParseDateTime is a helper function to aid in parsing different Time/Date formats +// isZero() can be used to determine if parsing failed. +func ParseDateTime(query string) time.Time { + return convertTimeString(query).Interface().(time.Time) +} diff --git a/pkg/api/handlers/events.go b/pkg/api/handlers/events.go index 900efa3da..44bf35254 100644 --- a/pkg/api/handlers/events.go +++ b/pkg/api/handlers/events.go @@ -1,9 +1,10 @@ package handlers import ( - "encoding/json" "fmt" "net/http" + "strings" + "time" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/pkg/errors" @@ -11,30 +12,24 @@ import ( func GetEvents(w http.ResponseWriter, r *http.Request) { query := struct { - Since string `json:"since"` - Until string `json:"until"` - Filters string `json:"filters"` + Since time.Time `schema:"since"` + Until time.Time `schema:"until"` + Filters map[string][]string `schema:"filters"` }{} if err := decodeQuery(r, &query); err != nil { utils.Error(w, "Failed to parse parameters", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) } - var filters = map[string][]string{} - if found := hasVar(r, "filters"); found { - if err := json.Unmarshal([]byte(query.Filters), &filters); err != nil { - utils.BadRequest(w, "filters", query.Filters, err) - return + var libpodFilters = []string{} + if _, found := r.URL.Query()["filters"]; found { + for k, v := range query.Filters { + libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0])) } } - var libpodFilters = make([]string, len(filters)) - for k, v := range filters { - libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0])) - } - libpodEvents, err := getRuntime(r).GetEvents(libpodFilters) if err != nil { - utils.BadRequest(w, "filters", query.Filters, err) + utils.BadRequest(w, "filters", strings.Join(r.URL.Query()["filters"], ", "), err) return } diff --git a/pkg/api/handlers/generic/containers.go b/pkg/api/handlers/generic/containers.go index 5a0a51fd7..8dc73ae14 100644 --- a/pkg/api/handlers/generic/containers.go +++ b/pkg/api/handlers/generic/containers.go @@ -1,7 +1,6 @@ package generic import ( - "context" "encoding/binary" "fmt" "net/http" @@ -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" @@ -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")) @@ -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) diff --git a/pkg/api/handlers/generic/containers_create.go b/pkg/api/handlers/generic/containers_create.go index f98872690..edefd5757 100644 --- a/pkg/api/handlers/generic/containers_create.go +++ b/pkg/api/handlers/generic/containers_create.go @@ -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()")) diff --git a/pkg/api/handlers/generic/images.go b/pkg/api/handlers/generic/images.go index 395f64064..93adb7f69 100644 --- a/pkg/api/handlers/generic/images.go +++ b/pkg/api/handlers/generic/images.go @@ -62,14 +62,14 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { // 200 no error // 500 internal var ( - dangling bool = true + dangling = true err error ) decoder := r.Context().Value("decoder").(*schema.Decoder) runtime := r.Context().Value("runtime").(*libpod.Runtime) query := struct { - filters map[string]string + Filters map[string][]string `schema:"filters"` }{ // This is where you can override the golang default value for one of fields } @@ -79,26 +79,25 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { return } - // FIXME This is likely wrong due to it not being a map[string][]string - // until ts is not supported on podman prune - if len(query.filters["until"]) > 0 { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "until is not supported yet")) + if v, found := query.Filters["until"]; found { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "until=%s is not supported yet", v)) return } // labels are not supported on podman prune - if len(query.filters["label"]) > 0 { + if _, found := query.Filters["since"]; found { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "labelis not supported yet")) return } - if len(query.filters["dangling"]) > 0 { - dangling, err = strconv.ParseBool(query.filters["dangling"]) + if v, found := query.Filters["dangling"]; found { + dangling, err = strconv.ParseBool(v[0]) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "processing dangling filter")) return } } + idr := []types.ImageDeleteResponseItem{} // // This code needs to be migrated to libpod to work correctly. I could not diff --git a/pkg/api/handlers/handler.go b/pkg/api/handlers/handler.go index 4f303f6ab..d60a5b239 100644 --- a/pkg/api/handlers/handler.go +++ b/pkg/api/handlers/handler.go @@ -15,10 +15,11 @@ func getVar(r *http.Request, k string) string { return mux.Vars(r)[k] } -func hasVar(r *http.Request, k string) bool { - _, found := mux.Vars(r)[k] - return found -} +// func hasVar(r *http.Request, k string) bool { +// _, found := mux.Vars(r)[k] +// return found +// } + func getName(r *http.Request) string { return getVar(r, "name") } diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index e16a4ea1f..df16843c7 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -1,7 +1,9 @@ package libpod import ( + "fmt" "net/http" + "strconv" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" @@ -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 } @@ -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) } @@ -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) { diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 0d4e220a8..bbc8c9346 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -1,6 +1,7 @@ package libpod import ( + "fmt" "io/ioutil" "net/http" "os" @@ -42,12 +43,12 @@ func ImageExists(w http.ResponseWriter, r *http.Request) { func ImageTree(w http.ResponseWriter, r *http.Request) { // tree is a bit of a mess ... logic is in adapter and therefore not callable from here. needs rework - //name := mux.Vars(r)["name"] - //_, layerInfoMap, _, err := s.Runtime.Tree(name) - //if err != nil { + // name := mux.Vars(r)["name"] + // _, layerInfoMap, _, err := s.Runtime.Tree(name) + // if err != nil { // Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to find image information for %q", name)) // return - //} + // } // it is not clear to me how to deal with this given all the processing of the image // is in main. we need to discuss how that really should be and return something useful. handlers.UnsupportedHandler(w, r) @@ -95,8 +96,8 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { - All bool `schema:"all"` - Filters []string `schema:"filters"` + All bool `schema:"all"` + Filters map[string][]string `schema:"filters"` }{ // override any golang type defaults } @@ -106,7 +107,14 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) return } - cids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, query.Filters) + + var libpodFilters = []string{} + if _, found := r.URL.Query()["filters"]; found { + for k, v := range query.Filters { + libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0])) + } + } + cids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, libpodFilters) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index 14f8e8de7..656a75646 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -108,7 +108,7 @@ func Pods(w http.ResponseWriter, r *http.Request) { ) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { - filters []string `schema:"filters"` + Filters map[string][]string `schema:"filters"` }{ // override any golang type defaults } @@ -117,10 +117,12 @@ func Pods(w http.ResponseWriter, r *http.Request) { errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) return } - if len(query.filters) > 0 { + + if _, found := r.URL.Query()["filters"]; found { utils.Error(w, "filters are not implemented yet", http.StatusInternalServerError, define.ErrNotImplemented) return } + pods, err := runtime.GetAllPods() if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) @@ -164,7 +166,7 @@ func PodStop(w http.ResponseWriter, r *http.Request) { decoder = r.Context().Value("decoder").(*schema.Decoder) ) query := struct { - timeout int `schema:"t"` + Timeout int `schema:"t"` }{ // override any golang type defaults } @@ -207,8 +209,8 @@ func PodStop(w http.ResponseWriter, r *http.Request) { return } - if query.timeout > 0 { - _, stopError = pod.StopWithTimeout(r.Context(), false, query.timeout) + if query.Timeout > 0 { + _, stopError = pod.StopWithTimeout(r.Context(), false, query.Timeout) } else { _, stopError = pod.Stop(r.Context(), false) } diff --git a/pkg/api/handlers/swagger.go b/pkg/api/handlers/swagger.go index 5509c1d46..faae98798 100644 --- a/pkg/api/handlers/swagger.go +++ b/pkg/api/handlers/swagger.go @@ -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 { diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 2526a3317..33cd51164 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -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 diff --git a/pkg/api/handlers/utils/containers.go b/pkg/api/handlers/utils/containers.go index 64d3d378a..2c986db3a 100644 --- a/pkg/api/handlers/utils/containers.go +++ b/pkg/api/handlers/utils/containers.go @@ -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" @@ -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"] @@ -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 +} diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go index 2fd9bffba..f2ce26f1a 100644 --- a/pkg/api/handlers/utils/handler.go +++ b/pkg/api/handlers/utils/handler.go @@ -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 +} diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index 9445298ca..a0d340471 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -15,17 +15,18 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) { decoder := r.Context().Value("decoder").(*schema.Decoder) runtime := r.Context().Value("runtime").(*libpod.Runtime) query := struct { - //all bool # all is currently unused - filters []string - //digests bool # digests is currently unused + // all bool # all is currently unused + Filters map[string][]string `schema:"filters"` + // digests bool # digests is currently unused }{ // This is where you can override the golang default value for one of fields } if err := decoder.Decode(&query, r.URL.Query()); err != nil { return nil, err } - filters := query.filters - if len(filters) < 1 { + + var filters = []string{} + if _, found := r.URL.Query()["filters"]; found { filters = append(filters, fmt.Sprintf("reference=%s", "")) } return runtime.ImageRuntime().GetImagesWithFilters(filters) diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index 58920d106..b2d2ab388 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -42,6 +42,20 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // description: Returns a list of containers // parameters: // - in: query + // name: all + // type: boolean + // default: false + // description: Return all containers. By default, only running containers are shown + // - in: query + // name: limit + // description: Return this number of most recently created containers, including non-running ones. + // type: integer + // - in: query + // name: size + // type: boolean + // default: false + // description: Return the size of container as fields SizeRw and SizeRootFs. + // - in: query // name: filters // type: string // description: | @@ -91,7 +105,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // $ref: "#/responses/DocsContainerPruneReport" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/containers/prune"), APIHandler(s.Context, generic.PruneContainers)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/containers/prune"), APIHandler(s.Context, handlers.PruneContainers)).Methods(http.MethodPost) // swagger:operation DELETE /containers/{name} compat removeContainer // --- // tags: @@ -175,6 +189,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // type: string // default: TERM // description: signal to be sent to container + // default: SIGKILL // produces: // - application/json // responses: @@ -301,7 +316,8 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // - in: query // name: detachKeys // type: string - // description: needs description + // description: "Override the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _." + // default: ctrl-p,ctrl-q // produces: // - application/json // responses: @@ -431,7 +447,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // - in: query // name: condition // type: string - // description: Wait until the container reaches the given condition + // description: not supported // produces: // - application/json // responses: @@ -541,6 +557,55 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // - containers // summary: List containers // description: Returns a list of containers + // parameters: + // - in: query + // name: all + // type: boolean + // default: false + // description: Return all containers. By default, only running containers are shown + // - in: query + // name: limit + // description: Return this number of most recently created containers, including non-running ones. + // type: integer + // - in: query + // name: namespace + // type: boolean + // description: Include namespace information + // default: false + // - in: query + // name: pod + // type: boolean + // default: false + // description: Include Pod ID and Name if applicable + // - in: query + // name: size + // type: boolean + // default: false + // description: Return the size of container as fields SizeRw and SizeRootFs. + // - in: query + // name: sync + // type: boolean + // default: false + // description: Sync container state with OCI runtime + // - in: query + // name: filters + // type: string + // description: | + // Returns a list of containers. + // - ancestor=(<image-name>[:<tag>], <image id>, or <image@digest>) + // - before=(<container id> or <container name>) + // - expose=(<port>[/<proto>]|<startport-endport>/[<proto>]) + // - exited=<int> containers with exit code of <int> + // - health=(starting|healthy|unhealthy|none) + // - id=<ID> a container's ID + // - is-task=(true|false) + // - label=key or label="key=value" of a container label + // - name=<name> a container's name + // - network=(<network id> or <network name>) + // - publish=(<port>[/<proto>]|<startport-endport>/[<proto>]) + // - since=(<container id> or <container name>) + // - status=(created|restarting|running|removing|paused|exited|dead) + // - volume=(<volume name> or <mount point destination>) // produces: // - application/json // responses: @@ -551,18 +616,14 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.HandleFunc(VersionedPath("/libpod/containers/json"), APIHandler(s.Context, libpod.ListContainers)).Methods(http.MethodGet) - // swagger:operation POST /libpod/containers/prune libpod libpodPruneContainers + // swagger:operation POST /libpod/containers/prune libpod libpodPruneContainers // --- // tags: - // - containers - // summary: Prune unused containers - // description: Remove stopped and exited containers + // - containers + // summary: Delete stopped containers + // description: Remove containers not in use // parameters: // - in: query - // name: force - // type: boolean - // description: TODO This should be removed from API. Will always be true... - // - in: query // name: filters // type: string // description: | @@ -573,12 +634,10 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // - application/json // responses: // 200: - // type: string - // description: success - // example: OK + // $ref: "#/responses/DocsLibpodPruneResponse" // 500: // $ref: "#/responses/InternalError" - r.HandleFunc(VersionedPath("/libpod/containers/prune"), APIHandler(s.Context, libpod.PruneContainers)).Methods(http.MethodPost) + r.HandleFunc(VersionedPath("/libpod/containers/prune"), APIHandler(s.Context, handlers.PruneContainers)).Methods(http.MethodPost) // swagger:operation GET /libpod/containers/showmounted libpod showMounterContainers // --- // tags: @@ -796,7 +855,8 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // - in: query // name: detachKeys // type: string - // description: needs description + // description: "Override the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _." + // default: ctrl-p,ctrl-q // produces: // - application/json // responses: @@ -902,10 +962,6 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error { // type: string // required: true // description: the name or ID of the container - // - in: query - // name: condition - // type: string - // description: Wait until the container reaches the given condition // produces: // - application/json // responses: diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 847d11c3c..8c940763e 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -11,6 +11,7 @@ import ( "time" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers" "github.com/coreos/go-systemd/activation" "github.com/gorilla/mux" "github.com/gorilla/schema" @@ -71,7 +72,7 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li ReadTimeout: 20 * time.Second, WriteTimeout: 2 * time.Minute, }, - Decoder: schema.NewDecoder(), + Decoder: handlers.NewAPIDecoder(), Context: nil, Runtime: runtime, Listener: *listener, @@ -85,6 +86,7 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li }) ctx, cancelFn := context.WithCancel(context.Background()) + server.CancelFunc = cancelFn // TODO: Use ConnContext when ported to go 1.13 ctx = context.WithValue(ctx, "decoder", server.Decoder) @@ -92,9 +94,6 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li ctx = context.WithValue(ctx, "shutdownFunc", server.Shutdown) server.Context = ctx - server.CancelFunc = cancelFn - server.Decoder.IgnoreUnknownKeys(true) - router.NotFoundHandler = http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { // We can track user errors... diff --git a/pkg/api/server/swagger.go b/pkg/api/server/swagger.go index f6643600a..5098390bc 100644 --- a/pkg/api/server/swagger.go +++ b/pkg/api/server/swagger.go @@ -50,15 +50,6 @@ type swagInternalError struct { } } -// Generic error -// swagger:response GenericError -type swagGenericError struct { - // in:body - Body struct { - utils.ErrorModel - } -} - // Conflict error in operation // swagger:response ConflictError type swagConflictError struct { @@ -130,21 +121,6 @@ type swagListContainers struct { } } -// To be determined -// swagger:response tbd -type swagTBD struct { -} - -// Success -// swagger:response -type swag struct { - // in:body - Body struct { - // example: OK - ok string - } -} - // Success // swagger:response type ok struct { diff --git a/pkg/bindings/bindings.go b/pkg/bindings/bindings.go new file mode 100644 index 000000000..e83c4a5e1 --- /dev/null +++ b/pkg/bindings/bindings.go @@ -0,0 +1,9 @@ +// Package bindings provides golang-based access +// to the Podman REST API. Users can then interact with API endpoints +// to manage containers, images, pods, etc. +// +// This package exposes a series of methods that allow users to firstly +// create their connection with the API endpoints. Once the connection +// is established, users can then manage the Podman container runtime. + +package bindings diff --git a/vendor/github.com/fatih/camelcase/LICENSE.md b/pkg/util/camelcase/LICENSE.md index aa4a536ca..aa4a536ca 100644 --- a/vendor/github.com/fatih/camelcase/LICENSE.md +++ b/pkg/util/camelcase/LICENSE.md diff --git a/vendor/github.com/fatih/camelcase/README.md b/pkg/util/camelcase/README.md index 105a6ae33..105a6ae33 100644 --- a/vendor/github.com/fatih/camelcase/README.md +++ b/pkg/util/camelcase/README.md diff --git a/vendor/github.com/fatih/camelcase/camelcase.go b/pkg/util/camelcase/camelcase.go index 02160c9a4..0a82d1005 100644 --- a/vendor/github.com/fatih/camelcase/camelcase.go +++ b/pkg/util/camelcase/camelcase.go @@ -55,7 +55,7 @@ func Split(src string) (entries []string) { class := 0 // split into fields based on class of unicode character for _, r := range src { - switch true { + switch { case unicode.IsLower(r): class = 1 case unicode.IsUpper(r): @@ -86,5 +86,6 @@ func Split(src string) (entries []string) { entries = append(entries, string(s)) } } - return + + return entries } diff --git a/test/e2e/config.go b/test/e2e/config.go index 12d0e545e..96cc157be 100644 --- a/test/e2e/config.go +++ b/test/e2e/config.go @@ -19,8 +19,8 @@ var ( // The intention behind blocking all syscalls is to prevent // regressions in the future. The required syscalls can vary // depending on which runtime we're using. - alpineSeccomp = "docker.io/libpod/alpine-with-seccomp:latest" + alpineSeccomp = "docker.io/libpod/alpine-with-seccomp:label" // This image has a bogus/invalid seccomp profile which should // yield a json error when being read. - alpineBogusSeccomp = "docker.io/libpod/alpine-with-bogus-seccomp:latest" + alpineBogusSeccomp = "docker.io/libpod/alpine-with-bogus-seccomp:label" ) diff --git a/vendor/github.com/fatih/camelcase/.travis.yml b/vendor/github.com/fatih/camelcase/.travis.yml deleted file mode 100644 index 3489e3871..000000000 --- a/vendor/github.com/fatih/camelcase/.travis.yml +++ /dev/null @@ -1,3 +0,0 @@ -language: go -go: 1.x - diff --git a/vendor/modules.txt b/vendor/modules.txt index 2aa86270a..e28bbd41d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -261,8 +261,6 @@ github.com/docker/spdystream github.com/docker/spdystream/spdy # github.com/etcd-io/bbolt v1.3.3 github.com/etcd-io/bbolt -# github.com/fatih/camelcase v1.0.0 -github.com/fatih/camelcase # github.com/fsnotify/fsnotify v1.4.7 github.com/fsnotify/fsnotify # github.com/fsouza/go-dockerclient v1.6.0 |