diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/api/handlers/libpod/pods.go | 43 | ||||
-rw-r--r-- | pkg/api/handlers/swagger.go | 13 | ||||
-rw-r--r-- | pkg/api/handlers/types.go | 17 | ||||
-rw-r--r-- | pkg/api/server/register_containers.go | 4 | ||||
-rw-r--r-- | pkg/api/server/register_pods.go | 31 | ||||
-rw-r--r-- | pkg/bindings/pods/pods.go | 36 | ||||
-rw-r--r-- | pkg/bindings/test/containers_test.go | 6 | ||||
-rw-r--r-- | pkg/bindings/test/pods_test.go | 30 | ||||
-rw-r--r-- | pkg/domain/entities/containers.go | 17 | ||||
-rw-r--r-- | pkg/domain/entities/engine_container.go | 2 | ||||
-rw-r--r-- | pkg/domain/entities/pods.go | 10 | ||||
-rw-r--r-- | pkg/domain/infra/abi/containers.go | 48 | ||||
-rw-r--r-- | pkg/domain/infra/abi/pods.go | 22 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/containers.go | 38 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/pods.go | 16 |
15 files changed, 323 insertions, 10 deletions
diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index 7e9c2e2c0..e834029b2 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "strings" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" @@ -287,6 +288,48 @@ func PodUnpause(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, &report) } +func PodTop(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + + query := struct { + PsArgs string `schema:"ps_args"` + }{ + PsArgs: "", + } + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + + name := utils.GetName(r) + pod, err := runtime.LookupPod(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + args := []string{} + if query.PsArgs != "" { + args = append(args, query.PsArgs) + } + output, err := pod.GetPodPidInformation(args) + if err != nil { + utils.InternalServerError(w, err) + return + } + + var body = handlers.PodTopOKBody{} + if len(output) > 0 { + body.Titles = strings.Split(output[0], "\t") + for _, line := range output[1:] { + body.Processes = append(body.Processes, strings.Split(line, "\t")) + } + } + utils.WriteJSON(w, http.StatusOK, body) +} + func PodKill(w http.ResponseWriter, r *http.Request) { var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) diff --git a/pkg/api/handlers/swagger.go b/pkg/api/handlers/swagger.go index e6e937729..52763a050 100644 --- a/pkg/api/handlers/swagger.go +++ b/pkg/api/handlers/swagger.go @@ -97,14 +97,23 @@ type swagContainerInspectResponse struct { } // List processes in container -// swagger:response DockerTopResponse -type swagDockerTopResponse struct { +// swagger:response DocsContainerTopResponse +type swagContainerTopResponse struct { // in:body Body struct { ContainerTopOKBody } } +// List processes in pod +// swagger:response DocsPodTopResponse +type swagPodTopResponse struct { + // in:body + Body struct { + PodTopOKBody + } +} + // Inspect container // swagger:response LibpodInspectContainerResponse type swagLibpodInspectContainerResponse struct { diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 1ca5db3f9..89a571e67 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -133,6 +133,23 @@ type ContainerTopOKBody struct { dockerContainer.ContainerTopOKBody } +type PodTopOKBody 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"` } diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index 2656d1d89..08834ff01 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -429,7 +429,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // - application/json // responses: // 200: - // $ref: "#/responses/DockerTopResponse" + // $ref: "#/responses/DocsContainerTopResponse" // 404: // $ref: "#/responses/NoSuchContainer" // 500: @@ -1041,7 +1041,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // - application/json // responses: // 200: - // $ref: "#/responses/DockerTopResponse" + // $ref: "#/responses/DocsContainerTopResponse" // 404: // $ref: "#/responses/NoSuchContainer" // 500: diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go index 5ba2263e8..77415793b 100644 --- a/pkg/api/server/register_pods.go +++ b/pkg/api/server/register_pods.go @@ -263,5 +263,36 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/pods/{name}/unpause"), s.APIHandler(libpod.PodUnpause)).Methods(http.MethodPost) + // swagger:operation POST /libpod/pods/{name}/top pods topPod + // --- + // summary: List processes + // description: List processes running inside a pod + // produces: + // - application/json + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: | + // Name of pod to query for processes + // - in: query + // name: stream + // type: boolean + // default: true + // description: Stream the output + // - in: query + // name: ps_args + // type: string + // default: -ef + // description: arguments to pass to ps such as aux. Requires ps(1) to be installed in the container if no ps(1) compatible AIX descriptors are used. + // responses: + // 200: + // $ref: "#/responses/DocsPodTopResponse" + // 404: + // $ref: "#/responses/NoSuchContainer" + // 500: + // $ref: "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/pods/{name}/top"), s.APIHandler(libpod.PodTop)).Methods(http.MethodGet) return nil } diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go index bb0abebc4..ae87c00e9 100644 --- a/pkg/bindings/pods/pods.go +++ b/pkg/bindings/pods/pods.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/specgen" @@ -213,9 +214,38 @@ func Stop(ctx context.Context, nameOrID string, timeout *int) (*entities.PodStop return &report, response.Process(&report) } -func Top() error { - // TODO - return bindings.ErrNotImplemented // nolint:typecheck +// Top gathers statistics about the running processes in a pod. The nameOrID can be a pod name +// or a partial/full ID. The descriptors allow for specifying which data to collect from each 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, "/pods/%s/top", params, nameOrID) + if err != nil { + return nil, err + } + + body := handlers.PodTopOKBody{} + if err = response.Process(&body); err != nil { + return nil, err + } + + // handlers.PodTopOKBody{} 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 unpauses all paused containers in a Pod. diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index 9dd9cb707..a31181958 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -387,15 +387,15 @@ var _ = Describe("Podman containers ", func() { Expect(err).To(BeNil()) // By name - output, err := containers.Top(bt.conn, name, nil) + _, err = containers.Top(bt.conn, name, nil) Expect(err).To(BeNil()) // By id - output, err = containers.Top(bt.conn, cid, nil) + _, err = containers.Top(bt.conn, cid, nil) Expect(err).To(BeNil()) // With descriptors - output, err = containers.Top(bt.conn, cid, []string{"user,pid,hpid"}) + 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"} { diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go index 0f786e341..2599ec7ef 100644 --- a/pkg/bindings/test/pods_test.go +++ b/pkg/bindings/test/pods_test.go @@ -2,6 +2,7 @@ package test_bindings import ( "net/http" + "strings" "time" "github.com/containers/libpod/libpod/define" @@ -319,4 +320,33 @@ var _ = Describe("Podman pods", func() { Expect(err).To(BeNil()) Expect(exists).To(BeTrue()) }) + + // Test validates the pod top bindings + It("pod top", func() { + var name string = "podA" + + bt.Podcreate(&name) + _, err := pods.Start(bt.conn, name) + Expect(err).To(BeNil()) + + // By name + _, err = pods.Top(bt.conn, name, nil) + Expect(err).To(BeNil()) + + // With descriptors + output, err := pods.Top(bt.conn, name, []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 = pods.Top(bt.conn, "IdoNotExist", nil) + Expect(err).ToNot(BeNil()) + + // With bogus descriptors + _, err = pods.Top(bt.conn, name, []string{"Me,Neither"}) + Expect(err).ToNot(BeNil()) + }) }) diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index fbc0247ab..3389e4db5 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -1,6 +1,7 @@ package entities import ( + "io" "time" "github.com/containers/libpod/libpod/define" @@ -106,3 +107,19 @@ type ContainerInspectOptions struct { type ContainerInspectReport struct { *define.InspectContainerData } + +type CommitOptions struct { + Author string + Changes []string + Format string + ImageName string + IncludeVolumes bool + Message string + Pause bool + Quiet bool + Writer io.Writer +} + +type CommitReport struct { + Id string +} diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index fceed1003..dddaa6013 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -5,6 +5,7 @@ import ( ) type ContainerEngine interface { + ContainerCommit(ctx context.Context, nameOrId string, options CommitOptions) (*CommitReport, error) 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) @@ -24,6 +25,7 @@ type ContainerEngine interface { 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) + PodTop(ctx context.Context, options PodTopOptions) (*StringSliceReport, error) VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IdOrNameResponse, error) VolumeInspect(ctx context.Context, namesOrIds []string, opts VolumeInspectOptions) ([]*VolumeInspectReport, error) diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index efda17d65..d92d1bc7a 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -141,3 +141,13 @@ func (p PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) { // Cgroup s.CgroupParent = p.CGroupParent } + +type PodTopOptions struct { + // CLI flags. + ListDescriptors bool + Latest bool + + // Options for the API. + Descriptors []string + NameOrID string +} diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 3965c5f75..d25af24c5 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -7,8 +7,11 @@ import ( "io/ioutil" "strings" + "github.com/containers/buildah" + "github.com/containers/image/v5/manifest" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/adapter/shortcuts" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/signal" @@ -277,3 +280,48 @@ func (ic *ContainerEngine) ContainerTop(ctx context.Context, options entities.To report.Value, err = container.Top(options.Descriptors) return report, err } + +func (ic *ContainerEngine) ContainerCommit(ctx context.Context, nameOrId string, options entities.CommitOptions) (*entities.CommitReport, error) { + var ( + mimeType string + ) + ctr, err := ic.Libpod.LookupContainer(nameOrId) + if err != nil { + return nil, err + } + rtc, err := ic.Libpod.GetConfig() + if err != nil { + return nil, err + } + switch options.Format { + case "oci": + mimeType = buildah.OCIv1ImageManifest + if len(options.Message) > 0 { + return nil, errors.Errorf("messages are only compatible with the docker image format (-f docker)") + } + case "docker": + mimeType = manifest.DockerV2Schema2MediaType + default: + return nil, errors.Errorf("unrecognized image format %q", options.Format) + } + sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) + coptions := buildah.CommitOptions{ + SignaturePolicyPath: rtc.Engine.SignaturePolicyPath, + ReportWriter: options.Writer, + SystemContext: sc, + PreferredManifestType: mimeType, + } + opts := libpod.ContainerCommitOptions{ + CommitOptions: coptions, + Pause: options.Pause, + IncludeVolumes: options.IncludeVolumes, + Message: options.Message, + Changes: options.Changes, + Author: options.Author, + } + newImage, err := ctr.Commit(ctx, options.ImageName, opts) + if err != nil { + return nil, err + } + return &entities.CommitReport{Id: newImage.ID()}, nil +} diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go index 619e973cf..8abcc6e4b 100644 --- a/pkg/domain/infra/abi/pods.go +++ b/pkg/domain/infra/abi/pods.go @@ -250,3 +250,25 @@ func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreat } return &entities.PodCreateReport{Id: pod.ID()}, nil } + +func (ic *ContainerEngine) PodTop(ctx context.Context, options entities.PodTopOptions) (*entities.StringSliceReport, error) { + var ( + pod *libpod.Pod + err error + ) + + // Look up the pod. + if options.Latest { + pod, err = ic.Libpod.GetLatestPod() + } else { + pod, err = ic.Libpod.LookupPod(options.NameOrID) + } + if err != nil { + return nil, errors.Wrap(err, "unable to lookup requested container") + } + + // Run Top. + report := &entities.StringSliceReport{} + report.Value, err = pod.GetPodPidInformation(options.Descriptors) + return report, err +} diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 3db38ea5c..3c8be90dc 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -3,6 +3,8 @@ package tunnel import ( "context" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/libpod/pkg/bindings/containers" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" @@ -172,3 +174,39 @@ func (ic *ContainerEngine) ContainerTop(ctx context.Context, options entities.To } return &entities.StringSliceReport{Value: topOutput}, nil } + +func (ic *ContainerEngine) ContainerCommit(ctx context.Context, nameOrId string, options entities.CommitOptions) (*entities.CommitReport, error) { + var ( + repo string + tag string = "latest" + ) + if len(options.ImageName) > 0 { + ref, err := reference.Parse(options.ImageName) + if err != nil { + return nil, err + } + if t, ok := ref.(reference.Tagged); ok { + tag = t.Tag() + } + if r, ok := ref.(reference.Named); ok { + repo = r.Name() + } + if len(repo) < 1 { + return nil, errors.Errorf("invalid image name %q", options.ImageName) + } + } + commitOpts := containers.CommitOptions{ + Author: &options.Author, + Changes: options.Changes, + Comment: &options.Message, + Format: &options.Format, + Pause: &options.Pause, + Repo: &repo, + Tag: &tag, + } + response, err := containers.Commit(ic.ClientCxt, nameOrId, commitOpts) + if err != nil { + return nil, err + } + return &entities.CommitReport{Id: response.ID}, nil +} diff --git a/pkg/domain/infra/tunnel/pods.go b/pkg/domain/infra/tunnel/pods.go index 4894874e5..9561a9807 100644 --- a/pkg/domain/infra/tunnel/pods.go +++ b/pkg/domain/infra/tunnel/pods.go @@ -6,6 +6,7 @@ import ( "github.com/containers/libpod/pkg/bindings/pods" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/specgen" + "github.com/pkg/errors" ) func (ic *ContainerEngine) PodExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) { @@ -177,3 +178,18 @@ func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreat opts.ToPodSpecGen(podSpec) return pods.CreatePodFromSpec(ic.ClientCxt, podSpec) } + +func (ic *ContainerEngine) PodTop(ctx context.Context, options entities.PodTopOptions) (*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 := pods.Top(ic.ClientCxt, options.NameOrID, options.Descriptors) + if err != nil { + return nil, err + } + return &entities.StringSliceReport{Value: topOutput}, nil +} |