diff options
53 files changed, 1879 insertions, 622 deletions
diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index 99f389799..6d98aaf0e 100644 --- a/cmd/podman/cliconfig/config.go +++ b/cmd/podman/cliconfig/config.go @@ -321,6 +321,7 @@ type KubePlayValues struct { Authfile string CertDir string Creds string + Network string Quiet bool SignaturePolicy string TlsVerify bool diff --git a/cmd/podman/info.go b/cmd/podman/info.go index 7361525ce..79417b85d 100644 --- a/cmd/podman/info.go +++ b/cmd/podman/info.go @@ -2,6 +2,8 @@ package main import ( "fmt" + "html/template" + "os" rt "runtime" "strings" @@ -11,7 +13,6 @@ import ( "github.com/containers/libpod/pkg/adapter" "github.com/containers/libpod/version" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -49,42 +50,32 @@ func init() { } func infoCmd(c *cliconfig.InfoValues) error { - info := map[string]interface{}{} - remoteClientInfo := map[string]interface{}{} - runtime, err := adapter.GetRuntime(getContext(), &c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.DeferredShutdown(false) - infoArr, err := runtime.Info() + i, err := runtime.Info() if err != nil { return errors.Wrapf(err, "error getting info") } + info := infoWithExtra{Info: i} if runtime.Remote { endpoint, err := runtime.RemoteEndpoint() if err != nil { - logrus.Errorf("Failed to obtain server connection: %s", err.Error()) - } else { - remoteClientInfo["Connection"] = endpoint.Connection - remoteClientInfo["Connection Type"] = endpoint.Type.String() + return err } - - remoteClientInfo["RemoteAPI Version"] = version.RemoteAPIVersion - remoteClientInfo["Podman Version"] = version.Version - remoteClientInfo["OS Arch"] = fmt.Sprintf("%s/%s", rt.GOOS, rt.GOARCH) - infoArr = append(infoArr, define.InfoData{Type: "client", Data: remoteClientInfo}) + info.Remote = getRemote(endpoint) } if !runtime.Remote && c.Debug { - debugInfo := debugInfo(c) - infoArr = append(infoArr, define.InfoData{Type: "debug", Data: debugInfo}) - } - - for _, currInfo := range infoArr { - info[currInfo.Type] = currInfo.Data + d, err := getDebug() + if err != nil { + return err + } + info.Debug = d } var out formats.Writer @@ -98,19 +89,58 @@ func infoCmd(c *cliconfig.InfoValues) error { case "": out = formats.YAMLStruct{Output: info} default: - out = formats.StdoutTemplate{Output: info, Template: infoOutputFormat} + tmpl, err := template.New("info").Parse(c.Format) + if err != nil { + return err + } + err = tmpl.Execute(os.Stdout, info) + return err } return out.Out() } // top-level "debug" info -func debugInfo(c *cliconfig.InfoValues) map[string]interface{} { - info := map[string]interface{}{} - info["compiler"] = rt.Compiler - info["go version"] = rt.Version() - info["podman version"] = version.Version - version, _ := define.GetVersion() - info["git commit"] = version.GitCommit - return info +func getDebug() (*debugInfo, error) { + v, err := define.GetVersion() + if err != nil { + return nil, err + } + return &debugInfo{ + Compiler: rt.Compiler, + GoVersion: rt.Version(), + PodmanVersion: v.Version, + GitCommit: v.GitCommit, + }, nil +} + +func getRemote(endpoint *adapter.Endpoint) *remoteInfo { + return &remoteInfo{ + Connection: endpoint.Connection, + ConnectionType: endpoint.Type.String(), + RemoteAPIVersion: string(version.RemoteAPIVersion), + PodmanVersion: version.Version, + OSArch: fmt.Sprintf("%s/%s", rt.GOOS, rt.GOARCH), + } +} + +type infoWithExtra struct { + *define.Info + Remote *remoteInfo `json:"remote,omitempty"` + Debug *debugInfo `json:"debug,omitempty"` +} + +type remoteInfo struct { + Connection string `json:"connection"` + ConnectionType string `json:"connectionType"` + RemoteAPIVersion string `json:"remoteAPIVersion"` + PodmanVersion string `json:"podmanVersion"` + OSArch string `json:"OSArch"` +} + +type debugInfo struct { + Compiler string `json:"compiler"` + GoVersion string `json:"goVersion"` + PodmanVersion string `json:"podmanVersion"` + GitCommit string `json:"gitCommit"` } diff --git a/cmd/podman/main_local.go b/cmd/podman/main_local.go index 23b3f5ae7..a65e6acf8 100644 --- a/cmd/podman/main_local.go +++ b/cmd/podman/main_local.go @@ -11,7 +11,6 @@ import ( "os" "runtime/pprof" "strconv" - "strings" "syscall" "github.com/containers/common/pkg/config" @@ -192,7 +191,7 @@ func setupRootless(cmd *cobra.Command, args []string) error { } } - if os.Geteuid() == 0 || cmd == _searchCommand || cmd == _versionCommand || cmd == _mountCommand || cmd == _migrateCommand || strings.HasPrefix(cmd.Use, "help") { + if !executeCommandInUserNS(cmd) { return nil } @@ -243,6 +242,25 @@ func setupRootless(cmd *cobra.Command, args []string) error { return nil } +// Most podman commands when run in rootless mode, need to be executed in the +// users usernamespace. This function is updated with a list of commands that +// should NOT be run within the user namespace. +func executeCommandInUserNS(cmd *cobra.Command) bool { + if os.Geteuid() == 0 { + return false + } + switch cmd { + case _migrateCommand, + _mountCommand, + _renumberCommand, + _infoCommand, + _searchCommand, + _versionCommand: + return false + } + return true +} + func setRLimits() error { rlimits := new(syscall.Rlimit) rlimits.Cur = 1048576 diff --git a/cmd/podman/play_kube.go b/cmd/podman/play_kube.go index 2028d2ef4..a5669c595 100644 --- a/cmd/podman/play_kube.go +++ b/cmd/podman/play_kube.go @@ -51,6 +51,7 @@ func init() { flags.StringVar(&playKubeCommand.SeccompProfileRoot, "seccomp-profile-root", defaultSeccompRoot, "Directory path for seccomp profiles") markFlagHidden(flags, "signature-policy") } + flags.StringVar(&playKubeCommand.Network, "network", "", "Connect pod to CNI network(s)") } func playKubeCmd(c *cliconfig.KubePlayValues) error { diff --git a/cmd/podmanV2/containers/ps.go b/cmd/podmanV2/containers/ps.go index ce3d66c51..2397eb8c0 100644 --- a/cmd/podmanV2/containers/ps.go +++ b/cmd/podmanV2/containers/ps.go @@ -1,29 +1,379 @@ package containers import ( + "encoding/json" + "fmt" + "os" + "sort" + "strconv" "strings" + "text/tabwriter" + "text/template" + "time" + tm "github.com/buger/goterm" + "github.com/containers/buildah/pkg/formats" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/cmd/podmanV2/report" "github.com/containers/libpod/pkg/domain/entities" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/docker/go-units" + "github.com/pkg/errors" "github.com/spf13/cobra" ) var ( - // podman _ps_ - psCmd = &cobra.Command{ - Use: "ps", - Args: cobra.NoArgs, - Short: listCmd.Short, - Long: listCmd.Long, - PersistentPreRunE: preRunE, - RunE: containers, - Example: strings.Replace(listCmd.Example, "container list", "ps", -1), + psDescription = "Prints out information about the containers" + psCommand = &cobra.Command{ + Use: "ps", + Args: checkFlags, + Short: "List containers", + Long: psDescription, + RunE: ps, + PreRunE: preRunE, + Example: `podman ps -a + podman ps -a --format "{{.ID}} {{.Image}} {{.Labels}} {{.Mounts}}" + podman ps --size --sort names`, } ) +var ( + listOpts = entities.ContainerListOptions{ + Filters: make(map[string][]string), + } + filters []string + noTrunc bool + defaultHeaders string = "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES" + +// CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + +) func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, - Command: psCmd, + Command: psCommand, + }) + flags := psCommand.Flags() + flags.BoolVarP(&listOpts.All, "all", "a", false, "Show all the containers, default is only running containers") + flags.StringSliceVarP(&filters, "filter", "f", []string{}, "Filter output based on conditions given") + flags.StringVar(&listOpts.Format, "format", "", "Pretty-print containers to JSON or using a Go template") + flags.IntVarP(&listOpts.Last, "last", "n", -1, "Print the n last created containers (all states)") + flags.BoolVarP(&listOpts.Latest, "latest", "l", false, "Show the latest container created (all states)") + flags.BoolVar(&listOpts.Namespace, "namespace", false, "Display namespace information") + flags.BoolVar(&listOpts.Namespace, "ns", false, "Display namespace information") + flags.BoolVar(&noTrunc, "no-trunc", false, "Display the extended information") + flags.BoolVarP(&listOpts.Pod, "pod", "p", false, "Print the ID and name of the pod the containers are associated with") + flags.BoolVarP(&listOpts.Quiet, "quiet", "q", false, "Print the numeric IDs of the containers only") + flags.BoolVarP(&listOpts.Size, "size", "s", false, "Display the total file sizes") + flags.StringVar(&listOpts.Sort, "sort", "created", "Sort output by command, created, id, image, names, runningfor, size, or status") + flags.BoolVar(&listOpts.Sync, "sync", false, "Sync container state with OCI runtime") + flags.UintVarP(&listOpts.Watch, "watch", "w", 0, "Watch the ps output on an interval in seconds") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} +func checkFlags(c *cobra.Command, args []string) error { + // latest, and last are mutually exclusive. + if listOpts.Last >= 0 && listOpts.Latest { + return errors.Errorf("last and latest are mutually exclusive") + } + // Filter on status forces all + for _, filter := range filters { + splitFilter := strings.SplitN(filter, "=", 2) + if strings.ToLower(splitFilter[0]) == "status" { + listOpts.All = true + break + } + } + // Quiet conflicts with size and namespace and is overridden by a Go + // template. + if listOpts.Quiet { + if listOpts.Size || listOpts.Namespace { + return errors.Errorf("quiet conflicts with size and namespace") + } + if c.Flag("format").Changed && listOpts.Format != formats.JSONString { + // Quiet is overridden by Go template output. + listOpts.Quiet = false + } + } + // Size and namespace conflict with each other + if listOpts.Size && listOpts.Namespace { + return errors.Errorf("size and namespace options conflict") + } + + if listOpts.Watch > 0 && listOpts.Latest { + return errors.New("the watch and latest flags cannot be used together") + } + return nil +} + +func jsonOut(responses []entities.ListContainer) error { + b, err := json.MarshalIndent(responses, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil +} + +func quietOut(responses []entities.ListContainer) error { + for _, r := range responses { + id := r.ID + if !noTrunc { + id = id[0:12] + } + fmt.Println(id) + } + return nil +} + +func getResponses() ([]entities.ListContainer, error) { + responses, err := registry.ContainerEngine().ContainerList(registry.GetContext(), listOpts) + if err != nil { + return nil, err + } + if len(listOpts.Sort) > 0 { + responses, err = entities.SortPsOutput(listOpts.Sort, responses) + if err != nil { + return nil, err + } + } + return responses, nil +} + +func ps(cmd *cobra.Command, args []string) error { + // []string to map[string][]string + for _, f := range filters { + split := strings.SplitN(f, "=", 2) + if len(split) == 1 { + return errors.Errorf("invalid filter %q", f) + } + listOpts.Filters[split[0]] = append(listOpts.Filters[split[0]], split[1]) + } + responses, err := getResponses() + if err != nil { + return err + } + if len(listOpts.Sort) > 0 { + responses, err = entities.SortPsOutput(listOpts.Sort, responses) + if err != nil { + return err + } + } + if listOpts.Format == "json" { + return jsonOut(responses) + } + if listOpts.Quiet { + return quietOut(responses) + } + headers, row := createPsOut() + if cmd.Flag("format").Changed { + row = listOpts.Format + if !strings.HasPrefix(row, "\n") { + row += "\n" + } + } + format := "{{range . }}" + row + "{{end}}" + if !listOpts.Quiet && !cmd.Flag("format").Changed { + format = headers + format + } + funcs := report.AppendFuncMap(psFuncMap) + tmpl, err := template.New("listPods").Funcs(funcs).Parse(format) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + if listOpts.Watch > 0 { + for { + tm.Clear() + tm.MoveCursor(1, 1) + tm.Flush() + responses, err := getResponses() + if err != nil { + return err + } + if err := tmpl.Execute(w, responses); err != nil { + return nil + } + if err := w.Flush(); err != nil { + return err + } + time.Sleep(time.Duration(listOpts.Watch) * time.Second) + tm.Clear() + tm.MoveCursor(1, 1) + tm.Flush() + } + } else if listOpts.Watch < 1 { + if err := tmpl.Execute(w, responses); err != nil { + return err + } + return w.Flush() + } + return nil +} + +func createPsOut() (string, string) { + var row string + if listOpts.Namespace { + headers := "CONTAINER ID\tNAMES\tPID\tCGROUPNS\tIPC\tMNT\tNET\tPIDN\tUSERNS\tUTS\n" + row := "{{.ID}}\t{{names .Names}}\t{{.Pid}}\t{{.Namespaces.Cgroup}}\t{{.Namespaces.IPC}}\t{{.Namespaces.MNT}}\t{{.Namespaces.NET}}\t{{.Namespaces.PIDNS}}\t{{.Namespaces.User}}\t{{.Namespaces.UTS}}\n" + return headers, row + } + headers := defaultHeaders + if noTrunc { + row += "{{.ID}}" + } else { + row += "{{slice .ID 0 12}}" + } + row += "\t{{.Image}}\t{{cmd .Command}}\t{{humanDuration .Created}}\t{{state .}}\t{{ports .Ports}}\t{{names .Names}}" + + if listOpts.Pod { + headers += "\tPOD ID\tPODNAME" + if noTrunc { + row += "\t{{.Pod}}" + } else { + row += "\t{{slice .Pod 0 12}}" + } + row += "\t{{.PodName}}" + } + + if listOpts.Size { + headers += "\tSIZE" + row += "\t{{consize .Size}}" + } + if !strings.HasSuffix(headers, "\n") { + headers += "\n" + } + if !strings.HasSuffix(row, "\n") { + row += "\n" + } + return headers, row +} + +var psFuncMap = template.FuncMap{ + "cmd": func(conCommand []string) string { + return strings.Join(conCommand, " ") + }, + "state": func(con entities.ListContainer) string { + var state string + switch con.State { + case "running": + t := units.HumanDuration(time.Since(time.Unix(con.StartedAt, 0))) + state = "Up " + t + " ago" + case "configured": + state = "Created" + case "exited": + t := units.HumanDuration(time.Since(time.Unix(con.ExitedAt, 0))) + state = fmt.Sprintf("Exited (%d) %s ago", con.ExitCode, t) + default: + state = con.State + } + return state + }, + "ports": func(ports []ocicni.PortMapping) string { + if len(ports) == 0 { + return "" + } + return portsToString(ports) + }, + "names": func(names []string) string { + return names[0] + }, + "consize": func(csize shared.ContainerSize) string { + virt := units.HumanSizeWithPrecision(float64(csize.RootFsSize), 3) + s := units.HumanSizeWithPrecision(float64(csize.RwSize), 3) + return fmt.Sprintf("%s (virtual %s)", s, virt) + }, +} + +// portsToString converts the ports used to a string of the from "port1, port2" +// and also groups a continuous list of ports into a readable format. +func portsToString(ports []ocicni.PortMapping) string { + type portGroup struct { + first int32 + last int32 + } + var portDisplay []string + if len(ports) == 0 { + return "" + } + //Sort the ports, so grouping continuous ports become easy. + sort.Slice(ports, func(i, j int) bool { + return comparePorts(ports[i], ports[j]) }) + + // portGroupMap is used for grouping continuous ports. + portGroupMap := make(map[string]*portGroup) + var groupKeyList []string + + for _, v := range ports { + + hostIP := v.HostIP + if hostIP == "" { + hostIP = "0.0.0.0" + } + // If hostPort and containerPort are not same, consider as individual port. + if v.ContainerPort != v.HostPort { + portDisplay = append(portDisplay, fmt.Sprintf("%s:%d->%d/%s", hostIP, v.HostPort, v.ContainerPort, v.Protocol)) + continue + } + + portMapKey := fmt.Sprintf("%s/%s", hostIP, v.Protocol) + + portgroup, ok := portGroupMap[portMapKey] + if !ok { + portGroupMap[portMapKey] = &portGroup{first: v.ContainerPort, last: v.ContainerPort} + // This list is required to traverse portGroupMap. + groupKeyList = append(groupKeyList, portMapKey) + continue + } + + if portgroup.last == (v.ContainerPort - 1) { + portgroup.last = v.ContainerPort + continue + } + } + // For each portMapKey, format group list and appned to output string. + for _, portKey := range groupKeyList { + group := portGroupMap[portKey] + portDisplay = append(portDisplay, formatGroup(portKey, group.first, group.last)) + } + return strings.Join(portDisplay, ", ") +} + +func comparePorts(i, j ocicni.PortMapping) bool { + if i.ContainerPort != j.ContainerPort { + return i.ContainerPort < j.ContainerPort + } + + if i.HostIP != j.HostIP { + return i.HostIP < j.HostIP + } + + if i.HostPort != j.HostPort { + return i.HostPort < j.HostPort + } + + return i.Protocol < j.Protocol +} + +// formatGroup returns the group as <IP:startPort:lastPort->startPort:lastPort/Proto> +// e.g 0.0.0.0:1000-1006->1000-1006/tcp. +func formatGroup(key string, start, last int32) string { + parts := strings.Split(key, "/") + groupType := parts[0] + var ip string + if len(parts) > 1 { + ip = parts[0] + groupType = parts[1] + } + group := strconv.Itoa(int(start)) + if start != last { + group = fmt.Sprintf("%s-%d", group, last) + } + if ip != "" { + group = fmt.Sprintf("%s:%s->%s", ip, group, group) + } + return fmt.Sprintf("%s/%s", group, groupType) } diff --git a/cmd/podmanV2/containers/start.go b/cmd/podmanV2/containers/start.go new file mode 100644 index 000000000..0ae2f6d50 --- /dev/null +++ b/cmd/podmanV2/containers/start.go @@ -0,0 +1,87 @@ +package containers + +import ( + "fmt" + "os" + + "github.com/containers/libpod/cmd/podmanV2/common" + "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/cmd/podmanV2/utils" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + startDescription = `Starts one or more containers. The container name or ID can be used.` + startCommand = &cobra.Command{ + Use: "start [flags] CONTAINER [CONTAINER...]", + Short: "Start one or more containers", + Long: startDescription, + RunE: start, + PreRunE: preRunE, + Args: cobra.MinimumNArgs(1), + Example: `podman start --latest + podman start 860a4b231279 5421ab43b45 + podman start --interactive --attach imageID`, + } +) + +var ( + startOptions entities.ContainerStartOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: startCommand, + }) + flags := startCommand.Flags() + flags.BoolVarP(&startOptions.Attach, "attach", "a", false, "Attach container's STDOUT and STDERR") + flags.StringVar(&startOptions.DetachKeys, "detach-keys", common.GetDefaultDetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`") + flags.BoolVarP(&startOptions.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached") + flags.BoolVarP(&startOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVar(&startOptions.SigProxy, "sig-proxy", false, "Proxy received signals to the process (default true if attaching, false otherwise)") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + _ = flags.MarkHidden("sig-proxy") + } + +} + +func start(cmd *cobra.Command, args []string) error { + var errs utils.OutputErrors + if len(args) > 1 && startOptions.Attach { + return errors.Errorf("you cannot start and attach multiple containers at once") + } + + sigProxy := startOptions.SigProxy || startOptions.Attach + if cmd.Flag("sig-proxy").Changed { + sigProxy = startOptions.SigProxy + } + + if sigProxy && !startOptions.Attach { + return errors.Wrapf(define.ErrInvalidArg, "you cannot use sig-proxy without --attach") + } + if startOptions.Attach { + startOptions.Stdin = os.Stdin + startOptions.Stderr = os.Stderr + startOptions.Stdout = os.Stdout + } + + responses, err := registry.ContainerEngine().ContainerStart(registry.GetContext(), args, startOptions) + if err != nil { + return err + } + + for _, r := range responses { + if r.Err == nil { + fmt.Println(r.Id) + } else { + errs = append(errs, r.Err) + } + } + // TODO need to understand an implement exitcodes + return errs.PrintErrors() +} diff --git a/cmd/podmanV2/pods/ps.go b/cmd/podmanV2/pods/ps.go index 9546dff9e..9875263ac 100644 --- a/cmd/podmanV2/pods/ps.go +++ b/cmd/podmanV2/pods/ps.go @@ -88,7 +88,7 @@ func pods(cmd *cobra.Command, args []string) error { fmt.Println(string(b)) return nil } - headers, row := createPodPsOut(cmd) + headers, row := createPodPsOut() if psInput.Quiet { if noTrunc { row = "{{.Id}}\n" @@ -123,7 +123,7 @@ func pods(cmd *cobra.Command, args []string) error { return nil } -func createPodPsOut(cmd *cobra.Command) (string, string) { +func createPodPsOut() (string, string) { var row string headers := defaultHeaders if noTrunc { diff --git a/completions/bash/podman b/completions/bash/podman index 77f881d53..6997db3b5 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -2681,6 +2681,7 @@ _podman_play_kube() { --authfile --cert-dir --creds + --network " local boolean_options=" diff --git a/docs/source/markdown/podman-info.1.md b/docs/source/markdown/podman-info.1.md index b539f1d3c..d9da4c3f8 100644 --- a/docs/source/markdown/podman-info.1.md +++ b/docs/source/markdown/podman-info.1.md @@ -156,7 +156,7 @@ Run podman info with JSON formatted response: ``` Run podman info and only get the registries information. ``` -$ podman info --format={{".registries"}} +$ podman info --format={{".Registries"}} map[registries:[docker.io quay.io registry.fedoraproject.org registry.access.redhat.com]] ``` diff --git a/docs/source/markdown/podman-play-kube.1.md b/docs/source/markdown/podman-play-kube.1.md index 2367ff7fe..dd9441800 100644 --- a/docs/source/markdown/podman-play-kube.1.md +++ b/docs/source/markdown/podman-play-kube.1.md @@ -36,6 +36,10 @@ The [username[:password]] to use to authenticate with the registry if required. If one or both values are not supplied, a command line prompt will appear and the value can be entered. The password is entered without echo. +**--network**=*cni networks* + +A comma-separated list of the names of CNI networks the pod should join. + **--quiet**, **-q** Suppress output information when pulling images @@ -62,8 +66,16 @@ $ podman play kube demo.yml 52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6 ``` +CNI network(s) can be specified as comma-separated list using ``--network`` +``` +$ podman play kube demo.yml --network cni1,cni2 +52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6 +``` + +Please take into account that CNI networks must be created first using podman-network-create(1). + ## SEE ALSO -podman(1), podman-container(1), podman-pod(1), podman-generate-kube(1), podman-play(1) +podman(1), podman-container(1), podman-pod(1), podman-generate-kube(1), podman-play(1), podman-network-create(1) ## HISTORY December 2018, Originally compiled by Brent Baude (bbaude at redhat dot com) @@ -42,7 +42,7 @@ require ( github.com/opencontainers/runc v1.0.0-rc9 github.com/opencontainers/runtime-spec v0.1.2-0.20190618234442-a950415649c7 github.com/opencontainers/runtime-tools v0.9.0 - github.com/opencontainers/selinux v1.4.0 + github.com/opencontainers/selinux v1.5.0 github.com/opentracing/opentracing-go v1.1.0 github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.0 @@ -355,6 +355,8 @@ github.com/opencontainers/selinux v1.3.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwy github.com/opencontainers/selinux v1.3.2/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= github.com/opencontainers/selinux v1.4.0 h1:cpiX/2wWIju/6My60T6/z9CxNG7c8xTQyEmA9fChpUo= github.com/opencontainers/selinux v1.4.0/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= +github.com/opencontainers/selinux v1.5.0 h1:giFN+hbiSqvKWPyagmNk9sABaH7VUZ/+XS7tInqDQ6c= +github.com/opencontainers/selinux v1.5.0/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= github.com/openshift/api v0.0.0-20200106203948-7ab22a2c8316 h1:enQG2QUGwug4fR1yM6hL0Fjzx6Km/exZY6RbSPwMu3o= github.com/openshift/api v0.0.0-20200106203948-7ab22a2c8316/go.mod h1:dv+J0b/HWai0QnMVb37/H0v36klkLBi2TNpPeWDxX10= github.com/openshift/imagebuilder v1.1.3 h1:8TiphsD2wboU7tygtGZ5ZBfCP9FH2ZtvEAli67V2PJ4= diff --git a/libpod/define/info.go b/libpod/define/info.go new file mode 100644 index 000000000..e9809c367 --- /dev/null +++ b/libpod/define/info.go @@ -0,0 +1,101 @@ +package define + +import "github.com/containers/storage/pkg/idtools" + +// Info is the overall struct that describes the host system +// running libpod/podman +type Info struct { + Host *HostInfo `json:"host"` + Store *StoreInfo `json:"store"` + Registries map[string]interface{} `json:"registries"` +} + +//HostInfo describes the libpod host +type HostInfo struct { + Arch string `json:"arch"` + BuildahVersion string `json:"buildahVersion"` + CGroupsVersion string `json:"cgroupVersion"` + Conmon *ConmonInfo `json:"conmon"` + CPUs int `json:"cpus"` + Distribution DistributionInfo `json:"distribution"` + EventLogger string `json:"eventLogger"` + Hostname string `json:"hostname"` + IDMappings IDMappings `json:"idMappings,omitempty"` + Kernel string `json:"kernel"` + MemFree int64 `json:"memFree"` + MemTotal int64 `json:"memTotal"` + OCIRuntime *OCIRuntimeInfo `json:"ociRuntime"` + OS string `json:"os"` + Rootless bool `json:"rootless"` + RuntimeInfo map[string]interface{} `json:"runtimeInfo,omitempty"` + Slirp4NetNS SlirpInfo `json:"slirp4netns,omitempty"` + SwapFree int64 `json:"swapFree"` + SwapTotal int64 `json:"swapTotal"` + Uptime string `json:"uptime"` +} + +// SlirpInfo describes the slirp exectuable that +// is being being used. +type SlirpInfo struct { + Executable string `json:"executable"` + Package string `json:"package"` + Version string `json:"version"` +} + +// IDMappings describe the GID and UID mappings +type IDMappings struct { + GIDMap []idtools.IDMap `json:"gidmap"` + UIDMap []idtools.IDMap `json:"uidmap"` +} + +// DistributionInfo describes the host distribution +// for libpod +type DistributionInfo struct { + Distribution string `json:"distribution"` + Version string `json:"version"` +} + +// ConmonInfo describes the conmon executable being used +type ConmonInfo struct { + Package string `json:"package"` + Path string `json:"path"` + Version string `json:"version"` +} + +// OCIRuntimeInfo describes the runtime (crun or runc) being +// used with podman +type OCIRuntimeInfo struct { + Name string `json:"name"` + Package string `json:"package"` + Path string `json:"path"` + Version string `json:"version"` +} + +// StoreInfo describes the container storage and its +// attributes +type StoreInfo struct { + ConfigFile string `json:"configFile"` + ContainerStore ContainerStore `json:"containerStore"` + GraphDriverName string `json:"graphDriverName"` + GraphOptions map[string]interface{} `json:"graphOptions"` + GraphRoot string `json:"graphRoot"` + GraphStatus map[string]string `json:"graphStatus"` + ImageStore ImageStore `json:"imageStore"` + RunRoot string `json:"runRoot"` + VolumePath string `json:"volumePath"` +} + +// ImageStore describes the image store. Right now only the number +// of images present +type ImageStore struct { + Number int `json:"number"` +} + +// ContainerStore describes the quantity of containers in the +// store by status +type ContainerStore struct { + Number int `json:"number"` + Paused int `json:"paused"` + Running int `json:"running"` + Stopped int `json:"stopped"` +} diff --git a/libpod/filters/containers.go b/libpod/filters/containers.go new file mode 100644 index 000000000..c13372f91 --- /dev/null +++ b/libpod/filters/containers.go @@ -0,0 +1,157 @@ +package lpfilters + +import ( + "regexp" + "strconv" + "strings" + "time" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/timetype" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" +) + +// GenerateContainerFilterFuncs return ContainerFilter functions based of filter. +func GenerateContainerFilterFuncs(filter, filterValue string, r *libpod.Runtime) (func(container *libpod.Container) bool, error) { + switch filter { + case "id": + return func(c *libpod.Container) bool { + return strings.Contains(c.ID(), filterValue) + }, nil + case "label": + var filterArray = strings.SplitN(filterValue, "=", 2) + var filterKey = filterArray[0] + if len(filterArray) > 1 { + filterValue = filterArray[1] + } else { + filterValue = "" + } + return func(c *libpod.Container) bool { + for labelKey, labelValue := range c.Labels() { + if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) { + return true + } + } + return false + }, nil + case "name": + return func(c *libpod.Container) bool { + match, err := regexp.MatchString(filterValue, c.Name()) + if err != nil { + return false + } + return match + }, nil + case "exited": + exitCode, err := strconv.ParseInt(filterValue, 10, 32) + if err != nil { + return nil, errors.Wrapf(err, "exited code out of range %q", filterValue) + } + return func(c *libpod.Container) bool { + ec, exited, err := c.ExitCode() + if ec == int32(exitCode) && err == nil && exited { + return true + } + return false + }, nil + case "status": + if !util.StringInSlice(filterValue, []string{"created", "running", "paused", "stopped", "exited", "unknown"}) { + return nil, errors.Errorf("%s is not a valid status", filterValue) + } + return func(c *libpod.Container) bool { + status, err := c.State() + if err != nil { + return false + } + if filterValue == "stopped" { + filterValue = "exited" + } + state := status.String() + if status == define.ContainerStateConfigured { + state = "created" + } else if status == define.ContainerStateStopped { + state = "exited" + } + return state == filterValue + }, nil + case "ancestor": + // This needs to refine to match docker + // - ancestor=(<image-name>[:tag]|<image-id>| ⟨image@digest⟩) - containers created from an image or a descendant. + return func(c *libpod.Container) bool { + containerConfig := c.Config() + if strings.Contains(containerConfig.RootfsImageID, filterValue) || strings.Contains(containerConfig.RootfsImageName, filterValue) { + return true + } + return false + }, nil + case "before": + ctr, err := r.LookupContainer(filterValue) + if err != nil { + return nil, errors.Errorf("unable to find container by name or id of %s", filterValue) + } + containerConfig := ctr.Config() + createTime := containerConfig.CreatedTime + return func(c *libpod.Container) bool { + cc := c.Config() + return createTime.After(cc.CreatedTime) + }, nil + case "since": + ctr, err := r.LookupContainer(filterValue) + if err != nil { + return nil, errors.Errorf("unable to find container by name or id of %s", filterValue) + } + containerConfig := ctr.Config() + createTime := containerConfig.CreatedTime + return func(c *libpod.Container) bool { + cc := c.Config() + return createTime.Before(cc.CreatedTime) + }, nil + case "volume": + //- volume=(<volume-name>|<mount-point-destination>) + return func(c *libpod.Container) bool { + containerConfig := c.Config() + var dest string + arr := strings.Split(filterValue, ":") + source := arr[0] + if len(arr) == 2 { + dest = arr[1] + } + for _, mount := range containerConfig.Spec.Mounts { + if dest != "" && (mount.Source == source && mount.Destination == dest) { + return true + } + if dest == "" && mount.Source == source { + return true + } + } + return false + }, nil + case "health": + return func(c *libpod.Container) bool { + hcStatus, err := c.HealthCheckStatus() + if err != nil { + return false + } + return hcStatus == filterValue + }, nil + case "until": + ts, err := timetype.GetTimestamp(filterValue, time.Now()) + if err != nil { + return nil, err + } + seconds, nanoseconds, err := timetype.ParseTimestamps(ts, 0) + if err != nil { + return nil, err + } + until := time.Unix(seconds, nanoseconds) + return func(c *libpod.Container) bool { + if !until.IsZero() && c.CreatedTime().After((until)) { + return true + } + return false + }, nil + } + return nil, errors.Errorf("%s is an invalid filter", filter) +} diff --git a/libpod/podfilters/pods.go b/libpod/filters/pods.go index 54fa85edc..9bf436eab 100644 --- a/libpod/podfilters/pods.go +++ b/libpod/filters/pods.go @@ -1,4 +1,4 @@ -package podfilters +package lpfilters import ( "strconv" diff --git a/libpod/info.go b/libpod/info.go index 8d411f0d4..3cc767be6 100644 --- a/libpod/info.go +++ b/libpod/info.go @@ -13,7 +13,9 @@ import ( "time" "github.com/containers/buildah" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/cgroups" + registries2 "github.com/containers/libpod/pkg/registries" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/containers/storage/pkg/system" @@ -21,14 +23,80 @@ import ( "github.com/sirupsen/logrus" ) +// Info returns the store and host information +func (r *Runtime) info() (*define.Info, error) { + info := define.Info{} + // get host information + hostInfo, err := r.hostInfo() + if err != nil { + return nil, errors.Wrapf(err, "error getting host info") + } + info.Host = hostInfo + + // get store information + storeInfo, err := r.storeInfo() + if err != nil { + return nil, errors.Wrapf(err, "error getting store info") + } + info.Store = storeInfo + registries := make(map[string]interface{}) + data, err := registries2.GetRegistriesData() + if err != nil { + return nil, errors.Wrapf(err, "error getting registries") + } + for _, reg := range data { + registries[reg.Prefix] = reg + } + regs, err := registries2.GetRegistries() + if err != nil { + return nil, errors.Wrapf(err, "error getting registries") + } + if len(regs) > 0 { + registries["search"] = regs + } + + info.Registries = registries + return &info, nil +} + // top-level "host" info -func (r *Runtime) hostInfo() (map[string]interface{}, error) { +func (r *Runtime) hostInfo() (*define.HostInfo, error) { // lets say OS, arch, number of cpus, amount of memory, maybe os distribution/version, hostname, kernel version, uptime - info := map[string]interface{}{} - info["os"] = runtime.GOOS - info["arch"] = runtime.GOARCH - info["cpus"] = runtime.NumCPU() - info["rootless"] = rootless.IsRootless() + mi, err := system.ReadMemInfo() + if err != nil { + return nil, errors.Wrapf(err, "error reading memory info") + } + + hostDistributionInfo := r.GetHostDistributionInfo() + + kv, err := readKernelVersion() + if err != nil { + return nil, errors.Wrapf(err, "error reading kernel version") + } + + host, err := os.Hostname() + if err != nil { + return nil, errors.Wrapf(err, "error getting hostname") + } + info := define.HostInfo{ + Arch: runtime.GOARCH, + BuildahVersion: buildah.Version, + CPUs: runtime.NumCPU(), + Distribution: hostDistributionInfo, + EventLogger: r.eventer.String(), + Hostname: host, + IDMappings: define.IDMappings{}, + Kernel: kv, + MemFree: mi.MemFree, + MemTotal: mi.MemTotal, + OS: runtime.GOOS, + Rootless: rootless.IsRootless(), + Slirp4NetNS: define.SlirpInfo{}, + SwapFree: mi.SwapFree, + SwapTotal: mi.SwapTotal, + } + + // CGroups version unified, err := cgroups.IsCgroup2UnifiedMode() if err != nil { return nil, errors.Wrapf(err, "error reading cgroups mode") @@ -37,17 +105,8 @@ func (r *Runtime) hostInfo() (map[string]interface{}, error) { if unified { cgroupVersion = "v2" } - info["CgroupVersion"] = cgroupVersion - mi, err := system.ReadMemInfo() - if err != nil { - return nil, errors.Wrapf(err, "error reading memory info") - } - // TODO this might be a place for github.com/dustin/go-humanize - info["MemTotal"] = mi.MemTotal - info["MemFree"] = mi.MemFree - info["SwapTotal"] = mi.SwapTotal - info["SwapFree"] = mi.SwapFree - hostDistributionInfo := r.GetHostDistributionInfo() + info.CGroupsVersion = cgroupVersion + if rootless.IsRootless() { if path, err := exec.LookPath("slirp4netns"); err == nil { logrus.Warnf("Failed to retrieve program version for %s: %v", path, err) @@ -55,11 +114,12 @@ func (r *Runtime) hostInfo() (map[string]interface{}, error) { if err != nil { logrus.Warnf("Failed to retrieve program version for %s: %v", path, err) } - program := map[string]interface{}{} - program["Executable"] = path - program["Version"] = version - program["Package"] = packageVersion(path) - info["slirp4netns"] = program + program := define.SlirpInfo{ + Executable: path, + Package: packageVersion(path), + Version: version, + } + info.Slirp4NetNS = program } uidmappings, err := rootless.ReadMappingsProc("/proc/self/uid_map") if err != nil { @@ -69,29 +129,19 @@ func (r *Runtime) hostInfo() (map[string]interface{}, error) { if err != nil { return nil, errors.Wrapf(err, "error reading gid mappings") } - idmappings := make(map[string]interface{}) - idmappings["uidmap"] = uidmappings - idmappings["gidmap"] = gidmappings - info["IDMappings"] = idmappings - } - info["Distribution"] = map[string]interface{}{ - "distribution": hostDistributionInfo["Distribution"], - "version": hostDistributionInfo["Version"], - } - info["BuildahVersion"] = buildah.Version - kv, err := readKernelVersion() - if err != nil { - return nil, errors.Wrapf(err, "error reading kernel version") + idmappings := define.IDMappings{ + GIDMap: gidmappings, + UIDMap: uidmappings, + } + info.IDMappings = idmappings } - info["kernel"] = kv - runtimeInfo, err := r.defaultOCIRuntime.RuntimeInfo() + conmonInfo, ociruntimeInfo, err := r.defaultOCIRuntime.RuntimeInfo() if err != nil { logrus.Errorf("Error getting info on OCI runtime %s: %v", r.defaultOCIRuntime.Name(), err) } else { - for k, v := range runtimeInfo { - info[k] = v - } + info.Conmon = conmonInfo + info.OCIRuntime = ociruntimeInfo } up, err := readUptime() @@ -105,6 +155,7 @@ func (r *Runtime) hostInfo() (map[string]interface{}, error) { return nil, errors.Wrapf(err, "error parsing system uptime") } + // TODO Isnt there a simple lib for this, something like humantime? hoursFound := false var timeBuffer bytes.Buffer var hoursBuffer bytes.Buffer @@ -121,32 +172,75 @@ func (r *Runtime) hostInfo() (map[string]interface{}, error) { } } - info["uptime"] = timeBuffer.String() + info.Uptime = timeBuffer.String() if hoursFound { hours, err := strconv.ParseFloat(hoursBuffer.String(), 64) if err == nil { days := hours / 24 - info["uptime"] = fmt.Sprintf("%s (Approximately %.2f days)", info["uptime"], days) + info.Uptime = fmt.Sprintf("%s (Approximately %.2f days)", info.Uptime, days) } } - host, err := os.Hostname() + return &info, nil +} + +func (r *Runtime) getContainerStoreInfo() (define.ContainerStore, error) { + var ( + paused, running, stopped int + ) + cs := define.ContainerStore{} + cons, err := r.GetAllContainers() if err != nil { - return nil, errors.Wrapf(err, "error getting hostname") + return cs, err } - info["hostname"] = host - info["eventlogger"] = r.eventer.String() - - return info, nil + for _, con := range cons { + state, err := con.State() + if err != nil { + return cs, err + } + switch state { + case define.ContainerStateRunning: + running += 1 + case define.ContainerStatePaused: + paused += 1 + default: + stopped += 1 + } + } + cs.Number = len(cons) + cs.Paused = paused + cs.Stopped = stopped + cs.Running = running + return cs, nil } // top-level "store" info -func (r *Runtime) storeInfo() (map[string]interface{}, error) { +func (r *Runtime) storeInfo() (*define.StoreInfo, error) { // lets say storage driver in use, number of images, number of containers - info := map[string]interface{}{} - info["GraphRoot"] = r.store.GraphRoot() - info["RunRoot"] = r.store.RunRoot() - info["GraphDriverName"] = r.store.GraphDriverName() + configFile, err := storage.DefaultConfigFile(rootless.IsRootless()) + if err != nil { + return nil, err + } + images, err := r.store.Images() + if err != nil { + return nil, errors.Wrapf(err, "error getting number of images") + } + conInfo, err := r.getContainerStoreInfo() + if err != nil { + return nil, err + } + imageInfo := define.ImageStore{Number: len(images)} + + info := define.StoreInfo{ + ImageStore: imageInfo, + ContainerStore: conInfo, + GraphRoot: r.store.GraphRoot(), + RunRoot: r.store.RunRoot(), + GraphDriverName: r.store.GraphDriverName(), + GraphOptions: nil, + VolumePath: r.config.Engine.VolumePath, + ConfigFile: configFile, + } graphOptions := map[string]interface{}{} for _, o := range r.store.GraphOptions() { split := strings.SplitN(o, "=", 2) @@ -164,14 +258,8 @@ func (r *Runtime) storeInfo() (map[string]interface{}, error) { graphOptions[split[0]] = split[1] } } - info["GraphOptions"] = graphOptions - info["VolumePath"] = r.config.Engine.VolumePath + info.GraphOptions = graphOptions - configFile, err := storage.DefaultConfigFile(rootless.IsRootless()) - if err != nil { - return nil, err - } - info["ConfigFile"] = configFile statusPairs, err := r.store.Status() if err != nil { return nil, err @@ -180,24 +268,8 @@ func (r *Runtime) storeInfo() (map[string]interface{}, error) { for _, pair := range statusPairs { status[pair[0]] = pair[1] } - info["GraphStatus"] = status - images, err := r.store.Images() - if err != nil { - return nil, errors.Wrapf(err, "error getting number of images") - } - info["ImageStore"] = map[string]interface{}{ - "number": len(images), - } - - containers, err := r.store.Containers() - if err != nil { - return nil, errors.Wrapf(err, "error getting number of containers") - } - info["ContainerStore"] = map[string]interface{}{ - "number": len(containers), - } - - return info, nil + info.GraphStatus = status + return &info, nil } func readKernelVersion() (string, error) { @@ -225,14 +297,13 @@ func readUptime() (string, error) { } // GetHostDistributionInfo returns a map containing the host's distribution and version -func (r *Runtime) GetHostDistributionInfo() map[string]string { - dist := make(map[string]string) - +func (r *Runtime) GetHostDistributionInfo() define.DistributionInfo { // Populate values in case we cannot find the values // or the file - dist["Distribution"] = "unknown" - dist["Version"] = "unknown" - + dist := define.DistributionInfo{ + Distribution: "unknown", + Version: "unknown", + } f, err := os.Open("/etc/os-release") if err != nil { return dist @@ -242,10 +313,10 @@ func (r *Runtime) GetHostDistributionInfo() map[string]string { l := bufio.NewScanner(f) for l.Scan() { if strings.HasPrefix(l.Text(), "ID=") { - dist["Distribution"] = strings.TrimPrefix(l.Text(), "ID=") + dist.Distribution = strings.TrimPrefix(l.Text(), "ID=") } if strings.HasPrefix(l.Text(), "VERSION_ID=") { - dist["Version"] = strings.Trim(strings.TrimPrefix(l.Text(), "VERSION_ID="), "\"") + dist.Version = strings.Trim(strings.TrimPrefix(l.Text(), "VERSION_ID="), "\"") } } return dist diff --git a/libpod/oci.go b/libpod/oci.go index e4fbcb62e..6adf42497 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -5,7 +5,6 @@ import ( "net" "github.com/containers/libpod/libpod/define" - "k8s.io/client-go/tools/remotecommand" ) @@ -122,7 +121,7 @@ type OCIRuntime interface { ExitFilePath(ctr *Container) (string, error) // RuntimeInfo returns verbose information about the runtime. - RuntimeInfo() (map[string]interface{}, error) + RuntimeInfo() (*define.ConmonInfo, *define.OCIRuntimeInfo, error) } // ExecOptions are options passed into ExecContainer. They control the command diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index 2e96dbe57..c20e3f0b4 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -999,32 +999,30 @@ func (r *ConmonOCIRuntime) ExitFilePath(ctr *Container) (string, error) { } // RuntimeInfo provides information on the runtime. -func (r *ConmonOCIRuntime) RuntimeInfo() (map[string]interface{}, error) { +func (r *ConmonOCIRuntime) RuntimeInfo() (*define.ConmonInfo, *define.OCIRuntimeInfo, error) { runtimePackage := packageVersion(r.path) conmonPackage := packageVersion(r.conmonPath) runtimeVersion, err := r.getOCIRuntimeVersion() if err != nil { - return nil, errors.Wrapf(err, "error getting version of OCI runtime %s", r.name) + return nil, nil, errors.Wrapf(err, "error getting version of OCI runtime %s", r.name) } conmonVersion, err := r.getConmonVersion() if err != nil { - return nil, errors.Wrapf(err, "error getting conmon version") + return nil, nil, errors.Wrapf(err, "error getting conmon version") } - info := make(map[string]interface{}) - info["Conmon"] = map[string]interface{}{ - "path": r.conmonPath, - "package": conmonPackage, - "version": conmonVersion, + conmon := define.ConmonInfo{ + Package: conmonPackage, + Path: r.conmonPath, + Version: conmonVersion, } - info["OCIRuntime"] = map[string]interface{}{ - "name": r.name, - "path": r.path, - "package": runtimePackage, - "version": runtimeVersion, + ocirt := define.OCIRuntimeInfo{ + Name: r.name, + Path: r.path, + Package: runtimePackage, + Version: runtimeVersion, } - - return info, nil + return &conmon, &ocirt, nil } // makeAccessible changes the path permission and each parent directory to have --x--x--x diff --git a/libpod/oci_conmon_unsupported.go b/libpod/oci_conmon_unsupported.go index 395b6f6d9..1f9d89ff6 100644 --- a/libpod/oci_conmon_unsupported.go +++ b/libpod/oci_conmon_unsupported.go @@ -117,8 +117,8 @@ func (r *ConmonOCIRuntime) ExitFilePath(ctr *Container) (string, error) { } // RuntimeInfo is not supported on this OS. -func (r *ConmonOCIRuntime) RuntimeInfo() (map[string]interface{}, error) { - return nil, define.ErrNotImplemented +func (r *ConmonOCIRuntime) RuntimeInfo() (*define.ConmonInfo, *define.OCIRuntimeInfo, error) { + return nil, nil, define.ErrNotImplemented } // Package is not supported on this OS. diff --git a/libpod/oci_missing.go b/libpod/oci_missing.go index a5d589255..5284fb4b7 100644 --- a/libpod/oci_missing.go +++ b/libpod/oci_missing.go @@ -195,15 +195,14 @@ func (r *MissingRuntime) ExitFilePath(ctr *Container) (string, error) { } // RuntimeInfo returns information on the missing runtime -func (r *MissingRuntime) RuntimeInfo() (map[string]interface{}, error) { - info := make(map[string]interface{}) - info["OCIRuntime"] = map[string]interface{}{ - "name": r.name, - "path": "missing", - "package": "missing", - "version": "missing", +func (r *MissingRuntime) RuntimeInfo() (*define.ConmonInfo, *define.OCIRuntimeInfo, error) { + ocirt := define.OCIRuntimeInfo{ + Name: r.name, + Path: "missing", + Package: "missing", + Version: "missing", } - return info, nil + return nil, &ocirt, nil } // Return an error indicating the runtime is missing diff --git a/libpod/runtime.go b/libpod/runtime.go index 422b79359..a6032ad23 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -9,16 +9,14 @@ import ( "sync" "syscall" + "github.com/containers/common/pkg/config" is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/types" - - "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/libpod/lock" "github.com/containers/libpod/pkg/cgroups" - sysreg "github.com/containers/libpod/pkg/registries" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" "github.com/containers/storage" @@ -675,40 +673,8 @@ func (r *Runtime) refresh(alivePath string) error { } // Info returns the store and host information -func (r *Runtime) Info() ([]define.InfoData, error) { - info := []define.InfoData{} - // get host information - hostInfo, err := r.hostInfo() - if err != nil { - return nil, errors.Wrapf(err, "error getting host info") - } - info = append(info, define.InfoData{Type: "host", Data: hostInfo}) - - // get store information - storeInfo, err := r.storeInfo() - if err != nil { - return nil, errors.Wrapf(err, "error getting store info") - } - info = append(info, define.InfoData{Type: "store", Data: storeInfo}) - - registries := make(map[string]interface{}) - data, err := sysreg.GetRegistriesData() - if err != nil { - return nil, errors.Wrapf(err, "error getting registries") - } - for _, reg := range data { - registries[reg.Prefix] = reg - } - regs, err := sysreg.GetRegistries() - if err != nil { - return nil, errors.Wrapf(err, "error getting registries") - } - if len(regs) > 0 { - registries["search"] = regs - } - - info = append(info, define.InfoData{Type: "registries", Data: registries}) - return info, nil +func (r *Runtime) Info() (*define.Info, error) { + return r.info() } // generateName generates a unique name for a container or pod. diff --git a/pkg/adapter/info_remote.go b/pkg/adapter/info_remote.go index 0e8fb06d1..549b01f54 100644 --- a/pkg/adapter/info_remote.go +++ b/pkg/adapter/info_remote.go @@ -3,51 +3,62 @@ package adapter import ( - "encoding/json" - "github.com/containers/libpod/libpod/define" iopodman "github.com/containers/libpod/pkg/varlink" ) // Info returns information for the host system and its components -func (r RemoteRuntime) Info() ([]define.InfoData, error) { +func (r RemoteRuntime) Info() (*define.Info, error) { // TODO the varlink implementation for info should be updated to match the output for regular info var ( - reply []define.InfoData - regInfo map[string]interface{} - hostInfo map[string]interface{} - store map[string]interface{} + reply define.Info ) info, err := iopodman.GetInfo().Call(r.Conn) if err != nil { return nil, err } - - // info.host -> map[string]interface{} - h, err := json.Marshal(info.Host) - if err != nil { - return nil, err + hostInfo := define.HostInfo{ + Arch: info.Host.Arch, + BuildahVersion: info.Host.Buildah_version, + CPUs: int(info.Host.Cpus), + Distribution: define.DistributionInfo{ + Distribution: info.Host.Distribution.Distribution, + Version: info.Host.Distribution.Version, + }, + EventLogger: info.Host.Eventlogger, + Hostname: info.Host.Hostname, + Kernel: info.Host.Kernel, + MemFree: info.Host.Mem_free, + MemTotal: info.Host.Mem_total, + OS: info.Host.Os, + SwapFree: info.Host.Swap_free, + SwapTotal: info.Host.Swap_total, + Uptime: info.Host.Uptime, } - json.Unmarshal(h, &hostInfo) - - // info.store -> map[string]interface{} - s, err := json.Marshal(info.Store) - if err != nil { - return nil, err + storeInfo := define.StoreInfo{ + ContainerStore: define.ContainerStore{ + Number: int(info.Store.Containers), + }, + GraphDriverName: info.Store.Graph_driver_name, + GraphRoot: info.Store.Graph_root, + ImageStore: define.ImageStore{ + Number: int(info.Store.Images), + }, + RunRoot: info.Store.Run_root, } - json.Unmarshal(s, &store) - - // info.Registries -> map[string]interface{} - reg, err := json.Marshal(info.Registries) - if err != nil { - return nil, err + reply.Host = &hostInfo + reply.Store = &storeInfo + regs := make(map[string]interface{}) + if len(info.Registries.Search) > 0 { + regs["search"] = info.Registries.Search } - json.Unmarshal(reg, ®Info) - - // Add everything to the reply - reply = append(reply, define.InfoData{Type: "host", Data: hostInfo}) - reply = append(reply, define.InfoData{Type: "registries", Data: regInfo}) - reply = append(reply, define.InfoData{Type: "store", Data: store}) - return reply, nil + if len(info.Registries.Blocked) > 0 { + regs["blocked"] = info.Registries.Blocked + } + if len(info.Registries.Insecure) > 0 { + regs["insecure"] = info.Registries.Insecure + } + reply.Registries = regs + return &reply, nil } diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go index 102eabd8b..7c2a84cc7 100644 --- a/pkg/adapter/pods.go +++ b/pkg/adapter/pods.go @@ -343,7 +343,7 @@ func (r *LocalRuntime) CreatePod(ctx context.Context, cli *cliconfig.PodCreateVa logrus.Debugf("Pod will use host networking") options = append(options, libpod.WithPodHostNetwork()) case "": - return "", errors.Errorf("invalid value passed to --net: must provide a comma-separated list of CNI networks or host") + return "", errors.Errorf("invalid value passed to --network: must provide a comma-separated list of CNI networks or host") default: // We'll assume this is a comma-separated list of CNI // networks. @@ -595,6 +595,22 @@ func (r *LocalRuntime) PlayKubeYAML(ctx context.Context, c *cliconfig.KubePlayVa podPorts := getPodPorts(podYAML.Spec.Containers) podOptions = append(podOptions, libpod.WithInfraContainerPorts(podPorts)) + if c.Flag("network").Changed { + netValue := c.String("network") + switch strings.ToLower(netValue) { + case "bridge", "host": + return nil, errors.Errorf("invalid value passed to --network: bridge or host networking must be configured in YAML") + case "": + return nil, errors.Errorf("invalid value passed to --network: must provide a comma-separated list of CNI networks") + default: + // We'll assume this is a comma-separated list of CNI + // networks. + networks := strings.Split(netValue, ",") + logrus.Debugf("Pod joining CNI networks: %v", networks) + podOptions = append(podOptions, libpod.WithPodNetworks(networks)) + } + } + // Create the Pod pod, err = r.NewPod(ctx, podOptions...) if err != nil { diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index 2ce113d30..c53af0f26 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -94,15 +94,9 @@ func ListContainers(w http.ResponseWriter, r *http.Request) { } } // TODO filters still need to be applied - infoData, err := runtime.Info() - if err != nil { - utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain system info")) - return - } - var list = make([]*handlers.Container, len(containers)) for i, ctnr := range containers { - api, err := handlers.LibpodToContainer(ctnr, infoData, query.Size) + api, err := handlers.LibpodToContainer(ctnr, query.Size) if err != nil { utils.InternalServerError(w, err) return diff --git a/pkg/api/handlers/compat/info.go b/pkg/api/handlers/compat/info.go index 104d0793b..179b4a3e0 100644 --- a/pkg/api/handlers/compat/info.go +++ b/pkg/api/handlers/compat/info.go @@ -33,8 +33,6 @@ func GetInfo(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to obtain system memory info")) return } - hostInfo := infoData[0].Data - storeInfo := infoData[1].Data configInfo, err := runtime.GetConfig() if err != nil { @@ -64,44 +62,44 @@ func GetInfo(w http.ResponseWriter, r *http.Request) { ClusterAdvertise: "", ClusterStore: "", ContainerdCommit: docker.Commit{}, - Containers: storeInfo["ContainerStore"].(map[string]interface{})["number"].(int), + Containers: infoData.Store.ContainerStore.Number, ContainersPaused: stateInfo[define.ContainerStatePaused], ContainersRunning: stateInfo[define.ContainerStateRunning], ContainersStopped: stateInfo[define.ContainerStateStopped] + stateInfo[define.ContainerStateExited], Debug: log.IsLevelEnabled(log.DebugLevel), DefaultRuntime: configInfo.Engine.OCIRuntime, - DockerRootDir: storeInfo["GraphRoot"].(string), - Driver: storeInfo["GraphDriverName"].(string), - DriverStatus: getGraphStatus(storeInfo), + DockerRootDir: infoData.Store.GraphRoot, + Driver: infoData.Store.GraphDriverName, + DriverStatus: getGraphStatus(infoData.Store.GraphStatus), ExperimentalBuild: true, GenericResources: nil, HTTPProxy: getEnv("http_proxy"), HTTPSProxy: getEnv("https_proxy"), ID: uuid.New().String(), IPv4Forwarding: !sysInfo.IPv4ForwardingDisabled, - Images: storeInfo["ImageStore"].(map[string]interface{})["number"].(int), + Images: infoData.Store.ImageStore.Number, IndexServerAddress: "", InitBinary: "", InitCommit: docker.Commit{}, Isolation: "", KernelMemory: sysInfo.KernelMemory, KernelMemoryTCP: false, - KernelVersion: hostInfo["kernel"].(string), + KernelVersion: infoData.Host.Kernel, Labels: nil, LiveRestoreEnabled: false, LoggingDriver: "", - MemTotal: hostInfo["MemTotal"].(int64), + MemTotal: infoData.Host.MemTotal, MemoryLimit: sysInfo.MemoryLimit, NCPU: goRuntime.NumCPU(), NEventsListener: 0, NFd: getFdCount(), NGoroutines: goRuntime.NumGoroutine(), - Name: hostInfo["hostname"].(string), + Name: infoData.Host.Hostname, NoProxy: getEnv("no_proxy"), OSType: goRuntime.GOOS, - OSVersion: hostInfo["Distribution"].(map[string]interface{})["version"].(string), + OSVersion: infoData.Host.Distribution.Version, OomKillDisable: sysInfo.OomKillDisable, - OperatingSystem: hostInfo["Distribution"].(map[string]interface{})["distribution"].(string), + OperatingSystem: infoData.Host.Distribution.Distribution, PidsLimit: sysInfo.PidsLimit, Plugins: docker.PluginsInfo{}, ProductLicense: "Apache-2.0", @@ -118,21 +116,21 @@ func GetInfo(w http.ResponseWriter, r *http.Request) { SystemTime: time.Now().Format(time.RFC3339Nano), Warnings: []string{}, }, - BuildahVersion: hostInfo["BuildahVersion"].(string), + BuildahVersion: infoData.Host.BuildahVersion, CPURealtimePeriod: sysInfo.CPURealtimePeriod, CPURealtimeRuntime: sysInfo.CPURealtimeRuntime, - CgroupVersion: hostInfo["CgroupVersion"].(string), + CgroupVersion: infoData.Host.CGroupsVersion, Rootless: rootless.IsRootless(), - SwapFree: hostInfo["SwapFree"].(int64), - SwapTotal: hostInfo["SwapTotal"].(int64), - Uptime: hostInfo["uptime"].(string), + SwapFree: infoData.Host.SwapFree, + SwapTotal: infoData.Host.SwapTotal, + Uptime: infoData.Host.Uptime, } utils.WriteResponse(w, http.StatusOK, info) } -func getGraphStatus(storeInfo map[string]interface{}) [][2]string { +func getGraphStatus(storeInfo map[string]string) [][2]string { var graphStatus [][2]string - for k, v := range storeInfo["GraphStatus"].(map[string]string) { + for k, v := range storeInfo { graphStatus = append(graphStatus, [2]string{k, v}) } return graphStatus diff --git a/pkg/api/handlers/compat/version.go b/pkg/api/handlers/compat/version.go index c7f7917ac..35a95b562 100644 --- a/pkg/api/handlers/compat/version.go +++ b/pkg/api/handlers/compat/version.go @@ -30,8 +30,6 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to obtain system memory info")) return } - hostInfo := infoData[0].Data - components := []docker.ComponentVersion{{ Name: "Podman Engine", Version: versionInfo.Version, @@ -42,7 +40,7 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) { "Experimental": "true", "GitCommit": versionInfo.GitCommit, "GoVersion": versionInfo.GoVersion, - "KernelVersion": hostInfo["kernel"].(string), + "KernelVersion": infoData.Host.Kernel, "MinAPIVersion": handlers.MinimalApiVersion, "Os": goRuntime.GOOS, }, @@ -52,7 +50,7 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) { Platform: struct { Name string }{ - Name: fmt.Sprintf("%s/%s/%s", goRuntime.GOOS, goRuntime.GOARCH, hostInfo["Distribution"].(map[string]interface{})["distribution"].(string)), + Name: fmt.Sprintf("%s/%s/%s-%s", goRuntime.GOOS, goRuntime.GOARCH, infoData.Host.Distribution.Distribution, infoData.Host.Distribution.Version), }, APIVersion: components[0].Details["APIVersion"], Arch: components[0].Details["Arch"], diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index fde72552b..5cbfb11eb 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -4,21 +4,16 @@ import ( "io/ioutil" "net/http" "os" - "path/filepath" - "sort" "strconv" - "time" - "github.com/containers/libpod/pkg/api/handlers/compat" - - "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers/compat" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/ps" "github.com/gorilla/schema" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) func ContainerExists(w http.ResponseWriter, r *http.Request) { @@ -38,8 +33,8 @@ func ContainerExists(w http.ResponseWriter, r *http.Request) { func ListContainers(w http.ResponseWriter, r *http.Request) { var ( - filterFuncs []libpod.ContainerFilter - pss []ListContainer + //filterFuncs []libpod.ContainerFilter + //pss []entities.ListContainer ) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { @@ -61,66 +56,19 @@ func ListContainers(w http.ResponseWriter, r *http.Request) { } runtime := r.Context().Value("runtime").(*libpod.Runtime) - opts := shared.PsOptions{ + opts := entities.ContainerListOptions{ All: query.All, Last: query.Last, Size: query.Size, Sort: "", Namespace: query.Namespace, - NoTrunc: true, Pod: query.Pod, Sync: query.Sync, } - - all := query.All - if len(query.Filters) > 0 { - for k, v := range query.Filters { - for _, val := range v { - generatedFunc, err := shared.GenerateContainerFilterFuncs(k, val, runtime) - if err != nil { - utils.InternalServerError(w, err) - return - } - filterFuncs = append(filterFuncs, generatedFunc) - } - } - } - - // Docker thinks that if status is given as an input, then we should override - // the all setting and always deal with all containers. - if len(query.Filters["status"]) > 0 { - all = true - } - if !all { - runningOnly, err := shared.GenerateContainerFilterFuncs("status", define.ContainerStateRunning.String(), runtime) - if err != nil { - utils.InternalServerError(w, err) - return - } - filterFuncs = append(filterFuncs, runningOnly) - } - - cons, err := runtime.GetContainers(filterFuncs...) + pss, err := ps.GetContainerLists(runtime, opts) if err != nil { utils.InternalServerError(w, err) - } - if query.Last > 0 { - // Sort the containers we got - sort.Sort(psSortCreateTime{cons}) - // we should perform the lopping before we start getting - // the expensive information on containers - if query.Last < len(cons) { - cons = cons[len(cons)-query.Last:] - } - } - for _, con := range cons { - listCon, err := ListContainerBatch(runtime, con, opts) - if err != nil { - utils.InternalServerError(w, err) - return - } - pss = append(pss, listCon) - + return } utils.WriteResponse(w, http.StatusOK, pss) } @@ -212,125 +160,6 @@ func ShowMountedContainers(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, response) } -// BatchContainerOp is used in ps to reduce performance hits by "batching" -// locks. -func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts shared.PsOptions) (ListContainer, error) { - var ( - conConfig *libpod.ContainerConfig - conState define.ContainerStatus - err error - exitCode int32 - exited bool - pid int - size *shared.ContainerSize - startedTime time.Time - exitedTime time.Time - cgroup, ipc, mnt, net, pidns, user, uts string - ) - - batchErr := ctr.Batch(func(c *libpod.Container) error { - conConfig = c.Config() - conState, err = c.State() - if err != nil { - return errors.Wrapf(err, "unable to obtain container state") - } - - exitCode, exited, err = c.ExitCode() - if err != nil { - return errors.Wrapf(err, "unable to obtain container exit code") - } - startedTime, err = c.StartedTime() - if err != nil { - logrus.Errorf("error getting started time for %q: %v", c.ID(), err) - } - exitedTime, err = c.FinishedTime() - if err != nil { - logrus.Errorf("error getting exited time for %q: %v", c.ID(), err) - } - - if !opts.Size && !opts.Namespace { - return nil - } - - if opts.Namespace { - pid, err = c.PID() - if err != nil { - return errors.Wrapf(err, "unable to obtain container pid") - } - ctrPID := strconv.Itoa(pid) - cgroup, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "cgroup")) - ipc, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "ipc")) - mnt, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "mnt")) - net, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "net")) - pidns, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "pid")) - user, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "user")) - uts, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "uts")) - } - if opts.Size { - size = new(shared.ContainerSize) - - rootFsSize, err := c.RootFsSize() - if err != nil { - logrus.Errorf("error getting root fs size for %q: %v", c.ID(), err) - } - - rwSize, err := c.RWSize() - if err != nil { - logrus.Errorf("error getting rw size for %q: %v", c.ID(), err) - } - - size.RootFsSize = rootFsSize - size.RwSize = rwSize - } - return nil - }) - - if batchErr != nil { - return ListContainer{}, batchErr - } - - ps := ListContainer{ - Command: conConfig.Command, - Created: conConfig.CreatedTime.Unix(), - Exited: exited, - ExitCode: exitCode, - ExitedAt: exitedTime.Unix(), - ID: conConfig.ID, - Image: conConfig.RootfsImageName, - IsInfra: conConfig.IsInfra, - Labels: conConfig.Labels, - Mounts: ctr.UserVolumes(), - Names: []string{conConfig.Name}, - Pid: pid, - Pod: conConfig.Pod, - Ports: conConfig.PortMappings, - Size: size, - StartedAt: startedTime.Unix(), - State: conState.String(), - } - if opts.Pod && len(conConfig.Pod) > 0 { - pod, err := rt.GetPod(conConfig.Pod) - if err != nil { - return ListContainer{}, err - } - ps.PodName = pod.Name() - } - - if opts.Namespace { - ns := ListContainerNamespaces{ - Cgroup: cgroup, - IPC: ipc, - MNT: mnt, - NET: net, - PIDNS: pidns, - User: user, - UTS: uts, - } - ps.Namespaces = ns - } - return ps, nil -} - func Checkpoint(w http.ResponseWriter, r *http.Request) { var targetFile string decoder := r.Context().Value("decoder").(*schema.Decoder) diff --git a/pkg/api/handlers/libpod/info.go b/pkg/api/handlers/libpod/info.go new file mode 100644 index 000000000..cbf03aa17 --- /dev/null +++ b/pkg/api/handlers/libpod/info.go @@ -0,0 +1,18 @@ +package libpod + +import ( + "net/http" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers/utils" +) + +func GetInfo(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + info, err := runtime.Info() + if err != nil { + utils.InternalServerError(w, err) + return + } + utils.WriteResponse(w, http.StatusOK, info) +} diff --git a/pkg/api/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go index 1fad2dd1a..ed19462c6 100644 --- a/pkg/api/handlers/libpod/swagger.go +++ b/pkg/api/handlers/libpod/swagger.go @@ -5,6 +5,7 @@ import ( "os" "github.com/containers/image/v5/manifest" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" @@ -17,7 +18,7 @@ const DefaultPodmanSwaggerSpec = "/usr/share/containers/podman/swagger.yaml" // swagger:response ListContainers type swagInspectPodResponse struct { // in:body - Body []ListContainer + Body []entities.ListContainer } // Inspect Manifest @@ -76,6 +77,13 @@ type swagRmPodResponse struct { Body entities.PodRmReport } +// Info +// swagger:response InfoResponse +type swagInfoResponse struct { + // in:body + Body define.Info +} + 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/types.go b/pkg/api/handlers/libpod/types.go deleted file mode 100644 index 0949b2a72..000000000 --- a/pkg/api/handlers/libpod/types.go +++ /dev/null @@ -1,82 +0,0 @@ -package libpod - -import ( - "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/libpod" - "github.com/cri-o/ocicni/pkg/ocicni" -) - -// Listcontainer describes a container suitable for listing -type ListContainer struct { - // Container command - Command []string - // Container creation time - Created int64 - // If container has exited/stopped - Exited bool - // Time container exited - ExitedAt int64 - // If container has exited, the return code from the command - ExitCode int32 - // The unique identifier for the container - ID string `json:"Id"` - // Container image - Image string - // If this container is a Pod infra container - IsInfra bool - // Labels for container - Labels map[string]string - // User volume mounts - Mounts []string - // The names assigned to the container - Names []string - // Namespaces the container belongs to. Requires the - // namespace boolean to be true - Namespaces ListContainerNamespaces - // The process id of the container - Pid int - // If the container is part of Pod, the Pod ID. Requires the pod - // boolean to be set - Pod string - // If the container is part of Pod, the Pod name. Requires the pod - // boolean to be set - PodName string - // Port mappings - Ports []ocicni.PortMapping - // Size of the container rootfs. Requires the size boolean to be true - Size *shared.ContainerSize - // Time when container started - StartedAt int64 - // State of container - State string -} - -// ListContainer Namespaces contains the identifiers of the container's Linux namespaces -type ListContainerNamespaces struct { - // Mount namespace - MNT string `json:"Mnt,omitempty"` - // Cgroup namespace - Cgroup string `json:"Cgroup,omitempty"` - // IPC namespace - IPC string `json:"Ipc,omitempty"` - // Network namespace - NET string `json:"Net,omitempty"` - // PID namespace - PIDNS string `json:"Pidns,omitempty"` - // UTS namespace - UTS string `json:"Uts,omitempty"` - // User namespace - User string `json:"User,omitempty"` -} - -// sortContainers helps us set-up ability to sort by createTime -type sortContainers []*libpod.Container - -func (a sortContainers) Len() int { return len(a) } -func (a sortContainers) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -type psSortCreateTime struct{ sortContainers } - -func (a psSortCreateTime) Less(i, j int) bool { - return a.sortContainers[i].CreatedTime().Before(a.sortContainers[j].CreatedTime()) -} diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 496512f2e..f1c932ebc 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -353,7 +353,7 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI } -func LibpodToContainer(l *libpod.Container, infoData []define.InfoData, sz bool) (*Container, error) { +func LibpodToContainer(l *libpod.Container, sz bool) (*Container, error) { imageId, imageName := l.Image() var ( diff --git a/pkg/api/server/register_info.go b/pkg/api/server/register_info.go index b4ab8871c..75aaa957b 100644 --- a/pkg/api/server/register_info.go +++ b/pkg/api/server/register_info.go @@ -4,14 +4,15 @@ import ( "net/http" "github.com/containers/libpod/pkg/api/handlers/compat" + "github.com/containers/libpod/pkg/api/handlers/libpod" "github.com/gorilla/mux" ) func (s *APIServer) registerInfoHandlers(r *mux.Router) error { - // swagger:operation GET /info libpod libpodGetInfo + // swagger:operation GET /info compat getInfo // --- // tags: - // - system + // - system (compat) // summary: Get info // description: Returns information on the system and libpod configuration // produces: @@ -24,5 +25,19 @@ func (s *APIServer) registerInfoHandlers(r *mux.Router) error { r.Handle(VersionedPath("/info"), s.APIHandler(compat.GetInfo)).Methods(http.MethodGet) // Added non version path to URI to support docker non versioned paths r.Handle("/info", s.APIHandler(compat.GetInfo)).Methods(http.MethodGet) + // swagger:operation GET /libpod/info libpod libpodGetInfo + // --- + // tags: + // - system + // summary: Get info + // description: Returns information on the system and libpod configuration + // produces: + // - application/json + // responses: + // 200: + // $ref: "#/responses/InfoResponse" + // 500: + // $ref: "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/info"), s.APIHandler(libpod.GetInfo)).Methods(http.MethodGet) return nil } diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index 49a2dfd58..a188d73a0 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -10,8 +10,8 @@ import ( "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" + "github.com/containers/libpod/pkg/domain/entities" ) // List obtains a list of containers in local storage. All parameters to this method are optional. @@ -19,12 +19,12 @@ import ( // the most recent number of containers. The pod and size booleans indicate that pod information and rootfs // size information should also be included. Finally, the sync bool synchronizes the OCI runtime and // container state. -func List(ctx context.Context, filters map[string][]string, all *bool, last *int, pod, size, sync *bool) ([]lpapiv2.ListContainer, error) { // nolint:typecheck +func List(ctx context.Context, filters map[string][]string, all *bool, last *int, pod, size, sync *bool) ([]entities.ListContainer, error) { // nolint:typecheck conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } - var containers []lpapiv2.ListContainer + var containers []entities.ListContainer params := url.Values{} if all != nil { params.Set("all", strconv.FormatBool(*all)) diff --git a/pkg/bindings/info.go b/pkg/bindings/info.go deleted file mode 100644 index 5f318d652..000000000 --- a/pkg/bindings/info.go +++ /dev/null @@ -1,3 +0,0 @@ -package bindings - -func (c Connection) Info() {} diff --git a/pkg/bindings/system/info.go b/pkg/bindings/system/info.go new file mode 100644 index 000000000..f8269cfd8 --- /dev/null +++ b/pkg/bindings/system/info.go @@ -0,0 +1,23 @@ +package system + +import ( + "context" + "net/http" + + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/bindings" +) + +// Info returns information about the libpod environment and its stores +func Info(ctx context.Context) (define.Info, error) { + info := define.Info{} + conn, err := bindings.GetClient(ctx) + if err != nil { + return info, err + } + response, err := conn.DoRequest(nil, http.MethodGet, "/info", nil) + if err != nil { + return info, err + } + return info, response.Process(&info) +} diff --git a/pkg/bindings/test/info_test.go b/pkg/bindings/test/info_test.go new file mode 100644 index 000000000..d0e651134 --- /dev/null +++ b/pkg/bindings/test/info_test.go @@ -0,0 +1,73 @@ +package test_bindings + +import ( + "runtime" + "time" + + "github.com/containers/libpod/pkg/bindings/containers" + "github.com/containers/libpod/pkg/bindings/images" + "github.com/containers/libpod/pkg/bindings/system" + "github.com/containers/libpod/pkg/specgen" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" +) + +var _ = Describe("Podman info", func() { + var ( + bt *bindingTest + s *gexec.Session + t bool = true + ) + + 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 info", func() { + info, err := system.Info(bt.conn) + Expect(err).To(BeNil()) + Expect(info.Host.Arch).To(Equal(runtime.GOARCH)) + Expect(info.Host.OS).To(Equal(runtime.GOOS)) + i, err := images.List(bt.conn, &t, nil) + Expect(err).To(BeNil()) + Expect(info.Store.ImageStore.Number).To(Equal(len(i))) + }) + + It("podman info container counts", func() { + s := specgen.NewSpecGenerator(alpine.name) + _, err := containers.CreateWithSpec(bt.conn, s) + Expect(err).To(BeNil()) + + idPause, err := bt.RunTopContainer(nil, nil, nil) + Expect(err).To(BeNil()) + err = containers.Pause(bt.conn, idPause) + Expect(err).To(BeNil()) + + idStop, err := bt.RunTopContainer(nil, nil, nil) + Expect(err).To(BeNil()) + err = containers.Stop(bt.conn, idStop, nil) + Expect(err).To(BeNil()) + + _, err = bt.RunTopContainer(nil, nil, nil) + Expect(err).To(BeNil()) + + info, err := system.Info(bt.conn) + Expect(err).To(BeNil()) + + Expect(info.Store.ContainerStore.Number).To(BeNumerically("==", 4)) + Expect(info.Store.ContainerStore.Paused).To(Equal(1)) + Expect(info.Store.ContainerStore.Stopped).To(Equal(2)) + Expect(info.Store.ContainerStore.Running).To(Equal(1)) + }) +}) diff --git a/pkg/domain/entities/container_ps.go b/pkg/domain/entities/container_ps.go new file mode 100644 index 000000000..ceafecebc --- /dev/null +++ b/pkg/domain/entities/container_ps.go @@ -0,0 +1,174 @@ +package entities + +import ( + "sort" + "strings" + + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/pkg/errors" +) + +// Listcontainer describes a container suitable for listing +type ListContainer struct { + // Container command + Command []string + // Container creation time + Created int64 + // If container has exited/stopped + Exited bool + // Time container exited + ExitedAt int64 + // If container has exited, the return code from the command + ExitCode int32 + // The unique identifier for the container + ID string `json:"Id"` + // Container image + Image string + // If this container is a Pod infra container + IsInfra bool + // Labels for container + Labels map[string]string + // User volume mounts + Mounts []string + // The names assigned to the container + Names []string + // Namespaces the container belongs to. Requires the + // namespace boolean to be true + Namespaces ListContainerNamespaces + // The process id of the container + Pid int + // If the container is part of Pod, the Pod ID. Requires the pod + // boolean to be set + Pod string + // If the container is part of Pod, the Pod name. Requires the pod + // boolean to be set + PodName string + // Port mappings + Ports []ocicni.PortMapping + // Size of the container rootfs. Requires the size boolean to be true + Size *shared.ContainerSize + // Time when container started + StartedAt int64 + // State of container + State string +} + +// ListContainer Namespaces contains the identifiers of the container's Linux namespaces +type ListContainerNamespaces struct { + // Mount namespace + MNT string `json:"Mnt,omitempty"` + // Cgroup namespace + Cgroup string `json:"Cgroup,omitempty"` + // IPC namespace + IPC string `json:"Ipc,omitempty"` + // Network namespace + NET string `json:"Net,omitempty"` + // PID namespace + PIDNS string `json:"Pidns,omitempty"` + // UTS namespace + UTS string `json:"Uts,omitempty"` + // User namespace + User string `json:"User,omitempty"` +} + +// SortContainers helps us set-up ability to sort by createTime +type SortContainers []*libpod.Container + +func (a SortContainers) Len() int { return len(a) } +func (a SortContainers) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +type SortCreateTime struct{ SortContainers } + +func (a SortCreateTime) Less(i, j int) bool { + return a.SortContainers[i].CreatedTime().Before(a.SortContainers[j].CreatedTime()) +} + +type SortListContainers []ListContainer + +func (a SortListContainers) Len() int { return len(a) } +func (a SortListContainers) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +type psSortedCommand struct{ SortListContainers } + +func (a psSortedCommand) Less(i, j int) bool { + return strings.Join(a.SortListContainers[i].Command, " ") < strings.Join(a.SortListContainers[j].Command, " ") +} + +type psSortedId struct{ SortListContainers } + +func (a psSortedId) Less(i, j int) bool { + return a.SortListContainers[i].ID < a.SortListContainers[j].ID +} + +type psSortedImage struct{ SortListContainers } + +func (a psSortedImage) Less(i, j int) bool { + return a.SortListContainers[i].Image < a.SortListContainers[j].Image +} + +type psSortedNames struct{ SortListContainers } + +func (a psSortedNames) Less(i, j int) bool { + return a.SortListContainers[i].Names[0] < a.SortListContainers[j].Names[0] +} + +type psSortedPod struct{ SortListContainers } + +func (a psSortedPod) Less(i, j int) bool { + return a.SortListContainers[i].Pod < a.SortListContainers[j].Pod +} + +type psSortedRunningFor struct{ SortListContainers } + +func (a psSortedRunningFor) Less(i, j int) bool { + return a.SortListContainers[i].StartedAt < a.SortListContainers[j].StartedAt +} + +type psSortedStatus struct{ SortListContainers } + +func (a psSortedStatus) Less(i, j int) bool { + return a.SortListContainers[i].State < a.SortListContainers[j].State +} + +type psSortedSize struct{ SortListContainers } + +func (a psSortedSize) Less(i, j int) bool { + if a.SortListContainers[i].Size == nil || a.SortListContainers[j].Size == nil { + return false + } + return a.SortListContainers[i].Size.RootFsSize < a.SortListContainers[j].Size.RootFsSize +} + +type PsSortedCreateTime struct{ SortListContainers } + +func (a PsSortedCreateTime) Less(i, j int) bool { + return a.SortListContainers[i].Created < a.SortListContainers[j].Created +} + +func SortPsOutput(sortBy string, psOutput SortListContainers) (SortListContainers, error) { + switch sortBy { + case "id": + sort.Sort(psSortedId{psOutput}) + case "image": + sort.Sort(psSortedImage{psOutput}) + case "command": + sort.Sort(psSortedCommand{psOutput}) + case "runningfor": + sort.Sort(psSortedRunningFor{psOutput}) + case "status": + sort.Sort(psSortedStatus{psOutput}) + case "size": + sort.Sort(psSortedSize{psOutput}) + case "names": + sort.Sort(psSortedNames{psOutput}) + case "created": + sort.Sort(PsSortedCreateTime{psOutput}) + case "pod": + sort.Sort(psSortedPod{psOutput}) + default: + return nil, errors.Errorf("invalid option for --sort, options are: command, created, id, image, names, runningfor, size, or status") + } + return psOutput, nil +} diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index cf907eb1b..4f94e009f 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -186,3 +186,41 @@ type ExecOptions struct { User string WorkDir string } + +// ContainerStartOptions describes the val from the +// CLI needed to start a container +type ContainerStartOptions struct { + Attach bool + DetachKeys string + Interactive bool + Latest bool + SigProxy bool + Stdout *os.File + Stderr *os.File + Stdin *os.File +} + +// ContainerStartReport describes the response from starting +// containers from the cli +type ContainerStartReport struct { + Id string + Err error + ExitCode int +} + +// ContainerListOptions describes the CLI options +// for listing containers +type ContainerListOptions struct { + All bool + Filters map[string][]string + Format string + Last int + Latest bool + Namespace bool + Pod bool + Quiet bool + Size bool + Sort string + Sync bool + Watch uint +} diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 9bf3d51de..36c20c38d 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -19,8 +19,10 @@ type ContainerEngine interface { ContainerExport(ctx context.Context, nameOrId string, options ContainerExportOptions) error ContainerKill(ctx context.Context, namesOrIds []string, options KillOptions) ([]*KillReport, error) ContainerPause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) + ContainerList(ctx context.Context, options ContainerListOptions) ([]ListContainer, error) ContainerRestart(ctx context.Context, namesOrIds []string, options RestartOptions) ([]*RestartReport, error) ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error) + ContainerStart(ctx context.Context, namesOrIds []string, options ContainerStartOptions) ([]*ContainerStartReport, error) ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error) ContainerTop(ctx context.Context, options TopOptions) (*StringSliceReport, error) ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index b93e7665a..54f91a189 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -12,10 +12,12 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/checkpoint" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/infra/abi/terminal" + "github.com/containers/libpod/pkg/ps" "github.com/containers/libpod/pkg/signal" "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/specgen/generate" @@ -509,3 +511,114 @@ func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, o ec, err = terminal.ExecAttachCtr(ctx, ctr, options.Tty, options.Privileged, options.Envs, options.Cmd, options.User, options.WorkDir, &options.Streams, options.PreserveFDs, options.DetachKeys) return define.TranslateExecErrorToExitCode(ec, err), err } + +func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []string, options entities.ContainerStartOptions) ([]*entities.ContainerStartReport, error) { + var reports []*entities.ContainerStartReport + var exitCode = define.ExecErrorCodeGeneric + ctrs, err := getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + // There can only be one container if attach was used + for _, ctr := range ctrs { + ctrState, err := ctr.State() + if err != nil { + return nil, err + } + ctrRunning := ctrState == define.ContainerStateRunning + + if options.Attach { + err = terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, !ctrRunning, ctr.PodID() != "") + if errors.Cause(err) == define.ErrDetach { + // User manually detached + // Exit cleanly immediately + reports = append(reports, &entities.ContainerStartReport{ + Id: ctr.ID(), + Err: nil, + ExitCode: 0, + }) + return reports, nil + } + + if errors.Cause(err) == define.ErrWillDeadlock { + logrus.Debugf("Deadlock error: %v", err) + reports = append(reports, &entities.ContainerStartReport{ + Id: ctr.ID(), + Err: err, + ExitCode: define.ExitCode(err), + }) + return reports, errors.Errorf("attempting to start container %s would cause a deadlock; please run 'podman system renumber' to resolve", ctr.ID()) + } + + if ctrRunning { + reports = append(reports, &entities.ContainerStartReport{ + Id: ctr.ID(), + Err: nil, + ExitCode: 0, + }) + return reports, err + } + + if err != nil { + reports = append(reports, &entities.ContainerStartReport{ + Id: ctr.ID(), + Err: err, + ExitCode: exitCode, + }) + return reports, errors.Wrapf(err, "unable to start container %s", ctr.ID()) + } + + if ecode, err := ctr.Wait(); err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr { + // Check events + event, err := ic.Libpod.GetLastContainerEvent(ctr.ID(), events.Exited) + if err != nil { + logrus.Errorf("Cannot get exit code: %v", err) + exitCode = define.ExecErrorCodeNotFound + } else { + exitCode = event.ContainerExitCode + } + } + } else { + exitCode = int(ecode) + } + reports = append(reports, &entities.ContainerStartReport{ + Id: ctr.ID(), + Err: err, + ExitCode: exitCode, + }) + return reports, nil + } // end attach + + // Start the container if it's not running already. + if !ctrRunning { + // Handle non-attach start + // If the container is in a pod, also set to recursively start dependencies + report := &entities.ContainerStartReport{ + Id: ctr.ID(), + ExitCode: 125, + } + if err := ctr.Start(ctx, ctr.PodID() != ""); err != nil { + //if lastError != nil { + // fmt.Fprintln(os.Stderr, lastError) + //} + report.Err = err + if errors.Cause(err) == define.ErrWillDeadlock { + report.Err = errors.Wrapf(err, "please run 'podman system renumber' to resolve deadlocks") + reports = append(reports, report) + continue + } + report.Err = errors.Wrapf(err, "unable to start container %q", ctr.ID()) + reports = append(reports, report) + continue + } + report.ExitCode = 0 + reports = append(reports, report) + } + } + return reports, nil +} + +func (ic *ContainerEngine) ContainerList(ctx context.Context, options entities.ContainerListOptions) ([]entities.ListContainer, error) { + return ps.GetContainerLists(ic.Libpod, options) +} diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go index 073cd8d5c..c3e5d59bc 100644 --- a/pkg/domain/infra/abi/pods.go +++ b/pkg/domain/infra/abi/pods.go @@ -5,9 +5,10 @@ package abi import ( "context" + lpfilters "github.com/containers/libpod/libpod/filters" + "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/libpod/podfilters" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/signal" "github.com/containers/libpod/pkg/specgen" @@ -281,7 +282,7 @@ func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOpti ) for k, v := range options.Filters { for _, filter := range v { - f, err := podfilters.GeneratePodFilterFunc(k, filter) + f, err := lpfilters.GeneratePodFilterFunc(k, filter) if err != nil { return nil, err } diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 1e71cba2c..b6807d5b6 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -7,7 +7,6 @@ import ( "github.com/containers/image/v5/docker/reference" "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/api/handlers/libpod" "github.com/containers/libpod/pkg/bindings/containers" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/specgen" @@ -233,7 +232,7 @@ func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds [ var ( reports []*entities.CheckpointReport err error - ctrs []libpod.ListContainer + ctrs []entities.ListContainer ) if options.All { @@ -268,7 +267,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st var ( reports []*entities.RestoreReport err error - ctrs []libpod.ListContainer + ctrs []entities.ListContainer ) if options.All { allCtrs, err := getContainersByContext(ic.ClientCxt, true, []string{}) @@ -313,3 +312,11 @@ func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string, func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions) (int, error) { return 125, errors.New("not implemented") } + +func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []string, options entities.ContainerStartOptions) ([]*entities.ContainerStartReport, error) { + return nil, errors.New("not implemented") +} + +func (ic *ContainerEngine) ContainerList(ctx context.Context, options entities.ContainerListOptions) ([]entities.ListContainer, error) { + return containers.List(ic.ClientCxt, options.Filters, &options.All, &options.Last, &options.Pod, &options.Size, &options.Sync) +} diff --git a/pkg/domain/infra/tunnel/helpers.go b/pkg/domain/infra/tunnel/helpers.go index f9183c955..682d60d6a 100644 --- a/pkg/domain/infra/tunnel/helpers.go +++ b/pkg/domain/infra/tunnel/helpers.go @@ -5,7 +5,6 @@ import ( "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" @@ -14,9 +13,9 @@ import ( "github.com/pkg/errors" ) -func getContainersByContext(contextWithConnection context.Context, all bool, namesOrIds []string) ([]libpod.ListContainer, error) { +func getContainersByContext(contextWithConnection context.Context, all bool, namesOrIds []string) ([]entities.ListContainer, error) { var ( - cons []libpod.ListContainer + cons []entities.ListContainer ) if all && len(namesOrIds) > 0 { return nil, errors.New("cannot lookup containers and all") diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go new file mode 100644 index 000000000..58fcc2c21 --- /dev/null +++ b/pkg/ps/ps.go @@ -0,0 +1,189 @@ +package ps + +import ( + "path/filepath" + "sort" + "strconv" + "time" + + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + lpfilters "github.com/containers/libpod/libpod/filters" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOptions) ([]entities.ListContainer, error) { + var ( + filterFuncs []libpod.ContainerFilter + pss []entities.ListContainer + ) + all := options.All + if len(options.Filters) > 0 { + for k, v := range options.Filters { + for _, val := range v { + generatedFunc, err := lpfilters.GenerateContainerFilterFuncs(k, val, runtime) + if err != nil { + return nil, err + } + filterFuncs = append(filterFuncs, generatedFunc) + } + } + } + + // Docker thinks that if status is given as an input, then we should override + // the all setting and always deal with all containers. + if len(options.Filters["status"]) > 0 { + all = true + } + if !all { + runningOnly, err := lpfilters.GenerateContainerFilterFuncs("status", define.ContainerStateRunning.String(), runtime) + if err != nil { + return nil, err + } + filterFuncs = append(filterFuncs, runningOnly) + } + + cons, err := runtime.GetContainers(filterFuncs...) + if err != nil { + return nil, err + } + if options.Last > 0 { + // Sort the containers we got + sort.Sort(entities.SortCreateTime{SortContainers: cons}) + // we should perform the lopping before we start getting + // the expensive information on containers + if options.Last < len(cons) { + cons = cons[len(cons)-options.Last:] + } + } + for _, con := range cons { + listCon, err := ListContainerBatch(runtime, con, options) + if err != nil { + return nil, err + } + pss = append(pss, listCon) + + } + return pss, nil +} + +// BatchContainerOp is used in ps to reduce performance hits by "batching" +// locks. +func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities.ContainerListOptions) (entities.ListContainer, error) { + var ( + conConfig *libpod.ContainerConfig + conState define.ContainerStatus + err error + exitCode int32 + exited bool + pid int + size *shared.ContainerSize + startedTime time.Time + exitedTime time.Time + cgroup, ipc, mnt, net, pidns, user, uts string + ) + + batchErr := ctr.Batch(func(c *libpod.Container) error { + conConfig = c.Config() + conState, err = c.State() + if err != nil { + return errors.Wrapf(err, "unable to obtain container state") + } + + exitCode, exited, err = c.ExitCode() + if err != nil { + return errors.Wrapf(err, "unable to obtain container exit code") + } + startedTime, err = c.StartedTime() + if err != nil { + logrus.Errorf("error getting started time for %q: %v", c.ID(), err) + } + exitedTime, err = c.FinishedTime() + if err != nil { + logrus.Errorf("error getting exited time for %q: %v", c.ID(), err) + } + + if !opts.Size && !opts.Namespace { + return nil + } + + if opts.Namespace { + pid, err = c.PID() + if err != nil { + return errors.Wrapf(err, "unable to obtain container pid") + } + ctrPID := strconv.Itoa(pid) + cgroup, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "cgroup")) + ipc, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "ipc")) + mnt, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "mnt")) + net, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "net")) + pidns, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "pid")) + user, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "user")) + uts, _ = shared.GetNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "uts")) + } + if opts.Size { + size = new(shared.ContainerSize) + + rootFsSize, err := c.RootFsSize() + if err != nil { + logrus.Errorf("error getting root fs size for %q: %v", c.ID(), err) + } + + rwSize, err := c.RWSize() + if err != nil { + logrus.Errorf("error getting rw size for %q: %v", c.ID(), err) + } + + size.RootFsSize = rootFsSize + size.RwSize = rwSize + } + return nil + }) + + if batchErr != nil { + return entities.ListContainer{}, batchErr + } + + ps := entities.ListContainer{ + Command: conConfig.Command, + Created: conConfig.CreatedTime.Unix(), + Exited: exited, + ExitCode: exitCode, + ExitedAt: exitedTime.Unix(), + ID: conConfig.ID, + Image: conConfig.RootfsImageName, + IsInfra: conConfig.IsInfra, + Labels: conConfig.Labels, + Mounts: ctr.UserVolumes(), + Names: []string{conConfig.Name}, + Pid: pid, + Pod: conConfig.Pod, + Ports: conConfig.PortMappings, + Size: size, + StartedAt: startedTime.Unix(), + State: conState.String(), + } + if opts.Pod && len(conConfig.Pod) > 0 { + pod, err := rt.GetPod(conConfig.Pod) + if err != nil { + return entities.ListContainer{}, err + } + ps.PodName = pod.Name() + } + + if opts.Namespace { + ps.Namespaces = entities.ListContainerNamespaces{ + Cgroup: cgroup, + IPC: ipc, + MNT: mnt, + NET: net, + PIDNS: pidns, + User: user, + UTS: uts, + } + } + return ps, nil +} diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go index 04fb9f648..7bee643c2 100644 --- a/pkg/varlinkapi/system.go +++ b/pkg/varlinkapi/system.go @@ -44,28 +44,26 @@ func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error { return call.ReplyErrorOccurred(err.Error()) } - host := info[0].Data distribution := iopodman.InfoDistribution{ - Distribution: host["Distribution"].(map[string]interface{})["distribution"].(string), - Version: host["Distribution"].(map[string]interface{})["version"].(string), + Distribution: info.Host.Distribution.Distribution, + Version: info.Host.Distribution.Version, } infoHost := iopodman.InfoHost{ - Buildah_version: host["BuildahVersion"].(string), + Buildah_version: info.Host.BuildahVersion, Distribution: distribution, - Mem_free: host["MemFree"].(int64), - Mem_total: host["MemTotal"].(int64), - Swap_free: host["SwapFree"].(int64), - Swap_total: host["SwapTotal"].(int64), - Arch: host["arch"].(string), - Cpus: int64(host["cpus"].(int)), - Hostname: host["hostname"].(string), - Kernel: host["kernel"].(string), - Os: host["os"].(string), - Uptime: host["uptime"].(string), - Eventlogger: host["eventlogger"].(string), + Mem_free: info.Host.MemFree, + Mem_total: info.Host.MemTotal, + Swap_free: info.Host.SwapFree, + Swap_total: info.Host.SwapTotal, + Arch: info.Host.Arch, + Cpus: int64(info.Host.CPUs), + Hostname: info.Host.Hostname, + Kernel: info.Host.Kernel, + Os: info.Host.OS, + Uptime: info.Host.Uptime, + Eventlogger: info.Host.EventLogger, } podmanInfo.Host = infoHost - store := info[1].Data pmaninfo := iopodman.InfoPodmanBinary{ Compiler: goruntime.Compiler, Go_version: goruntime.Version(), @@ -74,36 +72,33 @@ func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error { } graphStatus := iopodman.InfoGraphStatus{ - Backing_filesystem: store["GraphStatus"].(map[string]string)["Backing Filesystem"], - Native_overlay_diff: store["GraphStatus"].(map[string]string)["Native Overlay Diff"], - Supports_d_type: store["GraphStatus"].(map[string]string)["Supports d_type"], + Backing_filesystem: info.Store.GraphStatus["Backing Filesystem"], + Native_overlay_diff: info.Store.GraphStatus["Native Overlay Diff"], + Supports_d_type: info.Store.GraphStatus["Supports d_type"], } infoStore := iopodman.InfoStore{ - Graph_driver_name: store["GraphDriverName"].(string), - Containers: int64(store["ContainerStore"].(map[string]interface{})["number"].(int)), - Images: int64(store["ImageStore"].(map[string]interface{})["number"].(int)), - Run_root: store["RunRoot"].(string), - Graph_root: store["GraphRoot"].(string), - Graph_driver_options: fmt.Sprintf("%v", store["GraphOptions"]), + Graph_driver_name: info.Store.GraphDriverName, + Containers: int64(info.Store.ContainerStore.Number), + Images: int64(info.Store.ImageStore.Number), + Run_root: info.Store.RunRoot, + Graph_root: info.Store.GraphRoot, + Graph_driver_options: fmt.Sprintf("%v", info.Store.GraphOptions), Graph_status: graphStatus, } // Registry information if any is stored as the second list item - if len(info) > 2 { - for key, val := range info[2].Data { - if key == "search" { - podmanInfo.Registries.Search = val.([]string) - continue - } - regData := val.(sysregistriesv2.Registry) - if regData.Insecure { - podmanInfo.Registries.Insecure = append(podmanInfo.Registries.Insecure, key) - } - if regData.Blocked { - podmanInfo.Registries.Blocked = append(podmanInfo.Registries.Blocked, key) - } + for key, val := range info.Registries { + if key == "search" { + podmanInfo.Registries.Search = val.([]string) + continue + } + regData := val.(sysregistriesv2.Registry) + if regData.Insecure { + podmanInfo.Registries.Insecure = append(podmanInfo.Registries.Insecure, key) + } + if regData.Blocked { + podmanInfo.Registries.Blocked = append(podmanInfo.Registries.Blocked, key) } - } podmanInfo.Store = infoStore podmanInfo.Podman = pmaninfo diff --git a/test/e2e/info_test.go b/test/e2e/info_test.go index d16661d5b..446dbc16e 100644 --- a/test/e2e/info_test.go +++ b/test/e2e/info_test.go @@ -43,10 +43,16 @@ var _ = Describe("Podman Info", func() { Expect(session.ExitCode()).To(Equal(0)) }) - It("podman info --format GO template", func() { + It("podman info --format JSON GO template", func() { session := podmanTest.Podman([]string{"info", "--format", "{{ json .}}"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) Expect(session.IsJSONOutputValid()).To(BeTrue()) }) + + It("podman info --format GO template", func() { + session := podmanTest.Podman([]string{"info", "--format", "{{ .Store.GraphRoot }}"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) }) diff --git a/test/system/005-info.bats b/test/system/005-info.bats index f229b0886..c53ba8125 100644 --- a/test/system/005-info.bats +++ b/test/system/005-info.bats @@ -8,19 +8,19 @@ load helpers run_podman info expected_keys=" -BuildahVersion: *[0-9.]\\\+ -Conmon:\\\s\\\+package: -Distribution: -OCIRuntime:\\\s\\\+name: +buildahVersion: *[0-9.]\\\+ +conmon:\\\s\\\+package: +distribution: +ociRuntime:\\\s\\\+name: os: rootless: registries: store: -GraphDriverName: -GraphRoot: -GraphStatus: -ImageStore:\\\s\\\+number: 1 -RunRoot: +graphDriverName: +graphRoot: +graphStatus: +imageStore:\\\s\\\+number: 1 +runRoot: " while read expect; do is "$output" ".*$expect" "output includes '$expect'" @@ -36,13 +36,13 @@ RunRoot: expr_path="/[a-z0-9\\\/.-]\\\+\\\$" tests=" -host.BuildahVersion | [0-9.] -host.Conmon.path | $expr_path -host.OCIRuntime.path | $expr_path -store.ConfigFile | $expr_path -store.GraphDriverName | [a-z0-9]\\\+\\\$ -store.GraphRoot | $expr_path -store.ImageStore.number | 1 +host.buildahVersion | [0-9.] +host.conmon.path | $expr_path +host.ociRuntime.path | $expr_path +store.configFile | $expr_path +store.graphDriverName | [a-z0-9]\\\+\\\$ +store.graphRoot | $expr_path +store.imageStore.number | 1 " parse_table "$tests" | while read field expect; do diff --git a/test/system/030-run.bats b/test/system/030-run.bats index 98c65f788..56e9fed3b 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -12,7 +12,7 @@ load helpers err_no_exec_dir="Error: .*: starting container process caused .*exec:.* permission denied" # ...but check the configured runtime engine, and switch to crun as needed - run_podman info --format '{{ .host.OCIRuntime.path }}' + run_podman info --format '{{ .Host.OCIRuntime.Path }}' if expr "$output" : ".*/crun"; then err_no_such_cmd="Error: executable file not found in \$PATH: No such file or directory: OCI runtime command not found error" err_no_exec_dir="Error: open executable: Operation not permitted: OCI runtime permission denied error" diff --git a/test/system/065-cp.bats b/test/system/065-cp.bats index 0701055f9..a350c2173 100644 --- a/test/system/065-cp.bats +++ b/test/system/065-cp.bats @@ -187,7 +187,7 @@ load helpers chmod 644 $srcdir/$rand_filename # Determine path to podman storage (eg /var/lib/c/s, or $HOME/.local/...) - run_podman info --format '{{.store.GraphRoot}}' + run_podman info --format '{{.Store.GraphRoot}}' graphroot=$output # Create that directory in the container, and sleep (to keep container diff --git a/test/system/400-unprivileged-access.bats b/test/system/400-unprivileged-access.bats index 56c40e9c8..98f8b8211 100644 --- a/test/system/400-unprivileged-access.bats +++ b/test/system/400-unprivileged-access.bats @@ -70,10 +70,10 @@ EOF chmod 755 $PODMAN_TMPDIR $test_script # get podman image and container storage directories - run_podman info --format '{{.store.GraphRoot}}' + run_podman info --format '{{.Store.GraphRoot}}' is "$output" "/var/lib/containers/storage" "GraphRoot in expected place" GRAPH_ROOT="$output" - run_podman info --format '{{.store.RunRoot}}' + run_podman info --format '{{.Store.RunRoot}}' is "$output" "/var/run/containers/storage" "RunRoot in expected place" RUN_ROOT="$output" diff --git a/test/system/helpers.bash b/test/system/helpers.bash index 2e856930e..51240edc9 100644 --- a/test/system/helpers.bash +++ b/test/system/helpers.bash @@ -391,7 +391,7 @@ function random_string() { # Return exec_pid hash files if exists, otherwise, return nothing # function find_exec_pid_files() { - run_podman info --format '{{.store.RunRoot}}' + run_podman info --format '{{.Store.RunRoot}}' local storage_path="$output" if [ -d $storage_path ]; then find $storage_path -type f -iname 'exec_pid_*' diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go index 599bdb6e2..9c979e5e2 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go @@ -31,6 +31,7 @@ const ( // Disabled constant to indicate SELinux is disabled Disabled = -1 + contextFile = "/usr/share/containers/selinux/contexts" selinuxDir = "/etc/selinux/" selinuxConfig = selinuxDir + "config" selinuxfsMount = "/sys/fs/selinux" @@ -684,23 +685,26 @@ func ROFileLabel() string { return roFileLabel } -/* -ContainerLabels returns an allocated processLabel and fileLabel to be used for -container labeling by the calling process. -*/ -func ContainerLabels() (processLabel string, fileLabel string) { +func openContextFile() (*os.File, error) { + if f, err := os.Open(contextFile); err == nil { + return f, nil + } + lxcPath := filepath.Join(getSELinuxPolicyRoot(), "/contexts/lxc_contexts") + return os.Open(lxcPath) +} + +var labels = loadLabels() + +func loadLabels() map[string]string { var ( val, key string bufin *bufio.Reader ) - if !GetEnabled() { - return "", "" - } - lxcPath := fmt.Sprintf("%s/contexts/lxc_contexts", getSELinuxPolicyRoot()) - in, err := os.Open(lxcPath) + labels := make(map[string]string) + in, err := openContextFile() if err != nil { - return "", "" + return labels } defer in.Close() @@ -712,7 +716,7 @@ func ContainerLabels() (processLabel string, fileLabel string) { if err == io.EOF { done = true } else { - goto exit + break } } line = strings.TrimSpace(line) @@ -726,26 +730,64 @@ func ContainerLabels() (processLabel string, fileLabel string) { } if groups := assignRegex.FindStringSubmatch(line); groups != nil { key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2]) - if key == "process" { - processLabel = strings.Trim(val, "\"") - } - if key == "file" { - fileLabel = strings.Trim(val, "\"") - } - if key == "ro_file" { - roFileLabel = strings.Trim(val, "\"") - } + labels[key] = strings.Trim(val, "\"") } } - if processLabel == "" || fileLabel == "" { + return labels +} + +/* +KVMContainerLabels returns the default processLabel and mountLabel to be used +for kvm containers by the calling process. +*/ +func KVMContainerLabels() (string, string) { + processLabel := labels["kvm_process"] + if processLabel == "" { + processLabel = labels["process"] + } + + return addMcs(processLabel, labels["file"]) +} + +/* +InitContainerLabels returns the default processLabel and file labels to be +used for containers running an init system like systemd by the calling process. +*/ +func InitContainerLabels() (string, string) { + processLabel := labels["init_process"] + if processLabel == "" { + processLabel = labels["process"] + } + + return addMcs(processLabel, labels["file"]) +} + +/* +ContainerLabels returns an allocated processLabel and fileLabel to be used for +container labeling by the calling process. +*/ +func ContainerLabels() (processLabel string, fileLabel string) { + if !GetEnabled() { return "", "" } + processLabel = labels["process"] + fileLabel = labels["file"] + roFileLabel = labels["ro_file"] + + if processLabel == "" || fileLabel == "" { + return "", fileLabel + } + if roFileLabel == "" { roFileLabel = fileLabel } -exit: + + return addMcs(processLabel, fileLabel) +} + +func addMcs(processLabel, fileLabel string) (string, string) { scon, _ := NewContext(processLabel) if scon["level"] != "" { mcs := uniqMcs(1024) diff --git a/vendor/modules.txt b/vendor/modules.txt index acefffd5c..f4663b04d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -411,7 +411,7 @@ github.com/opencontainers/runtime-tools/generate github.com/opencontainers/runtime-tools/generate/seccomp github.com/opencontainers/runtime-tools/specerror github.com/opencontainers/runtime-tools/validate -# github.com/opencontainers/selinux v1.4.0 +# github.com/opencontainers/selinux v1.5.0 github.com/opencontainers/selinux/go-selinux github.com/opencontainers/selinux/go-selinux/label github.com/opencontainers/selinux/pkg/pwalk |