diff options
Diffstat (limited to 'pkg')
60 files changed, 2327 insertions, 151 deletions
diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go index dc856cc8d..1417bd2b9 100644 --- a/pkg/adapter/pods.go +++ b/pkg/adapter/pods.go @@ -122,19 +122,31 @@ func (r *LocalRuntime) GetLatestPod() (*Pod, error) { return &pod, err } +// GetPodsWithFilters gets the filtered list of pods based on the filter parameters provided. +func (r *LocalRuntime) GetPodsWithFilters(filters string) ([]*Pod, error) { + pods, err := shared.GetPodsWithFilters(r.Runtime, filters) + if err != nil { + return nil, err + } + return r.podstoAdapterPods(pods) +} + +func (r *LocalRuntime) podstoAdapterPods(pod []*libpod.Pod) ([]*Pod, error) { + var pods []*Pod + for _, i := range pod { + + pods = append(pods, &Pod{i}) + } + return pods, nil +} + // GetAllPods gets all pods and wraps it in an adapter pod func (r *LocalRuntime) GetAllPods() ([]*Pod, error) { - var pods []*Pod allPods, err := r.Runtime.GetAllPods() if err != nil { return nil, err } - for _, p := range allPods { - pod := Pod{} - pod.Pod = p - pods = append(pods, &pod) - } - return pods, nil + return r.podstoAdapterPods(allPods) } // LookupPod gets a pod by name or id and wraps it in an adapter pod diff --git a/pkg/adapter/pods_remote.go b/pkg/adapter/pods_remote.go index 20f089628..6b8f22f15 100644 --- a/pkg/adapter/pods_remote.go +++ b/pkg/adapter/pods_remote.go @@ -10,7 +10,7 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/cmd/podman/varlink" + iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/varlinkapi" @@ -208,6 +208,11 @@ func (r *LocalRuntime) GetAllPods() ([]*Pod, error) { return pods, nil } +// This is a empty implementation stating remoteclient not yet implemented +func (r *LocalRuntime) GetPodsWithFilters(filters string) ([]*Pod, error) { + return nil, define.ErrNotImplemented +} + // GetPodsByStatus returns a slice of pods filtered by a libpod status func (r *LocalRuntime) GetPodsByStatus(statuses []string) ([]*Pod, error) { podIDs, err := iopodman.GetPodsByStatus().Call(r.Conn, statuses) diff --git a/pkg/adapter/terminal_linux.go b/pkg/adapter/terminal_linux.go index 3dc5864e2..ef5a6f926 100644 --- a/pkg/adapter/terminal_linux.go +++ b/pkg/adapter/terminal_linux.go @@ -16,7 +16,6 @@ import ( // ExecAttachCtr execs and attaches to a container func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *libpod.AttachStreams, preserveFDs uint, detachKeys string) (int, error) { resize := make(chan remotecommand.TerminalSize) - haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) // Check if we are attached to a terminal. If we are, generate resize @@ -33,7 +32,18 @@ func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged b } }() } - return ctr.Exec(tty, privileged, env, cmd, user, workDir, streams, preserveFDs, resize, detachKeys) + + execConfig := new(libpod.ExecConfig) + execConfig.Command = cmd + execConfig.Terminal = tty + execConfig.Privileged = privileged + execConfig.Environment = env + execConfig.User = user + execConfig.WorkDir = workDir + execConfig.DetachKeys = &detachKeys + execConfig.PreserveFDs = preserveFDs + + return ctr.Exec(execConfig, streams, resize) } // StartAttachCtr starts and (if required) attaches to a container diff --git a/pkg/adapter/terminal_unsupported.go b/pkg/adapter/terminal_unsupported.go new file mode 100644 index 000000000..3009f0a38 --- /dev/null +++ b/pkg/adapter/terminal_unsupported.go @@ -0,0 +1,23 @@ +// +build !linux + +package adapter + +import ( + "context" + "os" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" +) + +// ExecAttachCtr execs and attaches to a container +func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *libpod.AttachStreams, preserveFDs uint, detachKeys string) (int, error) { + return -1, define.ErrNotImplemented +} + +// StartAttachCtr starts and (if required) attaches to a container +// if you change the signature of this function from os.File to io.Writer, it will trigger a downstream +// error. we may need to just lint disable this one. +func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool, recursive bool) error { //nolint-interfacer + return define.ErrNotImplemented +} diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index 1298e7fa4..3a269fe50 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -87,7 +87,7 @@ func ListContainers(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - if _, found := r.URL.Query()["limit"]; found { + if _, found := r.URL.Query()["limit"]; found && query.Limit != -1 { last := query.Limit if len(containers) > last { containers = containers[len(containers)-last:] @@ -326,7 +326,6 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) { builder.WriteRune(' ') } builder.WriteString(line.Msg) - // Build header and output entry binary.BigEndian.PutUint32(header[4:], uint32(len(header)+builder.Len())) if _, err := w.Write(header[:]); err != nil { @@ -335,7 +334,6 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) { if _, err := fmt.Fprint(w, builder.String()); err != nil { log.Errorf("unable to write builder string: %q", err) } - if flusher, ok := w.(http.Flusher); ok { flusher.Flush() } diff --git a/pkg/api/handlers/compat/images_history.go b/pkg/api/handlers/compat/images_history.go index 04304caa4..afadf4c48 100644 --- a/pkg/api/handlers/compat/images_history.go +++ b/pkg/api/handlers/compat/images_history.go @@ -28,7 +28,7 @@ func HistoryImage(w http.ResponseWriter, r *http.Request) { for _, h := range history { l := handlers.HistoryResponse{ ID: h.ID, - Created: h.Created.UnixNano(), + Created: h.Created.Unix(), CreatedBy: h.CreatedBy, Tags: h.Tags, Size: h.Size, diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index 8020c391d..cdc34004f 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -21,8 +21,12 @@ func ContainerExists(w http.ResponseWriter, r *http.Request) { name := utils.GetName(r) _, err := runtime.LookupContainer(name) if err != nil { - utils.ContainerNotFound(w, name, err) + if errors.Cause(err) == define.ErrNoSuchCtr { + utils.ContainerNotFound(w, name, err) + } + utils.InternalServerError(w, err) return + } utils.WriteResponse(w, http.StatusNoContent, "") } diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index f93c8f8d5..27ec64d89 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -103,7 +103,6 @@ func PodCreate(w http.ResponseWriter, r *http.Request) { func Pods(w http.ResponseWriter, r *http.Request) { var ( - runtime = r.Context().Value("runtime").(*libpod.Runtime) podInspectData []*libpod.PodInspect ) decoder := r.Context().Value("decoder").(*schema.Decoder) @@ -118,12 +117,8 @@ func Pods(w http.ResponseWriter, r *http.Request) { return } - if len(query.Filters) > 0 { - utils.Error(w, "filters are not implemented yet", http.StatusInternalServerError, define.ErrNotImplemented) - return - } + pods, err := utils.GetPods(w, r) - pods, err := runtime.GetAllPods() if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go index 9b10ee890..06ca1d225 100644 --- a/pkg/api/handlers/libpod/volumes.go +++ b/pkg/api/handlers/libpod/volumes.go @@ -8,8 +8,8 @@ import ( "github.com/containers/libpod/cmd/podman/shared" "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" + "github.com/containers/libpod/pkg/domain/entities" "github.com/gorilla/schema" "github.com/pkg/errors" log "github.com/sirupsen/logrus" @@ -25,7 +25,7 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) { }{ // override any golang type defaults } - input := handlers.VolumeCreateConfig{} + input := entities.VolumeCreateOptions{} 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())) @@ -46,8 +46,8 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) { if len(input.Label) > 0 { volumeOptions = append(volumeOptions, libpod.WithVolumeLabels(input.Label)) } - if len(input.Opts) > 0 { - parsedOptions, err := shared.ParseVolumeOptions(input.Opts) + if len(input.Options) > 0 { + parsedOptions, err := shared.ParseVolumeOptions(input.Options) if err != nil { utils.InternalServerError(w, err) return @@ -64,7 +64,17 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - utils.WriteResponse(w, http.StatusOK, config) + volResponse := entities.VolumeConfigResponse{ + Name: config.Name, + Labels: config.Labels, + Driver: config.Driver, + MountPoint: config.MountPoint, + CreatedTime: config.CreatedTime, + Options: config.Options, + UID: config.UID, + GID: config.GID, + } + utils.WriteResponse(w, http.StatusOK, volResponse) } func InspectVolume(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index ce4a9957b..c6b70251b 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -128,18 +128,6 @@ type CreateContainerConfig struct { NetworkingConfig dockerNetwork.NetworkingConfig } -// swagger:model VolumeCreate -type VolumeCreateConfig struct { - // New volume's name. Can be left blank - Name string `schema:"name"` - // Volume driver to use - Driver string `schema:"driver"` - // User-defined key/value metadata. - Label map[string]string `schema:"label"` - // Mapping of driver options and values. - Opts map[string]string `schema:"opts"` -} - // swagger:model IDResponse type IDResponse struct { // ID diff --git a/pkg/api/handlers/utils/containers.go b/pkg/api/handlers/utils/containers.go index d5a79bdc8..bbe4cee3c 100644 --- a/pkg/api/handlers/utils/containers.go +++ b/pkg/api/handlers/utils/containers.go @@ -16,7 +16,7 @@ import ( // ContainerCreateResponse is the response struct for creating a container type ContainerCreateResponse struct { // ID of the container created - ID string `json:"id"` + ID string `json:"Id"` // Warnings during container creation Warnings []string `json:"Warnings"` } diff --git a/pkg/api/handlers/utils/pods.go b/pkg/api/handlers/utils/pods.go new file mode 100644 index 000000000..266ad9a4b --- /dev/null +++ b/pkg/api/handlers/utils/pods.go @@ -0,0 +1,45 @@ +package utils + +import ( + "fmt" + "net/http" + + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" + "github.com/gorilla/schema" +) + +func GetPods(w http.ResponseWriter, r *http.Request) ([]*libpod.Pod, error) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + + query := struct { + All bool + Filters map[string][]string `schema:"filters"` + Digests bool + }{} + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + return nil, err + } + var filters = []string{} + if _, found := r.URL.Query()["digests"]; found && query.Digests { + UnSupportedParameter("digests") + } + + if len(query.Filters) > 0 { + for k, v := range query.Filters { + for _, val := range v { + filters = append(filters, fmt.Sprintf("%s=%s", k, val)) + } + } + filterFuncs, err := shared.GenerateFilterFunction(runtime, filters) + if err != nil { + return nil, err + } + return shared.FilterAllPodsWithFilterFunc(runtime, filterFuncs...) + } + + return runtime.GetAllPods() + +} diff --git a/pkg/api/server/register_events.go b/pkg/api/server/register_events.go index b0f403709..e909303da 100644 --- a/pkg/api/server/register_events.go +++ b/pkg/api/server/register_events.go @@ -63,6 +63,6 @@ func (s *APIServer) registerEventsHandlers(r *mux.Router) error { // description: returns a string of json data describing an event // 500: // "$ref": "#/responses/InternalError" - r.Handle(VersionedPath("/events"), s.APIHandler(compat.GetEvents)).Methods(http.MethodGet) + r.Handle(VersionedPath("/libpod/events"), s.APIHandler(compat.GetEvents)).Methods(http.MethodGet) return nil } diff --git a/pkg/api/server/swagger.go b/pkg/api/server/swagger.go index d2cf7503e..2e1a269f2 100644 --- a/pkg/api/server/swagger.go +++ b/pkg/api/server/swagger.go @@ -4,6 +4,7 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/domain/entities" ) // No such image @@ -155,7 +156,7 @@ type ok struct { type swagVolumeCreateResponse struct { // in:body Body struct { - libpod.VolumeConfig + entities.VolumeConfigResponse } } diff --git a/pkg/bindings/bindings.go b/pkg/bindings/bindings.go index e83c4a5e1..4b07847d1 100644 --- a/pkg/bindings/bindings.go +++ b/pkg/bindings/bindings.go @@ -7,3 +7,12 @@ // is established, users can then manage the Podman container runtime. package bindings + +var ( + // PTrue is a convenience variable that can be used in bindings where + // a pointer to a bool (optional parameter) is required. + PTrue bool = true + // PFalse is a convenience variable that can be used in bindings where + // a pointer to a bool (optional parameter) is required. + PFalse bool = false +) diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index ba5f9c3aa..4fe4dd72d 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -109,7 +109,7 @@ func NewConnection(ctx context.Context, uri string, identity ...string) (context } client, err = tcpClient(_url) default: - return nil, errors.Errorf("%s is not a support schema", _url.Scheme) + return nil, errors.Errorf("'%s' is not a supported schema", _url.Scheme) } if err != nil { return nil, errors.Wrapf(err, "Failed to create %sClient", _url.Scheme) @@ -165,8 +165,13 @@ func sshClient(_url *url.URL, identity string, secure bool) (*http.Client, error } } + port := _url.Port() + if port == "" { + port = "22" + } + bastion, err := ssh.Dial("tcp", - net.JoinHostPort(_url.Hostname(), _url.Port()), + net.JoinHostPort(_url.Hostname(), port), &ssh.ClientConfig{ User: _url.User.Username(), Auth: []ssh.AuthMethod{auth}, diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index 670321f21..534555a00 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -7,6 +7,7 @@ import ( "strconv" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" lpapiv2 "github.com/containers/libpod/pkg/api/handlers/libpod" "github.com/containers/libpod/pkg/bindings" ) @@ -139,7 +140,6 @@ func Kill(ctx context.Context, nameOrID string, signal string) error { return response.Process(nil) } -func Logs() {} // Pause pauses a given container. The nameOrID can be a container name // or a partial/full ID. @@ -213,7 +213,7 @@ func Unpause(ctx context.Context, nameOrID string) error { // Wait blocks until the given container reaches a condition. If not provided, the condition will // default to stopped. If the condition is stopped, an exit code for the container will be provided. The // nameOrID can be a container name or a partial/full ID. -func Wait(ctx context.Context, nameOrID string, condition *string) (int32, error) { +func Wait(ctx context.Context, nameOrID string, condition *define.ContainerStatus) (int32, error) { //nolint var exitCode int32 conn, err := bindings.GetClient(ctx) if err != nil { @@ -221,7 +221,7 @@ func Wait(ctx context.Context, nameOrID string, condition *string) (int32, error } params := url.Values{} if condition != nil { - params.Set("condition", *condition) + params.Set("condition", condition.String()) } response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/wait", params, nameOrID) if err != nil { diff --git a/pkg/bindings/containers/logs.go b/pkg/bindings/containers/logs.go new file mode 100644 index 000000000..b7ecb3c7e --- /dev/null +++ b/pkg/bindings/containers/logs.go @@ -0,0 +1,116 @@ +package containers + +import ( + "context" + "encoding/binary" + "io" + "net/http" + "net/url" + "strconv" + + "github.com/containers/libpod/pkg/bindings" + "github.com/pkg/errors" +) + +// Logs obtains a container's logs given the options provided. The logs are then sent to the +// stdout|stderr channels as strings. +func Logs(ctx context.Context, nameOrID string, opts LogOptions, stdoutChan, stderrChan chan string) error { + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + params := url.Values{} + if opts.Follow != nil { + params.Set("follow", strconv.FormatBool(*opts.Follow)) + } + if opts.Since != nil { + params.Set("since", *opts.Since) + } + if opts.Stderr != nil { + params.Set("stderr", strconv.FormatBool(*opts.Stderr)) + } + if opts.Stdout != nil { + params.Set("stdout", strconv.FormatBool(*opts.Stdout)) + } + if opts.Tail != nil { + params.Set("tail", *opts.Tail) + } + if opts.Timestamps != nil { + params.Set("timestamps", strconv.FormatBool(*opts.Timestamps)) + } + if opts.Until != nil { + params.Set("until", *opts.Until) + } + // The API requires either stdout|stderr be used. If neither are specified, we specify stdout + if opts.Stdout == nil && opts.Stderr == nil { + params.Set("stdout", strconv.FormatBool(true)) + } + response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/logs", params, nameOrID) + if err != nil { + return err + } + + // read 8 bytes + // first byte determines stderr=2|stdout=1 + // bytes 4-7 len(msg) in uint32 + for { + stream, msgSize, err := readHeader(response.Body) + if err != nil { + // In case the server side closes up shop because !follow + if err == io.EOF { + break + } + return errors.Wrap(err, "unable to read log header") + } + msg, err := readMsg(response.Body, msgSize) + if err != nil { + return errors.Wrap(err, "unable to read log message") + } + if stream == 1 { + stdoutChan <- msg + } else { + stderrChan <- msg + } + } + return nil +} + +func readMsg(r io.Reader, msgSize int) (string, error) { + var msg []byte + size := msgSize + for { + b := make([]byte, size) + _, err := r.Read(b) + if err != nil { + return "", err + } + msg = append(msg, b...) + if len(msg) == msgSize { + break + } + size = msgSize - len(msg) + } + return string(msg), nil +} + +func readHeader(r io.Reader) (byte, int, error) { + var ( + header []byte + size = 8 + ) + for { + b := make([]byte, size) + _, err := r.Read(b) + if err != nil { + return 0, 0, err + } + header = append(header, b...) + if len(header) == 8 { + break + } + size = 8 - len(header) + } + stream := header[0] + msgSize := int(binary.BigEndian.Uint32(header[4:]) - 8) + return stream, msgSize, nil +} diff --git a/pkg/bindings/containers/types.go b/pkg/bindings/containers/types.go new file mode 100644 index 000000000..87342f7ea --- /dev/null +++ b/pkg/bindings/containers/types.go @@ -0,0 +1,13 @@ +package containers + +// LogOptions describe finer control of log content or +// how the content is formatted. +type LogOptions struct { + Follow *bool + Since *string + Stderr *bool + Stdout *bool + Tail *string + Timestamps *bool + Until *string +} diff --git a/pkg/bindings/system/system.go b/pkg/bindings/system/system.go new file mode 100644 index 000000000..fce8bbb8e --- /dev/null +++ b/pkg/bindings/system/system.go @@ -0,0 +1,61 @@ +package system + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/url" + + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/bindings" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// Events allows you to monitor libdpod related events like container creation and +// removal. The events are then passed to the eventChan provided. The optional cancelChan +// can be used to cancel the read of events and close down the HTTP connection. +func Events(ctx context.Context, eventChan chan (handlers.Event), cancelChan chan bool, since, until *string, filters map[string][]string) error { + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + params := url.Values{} + if since != nil { + params.Set("since", *since) + } + if until != nil { + params.Set("until", *until) + } + if filters != nil { + filterString, err := bindings.FiltersToString(filters) + if err != nil { + return errors.Wrap(err, "invalid filters") + } + params.Set("filters", filterString) + } + response, err := conn.DoRequest(nil, http.MethodGet, "/events", params) + if err != nil { + return err + } + if cancelChan != nil { + go func() { + <-cancelChan + err = response.Body.Close() + logrus.Error(errors.Wrap(err, "unable to close event response body")) + }() + } + dec := json.NewDecoder(response.Body) + for { + e := handlers.Event{} + if err := dec.Decode(&e); err != nil { + if err == io.EOF { + break + } + return errors.Wrap(err, "unable to decode event response") + } + eventChan <- e + } + return nil +} diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go index 5cd8f7e4f..6b8d6788c 100644 --- a/pkg/bindings/test/common_test.go +++ b/pkg/bindings/test/common_test.go @@ -3,6 +3,7 @@ package test_bindings import ( "context" "fmt" + "github.com/containers/libpod/libpod/define" "io/ioutil" "os" "os/exec" @@ -152,7 +153,7 @@ func (b *bindingTest) startAPIService() *gexec.Session { var ( cmd []string ) - cmd = append(cmd, "--log-level=debug", "system", "service", "--timeout=0", b.sock) + cmd = append(cmd, "--log-level=debug", "--events-backend=file", "system", "service", "--timeout=0", b.sock) return b.runPodman(cmd) } @@ -205,8 +206,8 @@ func (b *bindingTest) RunTopContainer(containerName *string, insidePod *bool, po if err != nil { return "", err } - waiting := "running" - _, err = containers.Wait(b.conn, ctr.ID, &waiting) + wait := define.ContainerStateRunning + _, err = containers.Wait(b.conn, ctr.ID, &wait) return ctr.ID, err } diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index 34a9c3136..f5465c803 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -1,12 +1,15 @@ package test_bindings import ( + "github.com/containers/libpod/libpod/define" "net/http" "strconv" + "strings" "time" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/bindings/containers" + "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -15,11 +18,9 @@ import ( var _ = Describe("Podman containers ", func() { var ( - bt *bindingTest - s *gexec.Session - err error - falseFlag bool = false - trueFlag bool = true + bt *bindingTest + s *gexec.Session + err error ) BeforeEach(func() { @@ -55,7 +56,7 @@ var _ = Describe("Podman containers ", func() { It("podman pause a running container by name", func() { // Pausing by name should work var name = "top" - _, err := bt.RunTopContainer(&name, &falseFlag, nil) + _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) Expect(err).To(BeNil()) err = containers.Pause(bt.conn, name) Expect(err).To(BeNil()) @@ -69,7 +70,7 @@ var _ = Describe("Podman containers ", func() { It("podman pause a running container by id", func() { // Pausing by id should work var name = "top" - cid, err := bt.RunTopContainer(&name, &falseFlag, nil) + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) Expect(err).To(BeNil()) err = containers.Pause(bt.conn, cid) Expect(err).To(BeNil()) @@ -83,7 +84,7 @@ var _ = Describe("Podman containers ", func() { It("podman unpause a running container by name", func() { // Unpausing by name should work var name = "top" - _, err := bt.RunTopContainer(&name, &falseFlag, nil) + _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) Expect(err).To(BeNil()) err = containers.Pause(bt.conn, name) Expect(err).To(BeNil()) @@ -99,7 +100,7 @@ var _ = Describe("Podman containers ", func() { It("podman unpause a running container by ID", func() { // Unpausing by ID should work var name = "top" - _, err := bt.RunTopContainer(&name, &falseFlag, nil) + _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) Expect(err).To(BeNil()) // Pause by name err = containers.Pause(bt.conn, name) @@ -118,7 +119,7 @@ var _ = Describe("Podman containers ", func() { It("podman pause a paused container by name", func() { // Pausing a paused container by name should fail var name = "top" - _, err := bt.RunTopContainer(&name, &falseFlag, nil) + _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) Expect(err).To(BeNil()) err = containers.Pause(bt.conn, name) Expect(err).To(BeNil()) @@ -131,7 +132,7 @@ var _ = Describe("Podman containers ", func() { It("podman pause a paused container by id", func() { // Pausing a paused container by id should fail var name = "top" - cid, err := bt.RunTopContainer(&name, &falseFlag, nil) + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) Expect(err).To(BeNil()) err = containers.Pause(bt.conn, cid) Expect(err).To(BeNil()) @@ -144,7 +145,7 @@ var _ = Describe("Podman containers ", func() { It("podman pause a stopped container by name", func() { // Pausing a stopped container by name should fail var name = "top" - _, err := bt.RunTopContainer(&name, &falseFlag, nil) + _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) Expect(err).To(BeNil()) err = containers.Stop(bt.conn, name, nil) Expect(err).To(BeNil()) @@ -157,7 +158,7 @@ var _ = Describe("Podman containers ", func() { It("podman pause a stopped container by id", func() { // Pausing a stopped container by id should fail var name = "top" - cid, err := bt.RunTopContainer(&name, &falseFlag, nil) + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) Expect(err).To(BeNil()) err = containers.Stop(bt.conn, cid, nil) Expect(err).To(BeNil()) @@ -170,11 +171,11 @@ var _ = Describe("Podman containers ", func() { It("podman remove a paused container by id without force", func() { // Removing a paused container without force should fail var name = "top" - cid, err := bt.RunTopContainer(&name, &falseFlag, nil) + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) Expect(err).To(BeNil()) err = containers.Pause(bt.conn, cid) Expect(err).To(BeNil()) - err = containers.Remove(bt.conn, cid, &falseFlag, &falseFlag) + err = containers.Remove(bt.conn, cid, &bindings.PFalse, &bindings.PFalse) Expect(err).ToNot(BeNil()) code, _ := bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) @@ -191,18 +192,18 @@ var _ = Describe("Podman containers ", func() { // Removing a paused container with force should work var name = "top" - cid, err := bt.RunTopContainer(&name, &falseFlag, nil) + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) Expect(err).To(BeNil()) err = containers.Pause(bt.conn, cid) Expect(err).To(BeNil()) - err = containers.Remove(bt.conn, cid, &trueFlag, &falseFlag) + err = containers.Remove(bt.conn, cid, &bindings.PTrue, &bindings.PFalse) Expect(err).To(BeNil()) }) It("podman stop a paused container by name", func() { // Stopping a paused container by name should fail var name = "top" - _, err := bt.RunTopContainer(&name, &falseFlag, nil) + _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) Expect(err).To(BeNil()) err = containers.Pause(bt.conn, name) Expect(err).To(BeNil()) @@ -215,7 +216,7 @@ var _ = Describe("Podman containers ", func() { It("podman stop a paused container by id", func() { // Stopping a paused container by id should fail var name = "top" - cid, err := bt.RunTopContainer(&name, &falseFlag, nil) + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) Expect(err).To(BeNil()) err = containers.Pause(bt.conn, cid) Expect(err).To(BeNil()) @@ -228,7 +229,7 @@ var _ = Describe("Podman containers ", func() { It("podman stop a running container by name", func() { // Stopping a running container by name should work var name = "top" - _, err := bt.RunTopContainer(&name, &falseFlag, nil) + _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) Expect(err).To(BeNil()) err = containers.Stop(bt.conn, name, nil) Expect(err).To(BeNil()) @@ -242,7 +243,7 @@ var _ = Describe("Podman containers ", func() { It("podman stop a running container by ID", func() { // Stopping a running container by ID should work var name = "top" - cid, err := bt.RunTopContainer(&name, &falseFlag, nil) + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) Expect(err).To(BeNil()) err = containers.Stop(bt.conn, cid, nil) Expect(err).To(BeNil()) @@ -282,8 +283,8 @@ var _ = Describe("Podman containers ", func() { var ( name = "top" exitCode int32 = -1 - pause = "paused" - unpause = "running" + pause = define.ContainerStatePaused + running = define.ContainerStateRunning ) errChan := make(chan error) _, err := bt.RunTopContainer(&name, nil, nil) @@ -301,8 +302,8 @@ var _ = Describe("Podman containers ", func() { errChan = make(chan error) go func() { - exitCode, err = containers.Wait(bt.conn, name, &unpause) - errChan <- err + _, waitErr := containers.Wait(bt.conn, name, &running) + errChan <- waitErr close(errChan) }() err = containers.Unpause(bt.conn, name) @@ -323,7 +324,7 @@ var _ = Describe("Podman containers ", func() { // a container that has no healthcheck should be a 409 var name = "top" - bt.RunTopContainer(&name, &falseFlag, nil) + bt.RunTopContainer(&name, &bindings.PFalse, nil) _, err = containers.RunHealthCheck(bt.conn, name) Expect(err).ToNot(BeNil()) code, _ = bindings.CheckResponseCode(err) @@ -357,4 +358,26 @@ var _ = Describe("Podman containers ", func() { //Expect(code).To(BeNumerically("==", http.StatusConflict)) }) + It("logging", func() { + stdoutChan := make(chan string, 10) + s := specgen.NewSpecGenerator(alpine.name) + s.Terminal = true + s.Command = []string{"date", "-R"} + r, err := containers.CreateWithSpec(bt.conn, s) + Expect(err).To(BeNil()) + err = containers.Start(bt.conn, r.ID, nil) + Expect(err).To(BeNil()) + + _, err = containers.Wait(bt.conn, r.ID, nil) + Expect(err).To(BeNil()) + + opts := containers.LogOptions{Stdout: &bindings.PTrue, Follow: &bindings.PTrue} + go func() { + containers.Logs(bt.conn, r.ID, opts, stdoutChan, nil) + }() + o := <-stdoutChan + o = strings.ReplaceAll(o, "\r", "") + _, err = time.Parse(time.RFC1123Z, o) + Expect(err).To(BeNil()) + }) }) diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go index 17b3b254a..5e4cfe7be 100644 --- a/pkg/bindings/test/images_test.go +++ b/pkg/bindings/test/images_test.go @@ -19,11 +19,9 @@ var _ = Describe("Podman images", func() { //tempdir string //err error //podmanTest *PodmanTestIntegration - bt *bindingTest - s *gexec.Session - err error - falseFlag bool = false - trueFlag bool = true + bt *bindingTest + s *gexec.Session + err error ) BeforeEach(func() { @@ -76,7 +74,7 @@ var _ = Describe("Podman images", func() { //Expect(data.Size).To(BeZero()) // Enabling the size parameter should result in size being populated - data, err = images.GetImage(bt.conn, alpine.name, &trueFlag) + data, err = images.GetImage(bt.conn, alpine.name, &bindings.PTrue) Expect(err).To(BeNil()) Expect(data.Size).To(BeNumerically(">", 0)) }) @@ -84,7 +82,7 @@ var _ = Describe("Podman images", func() { // Test to validate the remove image api It("remove image", func() { // Remove invalid image should be a 404 - _, err = images.Remove(bt.conn, "foobar5000", &falseFlag) + _, err = images.Remove(bt.conn, "foobar5000", &bindings.PFalse) Expect(err).ToNot(BeNil()) code, _ := bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusNotFound)) @@ -101,21 +99,21 @@ var _ = Describe("Podman images", func() { // Start a container with alpine image var top string = "top" - _, err = bt.RunTopContainer(&top, &falseFlag, nil) + _, err = bt.RunTopContainer(&top, &bindings.PFalse, nil) Expect(err).To(BeNil()) // we should now have a container called "top" running - containerResponse, err := containers.Inspect(bt.conn, "top", &falseFlag) + containerResponse, err := containers.Inspect(bt.conn, "top", &bindings.PFalse) Expect(err).To(BeNil()) Expect(containerResponse.Name).To(Equal("top")) // try to remove the image "alpine". This should fail since we are not force // deleting hence image cannot be deleted until the container is deleted. - response, err = images.Remove(bt.conn, alpine.shortName, &falseFlag) + response, err = images.Remove(bt.conn, alpine.shortName, &bindings.PFalse) code, _ = bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) // Removing the image "alpine" where force = true - response, err = images.Remove(bt.conn, alpine.shortName, &trueFlag) + response, err = images.Remove(bt.conn, alpine.shortName, &bindings.PTrue) Expect(err).To(BeNil()) // Checking if both the images are gone as well as the container is deleted @@ -127,7 +125,7 @@ var _ = Describe("Podman images", func() { code, _ = bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusNotFound)) - _, err = containers.Inspect(bt.conn, "top", &falseFlag) + _, err = containers.Inspect(bt.conn, "top", &bindings.PFalse) code, _ = bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusNotFound)) }) @@ -178,13 +176,13 @@ var _ = Describe("Podman images", func() { // List images with a filter filters := make(map[string][]string) filters["reference"] = []string{alpine.name} - filteredImages, err := images.List(bt.conn, &falseFlag, filters) + filteredImages, err := images.List(bt.conn, &bindings.PFalse, filters) Expect(err).To(BeNil()) Expect(len(filteredImages)).To(BeNumerically("==", 1)) // List images with a bad filter filters["name"] = []string{alpine.name} - _, err = images.List(bt.conn, &falseFlag, filters) + _, err = images.List(bt.conn, &bindings.PFalse, filters) Expect(err).ToNot(BeNil()) code, _ := bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go index 7e29265b7..bcf8e69b8 100644 --- a/pkg/bindings/test/pods_test.go +++ b/pkg/bindings/test/pods_test.go @@ -14,11 +14,10 @@ import ( var _ = Describe("Podman pods", func() { var ( - bt *bindingTest - s *gexec.Session - newpod string - err error - trueFlag bool = true + bt *bindingTest + s *gexec.Session + newpod string + err error ) BeforeEach(func() { @@ -57,7 +56,7 @@ var _ = Describe("Podman pods", func() { Expect(err).To(BeNil()) Expect(len(podSummary)).To(Equal(1)) // Adding an alpine container to the existing pod - _, err = bt.RunTopContainer(nil, &trueFlag, &newpod) + _, err = bt.RunTopContainer(nil, &bindings.PTrue, &newpod) Expect(err).To(BeNil()) podSummary, err = pods.List(bt.conn, nil) // Verify no errors. @@ -76,15 +75,66 @@ var _ = Describe("Podman pods", func() { } Expect(StringInSlice(newpod, names)).To(BeTrue()) Expect(StringInSlice("newpod2", names)).To(BeTrue()) + }) + + // The test validates the list pod endpoint with passing filters as the params. + It("List pods with filters", func() { + var newpod2 string = "newpod2" + bt.Podcreate(&newpod2) + _, err = bt.RunTopContainer(nil, &bindings.PTrue, &newpod) + Expect(err).To(BeNil()) + + // Expected err with invalid filter params + filters := make(map[string][]string) + filters["dummy"] = []string{"dummy"} + filteredPods, err := pods.List(bt.conn, filters) + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + + // Expected empty response with invalid filters + filters = make(map[string][]string) + filters["name"] = []string{"dummy"} + filteredPods, err = pods.List(bt.conn, filters) + Expect(err).To(BeNil()) + Expect(len(filteredPods)).To(BeNumerically("==", 0)) + + // Validate list pod with name filter + filters = make(map[string][]string) + filters["name"] = []string{newpod2} + filteredPods, err = pods.List(bt.conn, filters) + Expect(err).To(BeNil()) + Expect(len(filteredPods)).To(BeNumerically("==", 1)) + var names []string + for _, i := range filteredPods { + names = append(names, i.Config.Name) + } + Expect(StringInSlice("newpod2", names)).To(BeTrue()) + + // Validate list pod with id filter + filters = make(map[string][]string) + response, err := pods.Inspect(bt.conn, newpod) + id := response.Config.ID + filters["id"] = []string{id} + filteredPods, err = pods.List(bt.conn, filters) + Expect(err).To(BeNil()) + Expect(len(filteredPods)).To(BeNumerically("==", 1)) + names = names[:0] + for _, i := range filteredPods { + names = append(names, i.Config.Name) + } + Expect(StringInSlice("newpod", names)).To(BeTrue()) - // TODO not working Because: code to list based on filter - // "not yet implemented", - // Validate list pod with filters - //filters := make(map[string][]string) - //filters["name"] = []string{newpod} - //filteredPods, err := pods.List(bt.conn, filters) - //Expect(err).To(BeNil()) - //Expect(len(filteredPods)).To(BeNumerically("==", 1)) + // Using multiple filters + filters["name"] = []string{newpod} + filteredPods, err = pods.List(bt.conn, filters) + Expect(err).To(BeNil()) + Expect(len(filteredPods)).To(BeNumerically("==", 1)) + names = names[:0] + for _, i := range filteredPods { + names = append(names, i.Config.Name) + } + Expect(StringInSlice("newpod", names)).To(BeTrue()) }) // The test validates if the exists responds @@ -111,7 +161,7 @@ var _ = Describe("Podman pods", func() { Expect(code).To(BeNumerically("==", http.StatusNotFound)) // Adding an alpine container to the existing pod - _, err = bt.RunTopContainer(nil, &trueFlag, &newpod) + _, err = bt.RunTopContainer(nil, &bindings.PTrue, &newpod) Expect(err).To(BeNil()) // Binding needs to be modified to inspect the pod state. diff --git a/pkg/bindings/test/system_test.go b/pkg/bindings/test/system_test.go new file mode 100644 index 000000000..3abc26b34 --- /dev/null +++ b/pkg/bindings/test/system_test.go @@ -0,0 +1,51 @@ +package test_bindings + +import ( + "time" + + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/bindings/system" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" +) + +var _ = Describe("Podman system", func() { + var ( + bt *bindingTest + s *gexec.Session + ) + + BeforeEach(func() { + bt = newBindingTest() + bt.RestoreImagesFromCache() + s = bt.startAPIService() + time.Sleep(1 * time.Second) + err := bt.NewConnection() + Expect(err).To(BeNil()) + }) + + AfterEach(func() { + s.Kill() + bt.cleanup() + }) + + It("podman events", func() { + eChan := make(chan handlers.Event, 1) + var messages []handlers.Event + cancelChan := make(chan bool, 1) + go func() { + for e := range eChan { + messages = append(messages, e) + } + }() + go func() { + system.Events(bt.conn, eChan, cancelChan, nil, nil, nil) + }() + + _, err := bt.RunTopContainer(nil, nil, nil) + Expect(err).To(BeNil()) + cancelChan <- true + Expect(len(messages)).To(BeNumerically("==", 3)) + }) +}) diff --git a/pkg/bindings/test/volumes_test.go b/pkg/bindings/test/volumes_test.go index b1a742c43..9da034d24 100644 --- a/pkg/bindings/test/volumes_test.go +++ b/pkg/bindings/test/volumes_test.go @@ -6,11 +6,10 @@ import ( "net/http" "time" - "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/bindings/containers" "github.com/containers/libpod/pkg/bindings/volumes" - - "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/domain/entities" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" @@ -25,7 +24,6 @@ var _ = Describe("Podman volumes", func() { s *gexec.Session connText context.Context err error - trueFlag = true ) BeforeEach(func() { @@ -54,13 +52,13 @@ var _ = Describe("Podman volumes", func() { It("create volume", func() { // create a volume with blank config should work - _, err := volumes.Create(connText, handlers.VolumeCreateConfig{}) + _, err := volumes.Create(connText, entities.VolumeCreateOptions{}) Expect(err).To(BeNil()) - vcc := handlers.VolumeCreateConfig{ - Name: "foobar", - Label: nil, - Opts: nil, + vcc := entities.VolumeCreateOptions{ + Name: "foobar", + Label: nil, + Options: nil, } vol, err := volumes.Create(connText, vcc) Expect(err).To(BeNil()) @@ -74,7 +72,7 @@ var _ = Describe("Podman volumes", func() { }) It("inspect volume", func() { - vol, err := volumes.Create(connText, handlers.VolumeCreateConfig{}) + vol, err := volumes.Create(connText, entities.VolumeCreateOptions{}) Expect(err).To(BeNil()) data, err := volumes.Inspect(connText, vol.Name) Expect(err).To(BeNil()) @@ -88,13 +86,13 @@ var _ = Describe("Podman volumes", func() { Expect(code).To(BeNumerically("==", http.StatusNotFound)) // Removing an unused volume should work - vol, err := volumes.Create(connText, handlers.VolumeCreateConfig{}) + vol, err := volumes.Create(connText, entities.VolumeCreateOptions{}) Expect(err).To(BeNil()) err = volumes.Remove(connText, vol.Name, nil) Expect(err).To(BeNil()) // Removing a volume that is being used without force should be 409 - vol, err = volumes.Create(connText, handlers.VolumeCreateConfig{}) + vol, err = volumes.Create(connText, entities.VolumeCreateOptions{}) Expect(err).To(BeNil()) session := bt.runPodman([]string{"run", "-dt", "-v", fmt.Sprintf("%s:/foobar", vol.Name), "--name", "vtest", alpine.name, "top"}) session.Wait(45) @@ -107,7 +105,7 @@ var _ = Describe("Podman volumes", func() { zero := 0 err = containers.Stop(connText, "vtest", &zero) Expect(err).To(BeNil()) - err = volumes.Remove(connText, vol.Name, &trueFlag) + err = volumes.Remove(connText, vol.Name, &bindings.PTrue) Expect(err).To(BeNil()) }) @@ -120,7 +118,7 @@ var _ = Describe("Podman volumes", func() { // create a bunch of named volumes and make verify with list volNames := []string{"homer", "bart", "lisa", "maggie", "marge"} for i := 0; i < 5; i++ { - _, err = volumes.Create(connText, handlers.VolumeCreateConfig{Name: volNames[i]}) + _, err = volumes.Create(connText, entities.VolumeCreateOptions{Name: volNames[i]}) Expect(err).To(BeNil()) } vols, err = volumes.List(connText, nil) @@ -153,15 +151,15 @@ var _ = Describe("Podman volumes", func() { Expect(err).To(BeNil()) // Removing an unused volume should work - _, err = volumes.Create(connText, handlers.VolumeCreateConfig{}) + _, err = volumes.Create(connText, entities.VolumeCreateOptions{}) Expect(err).To(BeNil()) vols, err := volumes.Prune(connText) Expect(err).To(BeNil()) Expect(len(vols)).To(BeNumerically("==", 1)) - _, err = volumes.Create(connText, handlers.VolumeCreateConfig{Name: "homer"}) + _, err = volumes.Create(connText, entities.VolumeCreateOptions{Name: "homer"}) Expect(err).To(BeNil()) - _, err = volumes.Create(connText, handlers.VolumeCreateConfig{}) + _, err = volumes.Create(connText, entities.VolumeCreateOptions{}) Expect(err).To(BeNil()) session := bt.runPodman([]string{"run", "-dt", "-v", fmt.Sprintf("%s:/homer", "homer"), "--name", "vtest", alpine.name, "top"}) session.Wait(45) diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go index 0bc818605..a2164e0af 100644 --- a/pkg/bindings/volumes/volumes.go +++ b/pkg/bindings/volumes/volumes.go @@ -8,15 +8,15 @@ import ( "strings" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/domain/entities" jsoniter "github.com/json-iterator/go" ) // Create creates a volume given its configuration. -func Create(ctx context.Context, config handlers.VolumeCreateConfig) (*libpod.VolumeConfig, error) { +func Create(ctx context.Context, config entities.VolumeCreateOptions) (*entities.VolumeConfigResponse, error) { var ( - v libpod.VolumeConfig + v entities.VolumeConfigResponse ) conn, err := bindings.GetClient(ctx) if err != nil { diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go new file mode 100644 index 000000000..0e1208b3b --- /dev/null +++ b/pkg/domain/entities/containers.go @@ -0,0 +1,23 @@ +package entities + +import ( + "time" + + "github.com/containers/libpod/libpod/define" +) + +type WaitOptions struct { + Condition define.ContainerStatus + Interval time.Duration + Latest bool +} + +type WaitReport struct { + Id string + Error error + ExitCode int32 +} + +type BoolReport struct { + Value bool +} diff --git a/pkg/domain/entities/engine.go b/pkg/domain/entities/engine.go new file mode 100644 index 000000000..08ef1df92 --- /dev/null +++ b/pkg/domain/entities/engine.go @@ -0,0 +1,96 @@ +package entities + +import ( + "os/user" + "path/filepath" + + "github.com/containers/libpod/libpod/define" + "github.com/spf13/pflag" +) + +type EngineMode string + +const ( + ABIMode = EngineMode("abi") + TunnelMode = EngineMode("tunnel") +) + +func (m EngineMode) String() string { + return string(m) +} + +// FIXME: merge EngineOptions and EngineFlags +type EngineOptions struct { + Uri string + Identities []string + FlagSet *pflag.FlagSet + Flags EngineFlags + EngineMode EngineMode +} + +type EngineFlags struct { + CGroupManager string + CniConfigDir string + ConmonPath string + DefaultMountsFile string + EventsBackend string + HooksDir []string + MaxWorks int + Namespace string + Root string + Runroot string + Runtime string + StorageDriver string + StorageOpts []string + Syslog bool + Trace bool + NetworkCmdPath string + + Config string + CpuProfile string + LogLevel string + TmpDir string + + RemoteUserName string + RemoteHost string + VarlinkAddress string + ConnectionName string + RemoteConfigFilePath string + Port int + IdentityFile string + IgnoreHosts bool +} + +func NewEngineOptions() (EngineFlags, error) { + u, _ := user.Current() + return EngineFlags{ + CGroupManager: define.SystemdCgroupsManager, + CniConfigDir: "", + Config: "", + ConmonPath: filepath.Join("usr", "bin", "conmon"), + ConnectionName: "", + CpuProfile: "", + DefaultMountsFile: "", + EventsBackend: "", + HooksDir: nil, + IdentityFile: "", + IgnoreHosts: false, + LogLevel: "", + MaxWorks: 0, + Namespace: "", + NetworkCmdPath: "", + Port: 0, + RemoteConfigFilePath: "", + RemoteHost: "", + RemoteUserName: "", + Root: "", + Runroot: filepath.Join("run", "user", u.Uid), + Runtime: "", + StorageDriver: "overlayfs", + StorageOpts: nil, + Syslog: false, + TmpDir: filepath.Join("run", "user", u.Uid, "libpod", "tmp"), + Trace: false, + VarlinkAddress: "", + }, nil +} diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go new file mode 100644 index 000000000..5820c12c3 --- /dev/null +++ b/pkg/domain/entities/engine_container.go @@ -0,0 +1,18 @@ +package entities + +import ( + "context" +) + +type ContainerEngine interface { + ContainerDelete(ctx context.Context, opts ContainerDeleteOptions) (*ContainerDeleteReport, error) + ContainerPrune(ctx context.Context) (*ContainerPruneReport, error) + ContainerExists(ctx context.Context, nameOrId string) (*BoolReport, error) + ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error) + PodDelete(ctx context.Context, opts PodPruneOptions) (*PodDeleteReport, error) + PodExists(ctx context.Context, nameOrId string) (*BoolReport, error) + PodPrune(ctx context.Context) (*PodPruneReport, error) + VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IdOrNameResponse, error) + VolumeDelete(ctx context.Context, opts VolumeDeleteOptions) (*VolumeDeleteReport, error) + VolumePrune(ctx context.Context) (*VolumePruneReport, error) +} diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go new file mode 100644 index 000000000..27676d781 --- /dev/null +++ b/pkg/domain/entities/engine_image.go @@ -0,0 +1,12 @@ +package entities + +import ( + "context" +) + +type ImageEngine interface { + Delete(ctx context.Context, nameOrId string, opts ImageDeleteOptions) (*ImageDeleteReport, error) + History(ctx context.Context, nameOrId string, opts ImageHistoryOptions) (*ImageHistoryReport, error) + List(ctx context.Context, opts ImageListOptions) (*ImageListReport, error) + Prune(ctx context.Context, opts ImagePruneOptions) (*ImagePruneReport, error) +} diff --git a/pkg/domain/entities/filters.go b/pkg/domain/entities/filters.go new file mode 100644 index 000000000..c7e227244 --- /dev/null +++ b/pkg/domain/entities/filters.go @@ -0,0 +1,150 @@ +package entities + +import ( + "net/url" + "strings" +) + +// Identifier interface allows filters to access ID() of object +type Identifier interface { + Id() string +} + +// Named interface allows filters to access Name() of object +type Named interface { + Name() string +} + +// Named interface allows filters to access Name() of object +type Names interface { + Names() []string +} + +// IdOrName interface allows filters to access ID() or Name() of object +type IdOrNamed interface { + Identifier + Named +} + +// IdOrName interface allows filters to access ID() or Names() of object +type IdOrNames interface { + Identifier + Names +} + +type ImageFilter func(Image) bool +type VolumeFilter func(Volume) bool +type ContainerFilter func(Container) bool + +func CompileImageFilters(filters url.Values) ImageFilter { + var fns []interface{} + + for name, targets := range filters { + switch name { + case "id": + fns = append(fns, FilterIdFn(targets)) + case "name": + fns = append(fns, FilterNamesFn(targets)) + case "idOrName": + fns = append(fns, FilterIdOrNameFn(targets)) + } + } + + return func(image Image) bool { + for _, fn := range fns { + if !fn.(ImageFilter)(image) { + return false + } + } + return true + } +} + +func CompileContainerFilters(filters url.Values) ContainerFilter { + var fns []interface{} + + for name, targets := range filters { + switch name { + case "id": + fns = append(fns, FilterIdFn(targets)) + case "name": + fns = append(fns, FilterNameFn(targets)) + case "idOrName": + fns = append(fns, FilterIdOrNameFn(targets)) + } + } + + return func(ctnr Container) bool { + for _, fn := range fns { + if !fn.(ContainerFilter)(ctnr) { + return false + } + } + return true + } +} + +func CompileVolumeFilters(filters url.Values) VolumeFilter { + var fns []interface{} + + for name, targets := range filters { + if name == "id" { + fns = append(fns, FilterIdFn(targets)) + } + } + + return func(volume Volume) bool { + for _, fn := range fns { + if !fn.(VolumeFilter)(volume) { + return false + } + } + return true + } +} + +func FilterIdFn(id []string) func(Identifier) bool { + return func(obj Identifier) bool { + for _, v := range id { + if strings.Contains(obj.Id(), v) { + return true + } + } + return false + } +} + +func FilterNameFn(name []string) func(Named) bool { + return func(obj Named) bool { + for _, v := range name { + if strings.Contains(obj.Name(), v) { + return true + } + } + return false + } +} + +func FilterNamesFn(name []string) func(Names) bool { + return func(obj Names) bool { + for _, v := range name { + for _, n := range obj.Names() { + if strings.Contains(n, v) { + return true + } + } + } + return false + } +} + +func FilterIdOrNameFn(id []string) func(IdOrNamed) bool { + return func(obj IdOrNamed) bool { + for _, v := range id { + if strings.Contains(obj.Id(), v) || strings.Contains(obj.Name(), v) { + return true + } + } + return false + } +} diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go new file mode 100644 index 000000000..c84ed5351 --- /dev/null +++ b/pkg/domain/entities/images.go @@ -0,0 +1,151 @@ +package entities + +import ( + "net/url" + + "github.com/containers/image/v5/manifest" + docker "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/opencontainers/go-digest" + v1 "github.com/opencontainers/image-spec/specs-go/v1" +) + +type Image struct { + IdOrNamed + ID string `json:"Id"` + RepoTags []string `json:",omitempty"` + RepoDigests []string `json:",omitempty"` + Parent string `json:",omitempty"` + Comment string `json:",omitempty"` + Created string `json:",omitempty"` + Container string `json:",omitempty"` + ContainerConfig *container.Config `json:",omitempty"` + DockerVersion string `json:",omitempty"` + Author string `json:",omitempty"` + Config *container.Config `json:",omitempty"` + Architecture string `json:",omitempty"` + Variant string `json:",omitempty"` + Os string `json:",omitempty"` + OsVersion string `json:",omitempty"` + Size int64 `json:",omitempty"` + VirtualSize int64 `json:",omitempty"` + GraphDriver docker.GraphDriverData `json:",omitempty"` + RootFS docker.RootFS `json:",omitempty"` + Metadata docker.ImageMetadata `json:",omitempty"` + + // Podman extensions + Digest digest.Digest `json:",omitempty"` + PodmanVersion string `json:",omitempty"` + ManifestType string `json:",omitempty"` + User string `json:",omitempty"` + History []v1.History `json:",omitempty"` + NamesHistory []string `json:",omitempty"` + HealthCheck *manifest.Schema2HealthConfig `json:",omitempty"` +} + +func (i *Image) Id() string { + return i.ID +} + +type ImageSummary struct { + Identifier + ID string `json:"Id"` + ParentId string `json:",omitempty"` + RepoTags []string `json:",omitempty"` + Created int `json:",omitempty"` + Size int `json:",omitempty"` + SharedSize int `json:",omitempty"` + VirtualSize int `json:",omitempty"` + Labels string `json:",omitempty"` + Containers int `json:",omitempty"` + ReadOnly bool `json:",omitempty"` + Dangling bool `json:",omitempty"` + + // Podman extensions + Digest digest.Digest `json:",omitempty"` + ConfigDigest digest.Digest `json:",omitempty"` +} + +func (i *ImageSummary) Id() string { + return i.ID +} + +func (i *ImageSummary) IsReadOnly() bool { + return i.ReadOnly +} + +func (i *ImageSummary) IsDangling() bool { + return i.Dangling +} + +type ImageOptions struct { + All bool + Digests bool + Filter []string + Format string + Noheading bool + NoTrunc bool + Quiet bool + Sort string + History bool +} + +type ImageDeleteOptions struct { + Force bool +} + +// ImageDeleteResponse is the response for removing an image from storage and containers +// what was untagged vs actually removed +type ImageDeleteReport struct { + Untagged []string `json:"untagged"` + Deleted string `json:"deleted"` +} + +type ImageHistoryOptions struct{} + +type ImageHistoryLayer struct { + ID string `json:"Id"` + Created int64 `json:"Created,omitempty"` + CreatedBy string `json:",omitempty"` + Tags []string `json:",omitempty"` + Size int64 `json:",omitempty"` + Comment string `json:",omitempty"` +} + +type ImageHistoryReport struct { + Layers []ImageHistoryLayer +} + +type ImageInspectOptions struct { + TypeObject string `json:",omitempty"` + Format string `json:",omitempty"` + Size bool `json:",omitempty"` + Latest bool `json:",omitempty"` +} + +type ImageListOptions struct { + All bool `json:"all" schema:"all"` + Digests bool `json:"digests" schema:"digests"` + Filter []string `json:",omitempty"` + Filters url.Values `json:"filters" schema:"filters"` + Format string `json:",omitempty"` + History bool `json:",omitempty"` + Noheading bool `json:",omitempty"` + NoTrunc bool `json:",omitempty"` + Quiet bool `json:",omitempty"` + Sort string `json:",omitempty"` +} + +type ImageListReport struct { + Images []ImageSummary +} + +type ImagePruneOptions struct { + All bool + Filter ImageFilter +} + +type ImagePruneReport struct { + Report Report + Size int64 +} diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go new file mode 100644 index 000000000..6f947dc4d --- /dev/null +++ b/pkg/domain/entities/types.go @@ -0,0 +1,25 @@ +package entities + +type Container struct { + IdOrNamed +} + +type Volume struct { + Identifier +} + +type Report struct { + Id []string + Err map[string]error +} + +type ContainerDeleteOptions struct{} +type ContainerDeleteReport struct{ Report } +type ContainerPruneReport struct{ Report } + +type PodDeleteReport struct{ Report } +type PodPruneOptions struct{} +type PodPruneReport struct{ Report } +type VolumeDeleteOptions struct{} +type VolumeDeleteReport struct{ Report } +type VolumePruneReport struct{ Report } diff --git a/pkg/domain/entities/volumes.go b/pkg/domain/entities/volumes.go new file mode 100644 index 000000000..ad12d0d01 --- /dev/null +++ b/pkg/domain/entities/volumes.go @@ -0,0 +1,41 @@ +package entities + +import "time" + +// swagger:model VolumeCreate +type VolumeCreateOptions struct { + // New volume's name. Can be left blank + Name string `schema:"name"` + // Volume driver to use + Driver string `schema:"driver"` + // User-defined key/value metadata. + Label map[string]string `schema:"label"` + // Mapping of driver options and values. + Options map[string]string `schema:"opts"` +} + +type IdOrNameResponse struct { + // The Id or Name of an object + IdOrName string +} + +type VolumeConfigResponse struct { + // Name of the volume. + Name string `json:"name"` + Labels map[string]string `json:"labels"` + // The volume driver. Empty string or local does not activate a volume + // driver, all other volumes will. + Driver string `json:"volumeDriver"` + // The location the volume is mounted at. + MountPoint string `json:"mountPoint"` + // Time the volume was created. + CreatedTime time.Time `json:"createdAt,omitempty"` + // Options to pass to the volume driver. For the local driver, this is + // a list of mount options. For other drivers, they are passed to the + // volume driver handling the volume. + Options map[string]string `json:"volumeOptions,omitempty"` + // UID the volume will be created as. + UID int `json:"uid"` + // GID the volume will be created as. + GID int `json:"gid"` +} diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go new file mode 100644 index 000000000..cdcd77246 --- /dev/null +++ b/pkg/domain/infra/abi/containers.go @@ -0,0 +1,66 @@ +// +build ABISupport + +package abi + +import ( + "context" + + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/adapter/shortcuts" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" +) + +// TODO: Should return *entities.ContainerExistsReport, error +func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) { + _, err := ic.Libpod.LookupContainer(nameOrId) + if err != nil && errors.Cause(err) != define.ErrNoSuchCtr { + return nil, err + } + return &entities.BoolReport{Value: err == nil}, nil +} + +func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []string, options entities.WaitOptions) ([]entities.WaitReport, error) { + var ( + responses []entities.WaitReport + ) + ctrs, err := shortcuts.GetContainersByContext(false, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, c := range ctrs { + response := entities.WaitReport{Id: c.ID()} + exitCode, err := c.WaitForConditionWithInterval(options.Interval, options.Condition) + if err != nil { + response.Error = err + } else { + response.ExitCode = exitCode + } + responses = append(responses, response) + } + return responses, nil +} + +func (ic *ContainerEngine) ContainerDelete(ctx context.Context, opts entities.ContainerDeleteOptions) (*entities.ContainerDeleteReport, error) { + panic("implement me") +} + +func (ic *ContainerEngine) ContainerPrune(ctx context.Context) (*entities.ContainerPruneReport, error) { + panic("implement me") +} + +func (ic *ContainerEngine) PodDelete(ctx context.Context, opts entities.PodPruneOptions) (*entities.PodDeleteReport, error) { + panic("implement me") +} + +func (ic *ContainerEngine) PodPrune(ctx context.Context) (*entities.PodPruneReport, error) { + panic("implement me") +} + +func (ic *ContainerEngine) VolumeDelete(ctx context.Context, opts entities.VolumeDeleteOptions) (*entities.VolumeDeleteReport, error) { + panic("implement me") +} + +func (ic *ContainerEngine) VolumePrune(ctx context.Context) (*entities.VolumePruneReport, error) { + panic("implement me") +} diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go new file mode 100644 index 000000000..2db08f259 --- /dev/null +++ b/pkg/domain/infra/abi/images.go @@ -0,0 +1,131 @@ +// +build ABISupport + +package abi + +import ( + "context" + + libpodImage "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/domain/utils" +) + +func (ir *ImageEngine) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) { + image, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId) + if err != nil { + return nil, err + } + + results, err := ir.Libpod.RemoveImage(ctx, image, opts.Force) + if err != nil { + return nil, err + } + + report := entities.ImageDeleteReport{} + if err := utils.DeepCopy(&report, results); err != nil { + return nil, err + } + return &report, nil +} + +func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) { + results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, []string{}) + if err != nil { + return nil, err + } + + report := entities.ImagePruneReport{} + copy(report.Report.Id, results) + return &report, nil +} + +func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) (*entities.ImageListReport, error) { + var ( + images []*libpodImage.Image + err error + ) + + filters := utils.ToLibpodFilters(opts.Filters) + if len(filters) > 0 { + images, err = ir.Libpod.ImageRuntime().GetImagesWithFilters(filters) + } else { + images, err = ir.Libpod.ImageRuntime().GetImages() + } + if err != nil { + return nil, err + } + + report := entities.ImageListReport{ + Images: make([]entities.ImageSummary, len(images)), + } + for i, img := range images { + hold := entities.ImageSummary{} + if err := utils.DeepCopy(&hold, img); err != nil { + return nil, err + } + report.Images[i] = hold + } + return &report, nil +} + +func (ir *ImageEngine) History(ctx context.Context, nameOrId string, opts entities.ImageHistoryOptions) (*entities.ImageHistoryReport, error) { + image, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId) + if err != nil { + return nil, err + } + results, err := image.History(ctx) + if err != nil { + return nil, err + } + + history := entities.ImageHistoryReport{ + Layers: make([]entities.ImageHistoryLayer, len(results)), + } + + for i, layer := range results { + history.Layers[i] = ToDomainHistoryLayer(layer) + } + return &history, nil +} + +func ToDomainHistoryLayer(layer *libpodImage.History) entities.ImageHistoryLayer { + l := entities.ImageHistoryLayer{} + l.ID = layer.ID + l.Created = layer.Created.Unix() + l.CreatedBy = layer.CreatedBy + copy(l.Tags, layer.Tags) + l.Size = layer.Size + l.Comment = layer.Comment + return l +} + +// func (r *imageRuntime) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) { +// image, err := r.libpod.ImageEngine().NewFromLocal(nameOrId) +// if err != nil { +// return nil, err +// } +// +// results, err := r.libpod.RemoveImage(ctx, image, opts.Force) +// if err != nil { +// return nil, err +// } +// +// report := entities.ImageDeleteReport{} +// if err := utils.DeepCopy(&report, results); err != nil { +// return nil, err +// } +// return &report, nil +// } +// +// func (r *imageRuntime) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) { +// // TODO: map FilterOptions +// id, err := r.libpod.ImageEngine().PruneImages(ctx, opts.All, []string{}) +// if err != nil { +// return nil, err +// } +// +// // TODO: Determine Size +// report := entities.ImagePruneReport{} +// copy(report.Report.Id, id) +// return &report, nil +// } diff --git a/pkg/domain/infra/abi/images_test.go b/pkg/domain/infra/abi/images_test.go new file mode 100644 index 000000000..20ef1b150 --- /dev/null +++ b/pkg/domain/infra/abi/images_test.go @@ -0,0 +1,37 @@ +package abi + +// +// import ( +// "context" +// "testing" +// +// "github.com/stretchr/testify/mock" +// ) +// +// type MockImageRuntime struct { +// mock.Mock +// } +// +// func (m *MockImageRuntime) Delete(ctx context.Context, renderer func() interface{}, name string) error { +// _ = m.Called(ctx, renderer, name) +// return nil +// } +// +// func TestImageSuccess(t *testing.T) { +// actual := func() interface{} { return nil } +// +// m := new(MockImageRuntime) +// m.On( +// "Delete", +// mock.AnythingOfType("*context.emptyCtx"), +// mock.AnythingOfType("func() interface {}"), +// "fedora"). +// Return(nil) +// +// r := DirectImageRuntime{m} +// err := r.Delete(context.TODO(), actual, "fedora") +// if err != nil { +// t.Errorf("error should be nil, got: %v", err) +// } +// m.AssertExpectations(t) +// } diff --git a/pkg/domain/infra/abi/parse/parse.go b/pkg/domain/infra/abi/parse/parse.go new file mode 100644 index 000000000..6c0e1ee55 --- /dev/null +++ b/pkg/domain/infra/abi/parse/parse.go @@ -0,0 +1,68 @@ +package parse + +import ( + "strconv" + "strings" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// Handle volume options from CLI. +// Parse "o" option to find UID, GID. +func ParseVolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error) { + libpodOptions := []libpod.VolumeCreateOption{} + volumeOptions := make(map[string]string) + + for key, value := range opts { + switch key { + case "o": + // o has special handling to parse out UID, GID. + // These are separate Libpod options. + splitVal := strings.Split(value, ",") + finalVal := []string{} + for _, o := range splitVal { + // Options will be formatted as either "opt" or + // "opt=value" + splitO := strings.SplitN(o, "=", 2) + switch strings.ToLower(splitO[0]) { + case "uid": + if len(splitO) != 2 { + return nil, errors.Wrapf(define.ErrInvalidArg, "uid option must provide a UID") + } + intUID, err := strconv.Atoi(splitO[1]) + if err != nil { + return nil, errors.Wrapf(err, "cannot convert UID %s to integer", splitO[1]) + } + logrus.Debugf("Removing uid= from options and adding WithVolumeUID for UID %d", intUID) + libpodOptions = append(libpodOptions, libpod.WithVolumeUID(intUID)) + case "gid": + if len(splitO) != 2 { + return nil, errors.Wrapf(define.ErrInvalidArg, "gid option must provide a GID") + } + intGID, err := strconv.Atoi(splitO[1]) + if err != nil { + return nil, errors.Wrapf(err, "cannot convert GID %s to integer", splitO[1]) + } + logrus.Debugf("Removing gid= from options and adding WithVolumeGID for GID %d", intGID) + libpodOptions = append(libpodOptions, libpod.WithVolumeGID(intGID)) + default: + finalVal = append(finalVal, o) + } + } + if len(finalVal) > 0 { + volumeOptions[key] = strings.Join(finalVal, ",") + } + default: + volumeOptions[key] = value + } + } + + if len(volumeOptions) > 0 { + libpodOptions = append(libpodOptions, libpod.WithVolumeOptions(volumeOptions)) + } + + return libpodOptions, nil +} diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go new file mode 100644 index 000000000..de22de68e --- /dev/null +++ b/pkg/domain/infra/abi/pods.go @@ -0,0 +1,19 @@ +// +build ABISupport + +package abi + +import ( + "context" + "github.com/pkg/errors" + + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/domain/entities" +) + +func (ic *ContainerEngine) PodExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) { + _, err := ic.Libpod.LookupPod(nameOrId) + if err != nil && errors.Cause(err) != define.ErrNoSuchPod { + return nil, err + } + return &entities.BoolReport{Value: err == nil}, nil +} diff --git a/pkg/domain/infra/abi/runtime.go b/pkg/domain/infra/abi/runtime.go new file mode 100644 index 000000000..b53fb6d3a --- /dev/null +++ b/pkg/domain/infra/abi/runtime.go @@ -0,0 +1,17 @@ +// +build ABISupport + +package abi + +import ( + "github.com/containers/libpod/libpod" +) + +// Image-related runtime linked against libpod library +type ImageEngine struct { + Libpod *libpod.Runtime +} + +// Container-related runtime linked against libpod library +type ContainerEngine struct { + Libpod *libpod.Runtime +} diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go new file mode 100644 index 000000000..0783af441 --- /dev/null +++ b/pkg/domain/infra/abi/volumes.go @@ -0,0 +1,38 @@ +// +build ABISupport + +package abi + +import ( + "context" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/domain/infra/abi/parse" +) + +func (ic *ContainerEngine) VolumeCreate(ctx context.Context, opts entities.VolumeCreateOptions) (*entities.IdOrNameResponse, error) { + var ( + volumeOptions []libpod.VolumeCreateOption + ) + if len(opts.Name) > 0 { + volumeOptions = append(volumeOptions, libpod.WithVolumeName(opts.Name)) + } + if len(opts.Driver) > 0 { + volumeOptions = append(volumeOptions, libpod.WithVolumeDriver(opts.Driver)) + } + if len(opts.Label) > 0 { + volumeOptions = append(volumeOptions, libpod.WithVolumeLabels(opts.Label)) + } + if len(opts.Options) > 0 { + parsedOptions, err := parse.ParseVolumeOptions(opts.Options) + if err != nil { + return nil, err + } + volumeOptions = append(volumeOptions, parsedOptions...) + } + vol, err := ic.Libpod.NewVolume(ctx, volumeOptions...) + if err != nil { + return nil, err + } + return &entities.IdOrNameResponse{IdOrName: vol.Name()}, nil +} diff --git a/pkg/domain/infra/runtime_abi.go b/pkg/domain/infra/runtime_abi.go new file mode 100644 index 000000000..31f832423 --- /dev/null +++ b/pkg/domain/infra/runtime_abi.go @@ -0,0 +1,38 @@ +// +build ABISupport + +package infra + +import ( + "context" + "fmt" + + "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/domain/infra/tunnel" +) + +// NewContainerEngine factory provides a libpod runtime for container-related operations +func NewContainerEngine(opts entities.EngineOptions) (entities.ContainerEngine, error) { + switch opts.EngineMode { + case entities.ABIMode: + r, err := NewLibpodRuntime(opts.FlagSet, opts.Flags) + return r, err + case entities.TunnelMode: + ctx, err := bindings.NewConnection(context.Background(), opts.Uri, opts.Identities...) + return &tunnel.ContainerEngine{ClientCxt: ctx}, err + } + return nil, fmt.Errorf("runtime mode '%v' is not supported", opts.EngineMode) +} + +// NewContainerEngine factory provides a libpod runtime for image-related operations +func NewImageEngine(opts entities.EngineOptions) (entities.ImageEngine, error) { + switch opts.EngineMode { + case entities.ABIMode: + r, err := NewLibpodImageRuntime(opts.FlagSet, opts.Flags) + return r, err + case entities.TunnelMode: + ctx, err := bindings.NewConnection(context.Background(), opts.Uri, opts.Identities...) + return &tunnel.ImageEngine{ClientCxt: ctx}, err + } + return nil, fmt.Errorf("runtime mode '%v' is not supported", opts.EngineMode) +} diff --git a/pkg/domain/infra/runtime_image_proxy.go b/pkg/domain/infra/runtime_image_proxy.go new file mode 100644 index 000000000..d2e66c08c --- /dev/null +++ b/pkg/domain/infra/runtime_image_proxy.go @@ -0,0 +1,21 @@ +// +build ABISupport + +package infra + +import ( + "context" + + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/domain/infra/abi" + "github.com/spf13/pflag" +) + +// ContainerEngine Image Proxy will be EOL'ed after podmanV2 is separated from libpod repo + +func NewLibpodImageRuntime(flags *pflag.FlagSet, opts entities.EngineFlags) (entities.ImageEngine, error) { + r, err := GetRuntime(context.Background(), flags, opts) + if err != nil { + return nil, err + } + return &abi.ImageEngine{Libpod: r}, nil +} diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go new file mode 100644 index 000000000..b835152bf --- /dev/null +++ b/pkg/domain/infra/runtime_libpod.go @@ -0,0 +1,331 @@ +package infra + +import ( + "context" + "fmt" + "os" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/cgroups" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/namespaces" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/storage" + "github.com/containers/storage/pkg/idtools" + "github.com/pkg/errors" + flag "github.com/spf13/pflag" +) + +type engineOpts struct { + name string + renumber bool + migrate bool + noStore bool + withFDS bool + flags entities.EngineFlags +} + +// GetRuntimeMigrate gets a libpod runtime that will perform a migration of existing containers +func GetRuntimeMigrate(ctx context.Context, fs *flag.FlagSet, ef entities.EngineFlags, newRuntime string) (*libpod.Runtime, error) { + return getRuntime(ctx, fs, &engineOpts{ + name: newRuntime, + renumber: false, + migrate: true, + noStore: false, + withFDS: true, + flags: ef, + }) +} + +// GetRuntimeDisableFDs gets a libpod runtime that will disable sd notify +func GetRuntimeDisableFDs(ctx context.Context, fs *flag.FlagSet, ef entities.EngineFlags) (*libpod.Runtime, error) { + return getRuntime(ctx, fs, &engineOpts{ + renumber: false, + migrate: false, + noStore: false, + withFDS: false, + flags: ef, + }) +} + +// GetRuntimeRenumber gets a libpod runtime that will perform a lock renumber +func GetRuntimeRenumber(ctx context.Context, fs *flag.FlagSet, ef entities.EngineFlags) (*libpod.Runtime, error) { + return getRuntime(ctx, fs, &engineOpts{ + renumber: true, + migrate: false, + noStore: false, + withFDS: true, + flags: ef, + }) +} + +// GetRuntime generates a new libpod runtime configured by command line options +func GetRuntime(ctx context.Context, flags *flag.FlagSet, ef entities.EngineFlags) (*libpod.Runtime, error) { + return getRuntime(ctx, flags, &engineOpts{ + renumber: false, + migrate: false, + noStore: false, + withFDS: true, + flags: ef, + }) +} + +// GetRuntimeNoStore generates a new libpod runtime configured by command line options +func GetRuntimeNoStore(ctx context.Context, fs *flag.FlagSet, ef entities.EngineFlags) (*libpod.Runtime, error) { + return getRuntime(ctx, fs, &engineOpts{ + renumber: false, + migrate: false, + noStore: true, + withFDS: true, + flags: ef, + }) +} + +func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpod.Runtime, error) { + options := []libpod.RuntimeOption{} + storageOpts := storage.StoreOptions{} + storageSet := false + + uidmapFlag := fs.Lookup("uidmap") + gidmapFlag := fs.Lookup("gidmap") + subuidname := fs.Lookup("subuidname") + subgidname := fs.Lookup("subgidname") + if (uidmapFlag != nil && gidmapFlag != nil && subuidname != nil && subgidname != nil) && + (uidmapFlag.Changed || gidmapFlag.Changed || subuidname.Changed || subgidname.Changed) { + userns, _ := fs.GetString("userns") + uidmapVal, _ := fs.GetStringSlice("uidmap") + gidmapVal, _ := fs.GetStringSlice("gidmap") + subuidVal, _ := fs.GetString("subuidname") + subgidVal, _ := fs.GetString("subgidname") + mappings, err := ParseIDMapping(namespaces.UsernsMode(userns), uidmapVal, gidmapVal, subuidVal, subgidVal) + if err != nil { + return nil, err + } + storageOpts.UIDMap = mappings.UIDMap + storageOpts.GIDMap = mappings.GIDMap + + storageSet = true + } + + if fs.Changed("root") { + storageSet = true + storageOpts.GraphRoot = opts.flags.Root + } + if fs.Changed("runroot") { + storageSet = true + storageOpts.RunRoot = opts.flags.Runroot + } + if len(storageOpts.RunRoot) > 50 { + return nil, errors.New("the specified runroot is longer than 50 characters") + } + if fs.Changed("storage-driver") { + storageSet = true + storageOpts.GraphDriverName = opts.flags.StorageDriver + // Overriding the default storage driver caused GraphDriverOptions from storage.conf to be ignored + storageOpts.GraphDriverOptions = []string{} + } + // This should always be checked after storage-driver is checked + if len(opts.flags.StorageOpts) > 0 { + storageSet = true + storageOpts.GraphDriverOptions = opts.flags.StorageOpts + } + if opts.migrate { + options = append(options, libpod.WithMigrate()) + if opts.name != "" { + options = append(options, libpod.WithMigrateRuntime(opts.name)) + } + } + + if opts.renumber { + options = append(options, libpod.WithRenumber()) + } + + // Only set this if the user changes storage config on the command line + if storageSet { + options = append(options, libpod.WithStorageConfig(storageOpts)) + } + + if !storageSet && opts.noStore { + options = append(options, libpod.WithNoStore()) + } + // TODO CLI flags for image config? + // TODO CLI flag for signature policy? + + if len(opts.flags.Namespace) > 0 { + options = append(options, libpod.WithNamespace(opts.flags.Namespace)) + } + + if fs.Changed("runtime") { + options = append(options, libpod.WithOCIRuntime(opts.flags.Runtime)) + } + + if fs.Changed("conmon") { + options = append(options, libpod.WithConmonPath(opts.flags.ConmonPath)) + } + if fs.Changed("tmpdir") { + options = append(options, libpod.WithTmpDir(opts.flags.TmpDir)) + } + if fs.Changed("network-cmd-path") { + options = append(options, libpod.WithNetworkCmdPath(opts.flags.NetworkCmdPath)) + } + + if fs.Changed("events-backend") { + options = append(options, libpod.WithEventsLogger(opts.flags.EventsBackend)) + } + + if fs.Changed("cgroup-manager") { + options = append(options, libpod.WithCgroupManager(opts.flags.CGroupManager)) + } else { + unified, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return nil, err + } + if rootless.IsRootless() && !unified { + options = append(options, libpod.WithCgroupManager("cgroupfs")) + } + } + + // TODO flag to set libpod static dir? + // TODO flag to set libpod tmp dir? + + if fs.Changed("cni-config-dir") { + options = append(options, libpod.WithCNIConfigDir(opts.flags.CniConfigDir)) + } + if fs.Changed("default-mounts-file") { + options = append(options, libpod.WithDefaultMountsFile(opts.flags.DefaultMountsFile)) + } + if fs.Changed("hooks-dir") { + options = append(options, libpod.WithHooksDir(opts.flags.HooksDir...)) + } + + // TODO flag to set CNI plugins dir? + + // TODO I don't think these belong here? + // Will follow up with a different PR to address + // + // Pod create options + + infraImageFlag := fs.Lookup("infra-image") + if infraImageFlag != nil && infraImageFlag.Changed { + infraImage, _ := fs.GetString("infra-image") + options = append(options, libpod.WithDefaultInfraImage(infraImage)) + } + + infraCommandFlag := fs.Lookup("infra-command") + if infraCommandFlag != nil && infraImageFlag.Changed { + infraCommand, _ := fs.GetString("infra-command") + options = append(options, libpod.WithDefaultInfraCommand(infraCommand)) + } + + if !opts.withFDS { + options = append(options, libpod.WithEnableSDNotify()) + } + if fs.Changed("config") { + return libpod.NewRuntimeFromConfig(ctx, opts.flags.Config, options...) + } + return libpod.NewRuntime(ctx, options...) +} + +// ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping +func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []string, subUIDMap, subGIDMap string) (*storage.IDMappingOptions, error) { + options := storage.IDMappingOptions{ + HostUIDMapping: true, + HostGIDMapping: true, + } + + if mode.IsKeepID() { + if len(uidMapSlice) > 0 || len(gidMapSlice) > 0 { + return nil, errors.New("cannot specify custom mappings with --userns=keep-id") + } + if len(subUIDMap) > 0 || len(subGIDMap) > 0 { + return nil, errors.New("cannot specify subuidmap or subgidmap with --userns=keep-id") + } + if rootless.IsRootless() { + min := func(a, b int) int { + if a < b { + return a + } + return b + } + + uid := rootless.GetRootlessUID() + gid := rootless.GetRootlessGID() + + uids, gids, err := rootless.GetConfiguredMappings() + if err != nil { + return nil, errors.Wrapf(err, "cannot read mappings") + } + maxUID, maxGID := 0, 0 + for _, u := range uids { + maxUID += u.Size + } + for _, g := range gids { + maxGID += g.Size + } + + options.UIDMap, options.GIDMap = nil, nil + + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)}) + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1}) + if maxUID > uid { + options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid}) + } + + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)}) + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1}) + if maxGID > gid { + options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid}) + } + + options.HostUIDMapping = false + options.HostGIDMapping = false + } + // Simply ignore the setting and do not setup an inner namespace for root as it is a no-op + return &options, nil + } + + if subGIDMap == "" && subUIDMap != "" { + subGIDMap = subUIDMap + } + if subUIDMap == "" && subGIDMap != "" { + subUIDMap = subGIDMap + } + if len(gidMapSlice) == 0 && len(uidMapSlice) != 0 { + gidMapSlice = uidMapSlice + } + if len(uidMapSlice) == 0 && len(gidMapSlice) != 0 { + uidMapSlice = gidMapSlice + } + if len(uidMapSlice) == 0 && subUIDMap == "" && os.Getuid() != 0 { + uidMapSlice = []string{fmt.Sprintf("0:%d:1", os.Getuid())} + } + if len(gidMapSlice) == 0 && subGIDMap == "" && os.Getuid() != 0 { + gidMapSlice = []string{fmt.Sprintf("0:%d:1", os.Getgid())} + } + + if subUIDMap != "" && subGIDMap != "" { + mappings, err := idtools.NewIDMappings(subUIDMap, subGIDMap) + if err != nil { + return nil, err + } + options.UIDMap = mappings.UIDs() + options.GIDMap = mappings.GIDs() + } + parsedUIDMap, err := idtools.ParseIDMap(uidMapSlice, "UID") + if err != nil { + return nil, err + } + parsedGIDMap, err := idtools.ParseIDMap(gidMapSlice, "GID") + if err != nil { + return nil, err + } + options.UIDMap = append(options.UIDMap, parsedUIDMap...) + options.GIDMap = append(options.GIDMap, parsedGIDMap...) + if len(options.UIDMap) > 0 { + options.HostUIDMapping = false + } + if len(options.GIDMap) > 0 { + options.HostGIDMapping = false + } + return &options, nil +} diff --git a/pkg/domain/infra/runtime_proxy.go b/pkg/domain/infra/runtime_proxy.go new file mode 100644 index 000000000..4095ae6e2 --- /dev/null +++ b/pkg/domain/infra/runtime_proxy.go @@ -0,0 +1,21 @@ +// +build ABISupport + +package infra + +import ( + "context" + + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/domain/infra/abi" + flag "github.com/spf13/pflag" +) + +// ContainerEngine Proxy will be EOL'ed after podmanV2 is separated from libpod repo + +func NewLibpodRuntime(flags *flag.FlagSet, opts entities.EngineFlags) (entities.ContainerEngine, error) { + r, err := GetRuntime(context.Background(), flags, opts) + if err != nil { + return nil, err + } + return &abi.ContainerEngine{Libpod: r}, nil +} diff --git a/pkg/domain/infra/runtime_tunnel.go b/pkg/domain/infra/runtime_tunnel.go new file mode 100644 index 000000000..5816ef0c0 --- /dev/null +++ b/pkg/domain/infra/runtime_tunnel.go @@ -0,0 +1,35 @@ +// +build !ABISupport + +package infra + +import ( + "context" + "fmt" + + "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/domain/infra/tunnel" +) + +func NewContainerEngine(opts entities.EngineOptions) (entities.ContainerEngine, error) { + switch opts.EngineMode { + case entities.ABIMode: + return nil, fmt.Errorf("direct runtime not supported") + case entities.TunnelMode: + ctx, err := bindings.NewConnection(context.Background(), opts.Uri, opts.Identities...) + return &tunnel.ContainerEngine{ClientCxt: ctx}, err + } + return nil, fmt.Errorf("runtime mode '%v' is not supported", opts.EngineMode) +} + +// NewImageEngine factory provides a libpod runtime for image-related operations +func NewImageEngine(opts entities.EngineOptions) (entities.ImageEngine, error) { + switch opts.EngineMode { + case entities.ABIMode: + return nil, fmt.Errorf("direct image runtime not supported") + case entities.TunnelMode: + ctx, err := bindings.NewConnection(context.Background(), opts.Uri, opts.Identities...) + return &tunnel.ImageEngine{ClientCxt: ctx}, err + } + return nil, fmt.Errorf("runtime mode '%v' is not supported", opts.EngineMode) +} diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go new file mode 100644 index 000000000..8bf74126d --- /dev/null +++ b/pkg/domain/infra/tunnel/containers.go @@ -0,0 +1,42 @@ +package tunnel + +import ( + "context" + + "github.com/containers/libpod/pkg/bindings/containers" + "github.com/containers/libpod/pkg/domain/entities" +) + +func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) { + exists, err := containers.Exists(ic.ClientCxt, nameOrId) + return &entities.BoolReport{Value: exists}, err +} + +func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []string, options entities.WaitOptions) ([]entities.WaitReport, error) { + var ( + responses []entities.WaitReport + ) + cons, err := getContainersByContext(ic.ClientCxt, false, namesOrIds) + if err != nil { + return nil, err + } + for _, c := range cons { + response := entities.WaitReport{Id: c.ID} + exitCode, err := containers.Wait(ic.ClientCxt, c.ID, &options.Condition) + if err != nil { + response.Error = err + } else { + response.ExitCode = exitCode + } + responses = append(responses, response) + } + return responses, nil +} + +func (r *ContainerEngine) ContainerDelete(ctx context.Context, opts entities.ContainerDeleteOptions) (*entities.ContainerDeleteReport, error) { + panic("implement me") +} + +func (r *ContainerEngine) ContainerPrune(ctx context.Context) (*entities.ContainerPruneReport, error) { + panic("implement me") +} diff --git a/pkg/domain/infra/tunnel/helpers.go b/pkg/domain/infra/tunnel/helpers.go new file mode 100644 index 000000000..d5a3224c2 --- /dev/null +++ b/pkg/domain/infra/tunnel/helpers.go @@ -0,0 +1,41 @@ +package tunnel + +import ( + "context" + + "github.com/containers/libpod/pkg/api/handlers/libpod" + "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/bindings/containers" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" +) + +func getContainersByContext(contextWithConnection context.Context, all bool, namesOrIds []string) ([]libpod.ListContainer, error) { + var ( + cons []libpod.ListContainer + ) + if all && len(namesOrIds) > 0 { + return nil, errors.New("cannot lookup containers and all") + } + c, err := containers.List(contextWithConnection, nil, &bindings.PTrue, nil, nil, nil, &bindings.PTrue) + if err != nil { + return nil, err + } + if all { + return c, err + } + for _, id := range namesOrIds { + var found bool + for _, con := range c { + if id == con.ID || util.StringInSlice(id, con.Names) { + cons = append(cons, con) + found = true + break + } + } + if !found { + return nil, errors.Errorf("unable to find container %q", id) + } + } + return cons, nil +} diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go new file mode 100644 index 000000000..718685e57 --- /dev/null +++ b/pkg/domain/infra/tunnel/images.go @@ -0,0 +1,81 @@ +package tunnel + +import ( + "context" + "net/url" + + images "github.com/containers/libpod/pkg/bindings/images" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/domain/utils" +) + +func (ir *ImageEngine) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) { + results, err := images.Remove(ir.ClientCxt, nameOrId, &opts.Force) + if err != nil { + return nil, err + } + + report := entities.ImageDeleteReport{ + Untagged: nil, + Deleted: "", + } + + for _, e := range results { + if a, ok := e["Deleted"]; ok { + report.Deleted = a + } + + if a, ok := e["Untagged"]; ok { + report.Untagged = append(report.Untagged, a) + } + } + return &report, err +} + +func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) (*entities.ImageListReport, error) { + images, err := images.List(ir.ClientCxt, &opts.All, opts.Filters) + if err != nil { + return nil, err + } + + report := entities.ImageListReport{ + Images: make([]entities.ImageSummary, len(images)), + } + for i, img := range images { + hold := entities.ImageSummary{} + if err := utils.DeepCopy(&hold, img); err != nil { + return nil, err + } + report.Images[i] = hold + } + return &report, nil +} + +func (ir *ImageEngine) History(ctx context.Context, nameOrId string, opts entities.ImageHistoryOptions) (*entities.ImageHistoryReport, error) { + results, err := images.History(ir.ClientCxt, nameOrId) + if err != nil { + return nil, err + } + + history := entities.ImageHistoryReport{ + Layers: make([]entities.ImageHistoryLayer, len(results)), + } + + for i, layer := range results { + hold := entities.ImageHistoryLayer{} + _ = utils.DeepCopy(&hold, layer) + history.Layers[i] = hold + } + return &history, nil +} + +func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) { + results, err := images.Prune(ir.ClientCxt, url.Values{}) + if err != nil { + return nil, err + } + + report := entities.ImagePruneReport{} + copy(report.Report.Id, results) + return &report, nil +} diff --git a/pkg/domain/infra/tunnel/pods.go b/pkg/domain/infra/tunnel/pods.go new file mode 100644 index 000000000..500069d51 --- /dev/null +++ b/pkg/domain/infra/tunnel/pods.go @@ -0,0 +1,13 @@ +package tunnel + +import ( + "context" + + "github.com/containers/libpod/pkg/bindings/pods" + "github.com/containers/libpod/pkg/domain/entities" +) + +func (ic *ContainerEngine) PodExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) { + exists, err := pods.Exists(ic.ClientCxt, nameOrId) + return &entities.BoolReport{Value: exists}, err +} diff --git a/pkg/domain/infra/tunnel/runtime.go b/pkg/domain/infra/tunnel/runtime.go new file mode 100644 index 000000000..eb9b34e4a --- /dev/null +++ b/pkg/domain/infra/tunnel/runtime.go @@ -0,0 +1,33 @@ +package tunnel + +import ( + "context" + + "github.com/containers/libpod/pkg/domain/entities" +) + +// Image-related runtime using an ssh-tunnel to utilize Podman service +type ImageEngine struct { + ClientCxt context.Context +} + +// Container-related runtime using an ssh-tunnel to utilize Podman service +type ContainerEngine struct { + ClientCxt context.Context +} + +func (r *ContainerEngine) PodDelete(ctx context.Context, opts entities.PodPruneOptions) (*entities.PodDeleteReport, error) { + panic("implement me") +} + +func (r *ContainerEngine) PodPrune(ctx context.Context) (*entities.PodPruneReport, error) { + panic("implement me") +} + +func (r *ContainerEngine) VolumeDelete(ctx context.Context, opts entities.VolumeDeleteOptions) (*entities.VolumeDeleteReport, error) { + panic("implement me") +} + +func (r *ContainerEngine) VolumePrune(ctx context.Context) (*entities.VolumePruneReport, error) { + panic("implement me") +} diff --git a/pkg/domain/infra/tunnel/volumes.go b/pkg/domain/infra/tunnel/volumes.go new file mode 100644 index 000000000..49cf6a2f6 --- /dev/null +++ b/pkg/domain/infra/tunnel/volumes.go @@ -0,0 +1,16 @@ +package tunnel + +import ( + "context" + + "github.com/containers/libpod/pkg/bindings/volumes" + "github.com/containers/libpod/pkg/domain/entities" +) + +func (ic *ContainerEngine) VolumeCreate(ctx context.Context, opts entities.VolumeCreateOptions) (*entities.IdOrNameResponse, error) { + response, err := volumes.Create(ic.ClientCxt, opts) + if err != nil { + return nil, err + } + return &entities.IdOrNameResponse{IdOrName: response.Name}, nil +} diff --git a/pkg/domain/utils/utils.go b/pkg/domain/utils/utils.go new file mode 100644 index 000000000..c17769f62 --- /dev/null +++ b/pkg/domain/utils/utils.go @@ -0,0 +1,41 @@ +package utils + +import ( + "net/url" + "strings" + + jsoniter "github.com/json-iterator/go" +) + +var json = jsoniter.ConfigCompatibleWithStandardLibrary + +// DeepCopy does a deep copy of a structure +// Error checking of parameters delegated to json engine +var DeepCopy = func(dst interface{}, src interface{}) error { + payload, err := json.Marshal(src) + if err != nil { + return err + } + + err = json.Unmarshal(payload, dst) + if err != nil { + return err + } + return nil +} + +func ToLibpodFilters(f url.Values) (filters []string) { + for k, v := range f { + filters = append(filters, k+"="+v[0]) + } + return +} + +func ToUrlValues(f []string) (filters url.Values) { + filters = make(url.Values) + for _, v := range f { + t := strings.SplitN(v, "=", 2) + filters.Add(t[0], t[1]) + } + return +} diff --git a/pkg/specgen/config_unsupported.go b/pkg/specgen/config_unsupported.go index 5d24ac39c..c2d3257c9 100644 --- a/pkg/specgen/config_unsupported.go +++ b/pkg/specgen/config_unsupported.go @@ -3,10 +3,11 @@ package specgen import ( + "github.com/containers/libpod/libpod/image" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) -func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { +func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) { return nil, errors.New("function not supported on non-linux OS's") } diff --git a/pkg/specgen/pod.go b/pkg/specgen/pod.go new file mode 100644 index 000000000..1aada83c4 --- /dev/null +++ b/pkg/specgen/pod.go @@ -0,0 +1,140 @@ +package specgen + +import ( + "net" + + "github.com/cri-o/ocicni/pkg/ocicni" +) + +// PodBasicConfig contains basic configuration options for pods. +type PodBasicConfig struct { + // Name is the name of the pod. + // If not provided, a name will be generated when the pod is created. + // Optional. + Name string `json:"name,omitempty"` + // Hostname is the pod's hostname. If not set, the name of the pod will + // be used (if a name was not provided here, the name auto-generated for + // the pod will be used). This will be used by the infra container and + // all containers in the pod as long as the UTS namespace is shared. + // Optional. + Hostname string `json:"hostname,omitempty"` + // Labels are key-value pairs that are used to add metadata to pods. + // Optional. + Labels map[string]string `json:"labels,omitempty"` + // NoInfra tells the pod not to create an infra container. If this is + // done, many networking-related options will become unavailable. + // Conflicts with setting any options in PodNetworkConfig, and the + // InfraCommand and InfraImages in this struct. + // Optional. + NoInfra bool `json:"no_infra,omitempty"` + // InfraCommand sets the command that will be used to start the infra + // container. + // If not set, the default set in the Libpod configuration file will be + // used. + // Conflicts with NoInfra=true. + // Optional. + InfraCommand []string `json:"infra_command,omitempty"` + // InfraImage is the image that will be used for the infra container. + // If not set, the default set in the Libpod configuration file will be + // used. + // Conflicts with NoInfra=true. + // Optional. + InfraImage string `json:"infra_image,omitempty"` + // SharedNamespaces instructs the pod to share a set of namespaces. + // Shared namespaces will be joined (by default) by every container + // which joins the pod. + // If not set and NoInfra is false, the pod will set a default set of + // namespaces to share. + // Conflicts with NoInfra=true. + // Optional. + SharedNamespaces []string `json:"shared_namespaces,omitempty"` +} + +// PodNetworkConfig contains networking configuration for a pod. +type PodNetworkConfig struct { + // NetNS is the configuration to use for the infra container's network + // namespace. This network will, by default, be shared with all + // containers in the pod. + // Cannot be set to FromContainer and FromPod. + // Setting this to anything except "" conflicts with NoInfra=true. + // Defaults to Bridge as root and Slirp as rootless. + // Mandatory. + NetNS Namespace `json:"netns,omitempty"` + // StaticIP sets a static IP for the infra container. As the infra + // container's network is used for the entire pod by default, this will + // thus be a static IP for the whole pod. + // Only available if NetNS is set to Bridge (the default for root). + // As such, conflicts with NoInfra=true by proxy. + // Optional. + StaticIP *net.IP `json:"static_ip,omitempty"` + // StaticMAC sets a static MAC for the infra container. As the infra + // container's network is used for the entire pod by default, this will + // thus be a static MAC for the entire pod. + // Only available if NetNS is set to Bridge (the default for root). + // As such, conflicts with NoInfra=true by proxy. + // Optional. + StaticMAC *net.HardwareAddr `json:"static_mac,omitempty"` + // PortMappings is a set of ports to map into the infra container. + // As, by default, containers share their network with the infra + // container, this will forward the ports to the entire pod. + // Only available if NetNS is set to Bridge or Slirp. + // Optional. + PortMappings []ocicni.PortMapping `json:"portmappings,omitempty"` + // CNINetworks is a list of CNI networks that the infra container will + // join. As, by default, containers share their network with the infra + // container, these networks will effectively be joined by the + // entire pod. + // Only available when NetNS is set to Bridge, the default for root. + // Optional. + CNINetworks []string `json:"cni_networks,omitempty"` + // NoManageResolvConf indicates that /etc/resolv.conf should not be + // managed by the pod. Instead, each container will create and manage a + // separate resolv.conf as if they had not joined a pod. + // Conflicts with NoInfra=true and DNSServer, DNSSearch, DNSOption. + // Optional. + NoManageResolvConf bool `json:"no_manage_resolv_conf,omitempty"` + // DNSServer is a set of DNS servers that will be used in the infra + // container's resolv.conf, which will, by default, be shared with all + // containers in the pod. + // If not provided, the host's DNS servers will be used, unless the only + // server set is a localhost address. As the container cannot connect to + // the host's localhost, a default server will instead be set. + // Conflicts with NoInfra=true. + // Optional. + DNSServer []net.IP `json:"dns_server,omitempty"` + // DNSSearch is a set of DNS search domains that will be used in the + // infra container's resolv.conf, which will, by default, be shared with + // all containers in the pod. + // If not provided, DNS search domains from the host's resolv.conf will + // be used. + // Conflicts with NoInfra=true. + // Optional. + DNSSearch []string `json:"dns_search,omitempty"` + // DNSOption is a set of DNS options that will be used in the infra + // container's resolv.conf, which will, by default, be shared with all + // containers in the pod. + // Conflicts with NoInfra=true. + // Optional. + DNSOption []string `json:"dns_option,omitempty"` + // NoManageHosts indicates that /etc/hosts should not be managed by the + // pod. Instead, each container will create a separate /etc/hosts as + // they would if not in a pod. + // Conflicts with HostAdd. + NoManageHosts bool `json:"no_manage_hosts,omitempty"` + // HostAdd is a set of hosts that will be added to the infra container's + // /etc/hosts that will, by default, be shared with all containers in + // the pod. + // Conflicts with NoInfra=true and NoManageHosts. + // Optional. + HostAdd []string `json:"hostadd,omitempty"` +} + +// PodCgroupConfig contains configuration options about a pod's cgroups. +// This will be expanded in future updates to pods. +type PodCgroupConfig struct { + // CgroupParent is the parent for the CGroup that the pod will create. + // This pod cgroup will, in turn, be the default cgroup parent for all + // containers in the pod. + // Optional. + CgroupParent string `json:"cgroup_parent,omitempty"` +} diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 7a430652a..b123c1da5 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -53,7 +53,7 @@ type ContainerBasicConfig struct { Terminal bool `json:"terminal,omitempty"` // Stdin is whether the container will keep its STDIN open. Stdin bool `json:"stdin,omitempty"` - // Labels are key-valid labels that are used to add metadata to + // Labels are key-value pairs that are used to add metadata to // containers. // Optional. Labels map[string]string `json:"labels,omitempty"` diff --git a/pkg/util/utils_linux_test.go b/pkg/util/utils_linux_test.go new file mode 100644 index 000000000..38e6dbef9 --- /dev/null +++ b/pkg/util/utils_linux_test.go @@ -0,0 +1,29 @@ +package util + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetImageConfigStopSignal(t *testing.T) { + // Linux-only beause parsing signal names is not supported on non-Linux systems by + // pkg/signal. + stopSignalValidInt, err := GetImageConfig([]string{"STOPSIGNAL 9"}) + require.Nil(t, err) + assert.Equal(t, stopSignalValidInt.StopSignal, "9") + + stopSignalValidString, err := GetImageConfig([]string{"STOPSIGNAL SIGKILL"}) + require.Nil(t, err) + assert.Equal(t, stopSignalValidString.StopSignal, "9") + + _, err = GetImageConfig([]string{"STOPSIGNAL 0"}) + assert.NotNil(t, err) + + _, err = GetImageConfig([]string{"STOPSIGNAL garbage"}) + assert.NotNil(t, err) + + _, err = GetImageConfig([]string{"STOPSIGNAL "}) + assert.NotNil(t, err) +} diff --git a/pkg/util/utils_test.go b/pkg/util/utils_test.go index f4b03599d..0995d1e20 100644 --- a/pkg/util/utils_test.go +++ b/pkg/util/utils_test.go @@ -219,25 +219,6 @@ func TestGetImageConfigLabel(t *testing.T) { assert.NotNil(t, err) } -func TestGetImageConfigStopSignal(t *testing.T) { - stopSignalValidInt, err := GetImageConfig([]string{"STOPSIGNAL 9"}) - require.Nil(t, err) - assert.Equal(t, stopSignalValidInt.StopSignal, "9") - - stopSignalValidString, err := GetImageConfig([]string{"STOPSIGNAL SIGKILL"}) - require.Nil(t, err) - assert.Equal(t, stopSignalValidString.StopSignal, "9") - - _, err = GetImageConfig([]string{"STOPSIGNAL 0"}) - assert.NotNil(t, err) - - _, err = GetImageConfig([]string{"STOPSIGNAL garbage"}) - assert.NotNil(t, err) - - _, err = GetImageConfig([]string{"STOPSIGNAL "}) - assert.NotNil(t, err) -} - func TestGetImageConfigOnBuild(t *testing.T) { onBuildOne, err := GetImageConfig([]string{"ONBUILD ADD /testdir1"}) require.Nil(t, err) diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 94726bbbd..55427771c 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -846,11 +846,6 @@ func (i *LibpodAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.ExecO workDir = *opts.Workdir } - var detachKeys string - if opts.DetachKeys != nil { - detachKeys = *opts.DetachKeys - } - resizeChan := make(chan remotecommand.TerminalSize) reader, writer, _, pipeWriter, streams := setupStreams(call) @@ -870,8 +865,17 @@ func (i *LibpodAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.ExecO } }() + execConfig := new(libpod.ExecConfig) + execConfig.Command = opts.Cmd + execConfig.Terminal = opts.Tty + execConfig.Privileged = opts.Privileged + execConfig.Environment = envs + execConfig.User = user + execConfig.WorkDir = workDir + execConfig.DetachKeys = opts.DetachKeys + go func() { - ec, err := ctr.Exec(opts.Tty, opts.Privileged, envs, opts.Cmd, user, workDir, streams, 0, resizeChan, detachKeys) + ec, err := ctr.Exec(execConfig, streams, resizeChan) if err != nil { logrus.Errorf(err.Error()) } |