[CI:DOCS]Binding overhauls

Add binding for networks and begin documentation for binding methods for godoc.  Also, add major functions to their own subpackages so reduce the amount of of method confusion.  So instead of: bindings.ListImages(), we now do a [bindings].images.List().

Also, the connection is passed to each binding method via a context to allow for future growth.

Lastly, add first set of tests.  There are a couple of things to work out for rootless tests yet.

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude
2020-01-21 12:44:50 -06:00
parent d07c263106
commit 54587335be
30 changed files with 1297 additions and 623 deletions

View File

@ -263,7 +263,7 @@ localunit: test/goecho/goecho varlink_generate
ginkgo \
-r \
$(TESTFLAGS) \
--skipPackage test/e2e,pkg/apparmor,test/endpoint \
--skipPackage test/e2e,pkg/apparmor,test/endpoint,pkg/bindings \
--cover \
--covermode atomic \
--tags "$(BUILDTAGS)" \

View File

@ -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")

View File

@ -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)

View File

@ -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
}{

View File

@ -195,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:
@ -607,13 +607,13 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// 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"
// - 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"
// - 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>`)
@ -621,12 +621,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// - `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
// type: string
// produces:
// - application/json
// responses:
@ -753,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:

View File

@ -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
}

View File

@ -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
}
// 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
}
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 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
}
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)
},
},
}
newConn := Connection{
url: url,
client: &http.Client{},
client: &client,
address: address,
scheme: scheme,
}
response, err := http.Get(pingConnection)
if err != nil {
return newConn, err
}
if err := response.Body.Close(); err != nil {
return newConn, err
}
return newConn, err
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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -1,4 +0,0 @@
package bindings
func (c Connection) GenerateKube() {}
func (c Connection) GenerateSystemd() {}

View File

@ -0,0 +1,4 @@
package generate
func GenerateKube() {}
func GenerateSystemd() {}

View File

@ -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)
}

View File

@ -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) {}

View File

@ -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) {}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -1,3 +0,0 @@
package bindings
func (c Connection) PlayKube() {}

View File

@ -0,0 +1,7 @@
package play
import "github.com/containers/libpod/pkg/bindings"
func PlayKube() error {
return bindings.ErrNotImplemented
}

View File

@ -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)
}

196
pkg/bindings/pods/pods.go Normal file
View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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
})
})

View File

@ -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)
}

View File

@ -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)
}