diff options
Diffstat (limited to 'cmd/podman')
-rw-r--r-- | cmd/podman/main.go | 1 | ||||
-rw-r--r-- | cmd/podman/pod.go | 25 | ||||
-rw-r--r-- | cmd/podman/pod_create.go | 109 | ||||
-rw-r--r-- | cmd/podman/pod_ps.go | 600 | ||||
-rw-r--r-- | cmd/podman/pod_rm.go | 103 |
5 files changed, 838 insertions, 0 deletions
diff --git a/cmd/podman/main.go b/cmd/podman/main.go index f533a8b13..a83dc5fb4 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -71,6 +71,7 @@ func main() { mountCommand, pauseCommand, psCommand, + podCommand, portCommand, pullCommand, pushCommand, diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go new file mode 100644 index 000000000..6cf2920a5 --- /dev/null +++ b/cmd/podman/pod.go @@ -0,0 +1,25 @@ +package main + +import ( + "github.com/urfave/cli" +) + +var ( + podDescription = ` + podman pod + + Manage container pods. + Pods are a group of one or more containers sharing the same network, pid and ipc namespaces. +` + podCommand = cli.Command{ + Name: "pod", + Usage: "Manage pods", + Description: podDescription, + UseShortOptionHandling: true, + Subcommands: []cli.Command{ + podCreateCommand, + podPsCommand, + podRmCommand, + }, + } +) diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go new file mode 100644 index 000000000..f86faa409 --- /dev/null +++ b/cmd/podman/pod_create.go @@ -0,0 +1,109 @@ +package main + +import ( + "fmt" + "os" + + "github.com/pkg/errors" + "github.com/projectatomic/libpod/cmd/podman/libpodruntime" + "github.com/projectatomic/libpod/libpod" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +var podCreateDescription = "Creates a new empty pod. The pod ID is then" + + " printed to stdout. You can then start it at any time with the" + + " podman pod start <pod_id> command. The pod will be created with the" + + " initial state 'created'." + +var podCreateFlags = []cli.Flag{ + cli.StringFlag{ + Name: "cgroup-parent", + Usage: "Set parent cgroup for the pod", + }, + cli.StringSliceFlag{ + Name: "label-file", + Usage: "Read in a line delimited file of labels (default [])", + }, + cli.StringSliceFlag{ + Name: "label, l", + Usage: "Set metadata on pod (default [])", + }, + cli.StringFlag{ + Name: "name, n", + Usage: "Assign a name to the pod", + }, + cli.StringFlag{ + Name: "pod-id-file", + Usage: "Write the pod ID to the file", + }, +} + +var podCreateCommand = cli.Command{ + Name: "create", + Usage: "create a new empty pod", + Description: podCreateDescription, + Flags: podCreateFlags, + Action: podCreateCmd, + SkipArgReorder: true, + UseShortOptionHandling: true, +} + +func podCreateCmd(c *cli.Context) error { + var options []libpod.PodCreateOption + var err error + + if err = validateFlags(c, createFlags); err != nil { + return err + } + + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "error creating libpod runtime") + } + defer runtime.Shutdown(false) + + if c.IsSet("pod-id-file") { + if _, err = os.Stat(c.String("pod-id-file")); err == nil { + return errors.Errorf("pod id file exists. ensure another pod is not using it or delete %s", c.String("pod-id-file")) + } + if err = libpod.WriteFile("", c.String("pod-id-file")); err != nil { + return errors.Wrapf(err, "unable to write pod id file %s", c.String("pod-id-file")) + } + } + + if c.IsSet("cgroup-parent") { + options = append(options, libpod.WithPodCgroupParent(c.String("cgroup-parent"))) + } + + labels, err := getAllLabels(c.StringSlice("label-file"), c.StringSlice("label")) + if err != nil { + return errors.Wrapf(err, "unable to process labels") + } + if len(labels) != 0 { + options = append(options, libpod.WithPodLabels(labels)) + } + + if c.IsSet("name") { + options = append(options, libpod.WithPodName(c.String("name"))) + } + + // always have containers use pod cgroups + options = append(options, libpod.WithPodCgroups()) + + pod, err := runtime.NewPod(options...) + if err != nil { + return err + } + + if c.IsSet("pod-id-file") { + err = libpod.WriteFile(pod.ID(), c.String("pod-id-file")) + if err != nil { + logrus.Error(err) + } + } + + fmt.Printf("%s\n", pod.ID()) + + return nil +} diff --git a/cmd/podman/pod_ps.go b/cmd/podman/pod_ps.go new file mode 100644 index 000000000..470810901 --- /dev/null +++ b/cmd/podman/pod_ps.go @@ -0,0 +1,600 @@ +package main + +import ( + "reflect" + "sort" + "strconv" + "strings" + "time" + + "github.com/docker/go-units" + "github.com/pkg/errors" + "github.com/projectatomic/libpod/cmd/podman/batchcontainer" + "github.com/projectatomic/libpod/cmd/podman/formats" + "github.com/projectatomic/libpod/cmd/podman/libpodruntime" + "github.com/projectatomic/libpod/libpod" + "github.com/projectatomic/libpod/pkg/util" + "github.com/urfave/cli" +) + +const ( + STOPPED = "Stopped" + RUNNING = "Running" + PAUSED = "Paused" + EXITED = "Exited" + ERROR = "Error" + CREATED = "Created" + NUM_CTR_INFO = 10 +) + +var ( + bc_opts batchcontainer.PsOptions +) + +type podPsCtrInfo struct { + Name string `"json:name,omitempty"` + Id string `"json:id,omitempty"` + Status string `"json:status,omitempty"` +} + +type podPsOptions struct { + NoTrunc bool + Format string + Sort string + Quiet bool + NumberOfContainers bool + Cgroup bool + NamesOfContainers bool + IdsOfContainers bool + StatusOfContainers bool +} + +type podPsTemplateParams struct { + Created string + ID string + Name string + NumberOfContainers int + Status string + Cgroup string + UsePodCgroup bool + ContainerInfo string +} + +// podPsJSONParams is used as a base structure for the psParams +// If template output is requested, podPsJSONParams will be converted to +// podPsTemplateParams. +// podPsJSONParams will be populated by data from libpod.Container, +// the members of the struct are the sama data types as their sources. +type podPsJSONParams struct { + CreatedAt time.Time `json:"createdAt"` + ID string `json:"id"` + Name string `json:"name"` + NumberOfContainers int `json:"numberofcontainers"` + Status string `json:"status"` + CtrsInfo []podPsCtrInfo `json:"containerinfo,omitempty"` + Cgroup string `json:"cgroup,omitempty"` + UsePodCgroup bool `json:"podcgroup,omitempty"` +} + +// Type declaration and functions for sorting the pod PS output +type podPsSorted []podPsJSONParams + +func (a podPsSorted) Len() int { return len(a) } +func (a podPsSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +type podPsSortedCreated struct{ podPsSorted } + +func (a podPsSortedCreated) Less(i, j int) bool { + return a.podPsSorted[i].CreatedAt.After(a.podPsSorted[j].CreatedAt) +} + +type podPsSortedId struct{ podPsSorted } + +func (a podPsSortedId) Less(i, j int) bool { return a.podPsSorted[i].ID < a.podPsSorted[j].ID } + +type podPsSortedNumber struct{ podPsSorted } + +func (a podPsSortedNumber) Less(i, j int) bool { + return len(a.podPsSorted[i].CtrsInfo) < len(a.podPsSorted[j].CtrsInfo) +} + +type podPsSortedName struct{ podPsSorted } + +func (a podPsSortedName) Less(i, j int) bool { return a.podPsSorted[i].Name < a.podPsSorted[j].Name } + +type podPsSortedStatus struct{ podPsSorted } + +func (a podPsSortedStatus) Less(i, j int) bool { + return a.podPsSorted[i].Status < a.podPsSorted[j].Status +} + +var ( + podPsFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "cgroup", + Usage: "Print the Cgroup information of the pod", + }, + cli.BoolFlag{ + Name: "ctr-names", + Usage: "Display the container names", + }, + cli.BoolFlag{ + Name: "ctr-ids", + Usage: "Display the container UUIDs. If no-trunc is not set they will be truncated", + }, + cli.BoolFlag{ + Name: "ctr-status", + Usage: "Display the container status", + }, + cli.StringFlag{ + Name: "filter, f", + Usage: "Filter output based on conditions given", + }, + cli.StringFlag{ + Name: "format", + Usage: "Pretty-print pods to JSON or using a Go template", + }, + cli.BoolFlag{ + Name: "latest, l", + Usage: "Show the latest pod created", + }, + cli.BoolFlag{ + Name: "no-trunc", + Usage: "Do not truncate pod and container IDs", + }, + cli.BoolFlag{ + Name: "quiet, q", + Usage: "Print the numeric IDs of the pods only", + }, + cli.StringFlag{ + Name: "sort", + Usage: "Sort output by created, id, name, or number", + Value: "created", + }, + } + podPsDescription = "List all pods on system including their names, ids and current state." + podPsCommand = cli.Command{ + Name: "ps", + Aliases: []string{"ls", "list"}, + Usage: "List pods", + Description: podPsDescription, + Flags: podPsFlags, + Action: podPsCmd, + UseShortOptionHandling: true, + } +) + +func podPsCmd(c *cli.Context) error { + if err := validateFlags(c, podPsFlags); err != nil { + return err + } + + if err := podPsCheckFlagsPassed(c); err != nil { + return errors.Wrapf(err, "error with flags passed") + } + + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "error creating libpod runtime") + } + defer runtime.Shutdown(false) + + if len(c.Args()) > 0 { + return errors.Errorf("too many arguments, ps takes no arguments") + } + + opts := podPsOptions{ + NoTrunc: c.Bool("no-trunc"), + Quiet: c.Bool("quiet"), + Sort: c.String("sort"), + IdsOfContainers: c.Bool("ctr-ids"), + NamesOfContainers: c.Bool("ctr-names"), + StatusOfContainers: c.Bool("ctr-status"), + } + + opts.Format = genPodPsFormat(c) + + var filterFuncs []libpod.PodFilter + if c.String("filter") != "" { + filters := strings.Split(c.String("filter"), ",") + for _, f := range filters { + filterSplit := strings.Split(f, "=") + if len(filterSplit) < 2 { + return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) + } + generatedFunc, err := generatePodFilterFuncs(filterSplit[0], filterSplit[1], runtime) + if err != nil { + return errors.Wrapf(err, "invalid filter") + } + filterFuncs = append(filterFuncs, generatedFunc) + } + } + + var pods []*libpod.Pod + if c.IsSet("latest") { + pod, err := runtime.GetLatestPod() + if err != nil { + return err + } + pods = append(pods, pod) + } else { + pods, err = runtime.GetAllPods() + if err != nil { + return err + } + } + + podsFiltered := make([]*libpod.Pod, 0, len(pods)) + for _, pod := range pods { + include := true + for _, filter := range filterFuncs { + include = include && filter(pod) + } + + if include { + podsFiltered = append(podsFiltered, pod) + } + } + + return generatePodPsOutput(podsFiltered, opts, runtime) +} + +// podPsCheckFlagsPassed checks if mutually exclusive flags are passed together +func podPsCheckFlagsPassed(c *cli.Context) error { + // quiet, and format with Go template are mutually exclusive + flags := 0 + if c.Bool("quiet") { + flags++ + } + if c.IsSet("format") && c.String("format") != formats.JSONString { + flags++ + } + if flags > 1 { + return errors.Errorf("quiet and format with Go template are mutually exclusive") + } + return nil +} + +func generatePodFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) (func(pod *libpod.Pod) bool, error) { + switch filter { + case "ctr-ids": + return func(p *libpod.Pod) bool { + ctrIds, err := p.AllContainersByID() + if err != nil { + return false + } + return util.StringInSlice(filterValue, ctrIds) + }, nil + case "ctr-names": + return func(p *libpod.Pod) bool { + ctrs, err := p.AllContainers() + if err != nil { + return false + } + for _, ctr := range ctrs { + if filterValue == ctr.Name() { + return true + } + } + return false + }, nil + case "ctr-number": + return func(p *libpod.Pod) bool { + ctrIds, err := p.AllContainersByID() + if err != nil { + return false + } + + fVint, err2 := strconv.Atoi(filterValue) + if err2 != nil { + return false + } + return len(ctrIds) == fVint + }, nil + case "ctr-status": + if !util.StringInSlice(filterValue, []string{"created", "restarting", "running", "paused", "exited", "unknown"}) { + return nil, errors.Errorf("%s is not a valid status", filterValue) + } + return func(p *libpod.Pod) bool { + ctrs, err := p.AllContainers() + if err != nil { + return false + } + for _, ctr := range ctrs { + status, err := ctr.State() + if err != nil { + return false + } + state := status.String() + if status == libpod.ContainerStateConfigured { + state = "created" + } + if state == filterValue { + return true + } + } + return false + }, nil + case "id": + return func(p *libpod.Pod) bool { + return strings.Contains(p.ID(), filterValue) + }, nil + case "name": + return func(p *libpod.Pod) bool { + return strings.Contains(p.Name(), filterValue) + }, nil + case "status": + if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created"}) { + return nil, errors.Errorf("%s is not a valid pod status", filterValue) + } + return func(p *libpod.Pod) bool { + ctrs, err := p.AllContainers() + if err != nil { + return false + } + status, err := getPodStatus(ctrs) + if err != nil { + return false + } + if strings.ToLower(status) == filterValue { + return true + } + return false + }, nil + } + return nil, errors.Errorf("%s is an invalid filter", filter) +} + +// generate the template based on conditions given +func genPodPsFormat(c *cli.Context) string { + format := "" + if c.String("format") != "" { + // "\t" from the command line is not being recognized as a tab + // replacing the string "\t" to a tab character if the user passes in "\t" + format = strings.Replace(c.String("format"), `\t`, "\t", -1) + } else if c.Bool("quiet") { + format = formats.IDString + } else { + format = "table {{.ID}}\t{{.Name}}\t{{.Status}}\t{{.Created}}" + if c.Bool("cgroup") { + format += "\t{{.Cgroup}}\t{{.UsePodCgroup}}" + } + if c.Bool("ctr-names") || c.Bool("ctr-ids") || c.Bool("ctr-status") { + format += "\t{{.ContainerInfo}}" + } else { + format += "\t{{.NumberOfContainers}}" + } + } + return format +} + +func podPsToGeneric(templParams []podPsTemplateParams, JSONParams []podPsJSONParams) (genericParams []interface{}) { + if len(templParams) > 0 { + for _, v := range templParams { + genericParams = append(genericParams, interface{}(v)) + } + return + } + for _, v := range JSONParams { + genericParams = append(genericParams, interface{}(v)) + } + return +} + +// generate the accurate header based on template given +func (p *podPsTemplateParams) podHeaderMap() map[string]string { + v := reflect.Indirect(reflect.ValueOf(p)) + values := make(map[string]string) + + for i := 0; i < v.NumField(); i++ { + key := v.Type().Field(i).Name + value := key + if value == "ID" { + value = "Pod" + value + } + values[key] = strings.ToUpper(splitCamelCase(value)) + } + return values +} + +func sortPodPsOutput(sortBy string, psOutput podPsSorted) (podPsSorted, error) { + switch sortBy { + case "created": + sort.Sort(podPsSortedCreated{psOutput}) + case "id": + sort.Sort(podPsSortedId{psOutput}) + case "name": + sort.Sort(podPsSortedName{psOutput}) + case "number": + sort.Sort(podPsSortedNumber{psOutput}) + case "status": + sort.Sort(podPsSortedStatus{psOutput}) + default: + return nil, errors.Errorf("invalid option for --sort, options are: id, names, or number") + } + return psOutput, nil +} + +// getPodTemplateOutput returns the modified container information +func getPodTemplateOutput(psParams []podPsJSONParams, opts podPsOptions) ([]podPsTemplateParams, error) { + var ( + psOutput []podPsTemplateParams + ) + + for _, psParam := range psParams { + podID := psParam.ID + var ctrStr string + + truncated := "" + if !opts.NoTrunc { + podID = shortID(podID) + if len(psParam.CtrsInfo) > NUM_CTR_INFO { + psParam.CtrsInfo = psParam.CtrsInfo[:NUM_CTR_INFO] + truncated = "..." + } + } + for _, ctrInfo := range psParam.CtrsInfo { + ctrStr += "[ " + if opts.IdsOfContainers { + if opts.NoTrunc { + ctrStr += ctrInfo.Id + } else { + ctrStr += shortID(ctrInfo.Id) + } + } + if opts.NamesOfContainers { + ctrStr += ctrInfo.Name + " " + } + if opts.StatusOfContainers { + ctrStr += ctrInfo.Status + " " + } + ctrStr += "] " + } + ctrStr += truncated + params := podPsTemplateParams{ + Created: units.HumanDuration(time.Since(psParam.CreatedAt)) + " ago", + ID: podID, + Name: psParam.Name, + Status: psParam.Status, + NumberOfContainers: psParam.NumberOfContainers, + UsePodCgroup: psParam.UsePodCgroup, + Cgroup: psParam.Cgroup, + ContainerInfo: ctrStr, + } + + psOutput = append(psOutput, params) + } + + return psOutput, nil +} + +func getPodStatus(ctrs []*libpod.Container) (string, error) { + ctrNum := len(ctrs) + if ctrNum == 0 { + return CREATED, nil + } + statuses := map[string]int{ + STOPPED: 0, + RUNNING: 0, + PAUSED: 0, + CREATED: 0, + ERROR: 0, + } + for _, ctr := range ctrs { + state, err := ctr.State() + if err != nil { + return "", err + } + switch state { + case libpod.ContainerStateStopped: + statuses[STOPPED]++ + case libpod.ContainerStateRunning: + statuses[RUNNING]++ + case libpod.ContainerStatePaused: + statuses[PAUSED]++ + case libpod.ContainerStateCreated, libpod.ContainerStateConfigured: + statuses[CREATED]++ + default: + statuses[ERROR]++ + } + } + + if statuses[RUNNING] > 0 { + return RUNNING, nil + } else if statuses[PAUSED] == ctrNum { + return PAUSED, nil + } else if statuses[STOPPED] == ctrNum { + return EXITED, nil + } else if statuses[STOPPED] > 0 { + return STOPPED, nil + } else if statuses[ERROR] > 0 { + return ERROR, nil + } else { + return CREATED, nil + } +} + +// getAndSortPodJSONOutput returns the container info in its raw, sorted form +func getAndSortPodJSONParams(pods []*libpod.Pod, opts podPsOptions, runtime *libpod.Runtime) ([]podPsJSONParams, error) { + var ( + psOutput []podPsJSONParams + ) + + for _, pod := range pods { + ctrs, err := pod.AllContainers() + ctrsInfo := make([]podPsCtrInfo, 0) + if err != nil { + return nil, err + } + ctrNum := len(ctrs) + status, err := getPodStatus(ctrs) + if err != nil { + return nil, err + } + + for _, ctr := range ctrs { + batchInfo, err := batchcontainer.BatchContainerOp(ctr, bc_opts) + if err != nil { + return nil, err + } + var status string + switch batchInfo.ConState { + case libpod.ContainerStateStopped: + status = EXITED + case libpod.ContainerStateRunning: + status = RUNNING + case libpod.ContainerStatePaused: + status = PAUSED + case libpod.ContainerStateCreated, libpod.ContainerStateConfigured: + status = CREATED + default: + status = ERROR + } + ctrsInfo = append(ctrsInfo, podPsCtrInfo{ + Name: batchInfo.ConConfig.Name, + Id: ctr.ID(), + Status: status, + }) + } + params := podPsJSONParams{ + CreatedAt: pod.CreatedTime(), + ID: pod.ID(), + Name: pod.Name(), + Status: status, + Cgroup: pod.CgroupParent(), + UsePodCgroup: pod.UsePodCgroup(), + NumberOfContainers: ctrNum, + CtrsInfo: ctrsInfo, + } + + psOutput = append(psOutput, params) + } + return sortPodPsOutput(opts.Sort, psOutput) +} + +func generatePodPsOutput(pods []*libpod.Pod, opts podPsOptions, runtime *libpod.Runtime) error { + if len(pods) == 0 && opts.Format != formats.JSONString { + return nil + } + psOutput, err := getAndSortPodJSONParams(pods, opts, runtime) + if err != nil { + return err + } + var out formats.Writer + + switch opts.Format { + case formats.JSONString: + if err != nil { + return errors.Wrapf(err, "unable to create JSON for output") + } + out = formats.JSONStructArray{Output: podPsToGeneric([]podPsTemplateParams{}, psOutput)} + default: + psOutput, err := getPodTemplateOutput(psOutput, opts) + if err != nil { + return errors.Wrapf(err, "unable to create output") + } + out = formats.StdoutTemplateArray{Output: podPsToGeneric(psOutput, []podPsJSONParams{}), Template: opts.Format, Fields: psOutput[0].podHeaderMap()} + } + + return formats.Writer(out).Out() +} diff --git a/cmd/podman/pod_rm.go b/cmd/podman/pod_rm.go new file mode 100644 index 000000000..8cc46761e --- /dev/null +++ b/cmd/podman/pod_rm.go @@ -0,0 +1,103 @@ +package main + +import ( + "fmt" + "os" + + "github.com/pkg/errors" + "github.com/projectatomic/libpod/cmd/podman/libpodruntime" + "github.com/projectatomic/libpod/libpod" + "github.com/urfave/cli" +) + +var ( + podRmFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "force, f", + Usage: "Force removal of a running pod by first stopping all containers, then removing all containers in the pod. The default is false", + }, + cli.BoolFlag{ + Name: "all, a", + Usage: "Remove all pods", + }, + LatestFlag, + } + podRmDescription = "Remove one or more pods" + podRmCommand = cli.Command{ + Name: "rm", + Usage: fmt.Sprintf(`podman rm will remove one or more pods from the host. The pod name or ID can be used. + A pod with containers will not be removed without --force. + If --force is specified, all containers will be stopped, then removed.`), + Description: podRmDescription, + Flags: podRmFlags, + Action: podRmCmd, + ArgsUsage: "[POD ...]", + UseShortOptionHandling: true, + } +) + +// saveCmd saves the image to either docker-archive or oci +func podRmCmd(c *cli.Context) error { + ctx := getContext() + if err := validateFlags(c, rmFlags); err != nil { + return err + } + + if c.Bool("latest") && c.Bool("all") { + return errors.Errorf("--all and --latest cannot be used together") + } + + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + args := c.Args() + + if len(args) == 0 && !c.Bool("all") && !c.Bool("latest") { + return errors.Errorf("specify one or more pods to remove") + } + + var delPods []*libpod.Pod + var lastError error + if c.IsSet("all") { + delPods, err = runtime.GetAllPods() + if err != nil { + return errors.Wrapf(err, "unable to get pod list") + } + } else if c.IsSet("latest") { + delPod, err := runtime.GetLatestPod() + if err != nil { + return errors.Wrapf(err, "unable to get latest pod") + } + delPods = append(delPods, delPod) + } else { + for _, i := range args { + pod, err := runtime.LookupPod(i) + if err != nil { + fmt.Fprintln(os.Stderr, err) + if lastError != nil { + fmt.Fprintln(os.Stderr, lastError) + } + lastError = errors.Wrapf(err, "unable to find pods %s", i) + continue + } + delPods = append(delPods, pod) + } + } + force := c.IsSet("force") + + for _, pod := range delPods { + err = runtime.RemovePod(ctx, pod, force, force) + if err != nil { + if lastError != nil { + fmt.Fprintln(os.Stderr, lastError) + } + lastError = errors.Wrapf(err, "failed to delete pod %v", pod.ID()) + } else { + fmt.Println(pod.ID()) + } + } + return lastError +} |