diff options
Diffstat (limited to 'pkg')
46 files changed, 1875 insertions, 155 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/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/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/swagger.go b/pkg/api/handlers/libpod/swagger.go index f6a26134b..149fa10dc 100644 --- a/pkg/api/handlers/libpod/swagger.go +++ b/pkg/api/handlers/libpod/swagger.go @@ -1,6 +1,16 @@ package libpod -import "github.com/containers/image/v5/manifest" +import ( + "net/http" + "os" + + "github.com/containers/image/v5/manifest" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/pkg/errors" +) + +// DefaultPodmanSwaggerSpec provides the default path to the podman swagger spec file +const DefaultPodmanSwaggerSpec = "/usr/share/containers/podman/swagger.yaml" // List Containers // swagger:response ListContainers @@ -15,3 +25,20 @@ type swagInspectManifestResponse struct { // in:body Body manifest.List } + +func ServeSwagger(w http.ResponseWriter, r *http.Request) { + path := DefaultPodmanSwaggerSpec + if p, found := os.LookupEnv("PODMAN_SWAGGER_SPEC"); found { + path = p + } + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + utils.InternalServerError(w, errors.Errorf("file %q does not exist", path)) + return + } + utils.InternalServerError(w, err) + return + } + w.Header().Set("Content-Type", "text/yaml") + http.ServeFile(w, r, path) +} 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/register_manifest.go b/pkg/api/server/register_manifest.go index ccfc2192d..8fd84f205 100644 --- a/pkg/api/server/register_manifest.go +++ b/pkg/api/server/register_manifest.go @@ -38,7 +38,7 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/manifests/create"), s.APIHandler(libpod.ManifestCreate)).Methods(http.MethodPost) - // swagger:operation GET /libpod/manifests/{name}/json manifests Inspect + // swagger:operation GET /libpod/manifests/{name:.*}/json manifests Inspect // --- // summary: Inspect // description: Display a manifest list @@ -46,7 +46,7 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // - application/json // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: the name or ID of the manifest @@ -58,14 +58,14 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/manifests/{name:.*}/json"), s.APIHandler(libpod.ManifestInspect)).Methods(http.MethodGet) - // swagger:operation POST /libpod/manifests/{name}/add manifests AddManifest + // swagger:operation POST /libpod/manifests/{name:.*}/add manifests AddManifest // --- // description: Add an image to a manifest list // produces: // - application/json // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: the name or ID of the manifest @@ -84,7 +84,7 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/manifests/{name:.*}/add"), s.APIHandler(libpod.ManifestAdd)).Methods(http.MethodPost) - // swagger:operation DELETE /libpod/manifests/{name} manifests RemoveManifest + // swagger:operation DELETE /libpod/manifests/{name:.*} manifests RemoveManifest // --- // summary: Remove // description: Remove an image from a manifest list @@ -92,7 +92,7 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // - application/json // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: the image associated with the manifest diff --git a/pkg/api/server/register_swagger.go b/pkg/api/server/register_swagger.go index 5564ec096..9048c1951 100644 --- a/pkg/api/server/register_swagger.go +++ b/pkg/api/server/register_swagger.go @@ -2,25 +2,14 @@ package server import ( "net/http" - "os" + "github.com/containers/libpod/pkg/api/handlers/libpod" "github.com/gorilla/mux" ) -// DefaultPodmanSwaggerSpec provides the default path to the podman swagger spec file -const DefaultPodmanSwaggerSpec = "/usr/share/containers/podman/swagger.yaml" - // RegisterSwaggerHandlers maps the swagger endpoint for the server func (s *APIServer) RegisterSwaggerHandlers(r *mux.Router) error { // This handler does _*NOT*_ provide an UI rather just a swagger spec that an UI could render - r.PathPrefix("/swagger/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - path := DefaultPodmanSwaggerSpec - if p, found := os.LookupEnv("PODMAN_SWAGGER_SPEC"); found { - path = p - } - w.Header().Set("Content-Type", "text/yaml") - - http.ServeFile(w, r, path) - }) + r.HandleFunc(VersionedPath("/libpod/swagger"), s.APIHandler(libpod.ServeSwagger)).Methods(http.MethodGet) return nil } diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 8496cd11c..59f1f95cb 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -83,10 +83,6 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li ConnectionCh: make(chan int), } - server.Timer = time.AfterFunc(server.Duration, func() { - server.ConnectionCh <- NOOPHandler - }) - router.NotFoundHandler = http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { // We can track user errors... @@ -109,6 +105,7 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li server.registerPingHandlers, server.registerPluginsHandlers, server.registerPodsHandlers, + server.RegisterSwaggerHandlers, server.registerSwarmHandlers, server.registerSystemHandlers, server.registerVersionHandlers, @@ -139,36 +136,15 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li // Serve starts responding to HTTP requests func (s *APIServer) Serve() error { - // stalker to count the connections. Should the timer expire it will shutdown the service. - go func() { - for delta := range s.ConnectionCh { - switch delta { - case EnterHandler: - s.Timer.Stop() - s.ActiveConnections += 1 - s.TotalConnections += 1 - case ExitHandler: - s.Timer.Stop() - s.ActiveConnections -= 1 - if s.ActiveConnections == 0 { - // Server will be shutdown iff the timer expires before being reset or stopped - s.Timer = time.AfterFunc(s.Duration, func() { - if err := s.Shutdown(); err != nil { - logrus.Errorf("Failed to shutdown APIServer: %v", err) - os.Exit(1) - } - }) - } else { - s.Timer.Reset(s.Duration) - } - case NOOPHandler: - // push the check out another duration... - s.Timer.Reset(s.Duration) - default: - logrus.Errorf("ConnectionCh received unsupported input %d", delta) - } - } - }() + // This is initialized here as Timer is not needed until Serve'ing + if s.Duration > 0 { + s.Timer = time.AfterFunc(s.Duration, func() { + s.ConnectionCh <- NOOPHandler + }) + go s.ReadChannelWithTimeout() + } else { + go s.ReadChannelNoTimeout() + } sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) @@ -193,6 +169,53 @@ func (s *APIServer) Serve() error { return nil } +func (s *APIServer) ReadChannelWithTimeout() { + // stalker to count the connections. Should the timer expire it will shutdown the service. + for delta := range s.ConnectionCh { + switch delta { + case EnterHandler: + s.Timer.Stop() + s.ActiveConnections += 1 + s.TotalConnections += 1 + case ExitHandler: + s.Timer.Stop() + s.ActiveConnections -= 1 + if s.ActiveConnections == 0 { + // Server will be shutdown iff the timer expires before being reset or stopped + s.Timer = time.AfterFunc(s.Duration, func() { + if err := s.Shutdown(); err != nil { + logrus.Errorf("Failed to shutdown APIServer: %v", err) + os.Exit(1) + } + }) + } else { + s.Timer.Reset(s.Duration) + } + case NOOPHandler: + // push the check out another duration... + s.Timer.Reset(s.Duration) + default: + logrus.Warnf("ConnectionCh received unsupported input %d", delta) + } + } +} + +func (s *APIServer) ReadChannelNoTimeout() { + // stalker to count the connections. + for delta := range s.ConnectionCh { + switch delta { + case EnterHandler: + s.ActiveConnections += 1 + s.TotalConnections += 1 + case ExitHandler: + s.ActiveConnections -= 1 + case NOOPHandler: + default: + logrus.Warnf("ConnectionCh received unsupported input %d", delta) + } + } +} + // Shutdown is a clean shutdown waiting on existing clients func (s *APIServer) Shutdown() error { // Duration == 0 flags no auto-shutdown of the server 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..289debd8c 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) diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index 670321f21..f298dbba1 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -139,7 +139,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. 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 a4d065a14..409e620a3 100644 --- a/pkg/bindings/test/common_test.go +++ b/pkg/bindings/test/common_test.go @@ -152,7 +152,7 @@ func (b *bindingTest) startAPIService() *gexec.Session { var ( cmd []string ) - cmd = append(cmd, "--log-level=debug", "system", "service", "--timeout=999999", b.sock) + cmd = append(cmd, "--log-level=debug", "--events-backend=file", "system", "service", "--timeout=0", b.sock) return b.runPodman(cmd) } diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index 34a9c3136..afb0cc19b 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -3,10 +3,12 @@ package test_bindings import ( "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 +17,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 +55,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 +69,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 +83,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 +99,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 +118,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 +131,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 +144,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 +157,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 +170,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 +191,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 +215,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 +228,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 +242,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()) @@ -301,8 +301,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, &unpause) + errChan <- waitErr close(errChan) }() err = containers.Unpause(bt.conn, name) @@ -323,7 +323,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 +357,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..fae1deca4 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, &trueFlag, &newpod) + Expect(err).To(BeNil()) - // 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)) + // 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()) + + // 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 c8940d46e..1d5ae1329 100644 --- a/pkg/bindings/test/volumes_test.go +++ b/pkg/bindings/test/volumes_test.go @@ -3,11 +3,12 @@ package test_bindings import ( "context" "fmt" + "net/http" + "time" + "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/bindings/containers" "github.com/containers/libpod/pkg/bindings/volumes" - "net/http" - "time" "github.com/containers/libpod/pkg/bindings" . "github.com/onsi/ginkgo" @@ -24,7 +25,6 @@ var _ = Describe("Podman volumes", func() { s *gexec.Session connText context.Context err error - trueFlag = true ) BeforeEach(func() { @@ -106,7 +106,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()) }) diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go new file mode 100644 index 000000000..1899b98f7 --- /dev/null +++ b/pkg/domain/entities/containers.go @@ -0,0 +1 @@ +package entities diff --git a/pkg/domain/entities/engine.go b/pkg/domain/entities/engine.go new file mode 100644 index 000000000..a1096f1f1 --- /dev/null +++ b/pkg/domain/entities/engine.go @@ -0,0 +1,97 @@ +package entities + +import ( + "net/url" + "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) +} + +type EngineOptions struct { + Uri *url.URL + Identities []string + FlagSet pflag.FlagSet + Flags EngineFlags +} + +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 + + EngineMode EngineMode +} + +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..d08f37d44 --- /dev/null +++ b/pkg/domain/entities/engine_container.go @@ -0,0 +1,26 @@ +package entities + +import ( + "context" +) + +type ContainerEngine interface { + ContainerRuntime + PodRuntime + VolumeRuntime +} + +type ContainerRuntime interface { + ContainerDelete(ctx context.Context, opts ContainerDeleteOptions) (*ContainerDeleteReport, error) + ContainerPrune(ctx context.Context) (*ContainerPruneReport, error) +} + +type PodRuntime interface { + PodDelete(ctx context.Context, opts PodPruneOptions) (*PodDeleteReport, error) + PodPrune(ctx context.Context) (*PodPruneReport, error) +} + +type VolumeRuntime interface { + 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/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/runtime.go b/pkg/domain/infra/abi/runtime.go new file mode 100644 index 000000000..479a69586 --- /dev/null +++ b/pkg/domain/infra/abi/runtime.go @@ -0,0 +1,19 @@ +// +build ABISupport + +package abi + +import ( + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/domain/entities" +) + +// Image-related runtime linked against libpod library +type ImageEngine struct { + Libpod *libpod.Runtime +} + +// Container-related runtime linked against libpod library +type ContainerEngine struct { + entities.ContainerEngine + Libpod *libpod.Runtime +} diff --git a/pkg/domain/infra/runtime_abi.go b/pkg/domain/infra/runtime_abi.go new file mode 100644 index 000000000..de996f567 --- /dev/null +++ b/pkg/domain/infra/runtime_abi.go @@ -0,0 +1,39 @@ +// +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/abi" + "github.com/containers/libpod/pkg/domain/infra/tunnel" +) + +// NewContainerEngine factory provides a libpod runtime for container-related operations +func NewContainerEngine(mode entities.EngineMode, opts entities.EngineOptions) (entities.ContainerEngine, error) { + switch mode { + case entities.ABIMode: + r, err := NewLibpodRuntime(opts.FlagSet, opts.Flags) + return &abi.ContainerEngine{ContainerEngine: r}, err + case entities.TunnelMode: + ctx, err := bindings.NewConnection(context.Background(), opts.Uri.String(), opts.Identities...) + return &tunnel.ContainerEngine{ClientCxt: ctx}, err + } + return nil, fmt.Errorf("runtime mode '%v' is not supported", mode) +} + +// NewContainerEngine factory provides a libpod runtime for image-related operations +func NewImageEngine(mode entities.EngineMode, opts entities.EngineOptions) (entities.ImageEngine, error) { + switch mode { + case entities.ABIMode: + r, err := NewLibpodImageRuntime(opts.FlagSet, opts.Flags) + return r, err + case entities.TunnelMode: + ctx, err := bindings.NewConnection(context.Background(), opts.Uri.String(), opts.Identities...) + return &tunnel.ImageEngine{ClientCxt: ctx}, err + } + return nil, fmt.Errorf("runtime mode '%v' is not supported", mode) +} diff --git a/pkg/domain/infra/runtime_image_proxy.go b/pkg/domain/infra/runtime_image_proxy.go new file mode 100644 index 000000000..480e7c8d7 --- /dev/null +++ b/pkg/domain/infra/runtime_image_proxy.go @@ -0,0 +1,25 @@ +// +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 +} + +func (ir *runtime) ShutdownImageRuntime(force bool) error { + return ir.Libpod.Shutdown(force) +} diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go new file mode 100644 index 000000000..b42c52b6e --- /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..d17b8efa1 --- /dev/null +++ b/pkg/domain/infra/runtime_proxy.go @@ -0,0 +1,54 @@ +// +build ABISupport + +package infra + +import ( + "context" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/domain/entities" + flag "github.com/spf13/pflag" +) + +// ContainerEngine Proxy will be EOL'ed after podmanV2 is separated from libpod repo + +type runtime struct { + entities.ContainerEngine + Libpod *libpod.Runtime +} + +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 &runtime{Libpod: r}, nil +} + +func (r *runtime) ShutdownRuntime(force bool) error { + return r.Libpod.Shutdown(force) +} + +func (r *runtime) ContainerDelete(ctx context.Context, opts entities.ContainerDeleteOptions) (*entities.ContainerDeleteReport, error) { + panic("implement me") +} + +func (r *runtime) ContainerPrune(ctx context.Context) (*entities.ContainerPruneReport, error) { + panic("implement me") +} + +func (r *runtime) PodDelete(ctx context.Context, opts entities.PodPruneOptions) (*entities.PodDeleteReport, error) { + panic("implement me") +} + +func (r *runtime) PodPrune(ctx context.Context) (*entities.PodPruneReport, error) { + panic("implement me") +} + +func (r *runtime) VolumeDelete(ctx context.Context, opts entities.VolumeDeleteOptions) (*entities.VolumeDeleteReport, error) { + panic("implement me") +} + +func (r *runtime) VolumePrune(ctx context.Context) (*entities.VolumePruneReport, error) { + panic("implement me") +} diff --git a/pkg/domain/infra/runtime_tunnel.go b/pkg/domain/infra/runtime_tunnel.go new file mode 100644 index 000000000..8a606deaf --- /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(mode entities.EngineMode, opts entities.EngineOptions) (entities.ContainerEngine, error) { + switch mode { + case entities.ABIMode: + return nil, fmt.Errorf("direct runtime not supported") + case entities.TunnelMode: + ctx, err := bindings.NewConnection(context.Background(), opts.Uri.String(), opts.Identities...) + return &tunnel.ContainerEngine{ClientCxt: ctx}, err + } + return nil, fmt.Errorf("runtime mode '%v' is not supported", mode) +} + +// NewImageEngine factory provides a libpod runtime for image-related operations +func NewImageEngine(mode entities.EngineMode, opts entities.EngineOptions) (entities.ImageEngine, error) { + switch mode { + case entities.ABIMode: + return nil, fmt.Errorf("direct image runtime not supported") + case entities.TunnelMode: + ctx, err := bindings.NewConnection(context.Background(), opts.Uri.String(), opts.Identities...) + return &tunnel.ImageEngine{ClientCxt: ctx}, err + } + return nil, fmt.Errorf("runtime mode '%v' is not supported", mode) +} 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/runtime.go b/pkg/domain/infra/tunnel/runtime.go new file mode 100644 index 000000000..af433a6d9 --- /dev/null +++ b/pkg/domain/infra/tunnel/runtime.go @@ -0,0 +1,45 @@ +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) Shutdown(force bool) error { + return 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") +} + +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/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/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index db898e706..6643bfbbf 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -108,10 +108,9 @@ do_pause () } static char ** -get_cmd_line_args (pid_t pid) +get_cmd_line_args () { int fd; - char path[PATH_MAX]; char *buffer; size_t allocated; size_t used = 0; @@ -119,11 +118,7 @@ get_cmd_line_args (pid_t pid) int i, argc = 0; char **argv; - if (pid) - sprintf (path, "/proc/%d/cmdline", pid); - else - strcpy (path, "/proc/self/cmdline"); - fd = open (path, O_RDONLY); + fd = open ("/proc/self/cmdline", O_RDONLY); if (fd < 0) return NULL; @@ -196,7 +191,7 @@ can_use_shortcut () return false; #endif - argv = get_cmd_line_args (0); + argv = get_cmd_line_args (); if (argv == NULL) return false; @@ -542,7 +537,6 @@ create_pause_process (const char *pause_pid_file_path, char **argv) int reexec_userns_join (int userns, int mountns, char *pause_pid_file_path) { - pid_t ppid = getpid (); char uid[16]; char gid[16]; char **argv; @@ -559,7 +553,7 @@ reexec_userns_join (int userns, int mountns, char *pause_pid_file_path) sprintf (uid, "%d", geteuid ()); sprintf (gid, "%d", getegid ()); - argv = get_cmd_line_args (ppid); + argv = get_cmd_line_args (); if (argv == NULL) { fprintf (stderr, "cannot read argv: %s\n", strerror (errno)); @@ -724,7 +718,6 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re int ret; pid_t pid; char b; - pid_t ppid = getpid (); char **argv; char uid[16]; char gid[16]; @@ -801,7 +794,7 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re _exit (EXIT_FAILURE); } - argv = get_cmd_line_args (ppid); + argv = get_cmd_line_args (); if (argv == NULL) { fprintf (stderr, "cannot read argv: %s\n", strerror (errno)); 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()) } diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go index 50aaaaa44..e88d010c5 100644 --- a/pkg/varlinkapi/system.go +++ b/pkg/varlinkapi/system.go @@ -10,7 +10,7 @@ import ( "time" "github.com/containers/image/v5/pkg/sysregistriesv2" - "github.com/containers/libpod/cmd/podman/varlink" + iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod/define" "github.com/sirupsen/logrus" ) |