aboutsummaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/adapter/pods.go4
-rw-r--r--pkg/adapter/pods_remote.go4
-rw-r--r--pkg/api/handlers/containers_create.go (renamed from pkg/api/handlers/generic/containers_create.go)9
-rw-r--r--pkg/api/handlers/decoder.go91
-rw-r--r--pkg/api/handlers/events.go25
-rw-r--r--pkg/api/handlers/generic/config.go9
-rw-r--r--pkg/api/handlers/generic/images.go73
-rw-r--r--pkg/api/handlers/generic/ping.go4
-rw-r--r--pkg/api/handlers/generic/swagger.go4
-rw-r--r--pkg/api/handlers/handler.go9
-rw-r--r--pkg/api/handlers/images.go41
-rw-r--r--pkg/api/handlers/libpod/containers.go6
-rw-r--r--pkg/api/handlers/libpod/images.go78
-rw-r--r--pkg/api/handlers/libpod/pods.go53
-rw-r--r--pkg/api/handlers/types.go8
-rw-r--r--pkg/api/handlers/utils/errors.go11
-rw-r--r--pkg/api/handlers/utils/images.go17
-rw-r--r--pkg/api/handlers/version.go (renamed from pkg/api/handlers/generic/version.go)5
-rw-r--r--pkg/api/server/docs.go3
-rw-r--r--pkg/api/server/register_containers.go4
-rw-r--r--pkg/api/server/register_images.go61
-rw-r--r--pkg/api/server/register_pods.go16
-rw-r--r--pkg/api/server/register_version.go6
-rw-r--r--pkg/api/server/server.go7
-rw-r--r--pkg/bindings/connection.go180
-rw-r--r--pkg/bindings/containers.go139
-rw-r--r--pkg/bindings/containers/containers.go255
-rw-r--r--pkg/bindings/containers/healthcheck.go26
-rw-r--r--pkg/bindings/containers/mount.go53
-rw-r--r--pkg/bindings/errors.go11
-rw-r--r--pkg/bindings/generate.go4
-rw-r--r--pkg/bindings/generate/generate.go4
-rw-r--r--pkg/bindings/healthcheck.go19
-rw-r--r--pkg/bindings/images.go111
-rw-r--r--pkg/bindings/images/images.go187
-rw-r--r--pkg/bindings/images/search.go40
-rw-r--r--pkg/bindings/mount.go26
-rw-r--r--pkg/bindings/network.go37
-rw-r--r--pkg/bindings/network/network.go50
-rw-r--r--pkg/bindings/play.go3
-rw-r--r--pkg/bindings/play/play.go7
-rw-r--r--pkg/bindings/pods.go129
-rw-r--r--pkg/bindings/pods/pods.go196
-rw-r--r--pkg/bindings/search.go39
-rw-r--r--pkg/bindings/test/common_test.go112
-rw-r--r--pkg/bindings/test/images_test.go92
-rw-r--r--pkg/bindings/volumes.go60
-rw-r--r--pkg/bindings/volumes/volumes.go85
-rw-r--r--pkg/inspect/inspect.go46
-rw-r--r--pkg/util/camelcase/LICENSE.md20
-rw-r--r--pkg/util/camelcase/README.md58
-rw-r--r--pkg/util/camelcase/camelcase.go91
52 files changed, 1776 insertions, 852 deletions
diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go
index 5891c361f..f89fc7011 100644
--- a/pkg/adapter/pods.go
+++ b/pkg/adapter/pods.go
@@ -58,9 +58,9 @@ func (r *LocalRuntime) PrunePods(ctx context.Context, cli *cliconfig.PodPruneVal
}
logrus.Debugf("Setting maximum rm workers to %d", maxWorkers)
- states := []string{shared.PodStateStopped, shared.PodStateExited}
+ states := []string{define.PodStateStopped, define.PodStateExited}
if cli.Force {
- states = append(states, shared.PodStateRunning)
+ states = append(states, define.PodStateRunning)
}
pods, err := r.GetPodsByStatus(states)
diff --git a/pkg/adapter/pods_remote.go b/pkg/adapter/pods_remote.go
index 16d34769e..5ef1a9216 100644
--- a/pkg/adapter/pods_remote.go
+++ b/pkg/adapter/pods_remote.go
@@ -540,9 +540,9 @@ func (r *LocalRuntime) PrunePods(ctx context.Context, cli *cliconfig.PodPruneVal
ok = []string{}
failures = map[string]error{}
)
- states := []string{shared.PodStateStopped, shared.PodStateExited}
+ states := []string{define.PodStateStopped, define.PodStateExited}
if cli.Force {
- states = append(states, shared.PodStateRunning)
+ states = append(states, define.PodStateRunning)
}
ids, err := iopodman.GetPodsByStatus().Call(r.Conn, states)
diff --git a/pkg/api/handlers/generic/containers_create.go b/pkg/api/handlers/containers_create.go
index edefd5757..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"`
}{
@@ -74,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/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/ping.go b/pkg/api/handlers/generic/ping.go
index 44a67d53f..00afd86bc 100644
--- a/pkg/api/handlers/generic/ping.go
+++ b/pkg/api/handlers/generic/ping.go
@@ -3,6 +3,8 @@ package generic
import (
"fmt"
"net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers"
)
func PingGET(w http.ResponseWriter, _ *http.Request) {
@@ -16,7 +18,7 @@ func PingHEAD(w http.ResponseWriter, _ *http.Request) {
}
func setHeaders(w http.ResponseWriter) {
- w.Header().Set("API-Version", DefaultApiVersion)
+ w.Header().Set("API-Version", handlers.DefaultApiVersion)
w.Header().Set("BuildKit-Version", "")
w.Header().Set("Docker-Experimental", "true")
w.Header().Set("Cache-Control", "no-cache")
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 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/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/libpod/containers.go b/pkg/api/handlers/libpod/containers.go
index df16843c7..db1cf26ff 100644
--- a/pkg/api/handlers/libpod/containers.go
+++ b/pkg/api/handlers/libpod/containers.go
@@ -19,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)
@@ -147,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/types.go b/pkg/api/handlers/types.go
index 33cd51164..33cf1e95d 100644
--- a/pkg/api/handlers/types.go
+++ b/pkg/api/handlers/types.go
@@ -537,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/errors.go b/pkg/api/handlers/utils/errors.go
index 9d2081cd8..8d499f40b 100644
--- a/pkg/api/handlers/utils/errors.go
+++ b/pkg/api/handlers/utils/errors.go
@@ -21,8 +21,9 @@ func Error(w http.ResponseWriter, apiMessage string, code int, err error) {
// Log detailed message of what happened to machine running podman service
log.Infof("Request Failed(%s): %s", http.StatusText(code), err.Error())
em := ErrorModel{
- Because: (errors.Cause(err)).Error(),
- Message: err.Error(),
+ Because: (errors.Cause(err)).Error(),
+ Message: err.Error(),
+ ResponseCode: code,
}
WriteJSON(w, code, em)
}
@@ -79,6 +80,8 @@ type ErrorModel struct {
// human error message, formatted for a human to read
// example: human error message
Message string `json:"message"`
+ // http response code
+ ResponseCode int `json:"response"`
}
func (e ErrorModel) Error() string {
@@ -89,6 +92,10 @@ func (e ErrorModel) Cause() error {
return errors.New(e.Because)
}
+func (e ErrorModel) Code() int {
+ return e.ResponseCode
+}
+
// UnsupportedParameter logs a given param by its string name as not supported.
func UnSupportedParameter(param string) {
log.Infof("API parameter %q: not supported", param)
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)
diff --git a/pkg/api/handlers/generic/version.go b/pkg/api/handlers/version.go
index 39423914d..94166952c 100644
--- a/pkg/api/handlers/generic/version.go
+++ b/pkg/api/handlers/version.go
@@ -1,4 +1,4 @@
-package generic
+package handlers
import (
"fmt"
@@ -8,7 +8,6 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
- "github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
docker "github.com/docker/docker/api/types"
"github.com/pkg/errors"
@@ -53,7 +52,7 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) {
},
}}
- utils.WriteResponse(w, http.StatusOK, handlers.Version{Version: docker.Version{
+ utils.WriteResponse(w, http.StatusOK, Version{Version: docker.Version{
Platform: struct {
Name string
}{
diff --git a/pkg/api/server/docs.go b/pkg/api/server/docs.go
index e028c6302..c989c7927 100644
--- a/pkg/api/server/docs.go
+++ b/pkg/api/server/docs.go
@@ -12,7 +12,8 @@
// Version: 0.0.1
// License: Apache-2.0 https://opensource.org/licenses/Apache-2.0
// Contact: Podman <podman@lists.podman.io> https://podman.io/community/
-// Extensions:
+//
+// InfoExtensions:
// x-logo:
// - url: https://raw.githubusercontent.com/containers/libpod/master/logo/podman-logo.png
// - altText: "Podman logo"
diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go
index b2d2ab388..abae81c38 100644
--- a/pkg/api/server/register_containers.go
+++ b/pkg/api/server/register_containers.go
@@ -33,7 +33,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error {
// $ref: "#/responses/ConflictError"
// 500:
// $ref: "#/responses/InternalError"
- r.HandleFunc(VersionedPath("/containers/create"), APIHandler(s.Context, generic.CreateContainer)).Methods(http.MethodPost)
+ r.HandleFunc(VersionedPath("/containers/create"), APIHandler(s.Context, handlers.CreateContainer)).Methods(http.MethodPost)
// swagger:operation GET /containers/json compat listContainers
// ---
// tags:
@@ -550,7 +550,7 @@ func (s *APIServer) RegisterContainersHandlers(r *mux.Router) error {
libpod endpoints
*/
- r.HandleFunc(VersionedPath("/libpod/containers/create"), APIHandler(s.Context, libpod.CreateContainer)).Methods(http.MethodPost)
+ r.HandleFunc(VersionedPath("/libpod/containers/create"), APIHandler(s.Context, handlers.CreateContainer)).Methods(http.MethodPost)
// swagger:operation GET /libpod/containers/json libpod libpodListContainers
// ---
// tags:
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index 6e8b79313..f1cc0574c 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -55,6 +55,27 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// - images (compat)
// summary: List Images
// description: Returns a list of images on the server. Note that it uses a different, smaller representation of an image than inspecting a single image.
+ // parameters:
+ // - name: all
+ // in: query
+ // description: "Show all images. Only images from a final layer (no children) are shown by default."
+ // type: boolean
+ // default: false
+ // - name: filters
+ // in: query
+ // description: |
+ // A JSON encoded value of the filters (a `map[string][]string`) to process on the images list. Available filters:
+ // - `before`=(`<image-name>[:<tag>]`, `<image id>` or `<image@digest>`)
+ // - `dangling=true`
+ // - `label=key` or `label="key=value"` of an image label
+ // - `reference`=(`<image-name>[:<tag>]`)
+ // - `since`=(`<image-name>[:<tag>]`, `<image id>` or `<image@digest>`)
+ // type: string
+ // - name: digests
+ // in: query
+ // description: Not supported
+ // type: boolean
+ // default: false
// produces:
// - application/json
// responses:
@@ -63,7 +84,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/images/json"), APIHandler(s.Context, generic.GetImages)).Methods(http.MethodGet)
- // swagger:operation POST /images/load compat loadImage
+ // swagger:operation POST /images/load compat importImage
// ---
// tags:
// - images (compat)
@@ -86,7 +107,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// description: no error
// 500:
// $ref: '#/responses/InternalError'
- r.Handle(VersionedPath("/images/load"), APIHandler(s.Context, handlers.LoadImage)).Methods(http.MethodPost)
+ r.Handle(VersionedPath("/images/load"), APIHandler(s.Context, libpod.ImportImage)).Methods(http.MethodPost)
// swagger:operation POST /images/prune compat pruneImages
// ---
// tags:
@@ -174,7 +195,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// $ref: '#/responses/ConflictError'
// 500:
// $ref: '#/responses/InternalError'
- r.Handle(VersionedPath("/images/name"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete)
+ r.Handle(VersionedPath("/images/{name}"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete)
// swagger:operation GET /images/{name}/get compat exportImage
// ---
// tags:
@@ -585,6 +606,22 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// - images
// summary: List Images
// description: Returns a list of images on the server
+ // parameters:
+ // - name: all
+ // in: query
+ // description: Show all images. Only images from a final layer (no children) are shown by default.
+ // type: boolean
+ // default: false
+ // - name: filters
+ // in: query
+ // description: |
+ // A JSON encoded value of the filters (a `map[string][]string`) to process on the images list. Available filters:
+ // - `before`=(`<image-name>[:<tag>]`, `<image id>` or `<image@digest>`)
+ // - `dangling=true`
+ // - `label=key` or `label="key=value"` of an image label
+ // - `reference`=(`<image-name>[:<tag>]`)
+ // - `since`=(`<image-name>[:<tag>]`, `<image id>` or `<image@digest>`)
+ // type: string
// produces:
// - application/json
// responses:
@@ -593,7 +630,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/libpod/images/json"), APIHandler(s.Context, libpod.GetImages)).Methods(http.MethodGet)
- // swagger:operation POST /libpod/images/load libpod libpodLoadImage
+ // swagger:operation POST /libpod/images/load libpod libpodImportImage
// ---
// tags:
// - images
@@ -604,6 +641,14 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// name: quiet
// type: boolean
// description: not supported
+ // - in: query
+ // name: change
+ // description: "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR. JSON encoded string"
+ // type: string
+ // - in: query
+ // name: message
+ // description: Set commit message for imported image
+ // type: string
// - in: body
// name: request
// description: tarball of container image
@@ -617,7 +662,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// description: no error
// 500:
// $ref: '#/responses/InternalError'
- r.Handle(VersionedPath("/libpod/images/load"), APIHandler(s.Context, handlers.LoadImage)).Methods(http.MethodPost)
+ r.Handle(VersionedPath("/libpod/images/load"), APIHandler(s.Context, libpod.ImportImage)).Methods(http.MethodPost)
// swagger:operation POST /libpod/images/prune libpod libpodPruneImages
// ---
// tags:
@@ -635,10 +680,6 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// (or `0`), all unused images are pruned.
// - `until=<string>` Prune images created before this timestamp. The `<timestamp>` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon machine’s time.
// - `label` (`label=<key>`, `label=<key>=<value>`, `label!=<key>`, or `label!=<key>=<value>`) Prune images with (or without, in case `label!=...` is used) the specified labels.
- // - in: query
- // name: all
- // type: boolean
- // description: prune all images
// produces:
// - application/json
// responses:
@@ -707,7 +748,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// $ref: '#/responses/ConflictError'
// 500:
// $ref: '#/responses/InternalError'
- r.Handle(VersionedPath("/libpod/images/name"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete)
+ r.Handle(VersionedPath("/libpod/images/{name}"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete)
// swagger:operation GET /libpod/images/{name}/get libpod libpoodExportImage
// ---
// tags:
diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go
index 1ef14b58c..5c7b51871 100644
--- a/pkg/api/server/register_pods.go
+++ b/pkg/api/server/register_pods.go
@@ -30,17 +30,15 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
// swagger:operation POST /libpod/pods/prune pods PrunePods
// ---
// summary: Prune unused pods
- // parameters:
- // - in: query
- // name: force
- // description: force delete
- // type: boolean
- // default: false
// produces:
// - application/json
// responses:
- // 204:
- // description: no error
+ // 200:
+ // description: tbd
+ // schema:
+ // type: object
+ // additionalProperties:
+ // type: string
// 400:
// $ref: "#/responses/BadParamError"
// 500:
@@ -60,7 +58,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
// - in: query
// name: force
// type: boolean
- // description: force delete
+ // description : force removal of a running pod by first stopping all containers, then removing all containers in the pod
// responses:
// 204:
// description: no error
diff --git a/pkg/api/server/register_version.go b/pkg/api/server/register_version.go
index 94216b1b6..d3b47c2a9 100644
--- a/pkg/api/server/register_version.go
+++ b/pkg/api/server/register_version.go
@@ -1,12 +1,12 @@
package server
import (
- "github.com/containers/libpod/pkg/api/handlers/generic"
+ "github.com/containers/libpod/pkg/api/handlers"
"github.com/gorilla/mux"
)
func (s *APIServer) registerVersionHandlers(r *mux.Router) error {
- r.Handle("/version", APIHandler(s.Context, generic.VersionHandler))
- r.Handle(VersionedPath("/version"), APIHandler(s.Context, generic.VersionHandler))
+ r.Handle("/version", APIHandler(s.Context, handlers.VersionHandler))
+ r.Handle(VersionedPath("/version"), APIHandler(s.Context, handlers.VersionHandler))
return nil
}
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/bindings/connection.go b/pkg/bindings/connection.go
index 551a63c62..3dec6ca20 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -1,14 +1,22 @@
package bindings
import (
+ "context"
"fmt"
"io"
+ "net"
"net/http"
+ "net/url"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ jsoniter "github.com/json-iterator/go"
+ "github.com/pkg/errors"
)
-const (
- defaultConnection string = "http://localhost:8080/v1.24/libpod"
- pingConnection string = "http://localhost:8080/_ping"
+var (
+ defaultConnectionPath string = filepath.Join(fmt.Sprintf("v%s", handlers.MinimalApiVersion), "libpod")
)
type APIResponse struct {
@@ -17,46 +25,170 @@ type APIResponse struct {
}
type Connection struct {
- url string
- client *http.Client
+ scheme string
+ address string
+ client *http.Client
}
-func NewConnection(url string) (Connection, error) {
- if len(url) < 1 {
- url = defaultConnection
+// NewConnection takes a URI as a string and returns a context with the
+// Connection embedded as a value. This context needs to be passed to each
+// endpoint to work correctly.
+//
+// A valid URI connection should be scheme://
+// For example tcp://localhost:<port>
+// or unix://run/podman/podman.sock
+func NewConnection(uri string) (context.Context, error) {
+ u, err := url.Parse(uri)
+ if err != nil {
+ return nil, err
}
- newConn := Connection{
- url: url,
- client: &http.Client{},
+ // TODO once ssh is implemented, remove this block and
+ // add it to the conditional beneath it
+ if u.Scheme == "ssh" {
+ return nil, ErrNotImplemented
+ }
+ if u.Scheme != "tcp" && u.Scheme != "unix" {
+ return nil, errors.Errorf("%s is not a support schema", u.Scheme)
+ }
+
+ if u.Scheme == "tcp" && !strings.HasPrefix(uri, "tcp://") {
+ return nil, errors.New("tcp URIs should begin with tcp://")
+ }
+
+ address := u.Path
+ if u.Scheme == "tcp" {
+ address = u.Host
+ }
+ newConn := newConnection(u.Scheme, address)
+ ctx := context.WithValue(context.Background(), "conn", &newConn)
+ if err := pingNewConnection(ctx); err != nil {
+ return nil, err
}
- response, err := http.Get(pingConnection)
+ return ctx, nil
+}
+
+// pingNewConnection pings to make sure the RESTFUL service is up
+// and running. it should only be used where initializing a connection
+func pingNewConnection(ctx context.Context) error {
+ conn, err := GetConnectionFromContext(ctx)
if err != nil {
- return newConn, err
+ return err
+ }
+ // the ping endpoint sits at / in this case
+ response, err := conn.DoRequest(nil, http.MethodGet, "../../../_ping", nil)
+ if err != nil {
+ return err
+ }
+ if response.StatusCode == http.StatusOK {
+ return nil
}
- if err := response.Body.Close(); err != nil {
- return newConn, err
+ return errors.Errorf("ping response was %q", response.StatusCode)
+}
+
+// newConnection takes a scheme and address and creates a connection from it
+func newConnection(scheme, address string) Connection {
+ client := http.Client{
+ Transport: &http.Transport{
+ DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
+ return net.Dial(scheme, address)
+ },
+ },
}
- return newConn, err
+ newConn := Connection{
+ client: &client,
+ address: address,
+ scheme: scheme,
+ }
+ return newConn
}
-func (c Connection) makeEndpoint(u string) string {
- return fmt.Sprintf("%s%s", defaultConnection, u)
+func (c *Connection) makeEndpoint(u string) string {
+ // The d character in the url is discarded and is meaningless
+ return fmt.Sprintf("http://d/%s%s", defaultConnectionPath, u)
}
-func (c Connection) newRequest(httpMethod, endpoint string, httpBody io.Reader, params map[string]string) (*APIResponse, error) {
- e := c.makeEndpoint(endpoint)
+// DoRequest assembles the http request and returns the response
+func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, queryParams map[string]string, pathValues ...string) (*APIResponse, error) {
+ var (
+ err error
+ response *http.Response
+ )
+ safePathValues := make([]interface{}, len(pathValues))
+ // Make sure path values are http url safe
+ for _, pv := range pathValues {
+ safePathValues = append(safePathValues, url.QueryEscape(pv))
+ }
+ safeEndpoint := fmt.Sprintf(endpoint, safePathValues...)
+
+ e := c.makeEndpoint(safeEndpoint)
req, err := http.NewRequest(httpMethod, e, httpBody)
if err != nil {
return nil, err
}
- if len(params) > 0 {
+ if len(queryParams) > 0 {
// if more desirable we could use url to form the encoded endpoint with params
r := req.URL.Query()
- for k, v := range params {
- r.Add(k, v)
+ for k, v := range queryParams {
+ r.Add(k, url.QueryEscape(v))
}
req.URL.RawQuery = r.Encode()
}
- response, err := c.client.Do(req) // nolint
+ // Give the Do three chances in the case of a comm/service hiccup
+ for i := 0; i < 3; i++ {
+ response, err = c.client.Do(req) // nolint
+ if err == nil {
+ break
+ }
+ }
return &APIResponse{response, req}, err
}
+
+// GetConnectionFromContext returns a bindings connection from the context
+// being passed into each method.
+func GetConnectionFromContext(ctx context.Context) (*Connection, error) {
+ c := ctx.Value("conn")
+ if c == nil {
+ return nil, errors.New("unable to get connection from context")
+ }
+ conn := c.(Connection)
+ return &conn, nil
+}
+
+// FiltersToHTML converts our typical filter format of a
+// map[string][]string to a query/html safe string.
+func FiltersToHTML(filters map[string][]string) (string, error) {
+ lowerCaseKeys := make(map[string][]string)
+ for k, v := range filters {
+ lowerCaseKeys[strings.ToLower(k)] = v
+ }
+ unsafeString, err := jsoniter.MarshalToString(lowerCaseKeys)
+ if err != nil {
+ return "", err
+ }
+ return url.QueryEscape(unsafeString), nil
+}
+
+// IsInformation returns true if the response code is 1xx
+func (h *APIResponse) IsInformational() bool {
+ return h.Response.StatusCode/100 == 1
+}
+
+// IsSuccess returns true if the response code is 2xx
+func (h *APIResponse) IsSuccess() bool {
+ return h.Response.StatusCode/100 == 2
+}
+
+// IsRedirection returns true if the response code is 3xx
+func (h *APIResponse) IsRedirection() bool {
+ return h.Response.StatusCode/100 == 3
+}
+
+// IsClientError returns true if the response code is 4xx
+func (h *APIResponse) IsClientError() bool {
+ return h.Response.StatusCode/100 == 4
+}
+
+// IsServerError returns true if the response code is 5xx
+func (h *APIResponse) IsServerError() bool {
+ return h.Response.StatusCode/100 == 5
+}
diff --git a/pkg/bindings/containers.go b/pkg/bindings/containers.go
deleted file mode 100644
index 057580088..000000000
--- a/pkg/bindings/containers.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
- "strconv"
-
- "github.com/containers/libpod/cmd/podman/shared"
- "github.com/containers/libpod/libpod"
-)
-
-func (c Connection) ListContainers(filter []string, last int, size, sync bool) ([]shared.PsContainerOutput, error) { // nolint:typecheck
- images := []shared.PsContainerOutput{}
- params := make(map[string]string)
- params["last"] = strconv.Itoa(last)
- params["size"] = strconv.FormatBool(size)
- params["sync"] = strconv.FormatBool(sync)
- response, err := c.newRequest(http.MethodGet, "/containers/json", nil, params)
- if err != nil {
- return images, err
- }
- return images, response.Process(nil)
-}
-
-func (c Connection) PruneContainers() ([]string, error) {
- var (
- pruned []string
- )
- response, err := c.newRequest(http.MethodPost, "/containers/prune", nil, nil)
- if err != nil {
- return pruned, err
- }
- return pruned, response.Process(nil)
-}
-
-func (c Connection) RemoveContainer(nameOrID string, force, volumes bool) error {
- params := make(map[string]string)
- params["force"] = strconv.FormatBool(force)
- params["vols"] = strconv.FormatBool(volumes)
- response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/containers/%s", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) InspectContainer(nameOrID string, size bool) (*libpod.InspectContainerData, error) {
- params := make(map[string]string)
- params["size"] = strconv.FormatBool(size)
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/containers/%s/json", nameOrID), nil, params)
- if err != nil {
- return nil, err
- }
- inspect := libpod.InspectContainerData{}
- return &inspect, response.Process(&inspect)
-}
-
-func (c Connection) KillContainer(nameOrID string, signal int) error {
- params := make(map[string]string)
- params["signal"] = strconv.Itoa(signal)
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/kill", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-
-}
-func (c Connection) ContainerLogs() {}
-func (c Connection) PauseContainer(nameOrID string) error {
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/pause", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) RestartContainer(nameOrID string, timeout int) error {
- // TODO how do we distinguish between an actual zero value and not wanting to change the timeout value
- params := make(map[string]string)
- params["timeout"] = strconv.Itoa(timeout)
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/restart", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) StartContainer(nameOrID, detachKeys string) error {
- params := make(map[string]string)
- if len(detachKeys) > 0 {
- params["detachKeys"] = detachKeys
- }
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/start", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) ContainerStats() {}
-func (c Connection) ContainerTop() {}
-
-func (c Connection) UnpauseContainer(nameOrID string) error {
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/unpause", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) WaitContainer(nameOrID string) error {
- // TODO when returns are ironed out, we can should use the newRequest approach
- _, err := http.Post(c.makeEndpoint(fmt.Sprintf("containers/%s/wait", nameOrID)), "application/json", nil) // nolint
- return err
-}
-
-func (c Connection) ContainerExists(nameOrID string) (bool, error) {
- response, err := http.Get(c.makeEndpoint(fmt.Sprintf("/containers/%s/exists", nameOrID))) // nolint
- defer closeResponseBody(response)
- if err != nil {
- return false, err
- }
- if response.StatusCode == http.StatusOK {
- return true, nil
- }
- return false, nil
-}
-
-func (c Connection) StopContainer(nameOrID string, timeout *int) error {
- params := make(map[string]string)
- if timeout != nil {
- params["t"] = strconv.Itoa(*timeout)
- }
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/stop", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go
new file mode 100644
index 000000000..334a656d4
--- /dev/null
+++ b/pkg/bindings/containers/containers.go
@@ -0,0 +1,255 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+ "strconv"
+
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+// List obtains a list of containers in local storage. All parameters to this method are optional.
+// The filters are used to determine which containers are listed. The last parameter indicates to only return
+// the most recent number of containers. The pod and size booleans indicate that pod information and rootfs
+// size information should also be included. Finally, the sync bool synchronizes the OCI runtime and
+// container state.
+func List(ctx context.Context, filters map[string][]string, last *int, pod, size, sync *bool) ([]*shared.PsContainerOutput, error) { // nolint:typecheck
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ var images []*shared.PsContainerOutput
+ params := make(map[string]string)
+ if last != nil {
+ params["last"] = strconv.Itoa(*last)
+ }
+ if pod != nil {
+ params["pod"] = strconv.FormatBool(*pod)
+ }
+ if size != nil {
+ params["size"] = strconv.FormatBool(*size)
+ }
+ if sync != nil {
+ params["sync"] = strconv.FormatBool(*sync)
+ }
+ if filters != nil {
+ filterString, err := bindings.FiltersToHTML(filters)
+ if err != nil {
+ return nil, err
+ }
+ params["filters"] = filterString
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/json", params)
+ if err != nil {
+ return images, err
+ }
+ return images, response.Process(nil)
+}
+
+// Prune removes stopped and exited containers from local storage. The optional filters can be
+// used for more granular selection of containers. The main error returned indicates if there were runtime
+// errors like finding containers. Errors specific to the removal of a container are in the PruneContainerResponse
+// structure.
+func Prune(ctx context.Context, filters map[string][]string) ([]string, error) {
+ var (
+ pruneResponse []string
+ )
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := make(map[string]string)
+ if filters != nil {
+ filterString, err := bindings.FiltersToHTML(filters)
+ if err != nil {
+ return nil, err
+ }
+ params["filters"] = filterString
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/prune", params)
+ if err != nil {
+ return pruneResponse, err
+ }
+ return pruneResponse, response.Process(pruneResponse)
+}
+
+// Remove removes a container from local storage. The force bool designates
+// that the container should be removed forcibly (example, even it is running). The volumes
+// bool dictates that a container's volumes should also be removed.
+func Remove(ctx context.Context, nameOrID string, force, volumes *bool) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ if force != nil {
+ params["force"] = strconv.FormatBool(*force)
+ }
+ if volumes != nil {
+ params["vols"] = strconv.FormatBool(*volumes)
+ }
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/containers/%s", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Inspect returns low level information about a Container. The nameOrID can be a container name
+// or a partial/full ID. The size bool determines whether the size of the container's root filesystem
+// should be calculated. Calculating the size of a container requires extra work from the filesystem and
+// is therefore slower.
+func Inspect(ctx context.Context, nameOrID string, size *bool) (*libpod.InspectContainerData, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := make(map[string]string)
+ if size != nil {
+ params["size"] = strconv.FormatBool(*size)
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/json", params, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ inspect := libpod.InspectContainerData{}
+ return &inspect, response.Process(&inspect)
+}
+
+// Kill sends a given signal to a given container. The signal should be the string
+// representation of a signal like 'SIGKILL'. The nameOrID can be a container name
+// or a partial/full ID
+func Kill(ctx context.Context, nameOrID string, signal string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ params["signal"] = signal
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/kill", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+
+}
+func Logs() {}
+
+// Pause pauses a given container. The nameOrID can be a container name
+// or a partial/full ID.
+func Pause(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/pause", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Restart restarts a running container. The nameOrID can be a container name
+// or a partial/full ID. The optional timeout specifies the number of seconds to wait
+// for the running container to stop before killing it.
+func Restart(ctx context.Context, nameOrID string, timeout *int) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ if timeout != nil {
+ params["t"] = strconv.Itoa(*timeout)
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restart", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Start starts a non-running container.The nameOrID can be a container name
+// or a partial/full ID. The optional parameter for detach keys are to override the default
+// detach key sequence.
+func Start(ctx context.Context, nameOrID string, detachKeys *string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ if detachKeys != nil {
+ params["detachKeys"] = *detachKeys
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/start", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func Stats() {}
+func Top() {}
+
+// Unpause resumes the given paused container. The nameOrID can be a container name
+// or a partial/full ID.
+func Unpause(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unpause", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Wait blocks until the given container exits and returns its exit code. The nameOrID can be a container name
+// or a partial/full ID.
+func Wait(ctx context.Context, nameOrID string) (int32, error) {
+ var exitCode int32
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return exitCode, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "containers/%s/wait", nil, nameOrID)
+ if err != nil {
+ return exitCode, err
+ }
+ return exitCode, response.Process(&exitCode)
+}
+
+// Exists is a quick, light-weight way to determine if a given container
+// exists in local storage. The nameOrID can be a container name
+// or a partial/full ID.
+func Exists(ctx context.Context, nameOrID string) (bool, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return false, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "containers/%s/exists", nil, nameOrID)
+ if err != nil {
+ return false, err
+ }
+ return response.IsSuccess(), nil
+}
+
+// Stop stops a running container. The timeout is optional. The nameOrID can be a container name
+// or a partial/full ID
+func Stop(ctx context.Context, nameOrID string, timeout *int) error {
+ params := make(map[string]string)
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ if timeout != nil {
+ params["t"] = strconv.Itoa(*timeout)
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/stop", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
diff --git a/pkg/bindings/containers/healthcheck.go b/pkg/bindings/containers/healthcheck.go
new file mode 100644
index 000000000..9ed7f858d
--- /dev/null
+++ b/pkg/bindings/containers/healthcheck.go
@@ -0,0 +1,26 @@
+package containers
+
+import (
+ "context"
+ "github.com/containers/libpod/pkg/bindings"
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+)
+
+// RunHealthCheck executes the container's healthcheck and returns the health status of the
+// container.
+func RunHealthCheck(ctx context.Context, nameOrID string) (*libpod.HealthCheckStatus, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ var (
+ status libpod.HealthCheckStatus
+ )
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/runhealthcheck", nil, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ return &status, response.Process(&status)
+}
diff --git a/pkg/bindings/containers/mount.go b/pkg/bindings/containers/mount.go
new file mode 100644
index 000000000..d68dee981
--- /dev/null
+++ b/pkg/bindings/containers/mount.go
@@ -0,0 +1,53 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+// Mount mounts an existing container to the filesystem. It returns the path
+// of the mounted container in string format.
+func Mount(ctx context.Context, nameOrID string) (string, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return "", err
+ }
+ var (
+ path string
+ )
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/mount", nil, nameOrID)
+ if err != nil {
+ return path, err
+ }
+ return path, response.Process(&path)
+}
+
+// Unmount unmounts a container from the filesystem. The container must not be running
+// or the unmount will fail.
+func Unmount(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unmount", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// GetMountedContainerPaths returns a map of mounted containers and their mount locations.
+func GetMountedContainerPaths(ctx context.Context) (map[string]string, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ mounts := make(map[string]string)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/showmounted", nil)
+ if err != nil {
+ return mounts, err
+ }
+ return mounts, response.Process(&mounts)
+}
diff --git a/pkg/bindings/errors.go b/pkg/bindings/errors.go
index 9a02925a3..8bd40f804 100644
--- a/pkg/bindings/errors.go
+++ b/pkg/bindings/errors.go
@@ -7,7 +7,6 @@ import (
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/pkg/errors"
- "github.com/sirupsen/logrus"
)
var (
@@ -37,10 +36,10 @@ func (a APIResponse) Process(unmarshalInto interface{}) error {
return handleError(data)
}
-func closeResponseBody(r *http.Response) {
- if r != nil {
- if err := r.Body.Close(); err != nil {
- logrus.Error(errors.Wrap(err, "unable to close response body"))
- }
+func CheckResponseCode(inError error) (int, error) {
+ e, ok := inError.(utils.ErrorModel)
+ if !ok {
+ return -1, errors.New("error is not type ErrorModel")
}
+ return e.Code(), nil
}
diff --git a/pkg/bindings/generate.go b/pkg/bindings/generate.go
deleted file mode 100644
index 534909062..000000000
--- a/pkg/bindings/generate.go
+++ /dev/null
@@ -1,4 +0,0 @@
-package bindings
-
-func (c Connection) GenerateKube() {}
-func (c Connection) GenerateSystemd() {}
diff --git a/pkg/bindings/generate/generate.go b/pkg/bindings/generate/generate.go
new file mode 100644
index 000000000..2916754b8
--- /dev/null
+++ b/pkg/bindings/generate/generate.go
@@ -0,0 +1,4 @@
+package generate
+
+func GenerateKube() {}
+func GenerateSystemd() {}
diff --git a/pkg/bindings/healthcheck.go b/pkg/bindings/healthcheck.go
deleted file mode 100644
index 32515e332..000000000
--- a/pkg/bindings/healthcheck.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
-
- "github.com/containers/libpod/libpod"
-)
-
-func (c Connection) RunHealthCheck(nameOrID string) (*libpod.HealthCheckStatus, error) {
- var (
- status libpod.HealthCheckStatus
- )
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/containers/%s/runhealthcheck", nameOrID), nil, nil)
- if err != nil {
- return nil, err
- }
- return &status, response.Process(&status)
-}
diff --git a/pkg/bindings/images.go b/pkg/bindings/images.go
deleted file mode 100644
index 3abc8c372..000000000
--- a/pkg/bindings/images.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "io"
- "net/http"
- "strconv"
-
- "github.com/containers/libpod/pkg/api/handlers"
- "github.com/containers/libpod/pkg/inspect"
-)
-
-func (c Connection) ImageExists(nameOrID string) (bool, error) {
- response, err := http.Get(c.makeEndpoint(fmt.Sprintf("/images/%s/exists", nameOrID))) // nolint
- defer closeResponseBody(response)
- if err != nil {
- return false, err
- }
- if response.StatusCode == http.StatusOK {
- return true, nil
- }
- return false, nil
-}
-
-func (c Connection) ListImages() ([]handlers.ImageSummary, error) {
- imageSummary := []handlers.ImageSummary{}
- response, err := c.newRequest(http.MethodGet, "/images/json", nil, nil)
- if err != nil {
- return imageSummary, err
- }
- return imageSummary, response.Process(&imageSummary)
-}
-
-func (c Connection) GetImage(nameOrID string) (*inspect.ImageData, error) {
- inspectedData := inspect.ImageData{}
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/images/%s/json", nameOrID), nil, nil)
- if err != nil {
- return &inspectedData, err
- }
- return &inspectedData, response.Process(&inspectedData)
-}
-
-func (c Connection) ImageTree(nameOrId string) error {
- return ErrNotImplemented
-}
-
-func (c Connection) ImageHistory(nameOrID string) ([]handlers.HistoryResponse, error) {
- history := []handlers.HistoryResponse{}
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/images/%s/history", nameOrID), nil, nil)
- if err != nil {
- return history, err
- }
- return history, response.Process(&history)
-}
-
-func (c Connection) LoadImage(r io.Reader) error {
- // TODO this still needs error handling added
- _, err := http.Post(c.makeEndpoint("/images/loads"), "application/json", r) //nolint
- return err
-}
-
-func (c Connection) RemoveImage(nameOrID string, force bool) ([]map[string]string, error) {
- deletes := []map[string]string{}
- params := make(map[string]string)
- params["force"] = strconv.FormatBool(force)
- response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/images/%s", nameOrID), nil, params)
- if err != nil {
- return nil, err
- }
- return deletes, response.Process(&deletes)
-}
-
-func (c Connection) ExportImage(nameOrID string, w io.Writer, format string, compress bool) error {
- params := make(map[string]string)
- params["format"] = format
- params["compress"] = strconv.FormatBool(compress)
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/images/%s/get", nameOrID), nil, params)
- if err != nil {
- return err
- }
- if err := response.Process(nil); err != nil {
- return err
- }
- _, err = io.Copy(w, response.Body)
- return err
-}
-
-func (c Connection) PruneImages(all bool, filters []string) ([]string, error) {
- var (
- deleted []string
- )
- params := make(map[string]string)
- // FIXME How do we do []strings?
- //params["filters"] = format
- response, err := c.newRequest(http.MethodPost, "/images/prune", nil, params)
- if err != nil {
- return deleted, err
- }
- return deleted, response.Process(nil)
-}
-
-func (c Connection) TagImage(nameOrID string) error {
- var ()
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/images/%s/tag", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) BuildImage(nameOrId string) {}
diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go
new file mode 100644
index 000000000..deaf93f0e
--- /dev/null
+++ b/pkg/bindings/images/images.go
@@ -0,0 +1,187 @@
+package images
+
+import (
+ "context"
+ "io"
+ "net/http"
+ "strconv"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/inspect"
+)
+
+// Exists a lightweight way to determine if an image exists in local storage. It returns a
+// boolean response.
+func Exists(ctx context.Context, nameOrID string) (bool, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return false, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/exists", nil, nameOrID)
+ if err != nil {
+ return false, err
+ }
+ return response.IsSuccess(), nil
+}
+
+// List returns a list of images in local storage. The all boolean and filters parameters are optional
+// ways to alter the image query.
+func List(ctx context.Context, all *bool, filters map[string][]string) ([]*handlers.ImageSummary, error) {
+ var imageSummary []*handlers.ImageSummary
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := make(map[string]string)
+ if all != nil {
+ params["all"] = strconv.FormatBool(*all)
+ }
+ if filters != nil {
+ strFilters, err := bindings.FiltersToHTML(filters)
+ if err != nil {
+ return nil, err
+ }
+ params["filters"] = strFilters
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/json", params)
+ if err != nil {
+ return imageSummary, err
+ }
+ return imageSummary, response.Process(&imageSummary)
+}
+
+// Get performs an image inspect. To have the on-disk size of the image calculated, you can
+// use the optional size parameter.
+func GetImage(ctx context.Context, nameOrID string, size *bool) (*inspect.ImageData, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := make(map[string]string)
+ if size != nil {
+ params["size"] = strconv.FormatBool(*size)
+ }
+ inspectedData := inspect.ImageData{}
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/json", params, nameOrID)
+ if err != nil {
+ return &inspectedData, err
+ }
+ return &inspectedData, response.Process(&inspectedData)
+}
+
+func ImageTree(ctx context.Context, nameOrId string) error {
+ return bindings.ErrNotImplemented
+}
+
+// History returns the parent layers of an image.
+func History(ctx context.Context, nameOrID string) ([]*handlers.HistoryResponse, error) {
+ var history []*handlers.HistoryResponse
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/history", nil, nameOrID)
+ if err != nil {
+ return history, err
+ }
+ return history, response.Process(&history)
+}
+
+func Load(ctx context.Context, r io.Reader) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ // TODO this still needs error handling added
+ //_, err := http.Post(c.makeEndpoint("/images/loads"), "application/json", r) //nolint
+ _ = conn
+ return bindings.ErrNotImplemented
+}
+
+// Remove deletes an image from local storage. The optional force parameter will forcibly remove
+// the image by removing all all containers, including those that are Running, first.
+func Remove(ctx context.Context, nameOrID string, force *bool) ([]map[string]string, error) {
+ var deletes []map[string]string
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := make(map[string]string)
+ if force != nil {
+ params["force"] = strconv.FormatBool(*force)
+ }
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ return deletes, response.Process(&deletes)
+}
+
+// Export saves an image from local storage as a tarball or image archive. The optional format
+// parameter is used to change the format of the output.
+func Export(ctx context.Context, nameOrID string, w io.Writer, format *string, compress *bool) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ if format != nil {
+ params["format"] = *format
+ }
+ if compress != nil {
+ params["compress"] = strconv.FormatBool(*compress)
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/get", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ if err := response.Process(nil); err != nil {
+ return err
+ }
+ _, err = io.Copy(w, response.Body)
+ return err
+}
+
+// Prune removes unused images from local storage. The optional filters can be used to further
+// define which images should be pruned.
+func Prune(ctx context.Context, filters map[string][]string) ([]string, error) {
+ var (
+ deleted []string
+ )
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := make(map[string]string)
+ if filters != nil {
+ stringFilter, err := bindings.FiltersToHTML(filters)
+ if err != nil {
+ return nil, err
+ }
+ params["filters"] = stringFilter
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/images/prune", params)
+ if err != nil {
+ return deleted, err
+ }
+ return deleted, response.Process(nil)
+}
+
+// Tag adds an additional name to locally-stored image. Both the tag and repo parameters are required.
+func Tag(ctx context.Context, nameOrID, tag, repo string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ params["tag"] = tag
+ params["repo"] = repo
+ response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/tag", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func Build(nameOrId string) {}
diff --git a/pkg/bindings/images/search.go b/pkg/bindings/images/search.go
new file mode 100644
index 000000000..d98ddf18d
--- /dev/null
+++ b/pkg/bindings/images/search.go
@@ -0,0 +1,40 @@
+package images
+
+import (
+ "context"
+ "net/http"
+ "strconv"
+
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+// Search looks for the given image (term) in container image registries. The optional limit parameter sets
+// a maximum number of results returned. The optional filters parameter allow for more specific image
+// searches.
+func Search(ctx context.Context, term string, limit *int, filters map[string][]string) ([]image.SearchResult, error) {
+ var (
+ searchResults []image.SearchResult
+ )
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := make(map[string]string)
+ params["term"] = term
+ if limit != nil {
+ params["limit"] = strconv.Itoa(*limit)
+ }
+ if filters != nil {
+ stringFilter, err := bindings.FiltersToHTML(filters)
+ if err != nil {
+ return nil, err
+ }
+ params["filters"] = stringFilter
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params)
+ if err != nil {
+ return searchResults, nil
+ }
+ return searchResults, response.Process(&searchResults)
+}
diff --git a/pkg/bindings/mount.go b/pkg/bindings/mount.go
deleted file mode 100644
index 2e3d6d7f6..000000000
--- a/pkg/bindings/mount.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
-)
-
-func (c Connection) MountContainer(nameOrID string) (string, error) {
- var (
- path string
- )
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/mount", nameOrID), nil, nil)
- if err != nil {
- return path, err
- }
- return path, response.Process(&path)
-}
-
-func (c Connection) GetMountedContainerPaths() (map[string]string, error) {
- mounts := make(map[string]string)
- response, err := c.newRequest(http.MethodGet, "/containers/showmounted", nil, nil)
- if err != nil {
- return mounts, err
- }
- return mounts, response.Process(&mounts)
-}
diff --git a/pkg/bindings/network.go b/pkg/bindings/network.go
deleted file mode 100644
index 383615e5d..000000000
--- a/pkg/bindings/network.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
-
- "github.com/containernetworking/cni/libcni"
-)
-
-func (c Connection) CreateNetwork() {}
-func (c Connection) InspectNetwork(nameOrID string) (map[string]interface{}, error) {
- n := make(map[string]interface{})
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/networks/%s/json", nameOrID), nil, nil)
- if err != nil {
- return n, err
- }
- return n, response.Process(&n)
-}
-
-func (c Connection) RemoveNetwork(nameOrID string) error {
- response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/networks/%s", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) ListNetworks() ([]*libcni.NetworkConfigList, error) {
- var (
- netList []*libcni.NetworkConfigList
- )
- response, err := c.newRequest(http.MethodGet, "/networks/json", nil, nil)
- if err != nil {
- return netList, err
- }
- return netList, response.Process(&netList)
-}
diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go
new file mode 100644
index 000000000..97bbb8c42
--- /dev/null
+++ b/pkg/bindings/network/network.go
@@ -0,0 +1,50 @@
+package network
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/containernetworking/cni/libcni"
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+func Create() {}
+func Inspect(ctx context.Context, nameOrID string) (map[string]interface{}, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ n := make(map[string]interface{})
+ response, err := conn.DoRequest(nil, http.MethodGet, "/networks/%s/json", nil, nameOrID)
+ if err != nil {
+ return n, err
+ }
+ return n, response.Process(&n)
+}
+
+func Remove(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/networks/%s", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func List(ctx context.Context) ([]*libcni.NetworkConfigList, error) {
+ var (
+ netList []*libcni.NetworkConfigList
+ )
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/networks/json", nil)
+ if err != nil {
+ return netList, err
+ }
+ return netList, response.Process(&netList)
+}
diff --git a/pkg/bindings/play.go b/pkg/bindings/play.go
deleted file mode 100644
index a9dee82b1..000000000
--- a/pkg/bindings/play.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package bindings
-
-func (c Connection) PlayKube() {}
diff --git a/pkg/bindings/play/play.go b/pkg/bindings/play/play.go
new file mode 100644
index 000000000..a6f03cad2
--- /dev/null
+++ b/pkg/bindings/play/play.go
@@ -0,0 +1,7 @@
+package play
+
+import "github.com/containers/libpod/pkg/bindings"
+
+func PlayKube() error {
+ return bindings.ErrNotImplemented
+}
diff --git a/pkg/bindings/pods.go b/pkg/bindings/pods.go
deleted file mode 100644
index 704d71477..000000000
--- a/pkg/bindings/pods.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
- "strconv"
-
- "github.com/containers/libpod/libpod"
-)
-
-func (c Connection) CreatePod() error {
- // TODO
- return ErrNotImplemented
-}
-
-func (c Connection) PodExists(nameOrID string) (bool, error) {
- response, err := http.Get(c.makeEndpoint(fmt.Sprintf("/pods/%s/exists", nameOrID))) // nolint
- defer closeResponseBody(response)
- if err != nil {
- return false, err
- }
- return response.StatusCode == http.StatusOK, err
-}
-
-func (c Connection) InspectPod(nameOrID string) (*libpod.PodInspect, error) {
- inspect := libpod.PodInspect{}
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/pods/%s/json", nameOrID), nil, nil)
- if err != nil {
- return &inspect, err
- }
- return &inspect, response.Process(&inspect)
-}
-
-func (c Connection) KillPod(nameOrID string, signal int) error {
- params := make(map[string]string)
- params["signal"] = strconv.Itoa(signal)
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/kill", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) PausePod(nameOrID string) error {
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/pause", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) PrunePods(force bool) error {
- params := make(map[string]string)
- params["force"] = strconv.FormatBool(force)
- response, err := c.newRequest(http.MethodPost, "/pods/prune", nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) ListPods(filters []string) (*[]libpod.PodInspect, error) {
- var (
- inspect []libpod.PodInspect
- )
- params := make(map[string]string)
- // TODO I dont remember how to do this for []string{}
- // FIXME
- //params["filters"] = strconv.FormatBool(force)
- response, err := c.newRequest(http.MethodPost, "/pods/json", nil, params)
- if err != nil {
- return &inspect, err
- }
- return &inspect, response.Process(&inspect)
-}
-
-func (c Connection) RestartPod(nameOrID string) error {
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/restart", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) RemovePod(nameOrID string, force bool) error {
- params := make(map[string]string)
- params["force"] = strconv.FormatBool(force)
- response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/pods/%s", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) StartPod(nameOrID string) error {
- response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/pods/%s/start", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) PodStats() error {
- // TODO
- return ErrNotImplemented
-}
-
-func (c Connection) StopPod(nameOrID string, timeout int) error {
- params := make(map[string]string)
- params["t"] = strconv.Itoa(timeout)
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/stop", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) PodTop() error {
- // TODO
- return ErrNotImplemented // nolint:typecheck
-}
-
-func (c Connection) UnpausePod(nameOrID string) error {
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/unpause", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go
new file mode 100644
index 000000000..a6b74c21d
--- /dev/null
+++ b/pkg/bindings/pods/pods.go
@@ -0,0 +1,196 @@
+package pods
+
+import (
+ "context"
+ "net/http"
+ "strconv"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+func CreatePod() error {
+ // TODO
+ return bindings.ErrNotImplemented
+}
+
+// Exists is a lightweight method to determine if a pod exists in local storage
+func Exists(ctx context.Context, nameOrID string) (bool, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return false, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/exists", nil, nameOrID)
+ if err != nil {
+ return false, err
+ }
+ return response.IsSuccess(), nil
+}
+
+// Inspect returns low-level information about the given pod.
+func Inspect(ctx context.Context, nameOrID string) (*libpod.PodInspect, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ inspect := libpod.PodInspect{}
+ response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/json", nil, nameOrID)
+ if err != nil {
+ return &inspect, err
+ }
+ return &inspect, response.Process(&inspect)
+}
+
+// Kill sends a SIGTERM to all the containers in a pod. The optional signal parameter
+// can be used to override SIGTERM.
+func Kill(ctx context.Context, nameOrID string, signal *string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ if signal != nil {
+ params["signal"] = *signal
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/kill", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Pause pauses all running containers in a given pod.
+func Pause(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/pause", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Prune removes all non-running pods in local storage.
+func Prune(ctx context.Context) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/prune", nil)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// List returns all pods in local storage. The optional filters parameter can
+// be used to refine which pods should be listed.
+func List(ctx context.Context, filters map[string][]string) (*[]libpod.PodInspect, error) {
+ var (
+ inspect []libpod.PodInspect
+ )
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := make(map[string]string)
+ if filters != nil {
+ stringFilter, err := bindings.FiltersToHTML(filters)
+ if err != nil {
+ return nil, err
+ }
+ params["filters"] = stringFilter
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/json", params)
+ if err != nil {
+ return &inspect, err
+ }
+ return &inspect, response.Process(&inspect)
+}
+
+// Restart restarts all containers in a pod.
+func Restart(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/restart", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Remove deletes a Pod from from local storage. The optional force parameter denotes
+// that the Pod can be removed even if in a running state.
+func Remove(ctx context.Context, nameOrID string, force *bool) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ if force != nil {
+ params["force"] = strconv.FormatBool(*force)
+ }
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Start starts all containers in a pod.
+func Start(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s/start", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func Stats() error {
+ // TODO
+ return bindings.ErrNotImplemented
+}
+
+// Stop stops all containers in a Pod. The optional timeout parameter can be
+// used to override the timeout before the container is killed.
+func Stop(ctx context.Context, nameOrID string, timeout *int) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ if timeout != nil {
+ params["t"] = strconv.Itoa(*timeout)
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/stop", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func Top() error {
+ // TODO
+ return bindings.ErrNotImplemented // nolint:typecheck
+}
+
+// Unpause unpauses all paused containers in a Pod.
+func Unpause(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/unpause", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
diff --git a/pkg/bindings/search.go b/pkg/bindings/search.go
deleted file mode 100644
index 0f462357c..000000000
--- a/pkg/bindings/search.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package bindings
-
-import (
- "net/http"
- "strconv"
-
- "github.com/containers/libpod/libpod/image"
-)
-
-type ImageSearchFilters struct {
- Automated bool `json:"automated"`
- Official bool `json:"official"`
- Stars int `json:"stars"`
-}
-
-// TODO This method can be concluded when we determine how we want the filters to work on the
-// API end
-func (i *ImageSearchFilters) ToMapJSON() string {
- return ""
-}
-
-func (c Connection) SearchImages(term string, limit int, filters *ImageSearchFilters) ([]image.SearchResult, error) {
- var (
- searchResults []image.SearchResult
- )
- params := make(map[string]string)
- params["term"] = term
- if limit > 0 {
- params["limit"] = strconv.Itoa(limit)
- }
- if filters != nil {
- params["filters"] = filters.ToMapJSON()
- }
- response, err := c.newRequest(http.MethodGet, "/images/search", nil, params)
- if err != nil {
- return searchResults, nil
- }
- return searchResults, response.Process(&searchResults)
-}
diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go
new file mode 100644
index 000000000..4f2a98f2b
--- /dev/null
+++ b/pkg/bindings/test/common_test.go
@@ -0,0 +1,112 @@
+package test_bindings
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+
+ "github.com/onsi/ginkgo"
+ "github.com/onsi/gomega/gexec"
+ "github.com/pkg/errors"
+)
+
+const (
+ defaultPodmanBinaryLocation string = "/usr/bin/podman"
+)
+
+type bindingTest struct {
+ artifactDirPath string
+ imageCacheDir string
+ sock string
+ tempDirPath string
+ runRoot string
+ crioRoot string
+}
+
+func (b *bindingTest) runPodman(command []string) *gexec.Session {
+ var cmd []string
+ podmanBinary := defaultPodmanBinaryLocation
+ val, ok := os.LookupEnv("PODMAN_BINARY")
+ if ok {
+ podmanBinary = val
+ }
+ val, ok = os.LookupEnv("CGROUP_MANAGER")
+ if ok {
+ cmd = append(cmd, "--cgroup-manager", val)
+ }
+ val, ok = os.LookupEnv("CNI_CONFIG_DIR")
+ if ok {
+ cmd = append(cmd, "--cni-config-dir", val)
+ }
+ val, ok = os.LookupEnv("CONMON")
+ if ok {
+ cmd = append(cmd, "--conmon", val)
+ }
+ val, ok = os.LookupEnv("ROOT")
+ if ok {
+ cmd = append(cmd, "--root", val)
+ } else {
+ cmd = append(cmd, "--root", b.crioRoot)
+ }
+ val, ok = os.LookupEnv("OCI_RUNTIME")
+ if ok {
+ cmd = append(cmd, "--runtime", val)
+ }
+ val, ok = os.LookupEnv("RUNROOT")
+ if ok {
+ cmd = append(cmd, "--runroot", val)
+ } else {
+ cmd = append(cmd, "--runroot", b.runRoot)
+ }
+ val, ok = os.LookupEnv("STORAGE_DRIVER")
+ if ok {
+ cmd = append(cmd, "--storage-driver", val)
+ }
+ val, ok = os.LookupEnv("STORAGE_OPTIONS")
+ if ok {
+ cmd = append(cmd, "--storage", val)
+ }
+ cmd = append(cmd, command...)
+ c := exec.Command(podmanBinary, cmd...)
+ fmt.Printf("Running: %s %s\n", podmanBinary, strings.Join(cmd, " "))
+ session, err := gexec.Start(c, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter)
+ if err != nil {
+ panic(errors.Errorf("unable to run podman command: %q", cmd))
+ }
+ return session
+}
+
+func newBindingTest() *bindingTest {
+ tmpPath, _ := createTempDirInTempDir()
+ b := bindingTest{
+ crioRoot: filepath.Join(tmpPath, "crio"),
+ runRoot: filepath.Join(tmpPath, "run"),
+ artifactDirPath: "",
+ imageCacheDir: "",
+ sock: fmt.Sprintf("unix:%s", filepath.Join(tmpPath, "api.sock")),
+ tempDirPath: tmpPath,
+ }
+ return &b
+}
+
+// createTempDirinTempDir create a temp dir with prefix podman_test
+func createTempDirInTempDir() (string, error) {
+ return ioutil.TempDir("", "libpod_api")
+}
+
+func (b *bindingTest) startAPIService() *gexec.Session {
+ var (
+ cmd []string
+ )
+ cmd = append(cmd, "--log-level=debug", "service", "--timeout=999999", b.sock)
+ return b.runPodman(cmd)
+}
+
+func (b *bindingTest) cleanup() {
+ if err := os.RemoveAll(b.tempDirPath); err != nil {
+ fmt.Println(err)
+ }
+}
diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go
new file mode 100644
index 000000000..d600197bb
--- /dev/null
+++ b/pkg/bindings/test/images_test.go
@@ -0,0 +1,92 @@
+package test_bindings
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/bindings/images"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Podman images", func() {
+ var (
+ //tempdir string
+ //err error
+ //podmanTest *PodmanTestIntegration
+ bt *bindingTest
+ s *gexec.Session
+ connText context.Context
+ err error
+ false bool
+ //true bool = true
+ )
+
+ BeforeEach(func() {
+ //tempdir, err = CreateTempDirInTempDir()
+ //if err != nil {
+ // os.Exit(1)
+ //}
+ //podmanTest = PodmanTestCreate(tempdir)
+ //podmanTest.Setup()
+ //podmanTest.SeedImages()
+ bt = newBindingTest()
+ p := bt.runPodman([]string{"pull", "docker.io/library/alpine:latest"})
+ p.Wait(45)
+ s = bt.startAPIService()
+ time.Sleep(1 * time.Second)
+ connText, err = bindings.NewConnection(bt.sock)
+ Expect(err).To(BeNil())
+ })
+
+ AfterEach(func() {
+ //podmanTest.Cleanup()
+ //f := CurrentGinkgoTestDescription()
+ //processTestResult(f)
+ s.Kill()
+ bt.cleanup()
+ })
+ It("inspect image", func() {
+ // Inspect invalid image be 404
+ _, err = images.GetImage(connText, "foobar5000", nil)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", 404))
+
+ // Inspect by short name
+ data, err := images.GetImage(connText, "alpine", nil)
+ Expect(err).To(BeNil())
+
+ // Inspect with full ID
+ _, err = images.GetImage(connText, data.ID, nil)
+ Expect(err).To(BeNil())
+
+ // Inspect with partial ID
+ _, err = images.GetImage(connText, data.ID[0:12], nil)
+ Expect(err).To(BeNil())
+ // Inspect by ID
+ // Inspect by long name should work, it doesnt (yet) i think it needs to be html escaped
+ //_, err = images.GetImage(connText, )
+ //Expect(err).To(BeNil())
+ })
+ It("remove image", func() {
+ // Remove invalid image should be a 404
+ _, err = images.RemoveImage(connText, "foobar5000", &false)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", 404))
+
+ _, err := images.GetImage(connText, "alpine", nil)
+ Expect(err).To(BeNil())
+
+ response, err := images.RemoveImage(connText, "alpine", &false)
+ Expect(err).To(BeNil())
+ fmt.Println(response)
+ // to be continued
+
+ })
+
+})
diff --git a/pkg/bindings/volumes.go b/pkg/bindings/volumes.go
deleted file mode 100644
index 219f924e7..000000000
--- a/pkg/bindings/volumes.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
- "strconv"
-
- "github.com/containers/libpod/libpod"
- "github.com/containers/libpod/pkg/api/handlers"
-)
-
-func (c Connection) CreateVolume(config handlers.VolumeCreateConfig) (string, error) {
- var (
- volumeID string
- )
- response, err := c.newRequest(http.MethodPost, "/volumes/create", nil, nil)
- if err != nil {
- return volumeID, err
- }
- return volumeID, response.Process(&volumeID)
-}
-
-func (c Connection) InspectVolume(nameOrID string) (*libpod.InspectVolumeData, error) {
- var (
- inspect libpod.InspectVolumeData
- )
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/volumes/%s/json", nameOrID), nil, nil)
- if err != nil {
- return &inspect, err
- }
- return &inspect, response.Process(&inspect)
-}
-
-func (c Connection) ListVolumes() error {
- // TODO
- // The API side of things for this one does a lot in main and therefore
- // is not implemented yet.
- return ErrNotImplemented // nolint:typecheck
-}
-
-func (c Connection) PruneVolumes() ([]string, error) {
- var (
- pruned []string
- )
- response, err := c.newRequest(http.MethodPost, "/volumes/prune", nil, nil)
- if err != nil {
- return pruned, err
- }
- return pruned, response.Process(&pruned)
-}
-
-func (c Connection) RemoveVolume(nameOrID string, force bool) error {
- params := make(map[string]string)
- params["force"] = strconv.FormatBool(force)
- response, err := c.newRequest(http.MethodPost, "/volumes/prune", nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go
new file mode 100644
index 000000000..05a4f73fd
--- /dev/null
+++ b/pkg/bindings/volumes/volumes.go
@@ -0,0 +1,85 @@
+package volumes
+
+import (
+ "context"
+ "net/http"
+ "strconv"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+// Create creates a volume given its configuration.
+func Create(ctx context.Context, config handlers.VolumeCreateConfig) (string, error) {
+ // TODO This is incomplete. The config needs to be sent via the body
+ var (
+ volumeID string
+ )
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return "", err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/create", nil)
+ if err != nil {
+ return volumeID, err
+ }
+ return volumeID, response.Process(&volumeID)
+}
+
+// Inspect returns low-level information about a volume.
+func Inspect(ctx context.Context, nameOrID string) (*libpod.InspectVolumeData, error) {
+ var (
+ inspect libpod.InspectVolumeData
+ )
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/%s/json", nil, nameOrID)
+ if err != nil {
+ return &inspect, err
+ }
+ return &inspect, response.Process(&inspect)
+}
+
+func List() error {
+ // TODO
+ // The API side of things for this one does a lot in main and therefore
+ // is not implemented yet.
+ return bindings.ErrNotImplemented // nolint:typecheck
+}
+
+// Prune removes unused volumes from the local filesystem.
+func Prune(ctx context.Context) ([]string, error) {
+ var (
+ pruned []string
+ )
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", nil)
+ if err != nil {
+ return pruned, err
+ }
+ return pruned, response.Process(&pruned)
+}
+
+// Remove deletes the given volume from storage. The optional force parameter
+// is used to remove a volume even if it is being used by a container.
+func Remove(ctx context.Context, nameOrID string, force *bool) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ if force != nil {
+ params["force"] = strconv.FormatBool(*force)
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/%s/prune", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
diff --git a/pkg/inspect/inspect.go b/pkg/inspect/inspect.go
index 8249dc4aa..569f208d9 100644
--- a/pkg/inspect/inspect.go
+++ b/pkg/inspect/inspect.go
@@ -3,6 +3,7 @@ package inspect
import (
"time"
+ "github.com/containers/image/v5/manifest"
"github.com/containers/libpod/libpod/driver"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go/v1"
@@ -10,28 +11,29 @@ import (
// ImageData holds the inspect information of an image
type ImageData struct {
- ID string `json:"Id"`
- Digest digest.Digest `json:"Digest"`
- RepoTags []string `json:"RepoTags"`
- RepoDigests []string `json:"RepoDigests"`
- Parent string `json:"Parent"`
- Comment string `json:"Comment"`
- Created *time.Time `json:"Created"`
- Config *v1.ImageConfig `json:"Config"`
- Version string `json:"Version"`
- Author string `json:"Author"`
- Architecture string `json:"Architecture"`
- Os string `json:"Os"`
- Size int64 `json:"Size"`
- VirtualSize int64 `json:"VirtualSize"`
- GraphDriver *driver.Data `json:"GraphDriver"`
- RootFS *RootFS `json:"RootFS"`
- Labels map[string]string `json:"Labels"`
- Annotations map[string]string `json:"Annotations"`
- ManifestType string `json:"ManifestType"`
- User string `json:"User"`
- History []v1.History `json:"History"`
- NamesHistory []string `json:"NamesHistory"`
+ ID string `json:"Id"`
+ Digest digest.Digest `json:"Digest"`
+ RepoTags []string `json:"RepoTags"`
+ RepoDigests []string `json:"RepoDigests"`
+ Parent string `json:"Parent"`
+ Comment string `json:"Comment"`
+ Created *time.Time `json:"Created"`
+ Config *v1.ImageConfig `json:"Config"`
+ Version string `json:"Version"`
+ Author string `json:"Author"`
+ Architecture string `json:"Architecture"`
+ Os string `json:"Os"`
+ Size int64 `json:"Size"`
+ VirtualSize int64 `json:"VirtualSize"`
+ GraphDriver *driver.Data `json:"GraphDriver"`
+ RootFS *RootFS `json:"RootFS"`
+ Labels map[string]string `json:"Labels"`
+ Annotations map[string]string `json:"Annotations"`
+ ManifestType string `json:"ManifestType"`
+ User string `json:"User"`
+ History []v1.History `json:"History"`
+ NamesHistory []string `json:"NamesHistory"`
+ HealthCheck *manifest.Schema2HealthConfig `json:"Healthcheck,omitempty"`
}
// RootFS holds the root fs information of an image
diff --git a/pkg/util/camelcase/LICENSE.md b/pkg/util/camelcase/LICENSE.md
new file mode 100644
index 000000000..aa4a536ca
--- /dev/null
+++ b/pkg/util/camelcase/LICENSE.md
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Fatih Arslan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/pkg/util/camelcase/README.md b/pkg/util/camelcase/README.md
new file mode 100644
index 000000000..105a6ae33
--- /dev/null
+++ b/pkg/util/camelcase/README.md
@@ -0,0 +1,58 @@
+# CamelCase [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/camelcase) [![Build Status](http://img.shields.io/travis/fatih/camelcase.svg?style=flat-square)](https://travis-ci.org/fatih/camelcase)
+
+CamelCase is a Golang (Go) package to split the words of a camelcase type
+string into a slice of words. It can be used to convert a camelcase word (lower
+or upper case) into any type of word.
+
+## Splitting rules:
+
+1. If string is not valid UTF-8, return it without splitting as
+ single item array.
+2. Assign all unicode characters into one of 4 sets: lower case
+ letters, upper case letters, numbers, and all other characters.
+3. Iterate through characters of string, introducing splits
+ between adjacent characters that belong to different sets.
+4. Iterate through array of split strings, and if a given string
+ is upper case:
+ * if subsequent string is lower case:
+ * move last character of upper case string to beginning of
+ lower case string
+
+## Install
+
+```bash
+go get github.com/fatih/camelcase
+```
+
+## Usage and examples
+
+```go
+splitted := camelcase.Split("GolangPackage")
+
+fmt.Println(splitted[0], splitted[1]) // prints: "Golang", "Package"
+```
+
+Both lower camel case and upper camel case are supported. For more info please
+check: [http://en.wikipedia.org/wiki/CamelCase](http://en.wikipedia.org/wiki/CamelCase)
+
+Below are some example cases:
+
+```
+"" => []
+"lowercase" => ["lowercase"]
+"Class" => ["Class"]
+"MyClass" => ["My", "Class"]
+"MyC" => ["My", "C"]
+"HTML" => ["HTML"]
+"PDFLoader" => ["PDF", "Loader"]
+"AString" => ["A", "String"]
+"SimpleXMLParser" => ["Simple", "XML", "Parser"]
+"vimRPCPlugin" => ["vim", "RPC", "Plugin"]
+"GL11Version" => ["GL", "11", "Version"]
+"99Bottles" => ["99", "Bottles"]
+"May5" => ["May", "5"]
+"BFG9000" => ["BFG", "9000"]
+"BöseÜberraschung" => ["Böse", "Überraschung"]
+"Two spaces" => ["Two", " ", "spaces"]
+"BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"]
+```
diff --git a/pkg/util/camelcase/camelcase.go b/pkg/util/camelcase/camelcase.go
new file mode 100644
index 000000000..0a82d1005
--- /dev/null
+++ b/pkg/util/camelcase/camelcase.go
@@ -0,0 +1,91 @@
+// Package camelcase is a micro package to split the words of a camelcase type
+// string into a slice of words.
+package camelcase
+
+import (
+ "unicode"
+ "unicode/utf8"
+)
+
+// Split splits the camelcase word and returns a list of words. It also
+// supports digits. Both lower camel case and upper camel case are supported.
+// For more info please check: http://en.wikipedia.org/wiki/CamelCase
+//
+// Examples
+//
+// "" => [""]
+// "lowercase" => ["lowercase"]
+// "Class" => ["Class"]
+// "MyClass" => ["My", "Class"]
+// "MyC" => ["My", "C"]
+// "HTML" => ["HTML"]
+// "PDFLoader" => ["PDF", "Loader"]
+// "AString" => ["A", "String"]
+// "SimpleXMLParser" => ["Simple", "XML", "Parser"]
+// "vimRPCPlugin" => ["vim", "RPC", "Plugin"]
+// "GL11Version" => ["GL", "11", "Version"]
+// "99Bottles" => ["99", "Bottles"]
+// "May5" => ["May", "5"]
+// "BFG9000" => ["BFG", "9000"]
+// "BöseÜberraschung" => ["Böse", "Überraschung"]
+// "Two spaces" => ["Two", " ", "spaces"]
+// "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"]
+//
+// Splitting rules
+//
+// 1) If string is not valid UTF-8, return it without splitting as
+// single item array.
+// 2) Assign all unicode characters into one of 4 sets: lower case
+// letters, upper case letters, numbers, and all other characters.
+// 3) Iterate through characters of string, introducing splits
+// between adjacent characters that belong to different sets.
+// 4) Iterate through array of split strings, and if a given string
+// is upper case:
+// if subsequent string is lower case:
+// move last character of upper case string to beginning of
+// lower case string
+func Split(src string) (entries []string) {
+ // don't split invalid utf8
+ if !utf8.ValidString(src) {
+ return []string{src}
+ }
+ entries = []string{}
+ var runes [][]rune
+ lastClass := 0
+ class := 0
+ // split into fields based on class of unicode character
+ for _, r := range src {
+ switch {
+ case unicode.IsLower(r):
+ class = 1
+ case unicode.IsUpper(r):
+ class = 2
+ case unicode.IsDigit(r):
+ class = 3
+ default:
+ class = 4
+ }
+ if class == lastClass {
+ runes[len(runes)-1] = append(runes[len(runes)-1], r)
+ } else {
+ runes = append(runes, []rune{r})
+ }
+ lastClass = class
+ }
+ // handle upper case -> lower case sequences, e.g.
+ // "PDFL", "oader" -> "PDF", "Loader"
+ for i := 0; i < len(runes)-1; i++ {
+ if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) {
+ runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...)
+ runes[i] = runes[i][:len(runes[i])-1]
+ }
+ }
+ // construct []string from results
+ for _, s := range runes {
+ if len(s) > 0 {
+ entries = append(entries, string(s))
+ }
+ }
+
+ return entries
+}