diff options
Diffstat (limited to 'pkg')
45 files changed, 1791 insertions, 305 deletions
diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index 32a84b60d..46db7ebe8 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -32,12 +32,12 @@ import ( ) // Inspect returns an inspect struct from varlink -func (c *Container) Inspect(size bool) (*libpod.InspectContainerData, error) { +func (c *Container) Inspect(size bool) (*define.InspectContainerData, error) { reply, err := iopodman.ContainerInspectData().Call(c.Runtime.Conn, c.ID(), size) if err != nil { return nil, err } - data := libpod.InspectContainerData{} + data := define.InspectContainerData{} if err := json.Unmarshal([]byte(reply), &data); err != nil { return nil, err } diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index 7817a1f98..76e221fae 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -356,11 +356,11 @@ func (r *LocalRuntime) PruneVolumes(ctx context.Context) ([]string, []error) { errs = append(errs, err) return vids, errs } - for _, r := range reports { - if r.Err == nil { - vids = append(vids, r.Id) + for k, v := range reports { + if v == nil { + vids = append(vids, k) } else { - errs = append(errs, r.Err) + errs = append(errs, v) } } return vids, errs diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go new file mode 100644 index 000000000..ec1a8ac96 --- /dev/null +++ b/pkg/api/handlers/compat/exec.go @@ -0,0 +1,107 @@ +package compat + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + "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/gorilla/mux" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// ExecCreateHandler creates an exec session for a given container. +func ExecCreateHandler(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + input := new(handlers.ExecCreateConfig) + if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + utils.InternalServerError(w, errors.Wrapf(err, "error decoding request body as JSON")) + return + } + + ctrName := utils.GetName(r) + ctr, err := runtime.LookupContainer(ctrName) + if err != nil { + utils.ContainerNotFound(w, ctrName, err) + return + } + + libpodConfig := new(libpod.ExecConfig) + libpodConfig.Command = input.Cmd + libpodConfig.Terminal = input.Tty + libpodConfig.AttachStdin = input.AttachStdin + libpodConfig.AttachStderr = input.AttachStderr + libpodConfig.AttachStdout = input.AttachStdout + if input.DetachKeys != "" { + libpodConfig.DetachKeys = &input.DetachKeys + } + libpodConfig.Environment = make(map[string]string) + for _, envStr := range input.Env { + split := strings.SplitN(envStr, "=", 2) + if len(split) != 2 { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, errors.Errorf("environment variable %q badly formed, must be key=value", envStr)) + return + } + libpodConfig.Environment[split[0]] = split[1] + } + libpodConfig.WorkDir = input.WorkingDir + libpodConfig.Privileged = input.Privileged + libpodConfig.User = input.User + + sessID, err := ctr.ExecCreate(libpodConfig) + if err != nil { + if errors.Cause(err) == define.ErrCtrStateInvalid { + // Check if the container is paused. If so, return a 409 + state, err := ctr.State() + if err == nil { + // Ignore the error != nil case. We're already + // throwing an InternalServerError below. + if state == define.ContainerStatePaused { + utils.Error(w, "Container is paused", http.StatusConflict, errors.Errorf("cannot create exec session as container %s is paused", ctr.ID())) + return + } + } + } + utils.InternalServerError(w, err) + return + } + + resp := new(handlers.ExecCreateResponse) + resp.ID = sessID + + utils.WriteResponse(w, http.StatusCreated, resp) +} + +// ExecInspectHandler inspects a given exec session. +func ExecInspectHandler(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + sessionID := mux.Vars(r)["id"] + sessionCtr, err := runtime.GetExecSessionContainer(sessionID) + if err != nil { + utils.Error(w, fmt.Sprintf("No such exec session: %s", sessionID), http.StatusNotFound, err) + return + } + + logrus.Debugf("Inspecting exec session %s of container %s", sessionID, sessionCtr.ID()) + + session, err := sessionCtr.ExecSession(sessionID) + if err != nil { + utils.InternalServerError(w, errors.Wrapf(err, "error retrieving exec session %s from container %s", sessionID, sessionCtr.ID())) + return + } + + inspectOut, err := session.Inspect() + if err != nil { + utils.InternalServerError(w, err) + return + } + + utils.WriteResponse(w, http.StatusOK, inspectOut) +} diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index cce718f54..354a13bf5 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -64,6 +64,7 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) query := struct { + All bool Filters map[string][]string `schema:"filters"` }{ // This is where you can override the golang default value for one of fields @@ -80,7 +81,7 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { filters = append(filters, fmt.Sprintf("%s=%s", k, val)) } } - pruneCids, err := runtime.ImageRuntime().PruneImages(r.Context(), false, filters) + pruneCids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, filters) if err != nil { utils.InternalServerError(w, err) return diff --git a/pkg/api/handlers/compat/images_remove.go b/pkg/api/handlers/compat/images_remove.go index 3d346543e..ed0153529 100644 --- a/pkg/api/handlers/compat/images_remove.go +++ b/pkg/api/handlers/compat/images_remove.go @@ -36,17 +36,23 @@ func RemoveImage(w http.ResponseWriter, r *http.Request) { return } - _, err = runtime.RemoveImage(r.Context(), newImage, query.Force) + results, err := runtime.RemoveImage(r.Context(), newImage, query.Force) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return } - // TODO - // This will need to be fixed for proper response, like Deleted: and Untagged: - m := make(map[string]string) - m["Deleted"] = newImage.ID() - foo := []map[string]string{} - foo = append(foo, m) - utils.WriteResponse(w, http.StatusOK, foo) + + response := make([]map[string]string, 0, len(results.Untagged)+1) + deleted := make(map[string]string, 1) + deleted["Deleted"] = results.Deleted + response = append(response, deleted) + + for _, u := range results.Untagged { + untagged := make(map[string]string, 1) + untagged["Untagged"] = u + response = append(response, untagged) + } + + utils.WriteResponse(w, http.StatusOK, response) } diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index f8e666451..ee85c1a41 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -119,12 +119,12 @@ func GetImages(w http.ResponseWriter, r *http.Request) { func PruneImages(w http.ResponseWriter, r *http.Request) { var ( - all bool err error ) runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { + All bool `schema:"all"` Filters map[string][]string `schema:"filters"` }{ // override any golang type defaults @@ -140,7 +140,7 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { if _, found := r.URL.Query()["filters"]; found { dangling := query.Filters["all"] if len(dangling) > 0 { - all, err = strconv.ParseBool(query.Filters["all"][0]) + query.All, err = strconv.ParseBool(query.Filters["all"][0]) if err != nil { utils.InternalServerError(w, err) return @@ -152,7 +152,8 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0])) } } - cids, err := runtime.ImageRuntime().PruneImages(r.Context(), all, libpodFilters) + + cids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, libpodFilters) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index 27ec64d89..7e9c2e2c0 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -4,14 +4,13 @@ import ( "encoding/json" "fmt" "net/http" - "strings" - "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/cmd/podman/shared/parse" "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/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/util" "github.com/gorilla/schema" "github.com/pkg/errors" @@ -20,76 +19,14 @@ import ( func PodCreate(w http.ResponseWriter, r *http.Request) { var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) - options []libpod.PodCreateOption err error ) - labels := make(map[string]string) - input := handlers.PodCreateConfig{} - if err := json.NewDecoder(r.Body).Decode(&input); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + var psg specgen.PodSpecGenerator + if err := json.NewDecoder(r.Body).Decode(&psg); err != nil { + utils.Error(w, "Failed to decode specgen", http.StatusInternalServerError, errors.Wrap(err, "failed to decode specgen")) return } - if len(input.InfraCommand) > 0 || len(input.InfraImage) > 0 { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, - errors.New("infra-command and infra-image are not implemented yet")) - return - } - // TODO long term we should break the following out of adapter and into libpod proper - // so that the cli and api can share the creation of a pod with the same options - if len(input.CGroupParent) > 0 { - options = append(options, libpod.WithPodCgroupParent(input.CGroupParent)) - } - - if len(input.Labels) > 0 { - labels, err = parse.GetAllLabels([]string{}, input.Labels) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - } - - if len(labels) != 0 { - options = append(options, libpod.WithPodLabels(labels)) - } - - if len(input.Name) > 0 { - options = append(options, libpod.WithPodName(input.Name)) - } - - if len(input.Hostname) > 0 { - options = append(options, libpod.WithPodHostname(input.Hostname)) - } - - if input.Infra { - // TODO infra-image and infra-command are not supported in the libpod API yet. Will fix - // when implemented in libpod - options = append(options, libpod.WithInfraContainer()) - sharedNamespaces := shared.DefaultKernelNamespaces - if len(input.Share) > 0 { - sharedNamespaces = input.Share - } - nsOptions, err := shared.GetNamespaceOptions(strings.Split(sharedNamespaces, ",")) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - options = append(options, nsOptions...) - } - - if len(input.Publish) > 0 { - portBindings, err := shared.CreatePortBindings(input.Publish) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - options = append(options, libpod.WithInfraContainerPorts(portBindings)) - - } - // always have containers use pod cgroups - // User Opt out is not yet supported - options = append(options, libpod.WithPodCgroups()) - - pod, err := runtime.NewPod(r.Context(), options...) + pod, err := psg.MakePod(runtime) if err != nil { http_code := http.StatusInternalServerError if errors.Cause(err) == define.ErrPodExists { @@ -102,9 +39,6 @@ func PodCreate(w http.ResponseWriter, r *http.Request) { } func Pods(w http.ResponseWriter, r *http.Request) { - var ( - podInspectData []*libpod.PodInspect - ) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { Filters map[string][]string `schema:"filters"` @@ -118,20 +52,11 @@ func Pods(w http.ResponseWriter, r *http.Request) { } pods, err := utils.GetPods(w, r) - if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - for _, pod := range pods { - data, err := pod.Inspect() - if err != nil { - utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) - return - } - podInspectData = append(podInspectData, data) - } - utils.WriteResponse(w, http.StatusOK, podInspectData) + utils.WriteResponse(w, http.StatusOK, pods) } func PodInspect(w http.ResponseWriter, r *http.Request) { @@ -155,6 +80,8 @@ func PodStop(w http.ResponseWriter, r *http.Request) { stopError error runtime = r.Context().Value("runtime").(*libpod.Runtime) decoder = r.Context().Value("decoder").(*schema.Decoder) + responses map[string]error + errs []error ) query := struct { Timeout int `schema:"t"` @@ -185,18 +112,28 @@ func PodStop(w http.ResponseWriter, r *http.Request) { } if query.Timeout > 0 { - _, stopError = pod.StopWithTimeout(r.Context(), false, query.Timeout) + responses, stopError = pod.StopWithTimeout(r.Context(), false, query.Timeout) } else { - _, stopError = pod.Stop(r.Context(), false) + responses, stopError = pod.Stop(r.Context(), false) } if stopError != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusOK, "") + for _, err := range responses { + errs = append(errs, err) + } + report := entities.PodStopReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodStart(w http.ResponseWriter, r *http.Request) { + var ( + errs []error + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) pod, err := runtime.LookupPod(name) @@ -213,11 +150,19 @@ func PodStart(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusNotModified, "") return } - if _, err := pod.Start(r.Context()); err != nil { + responses, err := pod.Start(r.Context()) + if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusOK, "") + for _, err := range responses { + errs = append(errs, err) + } + report := entities.PodStartReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodDelete(w http.ResponseWriter, r *http.Request) { @@ -246,10 +191,16 @@ func PodDelete(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusNoContent, "") + report := entities.PodRmReport{ + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodRestart(w http.ResponseWriter, r *http.Request) { + var ( + errs []error + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) pod, err := runtime.LookupPod(name) @@ -257,12 +208,19 @@ func PodRestart(w http.ResponseWriter, r *http.Request) { utils.PodNotFound(w, name, err) return } - _, err = pod.Restart(r.Context()) + responses, err := pod.Restart(r.Context()) if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusOK, "") + for _, err := range responses { + errs = append(errs, err) + } + report := entities.PodRestartReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodPrune(w http.ResponseWriter, r *http.Request) { @@ -278,6 +236,9 @@ func PodPrune(w http.ResponseWriter, r *http.Request) { } func PodPause(w http.ResponseWriter, r *http.Request) { + var ( + errs []error + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) pod, err := runtime.LookupPod(name) @@ -285,15 +246,25 @@ func PodPause(w http.ResponseWriter, r *http.Request) { utils.PodNotFound(w, name, err) return } - _, err = pod.Pause() + responses, err := pod.Pause() if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusNoContent, "") + for _, v := range responses { + errs = append(errs, v) + } + report := entities.PodPauseReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodUnpause(w http.ResponseWriter, r *http.Request) { + var ( + errs []error + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) pod, err := runtime.LookupPod(name) @@ -301,12 +272,19 @@ func PodUnpause(w http.ResponseWriter, r *http.Request) { utils.PodNotFound(w, name, err) return } - _, err = pod.Unpause() + responses, err := pod.Unpause() if err != nil { - utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + utils.Error(w, "failed to pause pod", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusOK, "") + for _, v := range responses { + errs = append(errs, v) + } + report := entities.PodUnpauseReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, &report) } func PodKill(w http.ResponseWriter, r *http.Request) { @@ -314,6 +292,7 @@ func PodKill(w http.ResponseWriter, r *http.Request) { runtime = r.Context().Value("runtime").(*libpod.Runtime) decoder = r.Context().Value("decoder").(*schema.Decoder) signal = "SIGKILL" + errs []error ) query := struct { Signal string `schema:"signal"` @@ -356,12 +335,23 @@ func PodKill(w http.ResponseWriter, r *http.Request) { utils.Error(w, msg, http.StatusConflict, errors.Errorf("cannot kill a pod with no running containers: %s", pod.ID())) return } - _, err = pod.Kill(uint(sig)) + + responses, err := pod.Kill(uint(sig)) if err != nil { - utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + utils.Error(w, "failed to kill pod", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusOK, "") + + for _, v := range responses { + if v != nil { + errs = append(errs, v) + } + } + report := &entities.PodKillReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodExists(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go index 149fa10dc..1fad2dd1a 100644 --- a/pkg/api/handlers/libpod/swagger.go +++ b/pkg/api/handlers/libpod/swagger.go @@ -6,6 +6,7 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" ) @@ -26,6 +27,55 @@ type swagInspectManifestResponse struct { Body manifest.List } +// Kill Pod +// swagger:response PodKillReport +type swagKillPodResponse struct { + // in:body + Body entities.PodKillReport +} + +// Pause pod +// swagger:response PodPauseReport +type swagPausePodResponse struct { + // in:body + Body entities.PodPauseReport +} + +// Unpause pod +// swagger:response PodUnpauseReport +type swagUnpausePodResponse struct { + // in:body + Body entities.PodUnpauseReport +} + +// Stop pod +// swagger:response PodStopReport +type swagStopPodResponse struct { + // in:body + Body entities.PodStopReport +} + +// Restart pod +// swagger:response PodRestartReport +type swagRestartPodResponse struct { + // in:body + Body entities.PodRestartReport +} + +// Start pod +// swagger:response PodStartReport +type swagStartPodResponse struct { + // in:body + Body entities.PodStartReport +} + +// Rm pod +// swagger:response PodRmReport +type swagRmPodResponse struct { + // in:body + Body entities.PodRmReport +} + func ServeSwagger(w http.ResponseWriter, r *http.Request) { path := DefaultPodmanSwaggerSpec if p, found := os.LookupEnv("PODMAN_SWAGGER_SPEC"); found { diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go index e61d272f4..5a6fc021e 100644 --- a/pkg/api/handlers/libpod/volumes.go +++ b/pkg/api/handlers/libpod/volumes.go @@ -149,13 +149,20 @@ func ListVolumes(w http.ResponseWriter, r *http.Request) { func PruneVolumes(w http.ResponseWriter, r *http.Request) { var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) + reports []*entities.VolumePruneReport ) pruned, err := runtime.PruneVolumes(r.Context()) if err != nil { utils.InternalServerError(w, err) return } - utils.WriteResponse(w, http.StatusOK, pruned) + for k, v := range pruned { + reports = append(reports, &entities.VolumePruneReport{ + Err: v, + Id: k, + }) + } + utils.WriteResponse(w, http.StatusOK, reports) } func RemoveVolume(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/handlers/swagger.go b/pkg/api/handlers/swagger.go index 4ba123ba9..e6e937729 100644 --- a/pkg/api/handlers/swagger.go +++ b/pkg/api/handlers/swagger.go @@ -2,7 +2,9 @@ package handlers import ( "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/inspect" "github.com/docker/docker/api/types" ) @@ -108,7 +110,7 @@ type swagDockerTopResponse struct { type swagLibpodInspectContainerResponse struct { // in:body Body struct { - libpod.InspectContainerData + define.InspectContainerData } } @@ -116,7 +118,7 @@ type swagLibpodInspectContainerResponse struct { // swagger:response ListPodsResponse type swagListPodsResponse struct { // in:body - Body []libpod.PodInspect + Body []entities.ListPodsReport } // Inspect pod diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 84ca0fbed..1ca5db3f9 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -133,19 +133,6 @@ type ContainerTopOKBody struct { dockerContainer.ContainerTopOKBody } -// swagger:model PodCreateConfig -type PodCreateConfig struct { - Name string `json:"name"` - CGroupParent string `json:"cgroup-parent"` - Hostname string `json:"hostname"` - Infra bool `json:"infra"` - InfraCommand string `json:"infra-command"` - InfraImage string `json:"infra-image"` - Labels []string `json:"labels"` - Publish []string `json:"publish"` - Share string `json:"share"` -} - type ErrorModel struct { Message string `json:"message"` } @@ -172,6 +159,14 @@ type ImageTreeResponse struct { Layers []ImageLayer `json:"layers"` } +type ExecCreateConfig struct { + docker.ExecConfig +} + +type ExecCreateResponse struct { + docker.IDResponse +} + func EventToApiEvent(e *events.Event) *Event { return &Event{dockerEvents.Message{ Type: e.Type.String(), diff --git a/pkg/api/handlers/utils/pods.go b/pkg/api/handlers/utils/pods.go index 266ad9a4b..79d1a5090 100644 --- a/pkg/api/handlers/utils/pods.go +++ b/pkg/api/handlers/utils/pods.go @@ -6,10 +6,16 @@ import ( "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/domain/entities" "github.com/gorilla/schema" ) -func GetPods(w http.ResponseWriter, r *http.Request) ([]*libpod.Pod, error) { +func GetPods(w http.ResponseWriter, r *http.Request) ([]*entities.ListPodsReport, error) { + var ( + lps []*entities.ListPodsReport + pods []*libpod.Pod + podErr error + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) @@ -37,9 +43,42 @@ func GetPods(w http.ResponseWriter, r *http.Request) ([]*libpod.Pod, error) { if err != nil { return nil, err } - return shared.FilterAllPodsWithFilterFunc(runtime, filterFuncs...) + pods, podErr = shared.FilterAllPodsWithFilterFunc(runtime, filterFuncs...) + } else { + pods, podErr = runtime.GetAllPods() } - - return runtime.GetAllPods() - + if podErr != nil { + return nil, podErr + } + for _, pod := range pods { + status, err := pod.GetPodStatus() + if err != nil { + return nil, err + } + ctrs, err := pod.AllContainers() + if err != nil { + return nil, err + } + lp := entities.ListPodsReport{ + Cgroup: pod.CgroupParent(), + Created: pod.CreatedTime(), + Id: pod.ID(), + Name: pod.Name(), + Namespace: pod.Namespace(), + Status: status, + } + for _, ctr := range ctrs { + state, err := ctr.State() + if err != nil { + return nil, err + } + lp.Containers = append(lp.Containers, &entities.ListPodContainer{ + Id: ctr.ID(), + Names: ctr.Name(), + Status: state.String(), + }) + } + lps = append(lps, &lp) + } + return lps, nil } diff --git a/pkg/api/server/register_exec.go b/pkg/api/server/register_exec.go index d27d21a04..71fb50307 100644 --- a/pkg/api/server/register_exec.go +++ b/pkg/api/server/register_exec.go @@ -8,7 +8,7 @@ import ( ) func (s *APIServer) registerExecHandlers(r *mux.Router) error { - // swagger:operation POST /containers/{name}/create compat createExec + // swagger:operation POST /containers/{name}/exec compat createExec // --- // tags: // - exec (compat) @@ -74,9 +74,9 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // description: container is paused // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/containers/{name}/create"), s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost) + r.Handle(VersionedPath("/containers/{name}/exec"), s.APIHandler(compat.ExecCreateHandler)).Methods(http.MethodPost) // Added non version path to URI to support docker non versioned paths - r.Handle("/containers/{name}/create", s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost) + r.Handle("/containers/{name}/exec", s.APIHandler(compat.ExecCreateHandler)).Methods(http.MethodPost) // swagger:operation POST /exec/{id}/start compat startExec // --- // tags: @@ -169,15 +169,15 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchExecInstance" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/exec/{id}/json"), s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodGet) + r.Handle(VersionedPath("/exec/{id}/json"), s.APIHandler(compat.ExecInspectHandler)).Methods(http.MethodGet) // Added non version path to URI to support docker non versioned paths - r.Handle("/exec/{id}/json", s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodGet) + r.Handle("/exec/{id}/json", s.APIHandler(compat.ExecInspectHandler)).Methods(http.MethodGet) /* libpod api follows */ - // swagger:operation POST /libpod/containers/{name}/create libpod libpodCreateExec + // swagger:operation POST /libpod/containers/{name}/exec libpod libpodCreateExec // --- // tags: // - exec @@ -243,7 +243,7 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // description: container is paused // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/containers/{name}/create"), s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/containers/{name}/exec"), s.APIHandler(compat.ExecCreateHandler)).Methods(http.MethodPost) // swagger:operation POST /libpod/exec/{id}/start libpod libpodStartExec // --- // tags: @@ -332,6 +332,6 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchExecInstance" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/exec/{id}/json"), s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodGet) + r.Handle(VersionedPath("/libpod/exec/{id}/json"), s.APIHandler(compat.ExecInspectHandler)).Methods(http.MethodGet) return nil } diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go index af2330665..5ba2263e8 100644 --- a/pkg/api/server/register_pods.go +++ b/pkg/api/server/register_pods.go @@ -37,7 +37,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // description: attributes for creating a pod // schema: // type: object - // $ref: "#/definitions/PodCreateConfig" + // $ref: "#/definitions/PodSpecGenerator" // responses: // 200: // $ref: "#/definitions/IdResponse" @@ -81,8 +81,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // type: boolean // description : force removal of a running pod by first stopping all containers, then removing all containers in the pod // responses: - // 204: - // description: no error + // 200: + // $ref: '#/responses/PodRmReport' // 400: // $ref: "#/responses/BadParamError" // 404: @@ -146,8 +146,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // description: signal to be sent to pod // default: SIGKILL // responses: - // 204: - // description: no error + // 200: + // $ref: "#/responses/PodKillReport" // 400: // $ref: "#/responses/BadParamError" // 404: @@ -170,8 +170,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // required: true // description: the name or ID of the pod // responses: - // 204: - // description: no error + // 200: + // $ref: '#/responses/PodPauseReport' // 404: // $ref: "#/responses/NoSuchPod" // 500: @@ -189,8 +189,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // required: true // description: the name or ID of the pod // responses: - // 204: - // description: no error + // 200: + // $ref: '#/responses/PodRestartReport' // 404: // $ref: "#/responses/NoSuchPod" // 500: @@ -208,8 +208,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // required: true // description: the name or ID of the pod // responses: - // 204: - // description: no error + // 200: + // $ref: '#/responses/PodStartReport' // 304: // $ref: "#/responses/PodAlreadyStartedError" // 404: @@ -233,8 +233,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // type: integer // description: timeout // responses: - // 204: - // description: no error + // 200: + // $ref: '#/responses/PodStopReport' // 304: // $ref: "#/responses/PodAlreadyStoppedError" // 400: @@ -256,8 +256,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // required: true // description: the name or ID of the pod // responses: - // 204: - // description: no error + // 200: + // $ref: '#/responses/PodUnpauseReport' // 404: // $ref: "#/responses/NoSuchPod" // 500: diff --git a/pkg/api/server/swagger.go b/pkg/api/server/swagger.go index 9156f3f8a..2433a6a05 100644 --- a/pkg/api/server/swagger.go +++ b/pkg/api/server/swagger.go @@ -2,6 +2,7 @@ package server import ( "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/domain/entities" ) @@ -178,6 +179,6 @@ type swagVolumeListResponse struct { type swagHealthCheckRunResponse struct { // in:body Body struct { - libpod.HealthCheckResults + define.HealthCheckResults } } diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index 231b6f232..bad1294f4 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -5,9 +5,10 @@ import ( "net/http" "net/url" "strconv" + "strings" - "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers" lpapiv2 "github.com/containers/libpod/pkg/api/handlers/libpod" "github.com/containers/libpod/pkg/bindings" ) @@ -106,7 +107,7 @@ func Remove(ctx context.Context, nameOrID string, force, volumes *bool) error { // or a partial/full ID. The size bool determines whether the size of the container's root filesystem // should be calculated. Calculating the size of a container requires extra work from the filesystem and // is therefore slower. -func Inspect(ctx context.Context, nameOrID string, size *bool) (*libpod.InspectContainerData, error) { +func Inspect(ctx context.Context, nameOrID string, size *bool) (*define.InspectContainerData, error) { conn, err := bindings.GetClient(ctx) if err != nil { return nil, err @@ -119,7 +120,7 @@ func Inspect(ctx context.Context, nameOrID string, size *bool) (*libpod.InspectC if err != nil { return nil, err } - inspect := libpod.InspectContainerData{} + inspect := define.InspectContainerData{} return &inspect, response.Process(&inspect) } @@ -194,7 +195,40 @@ func Start(ctx context.Context, nameOrID string, detachKeys *string) error { } func Stats() {} -func Top() {} + +// Top gathers statistics about the running processes in a container. The nameOrID can be a container name +// or a partial/full ID. The descriptors allow for specifying which data to collect from the process. +func Top(ctx context.Context, nameOrID string, descriptors []string) ([]string, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params := url.Values{} + + if len(descriptors) > 0 { + // flatten the slice into one string + params.Set("ps_args", strings.Join(descriptors, ",")) + } + response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/top", params, nameOrID) + if err != nil { + return nil, err + } + + body := handlers.ContainerTopOKBody{} + if err = response.Process(&body); err != nil { + return nil, err + } + + // handlers.ContainerTopOKBody{} returns a slice of slices where each cell in the top table is an item. + // In libpod land, we're just using a slice with cells being split by tabs, which allows for an idiomatic + // usage of the tabwriter. + topOutput := []string{strings.Join(body.Titles, "\t")} + for _, out := range body.Processes { + topOutput = append(topOutput, strings.Join(out, "\t")) + } + + return topOutput, err +} // Unpause resumes the given paused container. The nameOrID can be a container name // or a partial/full ID. diff --git a/pkg/bindings/containers/exec.go b/pkg/bindings/containers/exec.go new file mode 100644 index 000000000..48f9ed697 --- /dev/null +++ b/pkg/bindings/containers/exec.go @@ -0,0 +1,71 @@ +package containers + +import ( + "context" + "net/http" + "strings" + + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/bindings" + jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +var json = jsoniter.ConfigCompatibleWithStandardLibrary + +// ExecCreate creates a new exec session in an existing container. +// The exec session will not be started; that is done with ExecStart. +// Returns ID of new exec session, or an error if one occurred. +func ExecCreate(ctx context.Context, nameOrID string, config *handlers.ExecCreateConfig) (string, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return "", err + } + + if config == nil { + return "", errors.Errorf("must provide a configuration for exec session") + } + + requestJSON, err := json.Marshal(config) + if err != nil { + return "", errors.Wrapf(err, "error marshalling exec config to JSON") + } + jsonReader := strings.NewReader(string(requestJSON)) + + resp, err := conn.DoRequest(jsonReader, http.MethodPost, "/containers/%s/exec", nil, nameOrID) + if err != nil { + return "", err + } + + respStruct := new(handlers.ExecCreateResponse) + if err := resp.Process(respStruct); err != nil { + return "", err + } + + return respStruct.ID, nil +} + +// ExecInspect inspects an existing exec session, returning detailed information +// about it. +func ExecInspect(ctx context.Context, sessionID string) (*define.InspectExecSession, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + + logrus.Debugf("Inspecting session ID %q", sessionID) + + resp, err := conn.DoRequest(nil, http.MethodGet, "/exec/%s/json", nil, sessionID) + if err != nil { + return nil, err + } + + respStruct := new(define.InspectExecSession) + if err := resp.Process(respStruct); err != nil { + return nil, err + } + + return respStruct, nil +} diff --git a/pkg/bindings/containers/healthcheck.go b/pkg/bindings/containers/healthcheck.go index 85cc2814c..2b783ac73 100644 --- a/pkg/bindings/containers/healthcheck.go +++ b/pkg/bindings/containers/healthcheck.go @@ -4,19 +4,19 @@ import ( "context" "net/http" - "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/bindings" ) // RunHealthCheck executes the container's healthcheck and returns the health status of the // container. -func RunHealthCheck(ctx context.Context, nameOrID string) (*libpod.HealthCheckResults, error) { +func RunHealthCheck(ctx context.Context, nameOrID string) (*define.HealthCheckResults, error) { conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } var ( - status libpod.HealthCheckResults + status define.HealthCheckResults ) response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/healthcheck", nil, nameOrID) if err != nil { diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index e67965042..5e3af7a60 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -154,7 +154,7 @@ func Export(ctx context.Context, nameOrID string, w io.Writer, format *string, c // Prune removes unused images from local storage. The optional filters can be used to further // define which images should be pruned. -func Prune(ctx context.Context, filters map[string][]string) ([]string, error) { +func Prune(ctx context.Context, all *bool, filters map[string][]string) ([]string, error) { var ( deleted []string ) @@ -163,6 +163,9 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) { return nil, err } params := url.Values{} + if all != nil { + params.Set("all", strconv.FormatBool(*all)) + } if filters != nil { stringFilter, err := bindings.FiltersToString(filters) if err != nil { @@ -174,7 +177,7 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) { if err != nil { return deleted, err } - return deleted, response.Process(nil) + return deleted, response.Process(&deleted) } // Tag adds an additional name to locally-stored image. Both the tag and repo parameters are required. diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go index 1a8c31be1..bb0abebc4 100644 --- a/pkg/bindings/pods/pods.go +++ b/pkg/bindings/pods/pods.go @@ -5,14 +5,33 @@ import ( "net/http" "net/url" "strconv" + "strings" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/specgen" + jsoniter "github.com/json-iterator/go" ) -func CreatePod() error { - // TODO - return bindings.ErrNotImplemented +func CreatePodFromSpec(ctx context.Context, s *specgen.PodSpecGenerator) (*entities.PodCreateReport, error) { + var ( + pcr entities.PodCreateReport + ) + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + specgenString, err := jsoniter.MarshalToString(s) + if err != nil { + return nil, err + } + stringReader := strings.NewReader(specgenString) + response, err := conn.DoRequest(stringReader, http.MethodPost, "/pods/create", nil) + if err != nil { + return nil, err + } + return &pcr, response.Process(&pcr) } // Exists is a lightweight method to determine if a pod exists in local storage @@ -44,10 +63,13 @@ func Inspect(ctx context.Context, nameOrID string) (*libpod.PodInspect, error) { // Kill sends a SIGTERM to all the containers in a pod. The optional signal parameter // can be used to override SIGTERM. -func Kill(ctx context.Context, nameOrID string, signal *string) error { +func Kill(ctx context.Context, nameOrID string, signal *string) (*entities.PodKillReport, error) { + var ( + report entities.PodKillReport + ) conn, err := bindings.GetClient(ctx) if err != nil { - return err + return nil, err } params := url.Values{} if signal != nil { @@ -55,22 +77,23 @@ func Kill(ctx context.Context, nameOrID string, signal *string) error { } response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/kill", params, nameOrID) if err != nil { - return err + return nil, err } - return response.Process(nil) + return &report, response.Process(&report) } // Pause pauses all running containers in a given pod. -func Pause(ctx context.Context, nameOrID string) error { +func Pause(ctx context.Context, nameOrID string) (*entities.PodPauseReport, error) { + var report entities.PodPauseReport conn, err := bindings.GetClient(ctx) if err != nil { - return err + return nil, err } response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/pause", nil, nameOrID) if err != nil { - return err + return nil, err } - return response.Process(nil) + return &report, response.Process(&report) } // Prune removes all non-running pods in local storage. @@ -88,9 +111,9 @@ func Prune(ctx context.Context) error { // List returns all pods in local storage. The optional filters parameter can // be used to refine which pods should be listed. -func List(ctx context.Context, filters map[string][]string) ([]*libpod.PodInspect, error) { +func List(ctx context.Context, filters map[string][]string) ([]*entities.ListPodsReport, error) { var ( - inspect []*libpod.PodInspect + podsReports []*entities.ListPodsReport ) conn, err := bindings.GetClient(ctx) if err != nil { @@ -106,30 +129,32 @@ func List(ctx context.Context, filters map[string][]string) ([]*libpod.PodInspec } response, err := conn.DoRequest(nil, http.MethodGet, "/pods/json", params) if err != nil { - return inspect, err + return podsReports, err } - return inspect, response.Process(&inspect) + return podsReports, response.Process(&podsReports) } // Restart restarts all containers in a pod. -func Restart(ctx context.Context, nameOrID string) error { +func Restart(ctx context.Context, nameOrID string) (*entities.PodRestartReport, error) { + var report entities.PodRestartReport conn, err := bindings.GetClient(ctx) if err != nil { - return err + return nil, err } response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/restart", nil, nameOrID) if err != nil { - return err + return nil, err } - return response.Process(nil) + return &report, response.Process(&report) } // Remove deletes a Pod from from local storage. The optional force parameter denotes // that the Pod can be removed even if in a running state. -func Remove(ctx context.Context, nameOrID string, force *bool) error { +func Remove(ctx context.Context, nameOrID string, force *bool) (*entities.PodRmReport, error) { + var report entities.PodRmReport conn, err := bindings.GetClient(ctx) if err != nil { - return err + return nil, err } params := url.Values{} if force != nil { @@ -137,22 +162,27 @@ func Remove(ctx context.Context, nameOrID string, force *bool) error { } response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s", params, nameOrID) if err != nil { - return err + return nil, err } - return response.Process(nil) + return &report, response.Process(&report) } // Start starts all containers in a pod. -func Start(ctx context.Context, nameOrID string) error { +func Start(ctx context.Context, nameOrID string) (*entities.PodStartReport, error) { + var report entities.PodStartReport conn, err := bindings.GetClient(ctx) if err != nil { - return err + return nil, err } response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/start", nil, nameOrID) if err != nil { - return err + return nil, err } - return response.Process(nil) + if response.StatusCode == http.StatusNotModified { + report.Id = nameOrID + return &report, nil + } + return &report, response.Process(&report) } func Stats() error { @@ -162,10 +192,11 @@ func Stats() error { // Stop stops all containers in a Pod. The optional timeout parameter can be // used to override the timeout before the container is killed. -func Stop(ctx context.Context, nameOrID string, timeout *int) error { +func Stop(ctx context.Context, nameOrID string, timeout *int) (*entities.PodStopReport, error) { + var report entities.PodStopReport conn, err := bindings.GetClient(ctx) if err != nil { - return err + return nil, err } params := url.Values{} if timeout != nil { @@ -173,9 +204,13 @@ func Stop(ctx context.Context, nameOrID string, timeout *int) error { } response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/stop", params, nameOrID) if err != nil { - return err + return nil, err } - return response.Process(nil) + if response.StatusCode == http.StatusNotModified { + report.Id = nameOrID + return &report, nil + } + return &report, response.Process(&report) } func Top() error { @@ -184,14 +219,15 @@ func Top() error { } // Unpause unpauses all paused containers in a Pod. -func Unpause(ctx context.Context, nameOrID string) error { +func Unpause(ctx context.Context, nameOrID string) (*entities.PodUnpauseReport, error) { + var report entities.PodUnpauseReport conn, err := bindings.GetClient(ctx) if err != nil { - return err + return nil, err } response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/unpause", nil, nameOrID) if err != nil { - return err + return nil, err } - return response.Process(nil) + return &report, response.Process(&report) } diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index f5465c803..9dd9cb707 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -1,12 +1,12 @@ package test_bindings import ( - "github.com/containers/libpod/libpod/define" "net/http" "strconv" "strings" "time" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/bindings/containers" "github.com/containers/libpod/pkg/specgen" @@ -34,7 +34,7 @@ var _ = Describe("Podman containers ", func() { AfterEach(func() { s.Kill() - //bt.cleanup() + bt.cleanup() }) It("podman pause a bogus container", func() { @@ -380,4 +380,34 @@ var _ = Describe("Podman containers ", func() { _, err = time.Parse(time.RFC1123Z, o) Expect(err).To(BeNil()) }) + + It("podman top", func() { + var name = "top" + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + + // By name + output, err := containers.Top(bt.conn, name, nil) + Expect(err).To(BeNil()) + + // By id + output, err = containers.Top(bt.conn, cid, nil) + Expect(err).To(BeNil()) + + // With descriptors + output, err = containers.Top(bt.conn, cid, []string{"user,pid,hpid"}) + Expect(err).To(BeNil()) + header := strings.Split(output[0], "\t") + for _, d := range []string{"USER", "PID", "HPID"} { + Expect(d).To(BeElementOf(header)) + } + + // With bogus ID + _, err = containers.Top(bt.conn, "IdoNotExist", nil) + Expect(err).ToNot(BeNil()) + + // With bogus descriptors + _, err = containers.Top(bt.conn, cid, []string{"Me,Neither"}) + Expect(err).To(BeNil()) + }) }) diff --git a/pkg/bindings/test/exec_test.go b/pkg/bindings/test/exec_test.go new file mode 100644 index 000000000..1ef2197b6 --- /dev/null +++ b/pkg/bindings/test/exec_test.go @@ -0,0 +1,77 @@ +package test_bindings + +import ( + "time" + + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/bindings/containers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" +) + +var _ = Describe("Podman containers exec", 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 exec create makes an exec session", func() { + name := "testCtr" + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + + execConfig := new(handlers.ExecCreateConfig) + execConfig.Cmd = []string{"echo", "hello world"} + + sessionID, err := containers.ExecCreate(bt.conn, name, execConfig) + Expect(err).To(BeNil()) + Expect(sessionID).To(Not(Equal(""))) + + inspectOut, err := containers.ExecInspect(bt.conn, sessionID) + Expect(err).To(BeNil()) + Expect(inspectOut.ContainerID).To(Equal(cid)) + Expect(inspectOut.ProcessConfig.Entrypoint).To(Equal("echo")) + Expect(len(inspectOut.ProcessConfig.Arguments)).To(Equal(1)) + Expect(inspectOut.ProcessConfig.Arguments[0]).To(Equal("hello world")) + }) + + It("Podman exec create with bad command fails", func() { + name := "testCtr" + _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + + execConfig := new(handlers.ExecCreateConfig) + + _, err = containers.ExecCreate(bt.conn, name, execConfig) + Expect(err).To(Not(BeNil())) + }) + + It("Podman exec create with invalid container fails", func() { + execConfig := new(handlers.ExecCreateConfig) + execConfig.Cmd = []string{"echo", "hello world"} + + _, err := containers.ExecCreate(bt.conn, "doesnotexist", execConfig) + Expect(err).To(Not(BeNil())) + }) + + It("Podman exec inspect on invalid session fails", func() { + _, err := containers.ExecInspect(bt.conn, "0000000000000000000000000000000000000000000000000000000000000000") + Expect(err).To(Not(BeNil())) + }) +}) diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go index 5e4cfe7be..13b6086c3 100644 --- a/pkg/bindings/test/images_test.go +++ b/pkg/bindings/test/images_test.go @@ -16,22 +16,22 @@ import ( var _ = Describe("Podman images", func() { var ( - //tempdir string - //err error - //podmanTest *PodmanTestIntegration + // tempdir string + // err error + // podmanTest *PodmanTestIntegration bt *bindingTest s *gexec.Session err error ) BeforeEach(func() { - //tempdir, err = CreateTempDirInTempDir() - //if err != nil { + // tempdir, err = CreateTempDirInTempDir() + // if err != nil { // os.Exit(1) - //} - //podmanTest = PodmanTestCreate(tempdir) - //podmanTest.Setup() - //podmanTest.SeedImages() + // } + // podmanTest = PodmanTestCreate(tempdir) + // podmanTest.Setup() + // podmanTest.SeedImages() bt = newBindingTest() bt.RestoreImagesFromCache() s = bt.startAPIService() @@ -41,12 +41,13 @@ var _ = Describe("Podman images", func() { }) AfterEach(func() { - //podmanTest.Cleanup() - //f := CurrentGinkgoTestDescription() - //processTestResult(f) + // podmanTest.Cleanup() + // f := CurrentGinkgoTestDescription() + // processTestResult(f) s.Kill() bt.cleanup() }) + It("inspect image", func() { // Inspect invalid image be 404 _, err = images.GetImage(bt.conn, "foobar5000", nil) @@ -71,7 +72,7 @@ var _ = Describe("Podman images", func() { Expect(err).To(BeNil()) // TODO it looks like the images API alwaays returns size regardless // of bool or not. What should we do ? - //Expect(data.Size).To(BeZero()) + // Expect(data.Size).To(BeZero()) // Enabling the size parameter should result in size being populated data, err = images.GetImage(bt.conn, alpine.name, &bindings.PTrue) @@ -142,7 +143,7 @@ var _ = Describe("Podman images", func() { err = images.Tag(bt.conn, alpine.shortName, "demo", alpine.shortName) Expect(err).To(BeNil()) - //Validates if name updates when the image is retagged. + // Validates if name updates when the image is retagged. _, err := images.GetImage(bt.conn, "alpine:demo", nil) Expect(err).To(BeNil()) @@ -165,7 +166,7 @@ var _ = Describe("Podman images", func() { Expect(err).To(BeNil()) Expect(len(imageSummary)).To(Equal(3)) - //Validate the image names. + // Validate the image names. var names []string for _, i := range imageSummary { names = append(names, i.RepoTags...) @@ -289,6 +290,7 @@ var _ = Describe("Podman images", func() { Expect(data.Comment).To(Equal(testMessage)) }) + It("History Image", func() { // a bogus name should return a 404 _, err := images.History(bt.conn, "foobar") @@ -343,4 +345,12 @@ var _ = Describe("Podman images", func() { Expect(len(imgs)).To(BeNumerically(">=", 1)) }) + It("Prune images", func() { + trueBoxed := true + results, err := images.Prune(bt.conn, &trueBoxed, nil) + Expect(err).NotTo(HaveOccurred()) + Expect(len(results)).To(BeNumerically(">", 0)) + Expect(results).To(ContainElement("docker.io/library/alpine:latest")) + }) + }) diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go index e94048a9c..0f786e341 100644 --- a/pkg/bindings/test/pods_test.go +++ b/pkg/bindings/test/pods_test.go @@ -7,6 +7,7 @@ import ( "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/bindings/pods" + "github.com/containers/libpod/pkg/specgen" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" @@ -71,7 +72,7 @@ var _ = Describe("Podman pods", func() { Expect(len(podSummary)).To(Equal(2)) var names []string for _, i := range podSummary { - names = append(names, i.Config.Name) + names = append(names, i.Name) } Expect(StringInSlice(newpod, names)).To(BeTrue()) Expect(StringInSlice("newpod2", names)).To(BeTrue()) @@ -79,9 +80,7 @@ var _ = Describe("Podman pods", func() { // The test validates the list pod endpoint with passing filters as the params. It("List pods with filters", func() { - var ( - newpod2 string = "newpod2" - ) + newpod2 := "newpod2" bt.Podcreate(&newpod2) _, err = bt.RunTopContainer(nil, &bindings.PTrue, &newpod) Expect(err).To(BeNil()) @@ -109,13 +108,14 @@ var _ = Describe("Podman pods", func() { Expect(len(filteredPods)).To(BeNumerically("==", 1)) var names []string for _, i := range filteredPods { - names = append(names, i.Config.Name) + names = append(names, i.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) + Expect(err).To(BeNil()) id := response.Config.ID filters["id"] = []string{id} filteredPods, err = pods.List(bt.conn, filters) @@ -123,7 +123,7 @@ var _ = Describe("Podman pods", func() { Expect(len(filteredPods)).To(BeNumerically("==", 1)) names = names[:0] for _, i := range filteredPods { - names = append(names, i.Config.Name) + names = append(names, i.Name) } Expect(StringInSlice("newpod", names)).To(BeTrue()) @@ -134,7 +134,7 @@ var _ = Describe("Podman pods", func() { Expect(len(filteredPods)).To(BeNumerically("==", 1)) names = names[:0] for _, i := range filteredPods { - names = append(names, i.Config.Name) + names = append(names, i.Name) } Expect(StringInSlice("newpod", names)).To(BeTrue()) }) @@ -157,7 +157,7 @@ var _ = Describe("Podman pods", func() { // TODO fix this Skip("Pod behavior is jacked right now.") // Pause invalid container - err := pods.Pause(bt.conn, "dummyName") + _, err := pods.Pause(bt.conn, "dummyName") Expect(err).ToNot(BeNil()) code, _ := bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusNotFound)) @@ -169,9 +169,10 @@ var _ = Describe("Podman pods", func() { // Binding needs to be modified to inspect the pod state. // Since we don't have a pod state we inspect the states of the containers within the pod. // Pause a valid container - err = pods.Pause(bt.conn, newpod) + _, err = pods.Pause(bt.conn, newpod) Expect(err).To(BeNil()) response, err := pods.Inspect(bt.conn, newpod) + Expect(err).To(BeNil()) Expect(response.State.Status).To(Equal(define.PodStatePaused)) for _, i := range response.Containers { Expect(define.StringToContainerStatus(i.State)). @@ -179,9 +180,10 @@ var _ = Describe("Podman pods", func() { } // Unpause a valid container - err = pods.Unpause(bt.conn, newpod) + _, err = pods.Unpause(bt.conn, newpod) Expect(err).To(BeNil()) response, err = pods.Inspect(bt.conn, newpod) + Expect(err).To(BeNil()) Expect(response.State.Status).To(Equal(define.PodStateRunning)) for _, i := range response.Containers { Expect(define.StringToContainerStatus(i.State)). @@ -191,28 +193,29 @@ var _ = Describe("Podman pods", func() { It("start stop restart pod", func() { // Start an invalid pod - err = pods.Start(bt.conn, "dummyName") + _, err = pods.Start(bt.conn, "dummyName") Expect(err).ToNot(BeNil()) code, _ := bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusNotFound)) // Stop an invalid pod - err = pods.Stop(bt.conn, "dummyName", nil) + _, err = pods.Stop(bt.conn, "dummyName", nil) Expect(err).ToNot(BeNil()) code, _ = bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusNotFound)) // Restart an invalid pod - err = pods.Restart(bt.conn, "dummyName") + _, err = pods.Restart(bt.conn, "dummyName") Expect(err).ToNot(BeNil()) code, _ = bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusNotFound)) // Start a valid pod and inspect status of each container - err = pods.Start(bt.conn, newpod) + _, err = pods.Start(bt.conn, newpod) Expect(err).To(BeNil()) response, err := pods.Inspect(bt.conn, newpod) + Expect(err).To(BeNil()) Expect(response.State.Status).To(Equal(define.PodStateRunning)) for _, i := range response.Containers { Expect(define.StringToContainerStatus(i.State)). @@ -220,11 +223,11 @@ var _ = Describe("Podman pods", func() { } // Start an already running pod - err = pods.Start(bt.conn, newpod) + _, err = pods.Start(bt.conn, newpod) Expect(err).To(BeNil()) // Stop the running pods - err = pods.Stop(bt.conn, newpod, nil) + _, err = pods.Stop(bt.conn, newpod, nil) Expect(err).To(BeNil()) response, _ = pods.Inspect(bt.conn, newpod) Expect(response.State.Status).To(Equal(define.PodStateExited)) @@ -234,10 +237,10 @@ var _ = Describe("Podman pods", func() { } // Stop an already stopped pod - err = pods.Stop(bt.conn, newpod, nil) + _, err = pods.Stop(bt.conn, newpod, nil) Expect(err).To(BeNil()) - err = pods.Restart(bt.conn, newpod) + _, err = pods.Restart(bt.conn, newpod) Expect(err).To(BeNil()) response, _ = pods.Inspect(bt.conn, newpod) Expect(response.State.Status).To(Equal(define.PodStateRunning)) @@ -262,11 +265,12 @@ var _ = Describe("Podman pods", func() { // Prune only one pod which is in exited state. // Start then stop a pod. // pod moves to exited state one pod should be pruned now. - err = pods.Start(bt.conn, newpod) + _, err = pods.Start(bt.conn, newpod) Expect(err).To(BeNil()) - err = pods.Stop(bt.conn, newpod, nil) + _, err = pods.Stop(bt.conn, newpod, nil) Expect(err).To(BeNil()) response, err := pods.Inspect(bt.conn, newpod) + Expect(err).To(BeNil()) Expect(response.State.Status).To(Equal(define.PodStateExited)) err = pods.Prune(bt.conn) Expect(err).To(BeNil()) @@ -276,21 +280,23 @@ var _ = Describe("Podman pods", func() { // Test prune all pods in exited state. bt.Podcreate(&newpod) - err = pods.Start(bt.conn, newpod) + _, err = pods.Start(bt.conn, newpod) Expect(err).To(BeNil()) - err = pods.Start(bt.conn, newpod2) + _, err = pods.Start(bt.conn, newpod2) Expect(err).To(BeNil()) - err = pods.Stop(bt.conn, newpod, nil) + _, err = pods.Stop(bt.conn, newpod, nil) Expect(err).To(BeNil()) response, err = pods.Inspect(bt.conn, newpod) + Expect(err).To(BeNil()) Expect(response.State.Status).To(Equal(define.PodStateExited)) for _, i := range response.Containers { Expect(define.StringToContainerStatus(i.State)). To(Equal(define.ContainerStateStopped)) } - err = pods.Stop(bt.conn, newpod2, nil) + _, err = pods.Stop(bt.conn, newpod2, nil) Expect(err).To(BeNil()) response, err = pods.Inspect(bt.conn, newpod2) + Expect(err).To(BeNil()) Expect(response.State.Status).To(Equal(define.PodStateExited)) for _, i := range response.Containers { Expect(define.StringToContainerStatus(i.State)). @@ -302,4 +308,15 @@ var _ = Describe("Podman pods", func() { Expect(err).To(BeNil()) Expect(len(podSummary)).To(Equal(0)) }) + + It("simple create pod", func() { + ps := specgen.PodSpecGenerator{} + ps.Name = "foobar" + _, err := pods.CreatePodFromSpec(bt.conn, &ps) + Expect(err).To(BeNil()) + + exists, err := pods.Exists(bt.conn, "foobar") + Expect(err).To(BeNil()) + Expect(exists).To(BeTrue()) + }) }) diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 8b7406ae8..fbc0247ab 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -22,6 +22,11 @@ type BoolReport struct { Value bool } +// StringSliceReport wraps a string slice. +type StringSliceReport struct { + Value []string +} + type PauseUnPauseOptions struct { All bool } @@ -44,6 +49,16 @@ type StopReport struct { Id string } +type TopOptions struct { + // CLI flags. + ListDescriptors bool + Latest bool + + // Options for the API. + Descriptors []string + NameOrID string +} + type KillOptions struct { All bool Latest bool @@ -81,3 +96,13 @@ type RmReport struct { Err error Id string } + +type ContainerInspectOptions struct { + Format string + Latest bool + Size bool +} + +type ContainerInspectReport struct { + *define.InspectContainerData +} diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 2efdbd602..fceed1003 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -6,6 +6,7 @@ import ( type ContainerEngine interface { ContainerExists(ctx context.Context, nameOrId string) (*BoolReport, error) + ContainerInspect(ctx context.Context, namesOrIds []string, options ContainerInspectOptions) ([]*ContainerInspectReport, error) ContainerKill(ctx context.Context, namesOrIds []string, options KillOptions) ([]*KillReport, error) ContainerPause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) ContainerRestart(ctx context.Context, namesOrIds []string, options RestartOptions) ([]*RestartReport, error) @@ -13,7 +14,17 @@ type ContainerEngine interface { ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error) ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error) + ContainerTop(ctx context.Context, options TopOptions) (*StringSliceReport, error) + PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error) PodExists(ctx context.Context, nameOrId string) (*BoolReport, error) + PodKill(ctx context.Context, namesOrIds []string, options PodKillOptions) ([]*PodKillReport, error) + PodPause(ctx context.Context, namesOrIds []string, options PodPauseOptions) ([]*PodPauseReport, error) + PodRestart(ctx context.Context, namesOrIds []string, options PodRestartOptions) ([]*PodRestartReport, error) + PodStart(ctx context.Context, namesOrIds []string, options PodStartOptions) ([]*PodStartReport, error) + PodStop(ctx context.Context, namesOrIds []string, options PodStopOptions) ([]*PodStopReport, error) + PodRm(ctx context.Context, namesOrIds []string, options PodRmOptions) ([]*PodRmReport, error) + PodUnpause(ctx context.Context, namesOrIds []string, options PodunpauseOptions) ([]*PodUnpauseReport, error) + VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IdOrNameResponse, error) VolumeInspect(ctx context.Context, namesOrIds []string, opts VolumeInspectOptions) ([]*VolumeInspectReport, error) VolumeRm(ctx context.Context, namesOrIds []string, opts VolumeRmOptions) ([]*VolumeRmReport, error) diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go index d44fdaf53..d0c860a04 100644 --- a/pkg/domain/entities/engine_image.go +++ b/pkg/domain/entities/engine_image.go @@ -5,7 +5,8 @@ import ( ) type ImageEngine interface { - Delete(ctx context.Context, nameOrId string, opts ImageDeleteOptions) (*ImageDeleteReport, error) + Delete(ctx context.Context, nameOrId []string, opts ImageDeleteOptions) (*ImageDeleteReport, error) + Exists(ctx context.Context, nameOrId string) (*BoolReport, error) History(ctx context.Context, nameOrId string, opts ImageHistoryOptions) (*ImageHistoryReport, error) List(ctx context.Context, opts ImageListOptions) ([]*ImageSummary, error) Prune(ctx context.Context, opts ImagePruneOptions) (*ImagePruneReport, error) diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index f04317e37..20af0356f 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -81,14 +81,18 @@ func (i *ImageSummary) IsDangling() bool { } type ImageDeleteOptions struct { + All bool Force bool } -// ImageDeleteResponse is the response for removing an image from storage and containers -// what was untagged vs actually removed +// ImageDeleteResponse is the response for removing one or more image(s) from storage +// and containers what was untagged vs actually removed type ImageDeleteReport struct { - Untagged []string `json:"untagged"` - Deleted string `json:"deleted"` + Untagged []string `json:",omitempty"` + Deleted []string `json:",omitempty"` + Errors []error + ImageNotFound error + ImageInUse error } type ImageHistoryOptions struct{} @@ -115,7 +119,7 @@ type ImageInspectOptions struct { type ImageListOptions struct { All bool `json:"all" schema:"all"` - Filter []string `json:",omitempty"` + Filter []string `json:"Filter,omitempty"` Filters url.Values `json:"filters" schema:"filters"` } @@ -124,8 +128,9 @@ type ImageListOptions struct { // } type ImagePruneOptions struct { - All bool - Filter ImageFilter + All bool `json:"all" schema:"all"` + Filter []string `json:"filter" schema:"filter"` + Filters url.Values `json:"filters" schema:"filters"` } type ImagePruneReport struct { diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go new file mode 100644 index 000000000..efda17d65 --- /dev/null +++ b/pkg/domain/entities/pods.go @@ -0,0 +1,143 @@ +package entities + +import ( + "time" + + "github.com/containers/libpod/pkg/specgen" +) + +type PodKillOptions struct { + All bool + Latest bool + Signal string +} + +type PodKillReport struct { + Errs []error + Id string +} + +type ListPodsReport struct { + Cgroup string + Containers []*ListPodContainer + Created time.Time + Id string + Name string + Namespace string + Status string +} + +type ListPodContainer struct { + Id string + Names string + Status string +} + +type PodPauseOptions struct { + All bool + Latest bool +} + +type PodPauseReport struct { + Errs []error + Id string +} + +type PodunpauseOptions struct { + All bool + Latest bool +} + +type PodUnpauseReport struct { + Errs []error + Id string +} + +type PodStopOptions struct { + All bool + Ignore bool + Latest bool + Timeout int +} + +type PodStopReport struct { + Errs []error + Id string +} + +type PodRestartOptions struct { + All bool + Latest bool +} + +type PodRestartReport struct { + Errs []error + Id string +} + +type PodStartOptions struct { + All bool + Latest bool +} + +type PodStartReport struct { + Errs []error + Id string +} + +type PodRmOptions struct { + All bool + Force bool + Ignore bool + Latest bool +} + +type PodRmReport struct { + Err error + Id string +} + +type PodCreateOptions struct { + CGroupParent string + Hostname string + Infra bool + InfraImage string + InfraCommand string + Labels map[string]string + Name string + Net *NetOptions + Share []string +} + +type PodCreateReport struct { + Id string +} + +func (p PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) { + // Basic Config + s.Name = p.Name + s.Hostname = p.Hostname + s.Labels = p.Labels + s.NoInfra = !p.Infra + s.InfraCommand = []string{p.InfraCommand} + s.InfraImage = p.InfraImage + s.SharedNamespaces = p.Share + + // Networking config + s.NetNS = p.Net.Network + s.StaticIP = p.Net.StaticIP + s.StaticMAC = p.Net.StaticMAC + s.PortMappings = p.Net.PublishPorts + s.CNINetworks = p.Net.CNINetworks + if p.Net.DNSHost { + s.NoManageResolvConf = true + } + s.DNSServer = p.Net.DNSServers + s.DNSSearch = p.Net.DNSSearch + s.DNSOption = p.Net.DNSOptions + s.NoManageHosts = p.Net.NoHosts + s.HostAdd = p.Net.AddHosts + + // Cgroup + s.CgroupParent = p.CGroupParent +} diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go index e7757a74b..a1a729584 100644 --- a/pkg/domain/entities/types.go +++ b/pkg/domain/entities/types.go @@ -1,5 +1,12 @@ package entities +import ( + "net" + + "github.com/containers/libpod/pkg/specgen" + "github.com/cri-o/ocicni/pkg/ocicni" +) + type Container struct { IdOrNamed } @@ -15,3 +22,23 @@ type Report struct { type PodDeleteReport struct{ Report } type PodPruneOptions struct{} + +type PodPruneReport struct{ Report } +type VolumeDeleteOptions struct{} +type VolumeDeleteReport struct{ Report } + +// NetOptions reflect the shared network options between +// pods and containers +type NetOptions struct { + AddHosts []string + CNINetworks []string + DNSHost bool + DNSOptions []string + DNSSearch []string + DNSServers []net.IP + Network specgen.Namespace + NoHosts bool + PublishPorts []ocicni.PortMapping + StaticIP *net.IP + StaticMAC *net.HardwareAddr +} diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index a3da310c2..3965c5f75 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -239,3 +239,41 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, } return reports, nil } + +func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []string, options entities.ContainerInspectOptions) ([]*entities.ContainerInspectReport, error) { + var reports []*entities.ContainerInspectReport + ctrs, err := shortcuts.GetContainersByContext(false, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, c := range ctrs { + data, err := c.Inspect(options.Size) + if err != nil { + return nil, err + } + reports = append(reports, &entities.ContainerInspectReport{InspectContainerData: data}) + } + return reports, nil +} + +func (ic *ContainerEngine) ContainerTop(ctx context.Context, options entities.TopOptions) (*entities.StringSliceReport, error) { + var ( + container *libpod.Container + err error + ) + + // Look up the container. + if options.Latest { + container, err = ic.Libpod.GetLatestContainer() + } else { + container, err = ic.Libpod.LookupContainer(options.NameOrID) + } + if err != nil { + return nil, errors.Wrap(err, "unable to lookup requested container") + } + + // Run Top. + report := &entities.StringSliceReport{} + report.Value, err = container.Top(options.Descriptors) + return report, err +} diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 6e9d7f566..44420c1e1 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -4,38 +4,102 @@ package abi import ( "context" + "fmt" libpodImage "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/domain/entities" - "github.com/containers/libpod/pkg/domain/utils" + "github.com/containers/storage" + "github.com/pkg/errors" ) -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 +func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.BoolReport, error) { + if _, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId); err != nil { + return &entities.BoolReport{}, nil } + return &entities.BoolReport{Value: true}, nil +} - results, err := ir.Libpod.RemoveImage(ctx, image, opts.Force) - if err != nil { - return nil, err +func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) { + report := entities.ImageDeleteReport{} + + if opts.All { + var previousTargets []*libpodImage.Image + repeatRun: + targets, err := ir.Libpod.ImageRuntime().GetRWImages() + if err != nil { + return &report, errors.Wrapf(err, "unable to query local images") + } + + if len(targets) > 0 && len(targets) == len(previousTargets) { + return &report, errors.New("unable to delete all images; re-run the rmi command again.") + } + previousTargets = targets + + for _, img := range targets { + isParent, err := img.IsParent(ctx) + if err != nil { + return &report, err + } + if isParent { + continue + } + err = ir.deleteImage(ctx, img, opts, report) + report.Errors = append(report.Errors, err) + } + if len(previousTargets) != 1 { + goto repeatRun + } + return &report, nil } - report := entities.ImageDeleteReport{} - if err := utils.DeepCopy(&report, results); err != nil { - return nil, err + for _, id := range nameOrId { + image, err := ir.Libpod.ImageRuntime().NewFromLocal(id) + if err != nil { + return nil, err + } + + err = ir.deleteImage(ctx, image, opts, report) + if err != nil { + return &report, err + } } return &report, nil } +func (ir *ImageEngine) deleteImage(ctx context.Context, img *libpodImage.Image, opts entities.ImageDeleteOptions, report entities.ImageDeleteReport) error { + results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force) + switch errors.Cause(err) { + case nil: + break + case storage.ErrImageUsedByContainer: + report.ImageInUse = errors.New( + fmt.Sprintf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID())) + return nil + case libpodImage.ErrNoSuchImage: + report.ImageNotFound = err + return nil + default: + return err + } + + report.Deleted = append(report.Deleted, results.Deleted) + report.Untagged = append(report.Untagged, results.Untagged...) + return nil +} + func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) { - results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, []string{}) + results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, opts.Filter) if err != nil { return nil, err } - report := entities.ImagePruneReport{} - copy(report.Report.Id, results) + report := entities.ImagePruneReport{ + Report: entities.Report{ + Id: results, + Err: nil, + }, + Size: 0, + } return &report, nil } diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go index de22de68e..619e973cf 100644 --- a/pkg/domain/infra/abi/pods.go +++ b/pkg/domain/infra/abi/pods.go @@ -4,12 +4,47 @@ package abi import ( "context" - "github.com/pkg/errors" + "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/signal" + "github.com/containers/libpod/pkg/specgen" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) +// getPodsByContext returns a slice of pods. Note that all, latest and pods are +// mutually exclusive arguments. +func getPodsByContext(all, latest bool, pods []string, runtime *libpod.Runtime) ([]*libpod.Pod, error) { + var outpods []*libpod.Pod + if all { + return runtime.GetAllPods() + } + if latest { + p, err := runtime.GetLatestPod() + if err != nil { + return nil, err + } + outpods = append(outpods, p) + return outpods, nil + } + var err error + for _, p := range pods { + pod, e := runtime.LookupPod(p) + if e != nil { + // Log all errors here, so callers don't need to. + logrus.Debugf("Error looking up pod %q: %v", p, e) + if err == nil { + err = e + } + } else { + outpods = append(outpods, pod) + } + } + return outpods, err +} + 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 { @@ -17,3 +52,201 @@ func (ic *ContainerEngine) PodExists(ctx context.Context, nameOrId string) (*ent } return &entities.BoolReport{Value: err == nil}, nil } + +func (ic *ContainerEngine) PodKill(ctx context.Context, namesOrIds []string, options entities.PodKillOptions) ([]*entities.PodKillReport, error) { + var ( + reports []*entities.PodKillReport + ) + sig, err := signal.ParseSignalNameOrNumber(options.Signal) + if err != nil { + return nil, err + } + pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + + for _, p := range pods { + report := entities.PodKillReport{Id: p.ID()} + conErrs, err := p.Kill(uint(sig)) + if err != nil { + report.Errs = []error{err} + reports = append(reports, &report) + continue + } + if len(conErrs) > 0 { + for _, err := range conErrs { + report.Errs = append(report.Errs, err) + } + reports = append(reports, &report) + continue + } + reports = append(reports, &report) + } + return reports, nil +} + +func (ic *ContainerEngine) PodPause(ctx context.Context, namesOrIds []string, options entities.PodPauseOptions) ([]*entities.PodPauseReport, error) { + var ( + reports []*entities.PodPauseReport + ) + pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, p := range pods { + report := entities.PodPauseReport{Id: p.ID()} + errs, err := p.Pause() + if err != nil { + report.Errs = []error{err} + continue + } + if len(errs) > 0 { + for _, v := range errs { + report.Errs = append(report.Errs, v) + } + reports = append(reports, &report) + continue + } + reports = append(reports, &report) + } + return reports, nil +} + +func (ic *ContainerEngine) PodUnpause(ctx context.Context, namesOrIds []string, options entities.PodunpauseOptions) ([]*entities.PodUnpauseReport, error) { + var ( + reports []*entities.PodUnpauseReport + ) + pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, p := range pods { + report := entities.PodUnpauseReport{Id: p.ID()} + errs, err := p.Unpause() + if err != nil { + report.Errs = []error{err} + continue + } + if len(errs) > 0 { + for _, v := range errs { + report.Errs = append(report.Errs, v) + } + reports = append(reports, &report) + continue + } + reports = append(reports, &report) + } + return reports, nil +} + +func (ic *ContainerEngine) PodStop(ctx context.Context, namesOrIds []string, options entities.PodStopOptions) ([]*entities.PodStopReport, error) { + var ( + reports []*entities.PodStopReport + ) + pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, p := range pods { + report := entities.PodStopReport{Id: p.ID()} + errs, err := p.StopWithTimeout(ctx, false, options.Timeout) + if err != nil { + report.Errs = []error{err} + continue + } + if len(errs) > 0 { + for _, v := range errs { + report.Errs = append(report.Errs, v) + } + reports = append(reports, &report) + continue + } + reports = append(reports, &report) + } + return reports, nil +} + +func (ic *ContainerEngine) PodRestart(ctx context.Context, namesOrIds []string, options entities.PodRestartOptions) ([]*entities.PodRestartReport, error) { + var ( + reports []*entities.PodRestartReport + ) + pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, p := range pods { + report := entities.PodRestartReport{Id: p.ID()} + errs, err := p.Restart(ctx) + if err != nil { + report.Errs = []error{err} + continue + } + if len(errs) > 0 { + for _, v := range errs { + report.Errs = append(report.Errs, v) + } + reports = append(reports, &report) + continue + } + reports = append(reports, &report) + } + return reports, nil +} + +func (ic *ContainerEngine) PodStart(ctx context.Context, namesOrIds []string, options entities.PodStartOptions) ([]*entities.PodStartReport, error) { + var ( + reports []*entities.PodStartReport + ) + pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, p := range pods { + report := entities.PodStartReport{Id: p.ID()} + errs, err := p.Start(ctx) + if err != nil { + report.Errs = []error{err} + continue + } + if len(errs) > 0 { + for _, v := range errs { + report.Errs = append(report.Errs, v) + } + reports = append(reports, &report) + continue + } + reports = append(reports, &report) + } + return reports, nil +} + +func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, options entities.PodRmOptions) ([]*entities.PodRmReport, error) { + var ( + reports []*entities.PodRmReport + ) + pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, p := range pods { + report := entities.PodRmReport{Id: p.ID()} + err := ic.Libpod.RemovePod(ctx, p, true, options.Force) + if err != nil { + report.Err = err + continue + } + reports = append(reports, &report) + } + return reports, nil +} + +func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) { + podSpec := specgen.NewPodSpecGenerator() + opts.ToPodSpecGen(podSpec) + pod, err := podSpec.MakePod(ic.Libpod) + if err != nil { + return nil, err + } + return &entities.PodCreateReport{Id: pod.ID()}, nil +} diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go index 0cc20474e..bdae4359d 100644 --- a/pkg/domain/infra/abi/volumes.go +++ b/pkg/domain/infra/abi/volumes.go @@ -107,13 +107,26 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin UID: v.UID(), GID: v.GID(), } - reports = append(reports, &entities.VolumeInspectReport{&config}) + reports = append(reports, &entities.VolumeInspectReport{VolumeConfigResponse: &config}) } return reports, nil } func (ic *ContainerEngine) VolumePrune(ctx context.Context, opts entities.VolumePruneOptions) ([]*entities.VolumePruneReport, error) { - return ic.Libpod.PruneVolumes(ctx) + var ( + reports []*entities.VolumePruneReport + ) + pruned, err := ic.Libpod.PruneVolumes(ctx) + if err != nil { + return nil, err + } + for k, v := range pruned { + reports = append(reports, &entities.VolumePruneReport{ + Err: v, + Id: k, + }) + } + return reports, nil } func (ic *ContainerEngine) VolumeList(ctx context.Context, opts entities.VolumeListOptions) ([]*entities.VolumeListReport, error) { @@ -140,7 +153,7 @@ func (ic *ContainerEngine) VolumeList(ctx context.Context, opts entities.VolumeL UID: v.UID(), GID: v.GID(), } - reports = append(reports, &entities.VolumeListReport{config}) + reports = append(reports, &entities.VolumeListReport{VolumeConfigResponse: config}) } return reports, nil } diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index a8ecff41b..3db38ea5c 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -5,6 +5,7 @@ import ( "github.com/containers/libpod/pkg/bindings/containers" "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" ) func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) { @@ -138,3 +139,36 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, } return reports, nil } + +func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []string, options entities.ContainerInspectOptions) ([]*entities.ContainerInspectReport, error) { + var ( + reports []*entities.ContainerInspectReport + ) + ctrs, err := getContainersByContext(ic.ClientCxt, false, namesOrIds) + if err != nil { + return nil, err + } + for _, con := range ctrs { + data, err := containers.Inspect(ic.ClientCxt, con.ID, &options.Size) + if err != nil { + return nil, err + } + reports = append(reports, &entities.ContainerInspectReport{InspectContainerData: data}) + } + return reports, nil +} + +func (ic *ContainerEngine) ContainerTop(ctx context.Context, options entities.TopOptions) (*entities.StringSliceReport, error) { + switch { + case options.Latest: + return nil, errors.New("latest is not supported") + case options.NameOrID == "": + return nil, errors.New("NameOrID must be specified") + } + + topOutput, err := containers.Top(ic.ClientCxt, options.NameOrID, options.Descriptors) + if err != nil { + return nil, err + } + return &entities.StringSliceReport{Value: topOutput}, nil +} diff --git a/pkg/domain/infra/tunnel/helpers.go b/pkg/domain/infra/tunnel/helpers.go index 11fca5278..f9183c955 100644 --- a/pkg/domain/infra/tunnel/helpers.go +++ b/pkg/domain/infra/tunnel/helpers.go @@ -4,9 +4,12 @@ import ( "context" "strings" + "github.com/containers/libpod/libpod/define" "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/bindings/pods" + "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" ) @@ -40,3 +43,34 @@ func getContainersByContext(contextWithConnection context.Context, all bool, nam } return cons, nil } + +func getPodsByContext(contextWithConnection context.Context, all bool, namesOrIds []string) ([]*entities.ListPodsReport, error) { + var ( + sPods []*entities.ListPodsReport + ) + if all && len(namesOrIds) > 0 { + return nil, errors.New("cannot lookup specific pods and all") + } + + fPods, err := pods.List(contextWithConnection, nil) + if err != nil { + return nil, err + } + if all { + return fPods, nil + } + for _, nameOrId := range namesOrIds { + var found bool + for _, f := range fPods { + if f.Name == nameOrId || strings.HasPrefix(f.Id, nameOrId) { + sPods = append(sPods, f) + found = true + break + } + } + if !found { + return nil, errors.Wrapf(define.ErrNoSuchPod, "unable to find pod %q", nameOrId) + } + } + return sPods, nil +} diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 60df40498..6a3adc9ee 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -2,34 +2,36 @@ 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 - } +func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.BoolReport, error) { + found, err := images.Exists(ir.ClientCxt, nameOrId) + return &entities.BoolReport{Value: found}, err +} - report := entities.ImageDeleteReport{ - Untagged: nil, - Deleted: "", - } +func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) { + report := entities.ImageDeleteReport{} - for _, e := range results { - if a, ok := e["Deleted"]; ok { - report.Deleted = a + for _, id := range nameOrId { + results, err := images.Remove(ir.ClientCxt, id, &opts.Force) + if err != nil { + return nil, err } + for _, e := range results { + if a, ok := e["Deleted"]; ok { + report.Deleted = append(report.Deleted, a) + } - if a, ok := e["Untagged"]; ok { - report.Untagged = append(report.Untagged, a) + if a, ok := e["Untagged"]; ok { + report.Untagged = append(report.Untagged, a) + } } } - return &report, err + return &report, nil } func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) ([]*entities.ImageSummary, error) { @@ -69,12 +71,17 @@ func (ir *ImageEngine) History(ctx context.Context, nameOrId string, opts entiti } func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) { - results, err := images.Prune(ir.ClientCxt, url.Values{}) + results, err := images.Prune(ir.ClientCxt, &opts.All, opts.Filters) if err != nil { return nil, err } - report := entities.ImagePruneReport{} - copy(report.Report.Id, results) + report := entities.ImagePruneReport{ + Report: entities.Report{ + Id: results, + Err: nil, + }, + Size: 0, + } return &report, nil } diff --git a/pkg/domain/infra/tunnel/pods.go b/pkg/domain/infra/tunnel/pods.go index 500069d51..4894874e5 100644 --- a/pkg/domain/infra/tunnel/pods.go +++ b/pkg/domain/infra/tunnel/pods.go @@ -5,9 +5,175 @@ import ( "github.com/containers/libpod/pkg/bindings/pods" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/specgen" ) 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 } + +func (ic *ContainerEngine) PodKill(ctx context.Context, namesOrIds []string, options entities.PodKillOptions) ([]*entities.PodKillReport, error) { + var ( + reports []*entities.PodKillReport + ) + foundPods, err := getPodsByContext(ic.ClientCxt, options.All, namesOrIds) + if err != nil { + return nil, err + } + for _, p := range foundPods { + response, err := pods.Kill(ic.ClientCxt, p.Id, &options.Signal) + if err != nil { + report := entities.PodKillReport{ + Errs: []error{err}, + Id: p.Id, + } + reports = append(reports, &report) + continue + } + reports = append(reports, response) + } + return reports, nil +} + +func (ic *ContainerEngine) PodPause(ctx context.Context, namesOrIds []string, options entities.PodPauseOptions) ([]*entities.PodPauseReport, error) { + var ( + reports []*entities.PodPauseReport + ) + foundPods, err := getPodsByContext(ic.ClientCxt, options.All, namesOrIds) + if err != nil { + return nil, err + } + for _, p := range foundPods { + response, err := pods.Pause(ic.ClientCxt, p.Id) + if err != nil { + report := entities.PodPauseReport{ + Errs: []error{err}, + Id: p.Id, + } + reports = append(reports, &report) + continue + } + reports = append(reports, response) + } + return reports, nil +} + +func (ic *ContainerEngine) PodUnpause(ctx context.Context, namesOrIds []string, options entities.PodunpauseOptions) ([]*entities.PodUnpauseReport, error) { + var ( + reports []*entities.PodUnpauseReport + ) + foundPods, err := getPodsByContext(ic.ClientCxt, options.All, namesOrIds) + if err != nil { + return nil, err + } + for _, p := range foundPods { + response, err := pods.Unpause(ic.ClientCxt, p.Id) + if err != nil { + report := entities.PodUnpauseReport{ + Errs: []error{err}, + Id: p.Id, + } + reports = append(reports, &report) + continue + } + reports = append(reports, response) + } + return reports, nil +} + +func (ic *ContainerEngine) PodStop(ctx context.Context, namesOrIds []string, options entities.PodStopOptions) ([]*entities.PodStopReport, error) { + var ( + reports []*entities.PodStopReport + timeout int = -1 + ) + foundPods, err := getPodsByContext(ic.ClientCxt, options.All, namesOrIds) + if err != nil { + return nil, err + } + if options.Timeout != -1 { + timeout = options.Timeout + } + for _, p := range foundPods { + response, err := pods.Stop(ic.ClientCxt, p.Id, &timeout) + if err != nil { + report := entities.PodStopReport{ + Errs: []error{err}, + Id: p.Id, + } + reports = append(reports, &report) + continue + } + reports = append(reports, response) + } + return reports, nil +} + +func (ic *ContainerEngine) PodRestart(ctx context.Context, namesOrIds []string, options entities.PodRestartOptions) ([]*entities.PodRestartReport, error) { + var reports []*entities.PodRestartReport + foundPods, err := getPodsByContext(ic.ClientCxt, options.All, namesOrIds) + if err != nil { + return nil, err + } + for _, p := range foundPods { + response, err := pods.Restart(ic.ClientCxt, p.Id) + if err != nil { + report := entities.PodRestartReport{ + Errs: []error{err}, + Id: p.Id, + } + reports = append(reports, &report) + continue + } + reports = append(reports, response) + } + return reports, nil +} + +func (ic *ContainerEngine) PodStart(ctx context.Context, namesOrIds []string, options entities.PodStartOptions) ([]*entities.PodStartReport, error) { + var reports []*entities.PodStartReport + foundPods, err := getPodsByContext(ic.ClientCxt, options.All, namesOrIds) + if err != nil { + return nil, err + } + for _, p := range foundPods { + response, err := pods.Start(ic.ClientCxt, p.Id) + if err != nil { + report := entities.PodStartReport{ + Errs: []error{err}, + Id: p.Id, + } + reports = append(reports, &report) + continue + } + reports = append(reports, response) + } + return reports, nil +} + +func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, options entities.PodRmOptions) ([]*entities.PodRmReport, error) { + var reports []*entities.PodRmReport + foundPods, err := getPodsByContext(ic.ClientCxt, options.All, namesOrIds) + if err != nil { + return nil, err + } + for _, p := range foundPods { + response, err := pods.Remove(ic.ClientCxt, p.Id, &options.Force) + if err != nil { + report := entities.PodRmReport{ + Err: err, + Id: p.Id, + } + reports = append(reports, &report) + continue + } + reports = append(reports, response) + } + return reports, nil +} + +func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) { + podSpec := specgen.NewPodSpecGenerator() + opts.ToPodSpecGen(podSpec) + return pods.CreatePodFromSpec(ic.ClientCxt, podSpec) +} diff --git a/pkg/specgen/create.go b/pkg/specgen/container_create.go index aefbe7405..cf082441d 100644 --- a/pkg/specgen/create.go +++ b/pkg/specgen/container_create.go @@ -13,7 +13,7 @@ import ( // MakeContainer creates a container based on the SpecGenerator func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, error) { - if err := s.validate(rt); err != nil { + if err := s.validate(); err != nil { return nil, errors.Wrap(err, "invalid config provided") } rtc, err := rt.GetConfig() diff --git a/pkg/specgen/validate.go b/pkg/specgen/container_validate.go index dd5ca3a55..b27659f5f 100644 --- a/pkg/specgen/validate.go +++ b/pkg/specgen/container_validate.go @@ -3,7 +3,7 @@ package specgen import ( "strings" - "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" ) @@ -23,7 +23,7 @@ func exclusiveOptions(opt1, opt2 string) error { // Validate verifies that the given SpecGenerator is valid and satisfies required // input for creating a container. -func (s *SpecGenerator) validate(rt *libpod.Runtime) error { +func (s *SpecGenerator) validate() error { // // ContainerBasicConfig @@ -138,9 +138,6 @@ func (s *SpecGenerator) validate(rt *libpod.Runtime) error { if err := s.IpcNS.validate(); err != nil { return err } - if err := validateNetNS(&s.NetNS); err != nil { - return err - } if err := s.PidNS.validate(); err != nil { return err } @@ -155,5 +152,16 @@ func (s *SpecGenerator) validate(rt *libpod.Runtime) error { if len(s.WorkDir) < 1 { s.WorkDir = "/" } + + // Set defaults if network info is not provided + if s.NetNS.NSMode == "" { + s.NetNS.NSMode = Bridge + if rootless.IsRootless() { + s.NetNS.NSMode = Slirp + } + } + if err := validateNetNS(&s.NetNS); err != nil { + return err + } return nil } diff --git a/pkg/specgen/pod_create.go b/pkg/specgen/pod_create.go new file mode 100644 index 000000000..06aa24e22 --- /dev/null +++ b/pkg/specgen/pod_create.go @@ -0,0 +1,83 @@ +package specgen + +import ( + "context" + + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" + "github.com/sirupsen/logrus" +) + +func (p *PodSpecGenerator) MakePod(rt *libpod.Runtime) (*libpod.Pod, error) { + if err := p.validate(); err != nil { + return nil, err + } + options, err := p.createPodOptions() + if err != nil { + return nil, err + } + return rt.NewPod(context.Background(), options...) +} + +func (p *PodSpecGenerator) createPodOptions() ([]libpod.PodCreateOption, error) { + var ( + options []libpod.PodCreateOption + ) + if !p.NoInfra { + options = append(options, libpod.WithInfraContainer()) + nsOptions, err := shared.GetNamespaceOptions(p.SharedNamespaces) + if err != nil { + return nil, err + } + options = append(options, nsOptions...) + } + if len(p.CgroupParent) > 0 { + options = append(options, libpod.WithPodCgroupParent(p.CgroupParent)) + } + if len(p.Labels) > 0 { + options = append(options, libpod.WithPodLabels(p.Labels)) + } + if len(p.Name) > 0 { + options = append(options, libpod.WithPodName(p.Name)) + } + if len(p.Hostname) > 0 { + options = append(options, libpod.WithPodHostname(p.Hostname)) + } + if len(p.HostAdd) > 0 { + options = append(options, libpod.WithPodHosts(p.HostAdd)) + } + if len(p.DNSOption) > 0 { + options = append(options, libpod.WithPodDNSOption(p.DNSOption)) + } + if len(p.DNSSearch) > 0 { + options = append(options, libpod.WithPodDNSSearch(p.DNSSearch)) + } + if p.StaticIP != nil { + options = append(options, libpod.WithPodStaticIP(*p.StaticIP)) + } + if p.StaticMAC != nil { + options = append(options, libpod.WithPodStaticMAC(*p.StaticMAC)) + } + if p.NoManageResolvConf { + options = append(options, libpod.WithPodUseImageResolvConf()) + } + switch p.NetNS.NSMode { + case Bridge: + logrus.Debugf("Pod using default network mode") + case Host: + logrus.Debugf("Pod will use host networking") + options = append(options, libpod.WithPodHostNetwork()) + default: + logrus.Debugf("Pod joining CNI networks: %v", p.CNINetworks) + options = append(options, libpod.WithPodNetworks(p.CNINetworks)) + } + + if p.NoManageHosts { + options = append(options, libpod.WithPodUseImageHosts()) + } + if len(p.PortMappings) > 0 { + options = append(options, libpod.WithInfraContainerPorts(p.PortMappings)) + } + options = append(options, libpod.WithPodCgroups()) + return options, nil +} diff --git a/pkg/specgen/pod_validate.go b/pkg/specgen/pod_validate.go new file mode 100644 index 000000000..50309f096 --- /dev/null +++ b/pkg/specgen/pod_validate.go @@ -0,0 +1,104 @@ +package specgen + +import ( + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/rootless" + "github.com/pkg/errors" +) + +var ( + // ErrInvalidPodSpecConfig describes an error given when the podspecgenerator is invalid + ErrInvalidPodSpecConfig error = errors.New("invalid pod spec") +) + +func exclusivePodOptions(opt1, opt2 string) error { + return errors.Wrapf(ErrInvalidPodSpecConfig, "%s and %s are mutually exclusive pod options", opt1, opt2) +} + +func (p *PodSpecGenerator) validate() error { + // PodBasicConfig + if p.NoInfra { + if len(p.InfraCommand) > 0 { + return exclusivePodOptions("NoInfra", "InfraCommand") + } + if len(p.InfraImage) > 0 { + return exclusivePodOptions("NoInfra", "InfraImage") + } + if len(p.SharedNamespaces) > 0 { + return exclusivePodOptions("NoInfo", "SharedNamespaces") + } + } + + // PodNetworkConfig + if err := p.NetNS.validate(); err != nil { + return err + } + if p.NoInfra { + if p.NetNS.NSMode == NoNetwork { + return errors.New("NoInfra and a none network cannot be used toegther") + } + if p.StaticIP != nil { + return exclusivePodOptions("NoInfra", "StaticIP") + } + if p.StaticMAC != nil { + return exclusivePodOptions("NoInfra", "StaticMAC") + } + if len(p.DNSOption) > 0 { + return exclusivePodOptions("NoInfra", "DNSOption") + } + if len(p.DNSSearch) > 0 { + return exclusivePodOptions("NoInfo", "DNSSearch") + } + if len(p.DNSServer) > 0 { + return exclusivePodOptions("NoInfra", "DNSServer") + } + if len(p.HostAdd) > 0 { + return exclusivePodOptions("NoInfra", "HostAdd") + } + if p.NoManageResolvConf { + return exclusivePodOptions("NoInfra", "NoManageResolvConf") + } + } + if p.NetNS.NSMode != Bridge { + if len(p.PortMappings) > 0 { + return errors.New("PortMappings can only be used with Bridge mode networking") + } + if len(p.CNINetworks) > 0 { + return errors.New("CNINetworks can only be used with Bridge mode networking") + } + } + if p.NoManageResolvConf { + if len(p.DNSServer) > 0 { + return exclusivePodOptions("NoManageResolvConf", "DNSServer") + } + if len(p.DNSSearch) > 0 { + return exclusivePodOptions("NoManageResolvConf", "DNSSearch") + } + if len(p.DNSOption) > 0 { + return exclusivePodOptions("NoManageResolvConf", "DNSOption") + } + } + if p.NoManageHosts && len(p.HostAdd) > 0 { + return exclusivePodOptions("NoManageHosts", "HostAdd") + } + + if err := p.NetNS.validate(); err != nil { + return err + } + + // Set Defaults + if p.NetNS.Value == "" { + if rootless.IsRootless() { + p.NetNS.NSMode = Slirp + } else { + p.NetNS.NSMode = Bridge + } + } + if len(p.InfraImage) < 1 { + p.InfraImage = define.DefaultInfraImage + } + if len(p.InfraCommand) < 1 { + p.InfraCommand = []string{define.DefaultInfraCommand} + } + return nil +} diff --git a/pkg/specgen/pod.go b/pkg/specgen/podspecgen.go index 1aada83c4..3f830014d 100644 --- a/pkg/specgen/pod.go +++ b/pkg/specgen/podspecgen.go @@ -138,3 +138,16 @@ type PodCgroupConfig struct { // Optional. CgroupParent string `json:"cgroup_parent,omitempty"` } + +// PodSpecGenerator describes options to create a pod +// swagger:model PodSpecGenerator +type PodSpecGenerator struct { + PodBasicConfig + PodNetworkConfig + PodCgroupConfig +} + +// NewPodSpecGenerator creates a new pod spec +func NewPodSpecGenerator() *PodSpecGenerator { + return &PodSpecGenerator{} +} diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index b123c1da5..89c76c273 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -394,18 +394,18 @@ type SpecGenerator struct { // NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs func NewSpecGenerator(image string) *SpecGenerator { - net := ContainerNetworkConfig{ + networkConfig := ContainerNetworkConfig{ NetNS: Namespace{ NSMode: Bridge, }, } csc := ContainerStorageConfig{Image: image} if rootless.IsRootless() { - net.NetNS.NSMode = Slirp + networkConfig.NetNS.NSMode = Slirp } return &SpecGenerator{ ContainerStorageConfig: csc, - ContainerNetworkConfig: net, + ContainerNetworkConfig: networkConfig, } } diff --git a/pkg/varlinkapi/volumes.go b/pkg/varlinkapi/volumes.go index cbb4a70cc..e497cb537 100644 --- a/pkg/varlinkapi/volumes.go +++ b/pkg/varlinkapi/volumes.go @@ -6,7 +6,7 @@ import ( "encoding/json" "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" ) @@ -113,11 +113,11 @@ func (i *LibpodAPI) VolumesPrune(call iopodman.VarlinkCall) error { if err != nil { return call.ReplyVolumesPrune([]string{}, []string{err.Error()}) } - for _, i := range responses { - if i.Err == nil { - prunedNames = append(prunedNames, i.Id) + for k, v := range responses { + if v == nil { + prunedNames = append(prunedNames, k) } else { - prunedErrors = append(prunedErrors, i.Err.Error()) + prunedErrors = append(prunedErrors, v.Error()) } } return call.ReplyVolumesPrune(prunedNames, prunedErrors) |