diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 0fd30fa71f..bda8ff517e 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -57,6 +57,7 @@ func main() { mountCommand, pauseCommand, psCommand, + portCommand, pullCommand, pushCommand, rmCommand, diff --git a/cmd/podman/port.go b/cmd/podman/port.go new file mode 100644 index 0000000000..f105a14eda --- /dev/null +++ b/cmd/podman/port.go @@ -0,0 +1,145 @@ +package main + +import ( + "fmt" + "strconv" + "strings" + + "github.com/pkg/errors" + "github.com/projectatomic/libpod/libpod" + "github.com/urfave/cli" +) + +var ( + portFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "display port information for all containers", + }, + LatestFlag, + } + portDescription = ` + podman port + + List port mappings for the CONTAINER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT +` + + portCommand = cli.Command{ + Name: "port", + Usage: "List port mappings or a specific mapping for the container", + Description: portDescription, + Flags: portFlags, + Action: portCmd, + ArgsUsage: "CONTAINER-NAME [mapping]", + } +) + +func portCmd(c *cli.Context) error { + var ( + userProto, containerName string + userPort int + container *libpod.Container + containers []*libpod.Container + ) + + args := c.Args() + if err := validateFlags(c, portFlags); err != nil { + return err + } + + if c.Bool("latest") && c.Bool("all") { + return errors.Errorf("the 'all' and 'latest' options cannot be used together") + } + if c.Bool("all") && len(args) > 0 { + return errors.Errorf("no additional arguments can be used with 'all'") + } + if len(args) == 0 && !c.Bool("latest") && !c.Bool("all") { + return errors.Errorf("you must supply a running container name or id") + } + if !c.Bool("latest") && !c.Bool("all") { + containerName = args[0] + } + + port := "" + if len(args) > 1 && !c.Bool("latest") { + port = args[1] + } + if len(args) == 1 && c.Bool("latest") { + port = args[0] + } + if port != "" { + fields := strings.Split(port, "/") + // User supplied at least port + var err error + // User supplied port and protocol + if len(fields) == 2 { + userProto = fields[1] + } + if len(fields) >= 1 { + p := fields[0] + userPort, err = strconv.Atoi(p) + if err != nil { + return errors.Wrapf(err, "unable to format port") + } + } + // Format is incorrect + if len(fields) > 2 || len(fields) < 1 { + return errors.Errorf("port formats are port/protocol. '%s' is invalid", port) + } + } + + runtime, err := getRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + if !c.Bool("latest") && !c.Bool("all") { + container, err = runtime.LookupContainer(containerName) + if err != nil { + return errors.Wrapf(err, "unable to find container %s", containerName) + } + containers = append(containers, container) + } else if c.Bool("latest") { + container, err = runtime.GetLatestContainer() + containers = append(containers, container) + } else { + containers, err = runtime.GetRunningContainers() + if err != nil { + return errors.Wrapf(err, "unable to get all containers") + } + } + + for _, con := range containers { + if state, _ := con.State(); state != libpod.ContainerStateRunning { + continue + } + if c.Bool("all") { + fmt.Println(con.ID()) + } + // Iterate mappings + for _, v := range con.Config().PortMappings { + hostIP := v.HostIP + // Set host IP to 0.0.0.0 if blank + if hostIP == "" { + hostIP = "0.0.0.0" + } + // If not searching by port or port/proto, then dump what we see + if port == "" { + fmt.Printf("%d/%s -> %s:%d\n", v.ContainerPort, v.Protocol, hostIP, v.HostPort) + continue + } + // We have a match on ports + if v.ContainerPort == int32(userPort) { + if userProto == "" || userProto == v.Protocol { + fmt.Printf("%s:%d", hostIP, v.HostPort) + break + } + } else { + return errors.Errorf("No public port '%d' published for %s", userPort, containerName) + } + } + } + + return nil +} diff --git a/commands.md b/commands.md index 7ce60d309e..7eff993cdf 100644 --- a/commands.md +++ b/commands.md @@ -25,6 +25,7 @@ | [podman-logs(1)](/docs/podman-logs.1.md) | Display the logs of a container |[![...](/docs/play.png)](https://asciinema.org/a/MZPTWD5CVs3dMREkBxQBY9C5z)| | [podman-mount(1)](/docs/podman-mount.1.md) | Mount a working container's root filesystem |[![...](/docs/play.png)](https://asciinema.org/a/YSP6hNvZo0RGeMHDA97PhPAf3)| | [podman-pause(1)](/docs/podman-pause.1.md) | Pause one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/141292)| +| [podman-port(1)](/docs/podman-port.1.md) | List port mappings for running containers |[![...](/docs/play.png)]()| | [podman-ps(1)](/docs/podman-ps.1.md) | Prints out information about containers |[![...](/docs/play.png)](https://asciinema.org/a/bbT41kac6CwZ5giESmZLIaTLR)| | [podman-pull(1)](/docs/podman-pull.1.md) | Pull an image from a registry |[![...](/docs/play.png)](https://asciinema.org/a/lr4zfoynHJOUNu1KaXa1dwG2X)| | [podman-push(1)](/docs/podman-push.1.md) | Push an image to a specified destination |[![...](/docs/play.png)](https://asciinema.org/a/133276)| diff --git a/completions/bash/podman b/completions/bash/podman index b1a19f255d..f8086220ec 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1413,6 +1413,18 @@ _podman_pause() { _complete_ "$options_with_args" "$boolean_options" } +_podman_port() { + local options_with_args=" + --help -h + " + local boolean_options=" + --all + -a + -l + --latest" + _complete_ "$options_with_args" "$boolean_options" +} + _podman_ps() { local options_with_args=" --filter -f @@ -1566,6 +1578,7 @@ _podman_podman() { logs mount pause + port ps pull push diff --git a/docs/podman-port.1.md b/docs/podman-port.1.md new file mode 100644 index 0000000000..026c52cca1 --- /dev/null +++ b/docs/podman-port.1.md @@ -0,0 +1,60 @@ +% podman(1) podman-port - List port mappings for the CONTAINER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT +% Brent Baude +# podman-port "1" "January 2018" "podman" + +## NAME +podman port - List port mappings for a container + +## SYNOPSIS +**podman port [OPTIONS] CONTAINER [PRIVATE_PORT[/PROTO]]** + +## DESCRIPTION +List port mappings for the CONTAINER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT + +## OPTIONS + +**--all, a** + +List all known port mappings for running containers. When using this option, you cannot pass any container names +or private ports/protocols as filters. + +**--latest, -l** +Instead of providing the container name or ID, use the last created container. If you use methods other than Podman +to run containers such as CRI-O, the last started container could be from either of those methods. + +## EXAMPLE + +List all port mappings +``` +#podman port -a +b4d2f05432e482e017b1a4b2eae15fa7b4f6fb7e9f65c1bde46294fdef285906 +80/udp -> 0.0.0.0:44327 +80/tcp -> 0.0.0.0:44327 +# +``` + +List port mappings for a specific container +``` +#podman port b4d2f054 +80/udp -> 0.0.0.0:44327 +80/tcp -> 0.0.0.0:44327 +# +``` +List the port mappings for the latest container and port 80 +``` +#podman port b4d2f054 80 + 0.0.0.0:44327 +# +``` + +List the port mappings for a specific container for port 80 and the tcp protocol. +``` +#podman port b4d2f054 80/tcp +0.0.0.0:44327 +# +``` +## SEE ALSO +podman(1), podan-inspect(1) + +## HISTORY +January 2018, Originally compiled by Brent Baude diff --git a/test/podman_port.bats b/test/podman_port.bats new file mode 100644 index 0000000000..d13227b3c2 --- /dev/null +++ b/test/podman_port.bats @@ -0,0 +1,43 @@ +#!/usr/bin/env bats + +load helpers + +function teardown() { + cleanup_test +} + +function setup() { + copy_images +} + +@test "podman port all and latest" { + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} port -a -l + echo "$output" + echo "$status" + [ "$status" -ne 0 ] +} + +@test "podman port all and extra" { + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} port -a foobar + echo "$output" + echo "$status" + [ "$status" -ne 0 ] +} + +@test "podman port nginx" { + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} run -dt -P docker.io/library/nginx:latest + echo "$output" + [ "$status" -eq 0 ] + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} port -l + echo "$output" + [ "$status" -eq 0 ] + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} port -l 80 + echo "$output" + [ "$status" -eq 0 ] + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} port -l 80/tcp + echo "$output" + [ "$status" -eq 0 ] + run ${PODMAN_BINARY} ${PODMAN_OPTIONS} port -a + echo "$output" + [ "$status" -eq 0 ] +}