diff options
Diffstat (limited to 'pkg/api/handlers')
-rw-r--r-- | pkg/api/handlers/containers.go | 53 | ||||
-rw-r--r-- | pkg/api/handlers/containers_create.go (renamed from pkg/api/handlers/generic/containers_create.go) | 13 | ||||
-rw-r--r-- | pkg/api/handlers/decoder.go | 91 | ||||
-rw-r--r-- | pkg/api/handlers/events.go | 25 | ||||
-rw-r--r-- | pkg/api/handlers/generic/config.go | 9 | ||||
-rw-r--r-- | pkg/api/handlers/generic/containers.go | 80 | ||||
-rw-r--r-- | pkg/api/handlers/generic/images.go | 73 | ||||
-rw-r--r-- | pkg/api/handlers/generic/swagger.go | 4 | ||||
-rw-r--r-- | pkg/api/handlers/handler.go | 25 | ||||
-rw-r--r-- | pkg/api/handlers/images.go | 41 | ||||
-rw-r--r-- | pkg/api/handlers/images_build.go | 229 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/containers.go | 48 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/images.go | 78 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/pods.go | 53 | ||||
-rw-r--r-- | pkg/api/handlers/swagger.go | 11 | ||||
-rw-r--r-- | pkg/api/handlers/types.go | 14 | ||||
-rw-r--r-- | pkg/api/handlers/utils/containers.go | 21 | ||||
-rw-r--r-- | pkg/api/handlers/utils/errors.go | 22 | ||||
-rw-r--r-- | pkg/api/handlers/utils/handler.go | 8 | ||||
-rw-r--r-- | pkg/api/handlers/utils/images.go | 17 |
20 files changed, 533 insertions, 382 deletions
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/generic/containers_create.go b/pkg/api/handlers/containers_create.go index f98872690..4781b23bc 100644 --- a/pkg/api/handlers/generic/containers_create.go +++ b/pkg/api/handlers/containers_create.go @@ -1,4 +1,4 @@ -package generic +package handlers import ( "encoding/json" @@ -10,7 +10,6 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" image2 "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/namespaces" createconfig "github.com/containers/libpod/pkg/spec" @@ -25,7 +24,7 @@ import ( func CreateContainer(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) - input := handlers.CreateContainerConfig{} + input := CreateContainerConfig{} query := struct { Name string `schema:"name"` }{ @@ -40,7 +39,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()")) @@ -72,13 +73,13 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { } response := ContainerCreateResponse{ - Id: ctr.ID(), + ID: ctr.ID(), Warnings: []string{}} utils.WriteResponse(w, http.StatusCreated, response) } -func makeCreateConfig(input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { +func makeCreateConfig(input CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { var ( err error init bool 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/config.go b/pkg/api/handlers/generic/config.go deleted file mode 100644 index f715d25eb..000000000 --- a/pkg/api/handlers/generic/config.go +++ /dev/null @@ -1,9 +0,0 @@ -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.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/images.go b/pkg/api/handlers/generic/images.go index 395f64064..ba1cf47b4 100644 --- a/pkg/api/handlers/generic/images.go +++ b/pkg/api/handlers/generic/images.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "net/http" "os" - "strconv" "strings" "github.com/containers/buildah" @@ -16,12 +15,10 @@ import ( "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/util" - "github.com/containers/storage" "github.com/docker/docker/api/types" "github.com/gorilla/mux" "github.com/gorilla/schema" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) func ExportImage(w http.ResponseWriter, r *http.Request) { @@ -59,17 +56,14 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { } func PruneImages(w http.ResponseWriter, r *http.Request) { - // 200 no error - // 500 internal var ( - dangling bool = true - err error + filters []string ) 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,61 +73,24 @@ 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")) - return - } - // labels are not supported on podman prune - if len(query.filters["label"]) > 0 { - 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 err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "processing dangling filter")) - return + idr := []types.ImageDeleteResponseItem{} + for k, v := range query.Filters { + for _, val := range v { + filters = append(filters, fmt.Sprintf("%s=%s", k, val)) } } - idr := []types.ImageDeleteResponseItem{} - // - // This code needs to be migrated to libpod to work correctly. I could not - // work my around the information docker needs with the existing prune in libpod. - // - pruneImages, err := runtime.ImageRuntime().GetPruneImages(!dangling, []image2.ImageFilter{}) + pruneCids, err := runtime.ImageRuntime().PruneImages(r.Context(), false, filters) if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to get images to prune")) + utils.InternalServerError(w, err) return } - for _, p := range pruneImages { - repotags, err := p.RepoTags() - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to get repotags for image")) - return - } - if err := p.Remove(r.Context(), true); err != nil { - if errors.Cause(err) == storage.ErrImageUsedByContainer { - logrus.Warnf("Failed to prune image %s as it is in use: %v", p.ID(), err) - continue - } - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to prune image")) - return - } - // newimageevent is not export therefore we cannot record the event. this will be fixed - // when the prune is fixed in libpod - // defer p.newImageEvent(events.Prune) - response := types.ImageDeleteResponseItem{ - Deleted: fmt.Sprintf("sha256:%s", p.ID()), // I ack this is not ideal - } - if len(repotags) > 0 { - response.Untagged = repotags[0] - } - idr = append(idr, response) + for _, p := range pruneCids { + idr = append(idr, types.ImageDeleteResponseItem{ + Deleted: p, + }) } + + //FIXME/TODO to do this exacty correct, pruneimages needs to return idrs and space-reclaimed, then we are golden ipr := types.ImagesPruneReport{ ImagesDeleted: idr, SpaceReclaimed: 1, // TODO we cannot supply this right now @@ -343,8 +300,6 @@ func GetImage(w http.ResponseWriter, r *http.Request) { } func GetImages(w http.ResponseWriter, r *http.Request) { - // 200 ok - // 500 internal images, err := utils.GetImages(w, r) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images")) diff --git a/pkg/api/handlers/generic/swagger.go b/pkg/api/handlers/generic/swagger.go index 27e1fc18d..bfe527c41 100644 --- a/pkg/api/handlers/generic/swagger.go +++ b/pkg/api/handlers/generic/swagger.go @@ -1,11 +1,13 @@ package generic +import "github.com/containers/libpod/pkg/api/handlers" + // Create container // swagger:response ContainerCreateResponse type swagCtrCreateResponse struct { // in:body Body struct { - ContainerCreateResponse + handlers.ContainerCreateResponse } } diff --git a/pkg/api/handlers/handler.go b/pkg/api/handlers/handler.go index 2efeb1379..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") } @@ -36,11 +37,11 @@ func getRuntime(r *http.Request) *libpod.Runtime { return r.Context().Value("runtime").(*libpod.Runtime) } -func getHeader(r *http.Request, k string) string { - return r.Header.Get(k) -} - -func hasHeader(r *http.Request, k string) bool { - _, found := r.Header[k] - return found -} +// func getHeader(r *http.Request, k string) string { +// return r.Header.Get(k) +// } +// +// func hasHeader(r *http.Request, k string) bool { +// _, found := r.Header[k] +// return found +// } diff --git a/pkg/api/handlers/images.go b/pkg/api/handlers/images.go index b4acdc312..3f66a63c8 100644 --- a/pkg/api/handlers/images.go +++ b/pkg/api/handlers/images.go @@ -3,7 +3,6 @@ package handlers import ( "fmt" "io" - "io/ioutil" "net/http" "os" "strconv" @@ -127,46 +126,6 @@ func GetImage(r *http.Request, name string) (*image.Image, error) { return runtime.ImageRuntime().NewFromLocal(name) } -func LoadImage(w http.ResponseWriter, r *http.Request) { - decoder := r.Context().Value("decoder").(*schema.Decoder) - runtime := r.Context().Value("runtime").(*libpod.Runtime) - - query := struct { - //quiet bool # quiet 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 { - utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) - return - } - - var ( - err error - writer io.Writer - ) - f, err := ioutil.TempFile("", "api_load.tar") - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile")) - return - } - if err := SaveFromBody(f, r); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file")) - return - } - id, err := runtime.LoadImage(r.Context(), "", f.Name(), writer, "") - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to load image")) - return - } - utils.WriteResponse(w, http.StatusOK, struct { - Stream string `json:"stream"` - }{ - Stream: fmt.Sprintf("Loaded image: %s\n", id), - }) -} - func SaveFromBody(f *os.File, r *http.Request) error { // nolint if _, err := io.Copy(f, r.Body); err != nil { return err diff --git a/pkg/api/handlers/images_build.go b/pkg/api/handlers/images_build.go index c7c746392..b29c45574 100644 --- a/pkg/api/handlers/images_build.go +++ b/pkg/api/handlers/images_build.go @@ -1,6 +1,7 @@ package handlers import ( + "bytes" "encoding/base64" "encoding/json" "fmt" @@ -9,58 +10,66 @@ import ( "net/http" "os" "path/filepath" + "strconv" "strings" "github.com/containers/buildah" "github.com/containers/buildah/imagebuildah" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/storage/pkg/archive" - log "github.com/sirupsen/logrus" + "github.com/gorilla/mux" ) func BuildImage(w http.ResponseWriter, r *http.Request) { authConfigs := map[string]AuthConfig{} - if hasHeader(r, "X-Registry-Config") { - registryHeader := getHeader(r, "X-Registry-Config") - authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(registryHeader)) + if hdr, found := r.Header["X-Registry-Config"]; found && len(hdr) > 0 { + authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(hdr[0])) if json.NewDecoder(authConfigsJSON).Decode(&authConfigs) != nil { - utils.BadRequest(w, "X-Registry-Config", registryHeader, json.NewDecoder(authConfigsJSON).Decode(&authConfigs)) + utils.BadRequest(w, "X-Registry-Config", hdr[0], json.NewDecoder(authConfigsJSON).Decode(&authConfigs)) return } } + if hdr, found := r.Header["Content-Type"]; found && len(hdr) > 0 { + if hdr[0] != "application/x-tar" { + utils.BadRequest(w, "Content-Type", hdr[0], + fmt.Errorf("Content-Type: %s is not supported. Should be \"application/x-tar\"", hdr[0])) + } + } + anchorDir, err := extractTarFile(r, w) if err != nil { utils.InternalServerError(w, err) return } - // defer os.RemoveAll(anchorDir) + defer os.RemoveAll(anchorDir) query := struct { - Dockerfile string `json:"dockerfile"` - Tag string `json:"t"` - ExtraHosts string `json:"extrahosts"` - Remote string `json:"remote"` - Quiet bool `json:"q"` - NoCache bool `json:"nocache"` - CacheFrom string `json:"cachefrom"` - Pull string `json:"pull"` - Rm bool `json:"rm"` - ForceRm bool `json:"forcerm"` - Memory int `json:"memory"` - MemSwap int `json:"memswap"` - CpuShares int `json:"cpushares"` - CpuSetCpus string `json:"cpusetcpus"` - CpuPeriod int `json:"cpuperiod"` - CpuQuota int `json:"cpuquota"` - BuildArgs string `json:"buildargs"` - ShmSize int `json:"shmsize"` - Squash bool `json:"squash"` - Labels string `json:"labels"` - NetworkMode string `json:"networkmode"` - Platform string `json:"platform"` - Target string `json:"target"` - Outputs string `json:"outputs"` + Dockerfile string `schema:"dockerfile"` + Tag string `schema:"t"` + ExtraHosts string `schema:"extrahosts"` + Remote string `schema:"remote"` + Quiet bool `schema:"q"` + NoCache bool `schema:"nocache"` + CacheFrom string `schema:"cachefrom"` + Pull bool `schema:"pull"` + Rm bool `schema:"rm"` + ForceRm bool `schema:"forcerm"` + Memory int64 `schema:"memory"` + MemSwap int64 `schema:"memswap"` + CpuShares uint64 `schema:"cpushares"` + CpuSetCpus string `schema:"cpusetcpus"` + CpuPeriod uint64 `schema:"cpuperiod"` + CpuQuota int64 `schema:"cpuquota"` + BuildArgs string `schema:"buildargs"` + ShmSize int `schema:"shmsize"` + Squash bool `schema:"squash"` + Labels string `schema:"labels"` + NetworkMode string `schema:"networkmode"` + Platform string `schema:"platform"` + Target string `schema:"target"` + Outputs string `schema:"outputs"` + Registry string `schema:"registry"` }{ Dockerfile: "Dockerfile", Tag: "", @@ -69,7 +78,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { Quiet: false, NoCache: false, CacheFrom: "", - Pull: "", + Pull: false, Rm: true, ForceRm: false, Memory: 0, @@ -86,6 +95,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { Platform: "", Target: "", Outputs: "", + Registry: "docker.io", } if err := decodeQuery(r, &query); err != nil { @@ -93,80 +103,121 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { return } - // Tag is the name with optional tag... - var name = query.Tag - var tag string + var ( + // Tag is the name with optional tag... + name = query.Tag + tag = "latest" + ) if strings.Contains(query.Tag, ":") { tokens := strings.SplitN(query.Tag, ":", 2) name = tokens[0] tag = tokens[1] } + if t, found := mux.Vars(r)["target"]; found { + name = t + } + var buildArgs = map[string]string{} - if found := hasVar(r, "buildargs"); found { - if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil { - utils.BadRequest(w, "buildargs", query.BuildArgs, err) + if a, found := mux.Vars(r)["buildargs"]; found { + if err := json.Unmarshal([]byte(a), &buildArgs); err != nil { + utils.BadRequest(w, "buildargs", a, err) return } } // convert label formats var labels = []string{} - if hasVar(r, "labels") { + if l, found := mux.Vars(r)["labels"]; found { var m = map[string]string{} - if err := json.Unmarshal([]byte(query.Labels), &m); err != nil { - utils.BadRequest(w, "labels", query.Labels, err) + if err := json.Unmarshal([]byte(l), &m); err != nil { + utils.BadRequest(w, "labels", l, err) return } for k, v := range m { - labels = append(labels, fmt.Sprintf("%s=%v", k, v)) + labels = append(labels, k+"="+v) + } + } + + pullPolicy := buildah.PullIfMissing + if _, found := mux.Vars(r)["pull"]; found { + if query.Pull { + pullPolicy = buildah.PullAlways } } + // build events will be recorded here + var ( + buildEvents = []string{} + progress = bytes.Buffer{} + ) + buildOptions := imagebuildah.BuildOptions{ ContextDirectory: filepath.Join(anchorDir, "build"), - PullPolicy: 0, - Registry: "", - IgnoreUnrecognizedInstructions: false, + PullPolicy: pullPolicy, + Registry: query.Registry, + IgnoreUnrecognizedInstructions: true, Quiet: query.Quiet, - Isolation: 0, + Isolation: buildah.IsolationChroot, Runtime: "", RuntimeArgs: nil, TransientMounts: nil, - Compression: 0, + Compression: archive.Gzip, Args: buildArgs, Output: name, AdditionalTags: []string{tag}, - Log: nil, - In: nil, - Out: nil, - Err: nil, - SignaturePolicyPath: "", - ReportWriter: nil, - OutputFormat: "", - SystemContext: nil, - NamespaceOptions: nil, - ConfigureNetwork: 0, - CNIPluginPath: "", - CNIConfigDir: "", - IDMappingOptions: nil, - AddCapabilities: nil, - DropCapabilities: nil, - CommonBuildOpts: &buildah.CommonBuildOptions{}, - DefaultMountsFilePath: "", - IIDFile: "", - Squash: query.Squash, - Labels: labels, - Annotations: nil, - OnBuild: nil, - Layers: false, - NoCache: query.NoCache, - RemoveIntermediateCtrs: query.Rm, - ForceRmIntermediateCtrs: query.ForceRm, - BlobDirectory: "", - Target: query.Target, - Devices: nil, + Log: func(format string, args ...interface{}) { + buildEvents = append(buildEvents, fmt.Sprintf(format, args...)) + }, + In: nil, + Out: &progress, + Err: &progress, + SignaturePolicyPath: "", + ReportWriter: &progress, + OutputFormat: buildah.Dockerv2ImageManifest, + SystemContext: nil, + NamespaceOptions: nil, + ConfigureNetwork: 0, + CNIPluginPath: "", + CNIConfigDir: "", + IDMappingOptions: nil, + AddCapabilities: nil, + DropCapabilities: nil, + CommonBuildOpts: &buildah.CommonBuildOptions{ + AddHost: nil, + CgroupParent: "", + CPUPeriod: query.CpuPeriod, + CPUQuota: query.CpuQuota, + CPUShares: query.CpuShares, + CPUSetCPUs: query.CpuSetCpus, + CPUSetMems: "", + HTTPProxy: false, + Memory: query.Memory, + DNSSearch: nil, + DNSServers: nil, + DNSOptions: nil, + MemorySwap: query.MemSwap, + LabelOpts: nil, + SeccompProfilePath: "", + ApparmorProfile: "", + ShmSize: strconv.Itoa(query.ShmSize), + Ulimit: nil, + Volumes: nil, + }, + DefaultMountsFilePath: "", + IIDFile: "", + Squash: query.Squash, + Labels: labels, + Annotations: nil, + OnBuild: nil, + Layers: false, + NoCache: query.NoCache, + RemoveIntermediateCtrs: query.Rm, + ForceRmIntermediateCtrs: query.ForceRm, + BlobDirectory: "", + Target: query.Target, + Devices: nil, } id, _, err := getRuntime(r).Build(r.Context(), buildOptions, query.Dockerfile) @@ -179,17 +230,13 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { struct { Stream string `json:"stream"` }{ - Stream: fmt.Sprintf("Successfully built %s\n", id), + Stream: progress.String() + "\n" + + strings.Join(buildEvents, "\n") + + fmt.Sprintf("\nSuccessfully built %s\n", id), }) } func extractTarFile(r *http.Request, w http.ResponseWriter) (string, error) { - var ( - // length int64 - // n int64 - copyErr error - ) - // build a home for the request body anchorDir, err := ioutil.TempDir("", "libpod_builder") if err != nil { @@ -204,26 +251,14 @@ func extractTarFile(r *http.Request, w http.ResponseWriter) (string, error) { } defer tarBall.Close() - // if hasHeader(r, "Content-Length") { - // length, err := strconv.ParseInt(getHeader(r, "Content-Length"), 10, 64) - // if err != nil { - // return "", errors.New(fmt.Sprintf("Failed request: unable to parse Content-Length of '%s'", getHeader(r, "Content-Length"))) - // } - // n, copyErr = io.CopyN(tarBall, r.Body, length+1) - // } else { - _, copyErr = io.Copy(tarBall, r.Body) - // } + // Content-Length not used as too many existing API clients didn't honor it + _, err = io.Copy(tarBall, r.Body) r.Body.Close() - if copyErr != nil { + if err != nil { utils.InternalServerError(w, fmt.Errorf("failed Request: Unable to copy tar file from request body %s", r.RequestURI)) } - log.Debugf("Content-Length: %s", getVar(r, "Content-Length")) - - // if hasHeader(r, "Content-Length") && n != length { - // return "", errors.New(fmt.Sprintf("Failed request: Given Content-Length does not match file size %d != %d", n, length)) - // } _, _ = tarBall.Seek(0, 0) if err := archive.Untar(tarBall, buildDir, &archive.TarOptions{}); err != nil { diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index e16a4ea1f..db1cf26ff 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" @@ -17,8 +19,6 @@ func StopContainer(w http.ResponseWriter, r *http.Request) { } func ContainerExists(w http.ResponseWriter, r *http.Request) { - // 404 no such container - // 200 ok runtime := r.Context().Value("runtime").(*libpod.Runtime) name := mux.Vars(r)["name"] _, err := runtime.LookupContainer(name) @@ -46,12 +46,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 +69,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 +130,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) { @@ -139,10 +145,6 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) { // tail string } -func CreateContainer(w http.ResponseWriter, r *http.Request) { - -} - func UnmountContainer(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) name := mux.Vars(r)["name"] diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 0d4e220a8..9f0876618 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -1,9 +1,12 @@ package libpod import ( + "fmt" + "io" "io/ioutil" "net/http" "os" + "strconv" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/api/handlers" @@ -42,12 +45,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) @@ -90,13 +93,14 @@ func GetImages(w http.ResponseWriter, r *http.Request) { } func PruneImages(w http.ResponseWriter, r *http.Request) { - // 200 ok - // 500 internal + var ( + all bool + err error + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { - All bool `schema:"all"` - Filters []string `schema:"filters"` + Filters map[string][]string `schema:"filters"` }{ // override any golang type defaults } @@ -106,7 +110,19 @@ 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 { + all, err = strconv.ParseBool(query.Filters["all"][0]) + if err != nil { + utils.InternalServerError(w, err) + return + } + for k, v := range query.Filters { + libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0])) + } + } + cids, err := runtime.ImageRuntime().PruneImages(r.Context(), all, libpodFilters) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return @@ -163,3 +179,47 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { defer os.Remove(tmpfile.Name()) utils.WriteResponse(w, http.StatusOK, rdr) } + +func ImportImage(w http.ResponseWriter, r *http.Request) { + // TODO this is basically wrong + decoder := r.Context().Value("decoder").(*schema.Decoder) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + query := struct { + Changes map[string]string `json:"changes"` + Message string `json:"message"` + Quiet bool `json:"quiet"` + }{ + // This is where you can override the golang default value for one of fields + } + + 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 + } + + var ( + err error + writer io.Writer + ) + f, err := ioutil.TempFile("", "api_load.tar") + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile")) + return + } + if err := handlers.SaveFromBody(f, r); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file")) + return + } + id, err := runtime.LoadImage(r.Context(), "", f.Name(), writer, "") + //id, err := runtime.Import(r.Context()) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to load image")) + return + } + utils.WriteResponse(w, http.StatusOK, struct { + Stream string `json:"stream"` + }{ + Stream: fmt.Sprintf("Loaded image: %s\n", id), + }) +} diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index 14f8e8de7..50d763338 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) } @@ -266,7 +268,7 @@ func PodDelete(w http.ResponseWriter, r *http.Request) { decoder = r.Context().Value("decoder").(*schema.Decoder) ) query := struct { - force bool `schema:"force"` + Force bool `schema:"force"` }{ // override any golang type defaults } @@ -282,7 +284,7 @@ func PodDelete(w http.ResponseWriter, r *http.Request) { utils.PodNotFound(w, name, err) return } - if err := runtime.RemovePod(r.Context(), pod, true, query.force); err != nil { + if err := runtime.RemovePod(r.Context(), pod, true, query.Force); err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } @@ -307,43 +309,14 @@ func PodRestart(w http.ResponseWriter, r *http.Request) { func PodPrune(w http.ResponseWriter, r *http.Request) { var ( - err error - pods []*libpod.Pod runtime = r.Context().Value("runtime").(*libpod.Runtime) - decoder = r.Context().Value("decoder").(*schema.Decoder) ) - query := struct { - force bool `schema:"force"` - }{ - // override any golang type defaults - } - - if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) - return - } - - if query.force { - pods, err = runtime.GetAllPods() - if err != nil { - utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) - return - } - } else { - // TODO We need to make a libpod.PruneVolumes or this code will be a mess. Volumes - // already does this right. It will also help clean this code path up with less - // conditionals. We do this when we integrate with libpod again. - utils.Error(w, "not implemented", http.StatusInternalServerError, errors.New("not implemented")) + pruned, err := runtime.PrunePods() + if err != nil { + utils.InternalServerError(w, err) return } - for _, p := range pods { - if err := runtime.RemovePod(r.Context(), p, true, query.force); err != nil { - utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) - return - } - } - utils.WriteResponse(w, http.StatusNoContent, "") + utils.WriteResponse(w, http.StatusOK, pruned) } func PodPause(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/handlers/swagger.go b/pkg/api/handlers/swagger.go index 0db4e19b6..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 { @@ -96,9 +103,7 @@ type swagLibpodInspectContainerResponse struct { // swagger:response ListPodsResponse type swagListPodsResponse struct { // in:body - Body struct { - libpod.PodInspect - } + Body []libpod.PodInspect } // Inspect pod diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 2526a3317..33cf1e95d 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 @@ -531,3 +537,11 @@ func portsToPortSet(input map[string]struct{}) (nat.PortSet, error) { } return ports, nil } + +// 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/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/errors.go b/pkg/api/handlers/utils/errors.go index b6f125c58..9d2081cd8 100644 --- a/pkg/api/handlers/utils/errors.go +++ b/pkg/api/handlers/utils/errors.go @@ -27,34 +27,34 @@ func Error(w http.ResponseWriter, apiMessage string, code int, err error) { WriteJSON(w, code, em) } -func VolumeNotFound(w http.ResponseWriter, nameOrId string, err error) { +func VolumeNotFound(w http.ResponseWriter, name string, err error) { if errors.Cause(err) != define.ErrNoSuchVolume { InternalServerError(w, err) } - msg := fmt.Sprintf("No such volume: %s", nameOrId) + msg := fmt.Sprintf("No such volume: %s", name) Error(w, msg, http.StatusNotFound, err) } -func ContainerNotFound(w http.ResponseWriter, nameOrId string, err error) { +func ContainerNotFound(w http.ResponseWriter, name string, err error) { if errors.Cause(err) != define.ErrNoSuchCtr { InternalServerError(w, err) } - msg := fmt.Sprintf("No such container: %s", nameOrId) + msg := fmt.Sprintf("No such container: %s", name) Error(w, msg, http.StatusNotFound, err) } -func ImageNotFound(w http.ResponseWriter, nameOrId string, err error) { +func ImageNotFound(w http.ResponseWriter, name string, err error) { if errors.Cause(err) != define.ErrNoSuchImage { InternalServerError(w, err) } - msg := fmt.Sprintf("No such image: %s", nameOrId) + msg := fmt.Sprintf("No such image: %s", name) Error(w, msg, http.StatusNotFound, err) } -func PodNotFound(w http.ResponseWriter, nameOrId string, err error) { +func PodNotFound(w http.ResponseWriter, name string, err error) { if errors.Cause(err) != define.ErrNoSuchPod { InternalServerError(w, err) } - msg := fmt.Sprintf("No such pod: %s", nameOrId) + msg := fmt.Sprintf("No such pod: %s", name) Error(w, msg, http.StatusNotFound, err) } @@ -73,9 +73,11 @@ func BadRequest(w http.ResponseWriter, key string, value string, err error) { } type ErrorModel struct { - // root cause + // API root cause formatted for automated parsing + // example: API root cause Because string `json:"cause"` - // error message + // human error message, formatted for a human to read + // example: human error message Message string `json:"message"` } 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..2b651584a 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -6,6 +6,7 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" + "github.com/gorilla/mux" "github.com/gorilla/schema" ) @@ -15,17 +16,23 @@ 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 + Filters map[string][]string `schema:"filters"` + Digests bool }{ // This is where you can override the golang default value for one of fields } + // TODO I think all is implemented with a filter? + 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 := mux.Vars(r)["digests"]; found && query.Digests { + UnSupportedParameter("digests") + } + + if _, found := r.URL.Query()["filters"]; found { filters = append(filters, fmt.Sprintf("reference=%s", "")) } return runtime.ImageRuntime().GetImagesWithFilters(filters) |