diff options
41 files changed, 1037 insertions, 179 deletions
diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 33cba30cd..7467c184a 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -192,7 +192,6 @@ func getMemoryLimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []strin func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) error { var ( err error - // namespaces map[string]string ) // validate flags as needed @@ -234,9 +233,15 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // We are not handling the Expose flag yet. // s.PortsExpose = c.Expose s.PortMappings = c.Net.PublishPorts - s.PublishImagePorts = c.PublishAll + s.PublishExposedPorts = c.PublishAll s.Pod = c.Pod + expose, err := createExpose(c.Expose) + if err != nil { + return err + } + s.Expose = expose + for k, v := range map[string]*specgen.Namespace{ c.IPC: &s.IpcNS, c.PID: &s.PidNS, diff --git a/cmd/podman/common/util.go b/cmd/podman/common/util.go index 47bbe12fa..a3626b4e4 100644 --- a/cmd/podman/common/util.go +++ b/cmd/podman/common/util.go @@ -1,43 +1,201 @@ package common import ( + "net" "strconv" + "strings" - "github.com/cri-o/ocicni/pkg/ocicni" - "github.com/docker/go-connections/nat" + "github.com/containers/libpod/pkg/specgen" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) -// createPortBindings iterates ports mappings and exposed ports into a format CNI understands -func createPortBindings(ports []string) ([]ocicni.PortMapping, error) { - // TODO wants someone to rewrite this code in the future - var portBindings []ocicni.PortMapping - // The conversion from []string to natBindings is temporary while mheon reworks the port - // deduplication code. Eventually that step will not be required. - _, natBindings, err := nat.ParsePortSpecs(ports) - if err != nil { - return nil, err - } - for containerPb, hostPb := range natBindings { - var pm ocicni.PortMapping - pm.ContainerPort = int32(containerPb.Int()) - for _, i := range hostPb { - var hostPort int - var err error - pm.HostIP = i.HostIP - if i.HostPort == "" { - hostPort = containerPb.Int() +// createExpose parses user-provided exposed port definitions and converts them +// into SpecGen format. +// TODO: The SpecGen format should really handle ranges more sanely - we could +// be massively inflating what is sent over the wire with a large range. +func createExpose(expose []string) (map[uint16]string, error) { + toReturn := make(map[uint16]string) + + for _, e := range expose { + // Check for protocol + proto := "tcp" + splitProto := strings.Split(e, "/") + if len(splitProto) > 2 { + return nil, errors.Errorf("invalid expose format - protocol can only be specified once") + } else if len(splitProto) == 2 { + proto = splitProto[1] + } + + // Check for a range + start, len, err := parseAndValidateRange(splitProto[0]) + if err != nil { + return nil, err + } + + var index uint16 + for index = 0; index < len; index++ { + portNum := start + index + protocols, ok := toReturn[portNum] + if !ok { + toReturn[portNum] = proto } else { - hostPort, err = strconv.Atoi(i.HostPort) - if err != nil { - return nil, errors.Wrapf(err, "unable to convert host port to integer") - } + newProto := strings.Join(append(strings.Split(protocols, ","), strings.Split(proto, ",")...), ",") + toReturn[portNum] = newProto } + } + } + + return toReturn, nil +} + +// createPortBindings iterates ports mappings into SpecGen format. +func createPortBindings(ports []string) ([]specgen.PortMapping, error) { + // --publish is formatted as follows: + // [[hostip:]hostport[-endPort]:]containerport[-endPort][/protocol] + toReturn := make([]specgen.PortMapping, 0, len(ports)) + + for _, p := range ports { + var ( + ctrPort string + proto, hostIP, hostPort *string + ) + + splitProto := strings.Split(p, "/") + switch len(splitProto) { + case 1: + // No protocol was provided + case 2: + proto = &(splitProto[1]) + default: + return nil, errors.Errorf("invalid port format - protocol can only be specified once") + } - pm.HostPort = int32(hostPort) - pm.Protocol = containerPb.Proto() - portBindings = append(portBindings, pm) + splitPort := strings.Split(splitProto[0], ":") + switch len(splitPort) { + case 1: + ctrPort = splitPort[0] + case 2: + hostPort = &(splitPort[0]) + ctrPort = splitPort[1] + case 3: + hostIP = &(splitPort[0]) + hostPort = &(splitPort[1]) + ctrPort = splitPort[2] + default: + return nil, errors.Errorf("invalid port format - format is [[hostIP:]hostPort:]containerPort") + } + + newPort, err := parseSplitPort(hostIP, hostPort, ctrPort, proto) + if err != nil { + return nil, err + } + + toReturn = append(toReturn, newPort) + } + + return toReturn, nil +} + +// parseSplitPort parses individual components of the --publish flag to produce +// a single port mapping in SpecGen format. +func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string) (specgen.PortMapping, error) { + newPort := specgen.PortMapping{} + if ctrPort == "" { + return newPort, errors.Errorf("must provide a non-empty container port to publish") + } + ctrStart, ctrLen, err := parseAndValidateRange(ctrPort) + if err != nil { + return newPort, errors.Wrapf(err, "error parsing container port") + } + newPort.ContainerPort = ctrStart + newPort.Range = ctrLen + + if protocol != nil { + if *protocol == "" { + return newPort, errors.Errorf("must provide a non-empty protocol to publish") + } + newPort.Protocol = *protocol + } + if hostIP != nil { + if *hostIP == "" { + return newPort, errors.Errorf("must provide a non-empty container host IP to publish") } + testIP := net.ParseIP(*hostIP) + if testIP == nil { + return newPort, errors.Errorf("cannot parse %q as an IP address", *hostIP) + } + newPort.HostIP = testIP.String() + } + if hostPort != nil { + if *hostPort == "" { + return newPort, errors.Errorf("must provide a non-empty container host port to publish") + } + hostStart, hostLen, err := parseAndValidateRange(*hostPort) + if err != nil { + return newPort, errors.Wrapf(err, "error parsing host port") + } + if hostLen != ctrLen { + return newPort, errors.Errorf("host and container port ranges have different lengths: %d vs %d", hostLen, ctrLen) + } + newPort.HostPort = hostStart + } + + hport := newPort.HostPort + if hport == 0 { + hport = newPort.ContainerPort + } + logrus.Debugf("Adding port mapping from %d to %d length %d protocol %q", hport, newPort.ContainerPort, newPort.Range, newPort.Protocol) + + return newPort, nil +} + +// Parse and validate a port range. +// Returns start port, length of range, error. +func parseAndValidateRange(portRange string) (uint16, uint16, error) { + splitRange := strings.Split(portRange, "-") + if len(splitRange) > 2 { + return 0, 0, errors.Errorf("invalid port format - port ranges are formatted as startPort-stopPort") + } + + if splitRange[0] == "" { + return 0, 0, errors.Errorf("port numbers cannot be negative") + } + + startPort, err := parseAndValidatePort(splitRange[0]) + if err != nil { + return 0, 0, err + } + + var rangeLen uint16 = 1 + if len(splitRange) == 2 { + if splitRange[1] == "" { + return 0, 0, errors.Errorf("must provide ending number for port range") + } + endPort, err := parseAndValidatePort(splitRange[1]) + if err != nil { + return 0, 0, err + } + if endPort <= startPort { + return 0, 0, errors.Errorf("the end port of a range must be higher than the start port - %d is not higher than %d", endPort, startPort) + } + // Our range is the total number of ports + // involved, so we need to add 1 (8080:8081 is + // 2 ports, for example, not 1) + rangeLen = endPort - startPort + 1 + } + + return startPort, rangeLen, nil +} + +// Turn a single string into a valid U16 port. +func parseAndValidatePort(port string) (uint16, error) { + num, err := strconv.Atoi(port) + if err != nil { + return 0, errors.Wrapf(err, "cannot parse %q as a port number", port) + } + if num < 1 || num > 65535 { + return 0, errors.Errorf("port numbers must be between 1 and 65535 (inclusive), got %d", num) } - return portBindings, nil + return uint16(num), nil } diff --git a/cmd/podman/containers/logs.go b/cmd/podman/containers/logs.go index 5dec71fdd..2b8c3ed5f 100644 --- a/cmd/podman/containers/logs.go +++ b/cmd/podman/containers/logs.go @@ -27,7 +27,7 @@ var ( ` logsCommand = &cobra.Command{ Use: "logs [flags] CONTAINER [CONTAINER...]", - Short: "Fetch the logs of one or more container", + Short: "Fetch the logs of one or more containers", Long: logsDescription, RunE: logs, Example: `podman logs ctrID diff --git a/cmd/podman/containers/prune.go b/cmd/podman/containers/prune.go index d4bea48f9..38168a6e4 100644 --- a/cmd/podman/containers/prune.go +++ b/cmd/podman/containers/prune.go @@ -18,10 +18,10 @@ import ( var ( pruneDescription = fmt.Sprintf(`podman container prune - Removes all stopped | exited containers`) + Removes all non running containers`) pruneCommand = &cobra.Command{ Use: "prune [flags]", - Short: "Remove all stopped | exited containers", + Short: "Remove all non running containers", Long: pruneDescription, RunE: prune, Example: `podman container prune`, @@ -50,7 +50,7 @@ func prune(cmd *cobra.Command, args []string) error { } if !force { reader := bufio.NewReader(os.Stdin) - fmt.Println("WARNING! This will remove all stopped containers.") + fmt.Println("WARNING! This will remove all non running containers.") fmt.Print("Are you sure you want to continue? [y/N] ") answer, err := reader.ReadString('\n') if err != nil { diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go index 96549cead..2a0f9cc6a 100644 --- a/cmd/podman/containers/rm.go +++ b/cmd/podman/containers/rm.go @@ -35,7 +35,7 @@ var ( containerRmCommand = &cobra.Command{ Use: rmCommand.Use, - Short: rmCommand.Use, + Short: rmCommand.Short, Long: rmCommand.Long, RunE: rmCommand.RunE, Args: func(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/healthcheck/healthcheck.go b/cmd/podman/healthcheck/healthcheck.go index ce90dba31..f48701624 100644 --- a/cmd/podman/healthcheck/healthcheck.go +++ b/cmd/podman/healthcheck/healthcheck.go @@ -11,8 +11,8 @@ var ( // Command: healthcheck healthCmd = &cobra.Command{ Use: "healthcheck", - Short: "Manage Healthcheck", - Long: "Manage Healthcheck", + Short: "Manage health checks on containers", + Long: "Run health checks on containers", TraverseChildren: true, RunE: validate.SubCommandExists, } diff --git a/cmd/podman/build.go b/cmd/podman/images/build.go index 43a2f7ab5..06a7efd25 100644 --- a/cmd/podman/build.go +++ b/cmd/podman/images/build.go @@ -1,4 +1,4 @@ -package main +package images import ( "os" @@ -17,6 +17,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) // buildFlagsWrapper are local to cmd/ as the build code is using Buildah-internal @@ -48,6 +49,17 @@ var ( podman build --layers --force-rm --tag imageName .`, } + imageBuildCmd = &cobra.Command{ + Args: buildCmd.Args, + Use: buildCmd.Use, + Short: buildCmd.Short, + Long: buildCmd.Long, + RunE: buildCmd.RunE, + Example: `podman image build . + podman image build --creds=username:password -t imageName -f Containerfile.simple . + podman image build --layers --force-rm --tag imageName .`, + } + buildOpts = buildFlagsWrapper{} ) @@ -66,8 +78,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: buildCmd, }) - flags := buildCmd.Flags() + buildFlags(buildCmd.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageBuildCmd, + Parent: imageCmd, + }) + buildFlags(imageBuildCmd.Flags()) +} +func buildFlags(flags *pflag.FlagSet) { // Podman flags flags.BoolVarP(&buildOpts.SquashAll, "squash-all", "", false, "Squash all layers into a single layer") diff --git a/cmd/podman/images/diff.go b/cmd/podman/images/diff.go index 7cfacfc6c..c24f98369 100644 --- a/cmd/podman/images/diff.go +++ b/cmd/podman/images/diff.go @@ -6,6 +6,7 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -28,9 +29,11 @@ func init() { Command: diffCmd, Parent: imageCmd, }) + diffFlags(diffCmd.Flags()) +} +func diffFlags(flags *pflag.FlagSet) { diffOpts = &entities.DiffOptions{} - flags := diffCmd.Flags() flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") _ = flags.MarkDeprecated("archive", "Provided for backwards compatibility, has no impact on output.") flags.StringVar(&diffOpts.Format, "format", "", "Change the output format") diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go index ce153aa46..17a80557e 100644 --- a/cmd/podman/images/history.go +++ b/cmd/podman/images/history.go @@ -15,6 +15,7 @@ import ( "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -32,6 +33,15 @@ var ( RunE: history, } + imageHistoryCmd = &cobra.Command{ + Args: historyCmd.Args, + Use: historyCmd.Use, + Short: historyCmd.Short, + Long: historyCmd.Long, + RunE: historyCmd.RunE, + Example: `podman image history imageID`, + } + opts = struct { human bool noTrunc bool @@ -45,8 +55,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: historyCmd, }) + historyFlags(historyCmd.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageHistoryCmd, + Parent: imageCmd, + }) + historyFlags(imageHistoryCmd.Flags()) +} - flags := historyCmd.Flags() +func historyFlags(flags *pflag.FlagSet) { flags.StringVar(&opts.format, "format", "", "Change the output to JSON or a Go template") flags.BoolVarP(&opts.human, "human", "H", true, "Display sizes and dates in human readable format") flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate the output") diff --git a/cmd/podman/images/import.go b/cmd/podman/images/import.go index 1c0568762..0e16128ce 100644 --- a/cmd/podman/images/import.go +++ b/cmd/podman/images/import.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -26,6 +27,17 @@ var ( cat ctr.tar | podman -q import --message "importing the ctr.tar tarball" - image-imported cat ctr.tar | podman import -`, } + + imageImportCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), + Use: importCommand.Use, + Short: importCommand.Short, + Long: importCommand.Long, + RunE: importCommand.RunE, + Example: `podman image import http://example.com/ctr.tar url-image + cat ctr.tar | podman -q image import --message "importing the ctr.tar tarball" - image-imported + cat ctr.tar | podman image import -`, + } ) var ( @@ -37,8 +49,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: importCommand, }) + importFlags(importCommand.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageImportCommand, + Parent: imageCmd, + }) + importFlags(imageImportCommand.Flags()) +} - flags := importCommand.Flags() +func importFlags(flags *pflag.FlagSet) { flags.StringArrayVarP(&importOpts.Changes, "change", "c", []string{}, "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR") flags.StringVarP(&importOpts.Message, "message", "m", "", "Set commit message for imported image") flags.BoolVarP(&importOpts.Quiet, "quiet", "q", false, "Suppress output") diff --git a/cmd/podman/images/load.go b/cmd/podman/images/load.go index f49f95002..d34c794c6 100644 --- a/cmd/podman/images/load.go +++ b/cmd/podman/images/load.go @@ -15,6 +15,7 @@ import ( "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" "golang.org/x/crypto/ssh/terminal" ) @@ -27,6 +28,14 @@ var ( RunE: load, Args: cobra.MaximumNArgs(1), } + + imageLoadCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), + Use: loadCommand.Use, + Short: loadCommand.Short, + Long: loadCommand.Long, + RunE: loadCommand.RunE, + } ) var ( @@ -38,8 +47,16 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: loadCommand, }) + loadFlags(loadCommand.Flags()) + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageLoadCommand, + Parent: imageCmd, + }) + loadFlags(imageLoadCommand.Flags()) +} - flags := loadCommand.Flags() +func loadFlags(flags *pflag.FlagSet) { flags.StringVarP(&loadOpts.Input, "input", "i", "", "Read from specified archive file (default: stdin)") flags.BoolVarP(&loadOpts.Quiet, "quiet", "q", false, "Suppress the output") flags.StringVar(&loadOpts.SignaturePolicy, "signature-policy", "", "Pathname of signature policy file") diff --git a/cmd/podman/images/save.go b/cmd/podman/images/save.go index 8f7832074..56953e41c 100644 --- a/cmd/podman/images/save.go +++ b/cmd/podman/images/save.go @@ -13,6 +13,7 @@ import ( "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" "golang.org/x/crypto/ssh/terminal" ) @@ -43,6 +44,16 @@ var ( podman save --format docker-dir -o ubuntu-dir ubuntu podman save > alpine-all.tar alpine:latest`, } + imageSaveCommand = &cobra.Command{ + Args: saveCommand.Args, + Use: saveCommand.Use, + Short: saveCommand.Short, + Long: saveCommand.Long, + RunE: saveCommand.RunE, + Example: `podman image save --quiet -o myimage.tar imageID + podman image save --format docker-dir -o ubuntu-dir ubuntu + podman image save > alpine-all.tar alpine:latest`, + } ) var ( @@ -54,7 +65,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: saveCommand, }) - flags := saveCommand.Flags() + saveFlags(saveCommand.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageSaveCommand, + Parent: imageCmd, + }) + saveFlags(imageSaveCommand.Flags()) +} + +func saveFlags(flags *pflag.FlagSet) { flags.BoolVar(&saveOpts.Compress, "compress", false, "Compress tarball image layers when saving to a directory using the 'dir' transport. (default is same compression type as source)") flags.StringVar(&saveOpts.Format, "format", define.V2s2Archive, "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-archive, docker-dir (directory with v2s2 manifest type)") flags.StringVarP(&saveOpts.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)") diff --git a/cmd/podman/images/tag.go b/cmd/podman/images/tag.go index 411313a9b..dae3416c4 100644 --- a/cmd/podman/images/tag.go +++ b/cmd/podman/images/tag.go @@ -18,6 +18,17 @@ var ( podman tag imageID:latest myNewImage:newTag podman tag httpd myregistryhost:5000/fedora/httpd:v2`, } + + imageTagCommand = &cobra.Command{ + Args: tagCommand.Args, + Use: tagCommand.Use, + Short: tagCommand.Short, + Long: tagCommand.Long, + RunE: tagCommand.RunE, + Example: `podman image tag 0e3bbc2 fedora:latest + podman image tag imageID:latest myNewImage:newTag + podman image tag httpd myregistryhost:5000/fedora/httpd:v2`, + } ) func init() { @@ -25,6 +36,11 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: tagCommand, }) + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageTagCommand, + Parent: imageCmd, + }) } func tag(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/images/untag.go b/cmd/podman/images/untag.go index 3218844b7..266a3f115 100644 --- a/cmd/podman/images/untag.go +++ b/cmd/podman/images/untag.go @@ -17,6 +17,17 @@ var ( podman untag imageID:latest otherImageName:latest podman untag httpd myregistryhost:5000/fedora/httpd:v2`, } + + imageUntagCommand = &cobra.Command{ + Args: untagCommand.Args, + Use: untagCommand.Use, + Short: untagCommand.Short, + Long: untagCommand.Long, + RunE: untagCommand.RunE, + Example: `podman image untag 0e3bbc2 + podman image untag imageID:latest otherImageName:latest + podman image untag httpd myregistryhost:5000/fedora/httpd:v2`, + } ) func init() { @@ -24,6 +35,11 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: untagCommand, }) + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageUntagCommand, + Parent: imageCmd, + }) } func untag(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/pods/pod.go b/cmd/podman/pods/pod.go index edca08202..ed265ef90 100644 --- a/cmd/podman/pods/pod.go +++ b/cmd/podman/pods/pod.go @@ -16,7 +16,7 @@ var ( podCmd = &cobra.Command{ Use: "pod", Short: "Manage pods", - Long: "Manage pods", + Long: "Pods are a group of one or more containers sharing the same network, pid and ipc namespaces.", TraverseChildren: true, RunE: validate.SubCommandExists, } diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index b97dfeb66..5703bd172 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -26,7 +26,7 @@ var ( psCmd = &cobra.Command{ Use: "ps", Aliases: []string{"ls", "list"}, - Short: "list pods", + Short: "List pods", Long: psDescription, RunE: pods, Args: validate.NoArgs, diff --git a/cmd/podman/pods/stats.go b/cmd/podman/pods/stats.go index 7c3597d9a..d3950fdbc 100644 --- a/cmd/podman/pods/stats.go +++ b/cmd/podman/pods/stats.go @@ -35,7 +35,7 @@ var ( // Command: podman pod _pod_ statsCmd = &cobra.Command{ Use: "stats [flags] [POD...]", - Short: "Display resource-usage statistics of pods", + Short: "Display a live stream of resource usage statistics for the containers in one or more pods", Long: statsDescription, RunE: stats, Example: `podman pod stats diff --git a/cmd/podman/pods/top.go b/cmd/podman/pods/top.go index ad602f4ea..9cf2bd525 100644 --- a/cmd/podman/pods/top.go +++ b/cmd/podman/pods/top.go @@ -25,7 +25,7 @@ var ( topCommand = &cobra.Command{ Use: "top [flags] POD [FORMAT-DESCRIPTORS|ARGS]", - Short: "Display the running processes in a pod", + Short: "Display the running processes of containers in a pod", Long: topDescription, RunE: top, Args: cobra.ArbitraryArgs, diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 375faf8b1..502b6c03c 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -212,7 +212,7 @@ func rootFlags(opts *entities.PodmanConfig, flags *pflag.FlagSet) { flags.StringSliceVar(&opts.Identities, "identity", []string{}, "path to SSH identity file") cfg := opts.Config - flags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, opts.CGroupUsage) + flags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, "Cgroup manager to use (\"cgroupfs\"|\"systemd\")") flags.StringVar(&opts.CpuProfile, "cpu-profile", "", "Path for the cpu profiling results") flags.StringVar(&opts.ConmonPath, "conmon", "", "Path of the conmon binary") flags.StringVar(&cfg.Engine.NetworkCmdPath, "network-cmd-path", cfg.Engine.NetworkCmdPath, "Path to the command for configuring the network") diff --git a/cmd/podman/system/info.go b/cmd/podman/system/info.go index 26be794c5..dad63bcd4 100644 --- a/cmd/podman/system/info.go +++ b/cmd/podman/system/info.go @@ -10,6 +10,7 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/ghodss/yaml" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -25,6 +26,15 @@ var ( RunE: info, Example: `podman info`, } + + systemInfoCommand = &cobra.Command{ + Args: infoCommand.Args, + Use: infoCommand.Use, + Short: infoCommand.Short, + Long: infoCommand.Long, + RunE: infoCommand.RunE, + Example: `podman system info`, + } ) var ( @@ -37,7 +47,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: infoCommand, }) - flags := infoCommand.Flags() + infoFlags(infoCommand.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: systemInfoCommand, + Parent: systemCmd, + }) + infoFlags(systemInfoCommand.Flags()) +} + +func infoFlags(flags *pflag.FlagSet) { flags.BoolVarP(&debug, "debug", "D", false, "Display additional debug information") flags.StringVarP(&inFormat, "format", "f", "", "Change the output format to JSON or a Go template") } @@ -47,7 +47,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 github.com/rootless-containers/rootlesskit v0.9.4 github.com/seccomp/containers-golang v0.0.0-20190312124753-8ca8945ccf5f - github.com/sirupsen/logrus v1.5.0 + github.com/sirupsen/logrus v1.6.0 github.com/spf13/cobra v0.0.7 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.5.1 @@ -264,6 +264,8 @@ github.com/klauspost/pgzip v1.2.3/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -404,6 +406,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q= github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go index 9fbe04c9a..21ab025de 100644 --- a/pkg/domain/entities/types.go +++ b/pkg/domain/entities/types.go @@ -8,7 +8,6 @@ import ( "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/pkg/specgen" "github.com/containers/storage/pkg/archive" - "github.com/cri-o/ocicni/pkg/ocicni" ) type Container struct { @@ -40,7 +39,7 @@ type NetOptions struct { DNSServers []net.IP Network specgen.Namespace NoHosts bool - PublishPorts []ocicni.PortMapping + PublishPorts []specgen.PortMapping StaticIP *net.IP StaticMAC *net.HardwareAddr } diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 14836035d..be54b60d2 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -96,7 +96,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener return nil, err } - opts, err := createContainerOptions(rt, s, pod, finalVolumes) + opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, newImage) if err != nil { return nil, err } @@ -115,7 +115,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener return rt.NewContainer(ctx, runtimeSpec, options...) } -func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume) ([]libpod.CtrCreateOption, error) { +func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, img *image.Image) ([]libpod.CtrCreateOption, error) { var options []libpod.CtrCreateOption var err error @@ -134,7 +134,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l options = append(options, rt.WithPod(pod)) } destinations := []string{} - // // Take all mount and named volume destinations. + // Take all mount and named volume destinations. for _, mount := range s.Mounts { destinations = append(destinations, mount.Destination) } @@ -188,7 +188,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l options = append(options, libpod.WithPrivileged(s.Privileged)) // Get namespace related options - namespaceOptions, err := GenerateNamespaceOptions(s, rt, pod) + namespaceOptions, err := GenerateNamespaceOptions(ctx, s, rt, pod, img) if err != nil { return nil, err } diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 5c065cdda..96c65b551 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -1,12 +1,14 @@ package generate import ( + "context" "os" "strings" "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/util" @@ -76,7 +78,7 @@ func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod) // joining a pod. // TODO: Consider grouping options that are not directly attached to a namespace // elsewhere. -func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod) ([]libpod.CtrCreateOption, error) { +func GenerateNamespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod, img *image.Image) ([]libpod.CtrCreateOption, error) { toReturn := []libpod.CtrCreateOption{} // If pod is not nil, get infra container. @@ -204,7 +206,6 @@ func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod } // Net - // TODO image ports // TODO validate CNINetworks, StaticIP, StaticIPv6 are only set if we // are in bridge mode. postConfigureNetNS := !s.UserNS.IsHost() @@ -221,9 +222,17 @@ func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod } toReturn = append(toReturn, libpod.WithNetNSFrom(netCtr)) case specgen.Slirp: - toReturn = append(toReturn, libpod.WithNetNS(s.PortMappings, postConfigureNetNS, "slirp4netns", nil)) + portMappings, err := createPortMappings(ctx, s, img) + if err != nil { + return nil, err + } + toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, "slirp4netns", nil)) case specgen.Bridge: - toReturn = append(toReturn, libpod.WithNetNS(s.PortMappings, postConfigureNetNS, "bridge", s.CNINetworks)) + portMappings, err := createPortMappings(ctx, s, img) + if err != nil { + return nil, err + } + toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, "bridge", s.CNINetworks)) } if s.UseImageHosts { @@ -428,7 +437,7 @@ func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt if g.Config.Annotations == nil { g.Config.Annotations = make(map[string]string) } - if s.PublishImagePorts { + if s.PublishExposedPorts { g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue } else { g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go index babfba9bc..df5775f8b 100644 --- a/pkg/specgen/generate/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -83,7 +83,11 @@ func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, er options = append(options, libpod.WithPodUseImageHosts()) } if len(p.PortMappings) > 0 { - options = append(options, libpod.WithInfraContainerPorts(p.PortMappings)) + ports, _, _, err := parsePortMapping(p.PortMappings) + if err != nil { + return nil, err + } + options = append(options, libpod.WithInfraContainerPorts(ports)) } options = append(options, libpod.WithPodCgroups()) return options, nil diff --git a/pkg/specgen/generate/ports.go b/pkg/specgen/generate/ports.go new file mode 100644 index 000000000..91c8e68d1 --- /dev/null +++ b/pkg/specgen/generate/ports.go @@ -0,0 +1,333 @@ +package generate + +import ( + "context" + "net" + "strconv" + "strings" + + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/specgen" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +const ( + protoTCP = "tcp" + protoUDP = "udp" + protoSCTP = "sctp" +) + +// Parse port maps to OCICNI port mappings. +// Returns a set of OCICNI port mappings, and maps of utilized container and +// host ports. +func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, map[string]map[string]map[uint16]uint16, map[string]map[string]map[uint16]uint16, error) { + // First, we need to validate the ports passed in the specgen, and then + // convert them into CNI port mappings. + finalMappings := []ocicni.PortMapping{} + + // To validate, we need two maps: one for host ports, one for container + // ports. + // Each is a map of protocol to map of IP address to map of port to + // port (for hostPortValidate, it's host port to container port; + // for containerPortValidate, container port to host port. + // These will ensure no collisions. + hostPortValidate := make(map[string]map[string]map[uint16]uint16) + containerPortValidate := make(map[string]map[string]map[uint16]uint16) + + // Initialize the first level of maps (we can't really guess keys for + // the rest). + for _, proto := range []string{protoTCP, protoUDP, protoSCTP} { + hostPortValidate[proto] = make(map[string]map[uint16]uint16) + containerPortValidate[proto] = make(map[string]map[uint16]uint16) + } + + // Iterate through all port mappings, generating OCICNI PortMapping + // structs and validating there is no overlap. + for _, port := range portMappings { + // First, check proto + protocols, err := checkProtocol(port.Protocol, true) + if err != nil { + return nil, nil, nil, err + } + + // Validate host IP + hostIP := port.HostIP + if hostIP == "" { + hostIP = "0.0.0.0" + } + if ip := net.ParseIP(hostIP); ip == nil { + return nil, nil, nil, errors.Errorf("invalid IP address %s in port mapping", port.HostIP) + } + + // Validate port numbers and range. + len := port.Range + if len == 0 { + len = 1 + } + containerPort := port.ContainerPort + if containerPort == 0 { + return nil, nil, nil, errors.Errorf("container port number must be non-0") + } + hostPort := port.HostPort + if hostPort == 0 { + hostPort = containerPort + } + if uint32(len-1)+uint32(containerPort) > 65535 { + return nil, nil, nil, errors.Errorf("container port range exceeds maximum allowable port number") + } + if uint32(len-1)+uint32(hostPort) > 65536 { + return nil, nil, nil, errors.Errorf("host port range exceeds maximum allowable port number") + } + + // Iterate through ports, populating maps to check for conflicts + // and generating CNI port mappings. + for _, p := range protocols { + hostIPMap := hostPortValidate[p] + ctrIPMap := containerPortValidate[p] + + hostPortMap, ok := hostIPMap[hostIP] + if !ok { + hostPortMap = make(map[uint16]uint16) + hostIPMap[hostIP] = hostPortMap + } + ctrPortMap, ok := ctrIPMap[hostIP] + if !ok { + ctrPortMap = make(map[uint16]uint16) + ctrIPMap[hostIP] = ctrPortMap + } + + // Iterate through all port numbers in the requested + // range. + var index uint16 + for index = 0; index < len; index++ { + cPort := containerPort + index + hPort := hostPort + index + + if cPort == 0 || hPort == 0 { + return nil, nil, nil, errors.Errorf("host and container ports cannot be 0") + } + + testCPort := ctrPortMap[cPort] + if testCPort != 0 && testCPort != hPort { + // This is an attempt to redefine a port + return nil, nil, nil, errors.Errorf("conflicting port mappings for container port %d (protocol %s)", cPort, p) + } + ctrPortMap[cPort] = hPort + + testHPort := hostPortMap[hPort] + if testHPort != 0 && testHPort != cPort { + return nil, nil, nil, errors.Errorf("conflicting port mappings for host port %d (protocol %s)", hPort, p) + } + hostPortMap[hPort] = cPort + + // If we have an exact duplicate, just continue + if testCPort == hPort && testHPort == cPort { + continue + } + + // We appear to be clear. Make an OCICNI port + // struct. + // Don't use hostIP - we want to preserve the + // empty string hostIP by default for compat. + cniPort := ocicni.PortMapping{ + HostPort: int32(hPort), + ContainerPort: int32(cPort), + Protocol: p, + HostIP: port.HostIP, + } + finalMappings = append(finalMappings, cniPort) + } + } + } + + return finalMappings, containerPortValidate, hostPortValidate, nil +} + +// Make final port mappings for the container +func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, img *image.Image) ([]ocicni.PortMapping, error) { + finalMappings, containerPortValidate, hostPortValidate, err := parsePortMapping(s.PortMappings) + if err != nil { + return nil, err + } + + // If not publishing exposed ports, or if we are publishing and there is + // nothing to publish - then just return the port mappings we've made so + // far. + if !s.PublishExposedPorts || (len(s.Expose) == 0 && img == nil) { + return finalMappings, nil + } + + logrus.Debugf("Adding exposed ports") + + // We need to merge s.Expose into image exposed ports + expose := make(map[uint16]string) + for k, v := range s.Expose { + expose[k] = v + } + if img != nil { + inspect, err := img.InspectNoSize(ctx) + if err != nil { + return nil, errors.Wrapf(err, "error inspecting image to get exposed ports") + } + for imgExpose := range inspect.Config.ExposedPorts { + // Expose format is portNumber[/protocol] + splitExpose := strings.SplitN(imgExpose, "/", 2) + num, err := strconv.Atoi(splitExpose[0]) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert image EXPOSE statement %q to port number", imgExpose) + } + if num > 65535 || num < 1 { + return nil, errors.Errorf("%d from image EXPOSE statement %q is not a valid port number", num, imgExpose) + } + // No need to validate protocol, we'll do it below. + if len(splitExpose) == 1 { + expose[uint16(num)] = "tcp" + } else { + expose[uint16(num)] = splitExpose[1] + } + } + } + + // There's been a request to expose some ports. Let's do that. + // Start by figuring out what needs to be exposed. + // This is a map of container port number to protocols to expose. + toExpose := make(map[uint16][]string) + for port, proto := range expose { + // Validate protocol first + protocols, err := checkProtocol(proto, false) + if err != nil { + return nil, errors.Wrapf(err, "error validating protocols for exposed port %d", port) + } + + if port == 0 { + return nil, errors.Errorf("cannot expose 0 as it is not a valid port number") + } + + // Check to see if the port is already present in existing + // mappings. + for _, p := range protocols { + ctrPortMap, ok := containerPortValidate[p]["0.0.0.0"] + if !ok { + ctrPortMap = make(map[uint16]uint16) + containerPortValidate[p]["0.0.0.0"] = ctrPortMap + } + + if portNum := ctrPortMap[port]; portNum == 0 { + // We want to expose this port for this protocol + exposeProto, ok := toExpose[port] + if !ok { + exposeProto = []string{} + } + exposeProto = append(exposeProto, p) + toExpose[port] = exposeProto + } + } + } + + // We now have a final list of ports that we want exposed. + // Let's find empty, unallocated host ports for them. + for port, protocols := range toExpose { + for _, p := range protocols { + // Find an open port on the host. + // I see a faint possibility that this will infinite + // loop trying to find a valid open port, so I've + // included a max-tries counter. + hostPort := 0 + tries := 15 + for hostPort == 0 && tries > 0 { + // We can't select a specific protocol, which is + // unfortunate for the UDP case. + candidate, err := getRandomPort() + if err != nil { + return nil, err + } + + // Check if the host port is already bound + hostPortMap, ok := hostPortValidate[p]["0.0.0.0"] + if !ok { + hostPortMap = make(map[uint16]uint16) + hostPortValidate[p]["0.0.0.0"] = hostPortMap + } + + if checkPort := hostPortMap[uint16(candidate)]; checkPort != 0 { + // Host port is already allocated, try again + tries-- + continue + } + + hostPortMap[uint16(candidate)] = port + hostPort = candidate + logrus.Debugf("Mapping exposed port %d/%s to host port %d", port, p, hostPort) + + // Make a CNI port mapping + cniPort := ocicni.PortMapping{ + HostPort: int32(candidate), + ContainerPort: int32(port), + Protocol: p, + HostIP: "", + } + finalMappings = append(finalMappings, cniPort) + } + if tries == 0 && hostPort == 0 { + // We failed to find an open port. + return nil, errors.Errorf("failed to find an open port to expose container port %d on the host", port) + } + } + } + + return finalMappings, nil +} + +// Check a string to ensure it is a comma-separated set of valid protocols +func checkProtocol(protocol string, allowSCTP bool) ([]string, error) { + protocols := make(map[string]struct{}) + splitProto := strings.Split(protocol, ",") + // Don't error on duplicates - just deduplicate + for _, p := range splitProto { + switch p { + case protoTCP, "": + protocols[protoTCP] = struct{}{} + case protoUDP: + protocols[protoUDP] = struct{}{} + case protoSCTP: + if !allowSCTP { + return nil, errors.Errorf("protocol SCTP is not allowed for exposed ports") + } + protocols[protoSCTP] = struct{}{} + default: + return nil, errors.Errorf("unrecognized protocol %q in port mapping", p) + } + } + + finalProto := []string{} + for p := range protocols { + finalProto = append(finalProto, p) + } + + // This shouldn't be possible, but check anyways + if len(finalProto) == 0 { + return nil, errors.Errorf("no valid protocols specified for port mapping") + } + + return finalProto, nil +} + +// Find a random, open port on the host +func getRandomPort() (int, error) { + l, err := net.Listen("tcp", ":0") + if err != nil { + return 0, errors.Wrapf(err, "unable to get free TCP port") + } + defer l.Close() + _, randomPort, err := net.SplitHostPort(l.Addr().String()) + if err != nil { + return 0, errors.Wrapf(err, "unable to determine free port") + } + rp, err := strconv.Atoi(randomPort) + if err != nil { + return 0, errors.Wrapf(err, "unable to convert random port to int") + } + return rp, nil +} diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go index 3f830014d..682f3f215 100644 --- a/pkg/specgen/podspecgen.go +++ b/pkg/specgen/podspecgen.go @@ -2,8 +2,6 @@ package specgen import ( "net" - - "github.com/cri-o/ocicni/pkg/ocicni" ) // PodBasicConfig contains basic configuration options for pods. @@ -79,7 +77,7 @@ type PodNetworkConfig struct { // container, this will forward the ports to the entire pod. // Only available if NetNS is set to Bridge or Slirp. // Optional. - PortMappings []ocicni.PortMapping `json:"portmappings,omitempty"` + PortMappings []PortMapping `json:"portmappings,omitempty"` // CNINetworks is a list of CNI networks that the infra container will // join. As, by default, containers share their network with the infra // container, these networks will effectively be joined by the diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 20c8f8800..4f1c4fde1 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -6,7 +6,6 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/storage" - "github.com/cri-o/ocicni/pkg/ocicni" spec "github.com/opencontainers/runtime-spec/specs-go" ) @@ -306,11 +305,23 @@ type ContainerNetworkConfig struct { // PortBindings is a set of ports to map into the container. // Only available if NetNS is set to bridge or slirp. // Optional. - PortMappings []ocicni.PortMapping `json:"portmappings,omitempty"` - // PublishImagePorts will publish ports specified in the image to random - // ports outside. - // Requires Image to be set. - PublishImagePorts bool `json:"publish_image_ports,omitempty"` + PortMappings []PortMapping `json:"portmappings,omitempty"` + // PublishExposedPorts will publish ports specified in the image to + // random unused ports (guaranteed to be above 1024) on the host. + // This is based on ports set in Expose below, and any ports specified + // by the Image (if one is given). + // Only available if NetNS is set to Bridge or Slirp. + PublishExposedPorts bool `json:"publish_image_ports,omitempty"` + // Expose is a number of ports that will be forwarded to the container + // if PublishExposedPorts is set. + // Expose is a map of uint16 (port number) to a string representing + // protocol. Allowed protocols are "tcp", "udp", and "sctp", or some + // combination of the three separated by commas. + // If protocol is set to "" we will assume TCP. + // Only available if NetNS is set to Bridge or Slirp, and + // PublishExposedPorts is set. + // Optional. + Expose map[uint16]string `json:"expose,omitempty"` // CNINetworks is a list of CNI networks to join the container to. // If this list is empty, the default CNI network will be joined // instead. If at least one entry is present, we will not join the @@ -410,6 +421,35 @@ type NamedVolume struct { Options []string } +// PortMapping is one or more ports that will be mapped into the container. +type PortMapping struct { + // HostIP is the IP that we will bind to on the host. + // If unset, assumed to be 0.0.0.0 (all interfaces). + HostIP string `json:"host_ip,omitempty"` + // ContainerPort is the port number that will be exposed from the + // container. + // Mandatory. + ContainerPort uint16 `json:"container_port"` + // HostPort is the port number that will be forwarded from the host into + // the container. + // If omitted, will be assumed to be identical to + HostPort uint16 `json:"host_port,omitempty"` + // Range is the number of ports that will be forwarded, starting at + // HostPort and ContainerPort and counting up. + // This is 1-indexed, so 1 is assumed to be a single port (only the + // Hostport:Containerport mapping will be added), 2 is two ports (both + // Hostport:Containerport and Hostport+1:Containerport+1), etc. + // If unset, assumed to be 1 (a single port). + // Both hostport + range and containerport + range must be less than + // 65536. + Range uint16 `json:"range,omitempty"` + // Protocol is the protocol forward. + // Must be either "tcp", "udp", and "sctp", or some combination of these + // separated by commas. + // If unset, assumed to be TCP. + Protocol string `json:"protocol,omitempty"` +} + // NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs func NewSpecGenerator(arg string, rootfs bool) *SpecGenerator { csc := ContainerStorageConfig{} diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 5946f3b7a..375930948 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -19,7 +19,6 @@ var _ = Describe("Podman run networking", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) @@ -65,6 +64,110 @@ var _ = Describe("Podman run networking", func() { Expect(results.OutputToString()).To(ContainSubstring("223")) }) + It("podman run -p 80", func() { + name := "testctr" + session := podmanTest.Podman([]string{"create", "-t", "-p", "80", "--name", name, ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + inspectOut := podmanTest.InspectContainer(name) + Expect(len(inspectOut)).To(Equal(1)) + Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1)) + Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(80))) + Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80))) + Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp")) + Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("")) + }) + + It("podman run -p 8080:80", func() { + name := "testctr" + session := podmanTest.Podman([]string{"create", "-t", "-p", "8080:80", "--name", name, ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + inspectOut := podmanTest.InspectContainer(name) + Expect(len(inspectOut)).To(Equal(1)) + Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1)) + Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(8080))) + Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80))) + Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp")) + Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("")) + }) + + It("podman run -p 80/udp", func() { + name := "testctr" + session := podmanTest.Podman([]string{"create", "-t", "-p", "80/udp", "--name", name, ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + inspectOut := podmanTest.InspectContainer(name) + Expect(len(inspectOut)).To(Equal(1)) + Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1)) + Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(80))) + Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80))) + Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("udp")) + Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("")) + }) + + It("podman run -p 127.0.0.1:8080:80", func() { + name := "testctr" + session := podmanTest.Podman([]string{"create", "-t", "-p", "127.0.0.1:8080:80", "--name", name, ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + inspectOut := podmanTest.InspectContainer(name) + Expect(len(inspectOut)).To(Equal(1)) + Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1)) + Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(8080))) + Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80))) + Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp")) + Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("127.0.0.1")) + }) + + It("podman run -p 127.0.0.1:8080:80/udp", func() { + name := "testctr" + session := podmanTest.Podman([]string{"create", "-t", "-p", "127.0.0.1:8080:80/udp", "--name", name, ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + inspectOut := podmanTest.InspectContainer(name) + Expect(len(inspectOut)).To(Equal(1)) + Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1)) + Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(8080))) + Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80))) + Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("udp")) + Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("127.0.0.1")) + }) + + It("podman run --expose 80 -P", func() { + name := "testctr" + session := podmanTest.Podman([]string{"create", "-t", "--expose", "80", "-P", "--name", name, ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + inspectOut := podmanTest.InspectContainer(name) + Expect(len(inspectOut)).To(Equal(1)) + Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1)) + Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Not(Equal(int32(0)))) + Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80))) + Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp")) + Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("")) + }) + + It("podman run --expose 80/udp -P", func() { + name := "testctr" + session := podmanTest.Podman([]string{"create", "-t", "--expose", "80/udp", "-P", "--name", name, ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + inspectOut := podmanTest.InspectContainer(name) + Expect(len(inspectOut)).To(Equal(1)) + Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1)) + Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Not(Equal(int32(0)))) + Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80))) + Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("udp")) + Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("")) + }) + + It("podman run --expose 80 -p 80", func() { + name := "testctr" + session := podmanTest.Podman([]string{"create", "-t", "--expose", "80", "-p", "80", "--name", name, ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + inspectOut := podmanTest.InspectContainer(name) + Expect(len(inspectOut)).To(Equal(1)) + Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1)) + Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(80))) + Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80))) + Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp")) + Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("")) + }) + It("podman run network expose host port 80 to container port 8000", func() { SkipIfRootless() session := podmanTest.Podman([]string{"run", "-dt", "-p", "80:8000", ALPINE, "/bin/sh"}) diff --git a/transfer.md b/transfer.md index 7cfd5a85d..a9cc8a756 100644 --- a/transfer.md +++ b/transfer.md @@ -23,13 +23,13 @@ Following provides equivalent with `Podman` tools for gathering information or j | Existing Step | `Podman` (and friends) | | :--- | :--- | -| `docker run` | [`podman run`](./docs/podman-run.1.md) | -| `docker exec` | [`podman exec`](./docs/podman-exec.1.md) | -| `docker info` | [`podman info`](./docs/podman-info.1.md) | -| `docker inspect` | [`podman inspect`](./docs/podman-inspect.1.md) | -| `docker logs` | [`podman logs`](./docs/podman-logs.1.md) | -| `docker ps` | [`podman ps`](./docs/podman-ps.1.md) | -| `docker stats`| [`podman stats`](./docs/podman-stats.1.md)| +| `docker run` | [`podman run`](./docs/source/markdown/podman-run.1.md) | +| `docker exec` | [`podman exec`](./docs/source/markdown/podman-exec.1.md) | +| `docker info` | [`podman info`](./docs/source/markdown/podman-info.1.md) | +| `docker inspect` | [`podman inspect`](./docs/source/markdown/podman-inspect.1.md) | +| `docker logs` | [`podman logs`](./docs/source/markdown/podman-logs.1.md) | +| `docker ps` | [`podman ps`](./docs/source/markdown/podman-ps.1.md) | +| `docker stats`| [`podman stats`](./docs/source/markdown/podman-stats.1.md)| ## Development Transfer @@ -37,51 +37,51 @@ There are other equivalents for these tools | Existing Step | `Podman` (and friends) | | :--- | :--- | -| `docker attach` | [`podman attach`](./docs/podman-attach.1.md) | -| `docker cp` | [`podman cp`](./docs/podman-cp.1.md) | -| `docker build` | [`podman build`](./docs/podman-build.1.md) | -| `docker commit` | [`podman commit`](./docs/podman-commit.1.md) | -| `docker container`|[`podman container`](./docs/podman-container.1.md) | -| `docker create` | [`podman create`](./docs/podman-create.1.md) | -| `docker diff` | [`podman diff`](./docs/podman-diff.1.md) | -| `docker events` | [`podman events`](./docs/podman-events.1.md) | -| `docker export` | [`podman export`](./docs/podman-export.1.md) | -| `docker history` | [`podman history`](./docs/podman-history.1.md) | -| `docker image` | [`podman image`](./docs/podman-image.1.md) | -| `docker images` | [`podman images`](./docs/podman-images.1.md) | -| `docker import` | [`podman import`](./docs/podman-import.1.md) | -| `docker kill` | [`podman kill`](./docs/podman-kill.1.md) | -| `docker load` | [`podman load`](./docs/podman-load.1.md) | -| `docker login` | [`podman login`](./docs/podman-login.1.md) | -| `docker logout` | [`podman logout`](./docs/podman-logout.1.md) | -| `docker pause` | [`podman pause`](./docs/podman-pause.1.md) | -| `docker ps` | [`podman ps`](./docs/podman-ps.1.md) | -| `docker pull` | [`podman pull`](./docs/podman-pull.1.md) | -| `docker push` | [`podman push`](./docs/podman-push.1.md) | -| `docker port` | [`podman port`](./docs/podman-port.1.md) | -| `docker restart` | [`podman restart`](./docs/podman-restart.1.md) | -| `docker rm` | [`podman rm`](./docs/podman-rm.1.md) | -| `docker rmi` | [`podman rmi`](./docs/podman-rmi.1.md) | -| `docker run` | [`podman run`](./docs/podman-run.1.md) | -| `docker save` | [`podman save`](./docs/podman-save.1.md) | -| `docker search` | [`podman search`](./docs/podman-search.1.md) | -| `docker start` | [`podman start`](./docs/podman-start.1.md) | -| `docker stop` | [`podman stop`](./docs/podman-stop.1.md) | -| `docker tag` | [`podman tag`](./docs/podman-tag.1.md) | -| `docker top` | [`podman top`](./docs/podman-top.1.md) | -| `docker unpause` | [`podman unpause`](./docs/podman-unpause.1.md) | -| `docker version` | [`podman version`](./docs/podman-version.1.md) | -| `docker volume` | [`podman volume`](./docs/podman-volume.1.md) | -| `docker volume create` | [`podman volume create`](./docs/podman-volume-create.1.md) | -| `docker volume inspect`| [`podman volume inspect`](./docs/podman-volume-inspect.1.md)| -| `docker volume ls` | [`podman volume ls`](./docs/podman-volume-ls.1.md) | -| `docker volume prune` | [`podman volume prune`](./docs/podman-volume-prune.1.md) | -| `docker volume rm` | [`podman volume rm`](./docs/podman-volume-rm.1.md) | -| `docker system` | [`podman system`](./docs/podman-system.1.md) | -| `docker system df` | [`podman system df`](./docs/podman-system-df.1.md) | -| `docker system prune` | [`podman system prune`](./docs/podman-system-prune.1.md) | -| `docker system info` | [`podman system info`](./docs/podman-system-info.1.md) | -| `docker wait` | [`podman wait`](./docs/podman-wait.1.md) | +| `docker attach` | [`podman attach`](./docs/source/markdown/podman-attach.1.md) | +| `docker cp` | [`podman cp`](./docs/source/markdown/podman-cp.1.md) | +| `docker build` | [`podman build`](./docs/source/markdown/podman-build.1.md) | +| `docker commit` | [`podman commit`](./docs/source/markdown/podman-commit.1.md) | +| `docker container`|[`podman container`](./docs/source/markdown/podman-container.1.md) | +| `docker create` | [`podman create`](./docs/source/markdown/podman-create.1.md) | +| `docker diff` | [`podman diff`](./docs/source/markdown/podman-diff.1.md) | +| `docker events` | [`podman events`](./docs/source/markdown/podman-events.1.md) | +| `docker export` | [`podman export`](./docs/source/markdown/podman-export.1.md) | +| `docker history` | [`podman history`](./docs/source/markdown/podman-history.1.md) | +| `docker image` | [`podman image`](./docs/source/markdown/podman-image.1.md) | +| `docker images` | [`podman images`](./docs/source/markdown/podman-images.1.md) | +| `docker import` | [`podman import`](./docs/source/markdown/podman-import.1.md) | +| `docker kill` | [`podman kill`](./docs/source/markdown/podman-kill.1.md) | +| `docker load` | [`podman load`](./docs/source/markdown/podman-load.1.md) | +| `docker login` | [`podman login`](./docs/source/markdown/podman-login.1.md) | +| `docker logout` | [`podman logout`](./docs/source/markdown/podman-logout.1.md) | +| `docker pause` | [`podman pause`](./docs/source/markdown/podman-pause.1.md) | +| `docker ps` | [`podman ps`](./docs/source/markdown/podman-ps.1.md) | +| `docker pull` | [`podman pull`](./docs/source/markdown/podman-pull.1.md) | +| `docker push` | [`podman push`](./docs/source/markdown/podman-push.1.md) | +| `docker port` | [`podman port`](./docs/source/markdown/podman-port.1.md) | +| `docker restart` | [`podman restart`](./docs/source/markdown/podman-restart.1.md) | +| `docker rm` | [`podman rm`](./docs/source/markdown/podman-rm.1.md) | +| `docker rmi` | [`podman rmi`](./docs/source/markdown/podman-rmi.1.md) | +| `docker run` | [`podman run`](./docs/source/markdown/podman-run.1.md) | +| `docker save` | [`podman save`](./docs/source/markdown/podman-save.1.md) | +| `docker search` | [`podman search`](./docs/source/markdown/podman-search.1.md) | +| `docker start` | [`podman start`](./docs/source/markdown/podman-start.1.md) | +| `docker stop` | [`podman stop`](./docs/source/markdown/podman-stop.1.md) | +| `docker tag` | [`podman tag`](./docs/source/markdown/podman-tag.1.md) | +| `docker top` | [`podman top`](./docs/source/markdown/podman-top.1.md) | +| `docker unpause` | [`podman unpause`](./docs/source/markdown/podman-unpause.1.md) | +| `docker version` | [`podman version`](./docs/source/markdown/podman-version.1.md) | +| `docker volume` | [`podman volume`](./docs/source/markdown/podman-volume.1.md) | +| `docker volume create` | [`podman volume create`](./docs/source/markdown/podman-volume-create.1.md) | +| `docker volume inspect`| [`podman volume inspect`](./docs/source/markdown/podman-volume-inspect.1.md)| +| `docker volume ls` | [`podman volume ls`](./docs/source/markdown/podman-volume-ls.1.md) | +| `docker volume prune` | [`podman volume prune`](./docs/source/markdown/podman-volume-prune.1.md) | +| `docker volume rm` | [`podman volume rm`](./docs/source/markdown/podman-volume-rm.1.md) | +| `docker system` | [`podman system`](./docs/source/markdown/podman-system.1.md) | +| `docker system df` | [`podman system df`](./docs/source/markdown/podman-system-df.1.md) | +| `docker system prune` | [`podman system prune`](./docs/source/markdown/podman-system-prune.1.md) | +| `docker system info` | [`podman system info`](./docs/source/markdown/podman-system-info.1.md) | +| `docker wait` | [`podman wait`](./docs/source/markdown/podman-wait.1.md) | **** Use mount to take advantage of the entire linux tool chain rather then just cp. Read [`here`](./docs/podman-cp.1.md) for more information. @@ -106,33 +106,33 @@ Those Docker commands currently do not have equivalents in `podman`: The following podman commands do not have a Docker equivalent: -* [`podman generate`](./docs/podman-generate.1.md) -* [`podman generate kube`](./docs/podman-generate-kube.1.md) -* [`podman container checkpoint`](/docs/podman-container-checkpoint.1.md) -* [`podman container cleanup`](/docs/podman-container-cleanup.1.md) -* [`podman container exists`](/docs/podman-container-exists.1.md) -* [`podman container refresh`](/docs/podman-container-refresh.1.md) -* [`podman container runlabel`](/docs/podman-container-runlabel.1.md) -* [`podman container restore`](/docs/podman-container-restore.1.md) -* [`podman healthcheck run`](/docs/podman-healthcheck-run.1.md) -* [`podman image exists`](./docs/podman-image-exists.1.md) -* [`podman image sign`](./docs/podman-image-sign.1.md) -* [`podman image trust`](./docs/podman-image-trust.1.md) -* [`podman mount`](./docs/podman-mount.1.md) -* [`podman play`](./docs/podman-play.1.md) -* [`podman play kube`](./docs/podman-play-kube.1.md) -* [`podman pod`](./docs/podman-pod.1.md) -* [`podman pod create`](./docs/podman-pod-create.1.md) -* [`podman pod exists`](./docs/podman-pod-exists.1.md) -* [`podman pod inspect`](./docs/podman-pod-inspect.1.md) -* [`podman pod kill`](./docs/podman-pod-kill.1.md) -* [`podman pod pause`](./docs/podman-pod-pause.1.md) -* [`podman pod ps`](./docs/podman-pod-ps.1.md) -* [`podman pod restart`](./docs/podman-pod-restart.1.md) -* [`podman pod rm`](./docs/podman-pod-rm.1.md) -* [`podman pod start`](./docs/podman-pod-start.1.md) -* [`podman pod stop`](./docs/podman-pod-stop.1.md) -* [`podman pod top`](./docs/podman-pod-top.1.md) -* [`podman pod unpause`](./docs/podman-pod-unpause.1.md) -* [`podman varlink`](./docs/podman-varlink.1.md) -* [`podman umount`](./docs/podman-umount.1.md) +* [`podman generate`](./docs/source/markdown/podman-generate.1.md) +* [`podman generate kube`](./docs/source/markdown/podman-generate-kube.1.md) +* [`podman container checkpoint`](/docs/source/markdown/podman-container-checkpoint.1.md) +* [`podman container cleanup`](/docs/source/markdown/podman-container-cleanup.1.md) +* [`podman container exists`](/docs/source/markdown/podman-container-exists.1.md) +* [`podman container refresh`](/docs/source/markdown/podman-container-refresh.1.md) +* [`podman container runlabel`](/docs/source/markdown/podman-container-runlabel.1.md) +* [`podman container restore`](/docs/source/markdown/podman-container-restore.1.md) +* [`podman healthcheck run`](/docs/source/markdown/podman-healthcheck-run.1.md) +* [`podman image exists`](./docs/source/markdown/podman-image-exists.1.md) +* [`podman image sign`](./docs/source/markdown/podman-image-sign.1.md) +* [`podman image trust`](./docs/source/markdown/podman-image-trust.1.md) +* [`podman mount`](./docs/source/markdown/podman-mount.1.md) +* [`podman play`](./docs/source/markdown/podman-play.1.md) +* [`podman play kube`](./docs/source/markdown/podman-play-kube.1.md) +* [`podman pod`](./docs/source/markdown/podman-pod.1.md) +* [`podman pod create`](./docs/source/markdown/podman-pod-create.1.md) +* [`podman pod exists`](./docs/source/markdown/podman-pod-exists.1.md) +* [`podman pod inspect`](./docs/source/markdown/podman-pod-inspect.1.md) +* [`podman pod kill`](./docs/source/markdown/podman-pod-kill.1.md) +* [`podman pod pause`](./docs/source/markdown/podman-pod-pause.1.md) +* [`podman pod ps`](./docs/source/markdown/podman-pod-ps.1.md) +* [`podman pod restart`](./docs/source/markdown/podman-pod-restart.1.md) +* [`podman pod rm`](./docs/source/markdown/podman-pod-rm.1.md) +* [`podman pod start`](./docs/source/markdown/podman-pod-start.1.md) +* [`podman pod stop`](./docs/source/markdown/podman-pod-stop.1.md) +* [`podman pod top`](./docs/source/markdown/podman-pod-top.1.md) +* [`podman pod unpause`](./docs/source/markdown/podman-pod-unpause.1.md) +* [`podman varlink`](./docs/source/markdown/podman-varlink.1.md) +* [`podman umount`](./docs/source/markdown/podman-umount.1.md) diff --git a/troubleshooting.md b/troubleshooting.md index ea85df58a..14d1a867e 100644 --- a/troubleshooting.md +++ b/troubleshooting.md @@ -517,3 +517,23 @@ The runtime uses `setgroups(2)` hence the process looses all additional groups the non-root user has. If you use the `crun` runtime, 0.10.4 or newer, then you can enable a workaround by adding `--annotation io.crun.keep_original_groups=1` to the `podman` command line. + +### 22) A rootless container running in detached mode is closed at logout + +When running a container with a command like `podman run --detach httpd` as +a rootless user, the container is closed upon logout and is not kept running. + +#### Symptom + +When logging out of a rootless user session, all containers that were started +in detached mode are stopped and are not kept running. As the root user, these +same containers would survive the logout and continue running. + +#### Solution + +When systemd notes that a session that started a Podman container has exited, +it will also stop any containers that has been associated with it. To avoid +this, use the following command before logging out: `loginctl enable-linger`. +To later revert the linger functionality, use `loginctl disable-linger`. + +LOGINCTL(1), SYSTEMD(1) diff --git a/vendor/github.com/konsorten/go-windows-terminal-sequences/README.md b/vendor/github.com/konsorten/go-windows-terminal-sequences/README.md index 195333e51..09a4a35c9 100644 --- a/vendor/github.com/konsorten/go-windows-terminal-sequences/README.md +++ b/vendor/github.com/konsorten/go-windows-terminal-sequences/README.md @@ -27,6 +27,7 @@ We thank all the authors who provided code to this library: * Felix Kollmann * Nicolas Perraut +* @dirty49374 ## License diff --git a/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences.go b/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences.go index ef18d8f97..57f530ae8 100644 --- a/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences.go +++ b/vendor/github.com/konsorten/go-windows-terminal-sequences/sequences.go @@ -4,7 +4,6 @@ package sequences import ( "syscall" - "unsafe" ) var ( @@ -27,7 +26,7 @@ func EnableVirtualTerminalProcessing(stream syscall.Handle, enable bool) error { mode &^= ENABLE_VIRTUAL_TERMINAL_PROCESSING } - ret, _, err := setConsoleMode.Call(uintptr(unsafe.Pointer(stream)), uintptr(mode)) + ret, _, err := setConsoleMode.Call(uintptr(stream), uintptr(mode)) if ret == 0 { return err } diff --git a/vendor/github.com/sirupsen/logrus/CHANGELOG.md b/vendor/github.com/sirupsen/logrus/CHANGELOG.md index 51a7ab0ca..584026d67 100644 --- a/vendor/github.com/sirupsen/logrus/CHANGELOG.md +++ b/vendor/github.com/sirupsen/logrus/CHANGELOG.md @@ -1,9 +1,32 @@ +# 1.6.0 +Fixes: + * end of line cleanup + * revert the entry concurrency bug fix whic leads to deadlock under some circumstances + * update dependency on go-windows-terminal-sequences to fix a crash with go 1.14 + +Features: + * add an option to the `TextFormatter` to completely disable fields quoting + +# 1.5.0 +Code quality: + * add golangci linter run on travis + +Fixes: + * add mutex for hooks concurrent access on `Entry` data + * caller function field for go1.14 + * fix build issue for gopherjs target + +Feature: + * add an hooks/writer sub-package whose goal is to split output on different stream depending on the trace level + * add a `DisableHTMLEscape` option in the `JSONFormatter` + * add `ForceQuote` and `PadLevelText` options in the `TextFormatter` + # 1.4.2 * Fixes build break for plan9, nacl, solaris # 1.4.1 This new release introduces: * Enhance TextFormatter to not print caller information when they are empty (#944) - * Remove dependency on golang.org/x/crypto (#932, #943) + * Remove dependency on golang.org/x/crypto (#932, #943) Fixes: * Fix Entry.WithContext method to return a copy of the initial entry (#941) @@ -11,7 +34,7 @@ Fixes: # 1.4.0 This new release introduces: * Add `DeferExitHandler`, similar to `RegisterExitHandler` but prepending the handler to the list of handlers (semantically like `defer`) (#848). - * Add `CallerPrettyfier` to `JSONFormatter` and `TextFormatter (#909, #911) + * Add `CallerPrettyfier` to `JSONFormatter` and `TextFormatter` (#909, #911) * Add `Entry.WithContext()` and `Entry.Context`, to set a context on entries to be used e.g. in hooks (#919). Fixes: diff --git a/vendor/github.com/sirupsen/logrus/appveyor.yml b/vendor/github.com/sirupsen/logrus/appveyor.yml index 96c2ce15f..df9d65c3a 100644 --- a/vendor/github.com/sirupsen/logrus/appveyor.yml +++ b/vendor/github.com/sirupsen/logrus/appveyor.yml @@ -1,14 +1,14 @@ -version: "{build}"
-platform: x64
-clone_folder: c:\gopath\src\github.com\sirupsen\logrus
-environment:
- GOPATH: c:\gopath
-branches:
- only:
- - master
-install:
- - set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
- - go version
-build_script:
- - go get -t
- - go test
+version: "{build}" +platform: x64 +clone_folder: c:\gopath\src\github.com\sirupsen\logrus +environment: + GOPATH: c:\gopath +branches: + only: + - master +install: + - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% + - go version +build_script: + - go get -t + - go test diff --git a/vendor/github.com/sirupsen/logrus/entry.go b/vendor/github.com/sirupsen/logrus/entry.go index 27b14bfb1..f6e062a34 100644 --- a/vendor/github.com/sirupsen/logrus/entry.go +++ b/vendor/github.com/sirupsen/logrus/entry.go @@ -122,8 +122,6 @@ func (entry *Entry) WithField(key string, value interface{}) *Entry { // Add a map of fields to the Entry. func (entry *Entry) WithFields(fields Fields) *Entry { - entry.Logger.mu.Lock() - defer entry.Logger.mu.Unlock() data := make(Fields, len(entry.Data)+len(fields)) for k, v := range entry.Data { data[k] = v diff --git a/vendor/github.com/sirupsen/logrus/go.mod b/vendor/github.com/sirupsen/logrus/go.mod index 9ea6e841b..d41329679 100644 --- a/vendor/github.com/sirupsen/logrus/go.mod +++ b/vendor/github.com/sirupsen/logrus/go.mod @@ -2,7 +2,7 @@ module github.com/sirupsen/logrus require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/konsorten/go-windows-terminal-sequences v1.0.1 + github.com/konsorten/go-windows-terminal-sequences v1.0.3 github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/testify v1.2.2 golang.org/x/sys v0.0.0-20190422165155-953cdadca894 diff --git a/vendor/github.com/sirupsen/logrus/go.sum b/vendor/github.com/sirupsen/logrus/go.sum index 95a3f07de..49c690f23 100644 --- a/vendor/github.com/sirupsen/logrus/go.sum +++ b/vendor/github.com/sirupsen/logrus/go.sum @@ -2,6 +2,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= diff --git a/vendor/github.com/sirupsen/logrus/text_formatter.go b/vendor/github.com/sirupsen/logrus/text_formatter.go index 2d15a239f..3c28b54ca 100644 --- a/vendor/github.com/sirupsen/logrus/text_formatter.go +++ b/vendor/github.com/sirupsen/logrus/text_formatter.go @@ -37,6 +37,11 @@ type TextFormatter struct { // Force quoting of all values ForceQuote bool + // DisableQuote disables quoting for all values. + // DisableQuote will have a lower priority than ForceQuote. + // If both of them are set to true, quote will be forced on all values. + DisableQuote bool + // Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/ EnvironmentOverrideColors bool @@ -292,6 +297,9 @@ func (f *TextFormatter) needsQuoting(text string) bool { if f.QuoteEmptyFields && len(text) == 0 { return true } + if f.DisableQuote { + return false + } for _, ch := range text { if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || diff --git a/vendor/modules.txt b/vendor/modules.txt index 8656ab1fb..18c4442ef 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -330,7 +330,7 @@ github.com/klauspost/compress/zstd github.com/klauspost/compress/zstd/internal/xxhash # github.com/klauspost/pgzip v1.2.3 github.com/klauspost/pgzip -# github.com/konsorten/go-windows-terminal-sequences v1.0.2 +# github.com/konsorten/go-windows-terminal-sequences v1.0.3 github.com/konsorten/go-windows-terminal-sequences # github.com/mattn/go-shellwords v1.0.10 github.com/mattn/go-shellwords @@ -472,7 +472,7 @@ github.com/safchain/ethtool github.com/seccomp/containers-golang # github.com/seccomp/libseccomp-golang v0.9.1 github.com/seccomp/libseccomp-golang -# github.com/sirupsen/logrus v1.5.0 +# github.com/sirupsen/logrus v1.6.0 github.com/sirupsen/logrus github.com/sirupsen/logrus/hooks/syslog # github.com/spf13/cobra v0.0.7 |