Skip to content
This repository has been archived by the owner on Nov 27, 2023. It is now read-only.

Implement printing published ports #98

Merged
merged 3 commits into from
May 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@ WORKDIR ${PWD}
ADD go.* ${PWD}
ADD . ${PWD}

FROM golang:${GO_VERSION} AS lint-base
RUN go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.26.0

FROM protos-base AS make-protos
RUN make -f builder.Makefile protos

Expand All @@ -56,6 +53,3 @@ COPY --from=make-cross /api/bin/* .

FROM base as test
RUN make -f builder.Makefile test

FROM lint-base AS lint
RUN make -f builder.Makefile lint
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

GOOS ?= $(shell go env GOOS)
GOARCH ?= $(shell go env GOARCH)
PWD = $(shell pwd)

export DOCKER_BUILDKIT=1

Expand Down Expand Up @@ -61,8 +62,7 @@ cache-clear: ## Clear the builder cache
@docker builder prune --force --filter type=exec.cachemount --filter=unused-for=24h

lint: ## run linter(s)
@docker build . \
--target lint
docker run --rm -t -v $(PWD):/app -w /app golangci/golangci-lint:v1.27-alpine golangci-lint run ./...

help: ## Show help
@echo Please specify a build target. The choices are:
Expand Down
2 changes: 2 additions & 0 deletions azure/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,12 @@ func (cs *aciContainerService) List(ctx context.Context) ([]containers.Container
if container.InstanceView != nil && container.InstanceView.CurrentState != nil {
status = *container.InstanceView.CurrentState.State
}

res = append(res, containers.Container{
ID: containerID,
Image: *container.Image,
Status: status,
Ports: convert.ToPorts(group.IPAddress, *container.Ports),
})
}
}
Expand Down
37 changes: 37 additions & 0 deletions azure/convert/ports.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package convert

import (
"strings"

"github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance"

"github.com/docker/api/containers"
)

// ToPorts converts Azure container ports to api ports
func ToPorts(ipAddr *containerinstance.IPAddress, ports []containerinstance.ContainerPort) []containers.Port {
rumpl marked this conversation as resolved.
Show resolved Hide resolved
var result []containers.Port

for _, port := range ports {
if port.Port == nil {
continue
}
protocol := "tcp"
if port.Protocol != "" {
protocol = string(port.Protocol)
}
ip := ""
if ipAddr != nil {
ip = *ipAddr.IP
}

result = append(result, containers.Port{
HostPort: uint32(*port.Port),
ContainerPort: uint32(*port.Port),
HostIP: ip,
Protocol: strings.ToLower(protocol),
})
}

return result
}
89 changes: 89 additions & 0 deletions azure/convert/ports_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package convert

import (
"testing"

"github.com/Azure/azure-sdk-for-go/profiles/latest/containerinstance/mgmt/containerinstance"
"github.com/Azure/go-autorest/autorest/to"
"github.com/stretchr/testify/assert"

"github.com/docker/api/containers"
)

func TestPortConvert(t *testing.T) {
expectedPorts := []containers.Port{
{
HostPort: 80,
ContainerPort: 80,
HostIP: "10.0.0.1",
Protocol: "tcp",
},
}
testCases := []struct {
name string
ip *containerinstance.IPAddress
ports []containerinstance.ContainerPort
expected []containers.Port
}{
{
name: "convert port",
ip: &containerinstance.IPAddress{
IP: to.StringPtr("10.0.0.1"),
},
ports: []containerinstance.ContainerPort{
{
Protocol: "tcp",
Port: to.Int32Ptr(80),
},
},
expected: expectedPorts,
},
{
name: "with nil ip",
ip: nil,
ports: []containerinstance.ContainerPort{
{
Protocol: "tcp",
Port: to.Int32Ptr(80),
},
},
expected: []containers.Port{
{
HostPort: 80,
ContainerPort: 80,
HostIP: "",
Protocol: "tcp",
},
},
},
{
name: "skip nil ports",
ip: nil,
ports: []containerinstance.ContainerPort{
{
Protocol: "tcp",
Port: to.Int32Ptr(80),
},
{
Protocol: "tcp",
Port: nil,
},
},
expected: []containers.Port{
{
HostPort: 80,
ContainerPort: 80,
HostIP: "",
Protocol: "tcp",
},
},
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
ports := ToPorts(testCase.ip, testCase.ports)
assert.Equal(t, testCase.expected, ports)
})
}
}
10 changes: 6 additions & 4 deletions cli/cmd/ps.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"os"
"text/tabwriter"

"github.com/docker/docker/pkg/stringid"
"github.com/pkg/errors"
"github.com/spf13/cobra"

"github.com/docker/api/cli/formatter"
"github.com/docker/api/client"
)

Expand Down Expand Up @@ -50,11 +52,11 @@ func runPs(ctx context.Context, opts psOpts) error {
return nil
}

w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "NAME\tIMAGE\tSTATUS\tCOMMAND\n")
format := "%s\t%s\t%s\t%s\n"
w := tabwriter.NewWriter(os.Stdout, 0, 0, 8, ' ', 0)
fmt.Fprintf(w, "CONTAINER ID\tIMAGE\tCOMMAND\tSTATUS\tPORTS\n")
silvin-lubecki marked this conversation as resolved.
Show resolved Hide resolved
format := "%s\t%s\t%s\t%s\t%s\n"
for _, c := range containers {
fmt.Fprintf(w, format, c.ID, c.Image, c.Status, c.Command)
fmt.Fprintf(w, format, stringid.TruncateID(c.ID), c.Image, c.Command, c.Status, formatter.PortsString(c.Ports))
}

return w.Flush()
Expand Down
14 changes: 8 additions & 6 deletions cli/cmd/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ import (
"github.com/docker/docker/pkg/namesgenerator"
"github.com/spf13/cobra"

"github.com/docker/api/cli/options/run"
"github.com/docker/api/client"
)

// Command runs a container
func Command() *cobra.Command {
var opts runOpts
var opts run.Opts
cmd := &cobra.Command{
Use: "run",
Short: "Run a container",
Expand All @@ -50,27 +51,28 @@ func Command() *cobra.Command {
},
}

cmd.Flags().StringArrayVarP(&opts.publish, "publish", "p", []string{}, "Publish a container's port(s). [HOST_PORT:]CONTAINER_PORT")
cmd.Flags().StringVar(&opts.name, "name", getRandomName(), "Assign a name to the container")
cmd.Flags().StringArrayVarP(&opts.Publish, "publish", "p", []string{}, "Publish a container's port(s). [HOST_PORT:]CONTAINER_PORT")
cmd.Flags().StringVar(&opts.Name, "name", getRandomName(), "Assign a name to the container")

return cmd
}

func runRun(ctx context.Context, image string, opts runOpts) error {
func runRun(ctx context.Context, image string, opts run.Opts) error {
c, err := client.New(ctx)
if err != nil {
return err
}

project, err := opts.toContainerConfig(image)
project, err := opts.ToContainerConfig(image)
if err != nil {
return err
}

if err = c.ContainerService().Run(ctx, project); err != nil {
return err
}
fmt.Println(opts.name)
fmt.Println(opts.Name)

return nil

}
Expand Down
6 changes: 3 additions & 3 deletions cli/cmd/testdata/ps-out.golden
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
NAME IMAGE STATUS COMMAND
id nginx
1234 alpine
CONTAINER ID IMAGE COMMAND STATUS PORTS
id nginx
1234 alpine
110 changes: 110 additions & 0 deletions cli/formatter/container.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package formatter

import (
"fmt"
"sort"
"strconv"
"strings"

"github.com/docker/api/containers"
)

type portGroup struct {
first uint32
last uint32
}

// PortsString returns a human readable published ports
func PortsString(ports []containers.Port) string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case it's useful, I implemented some presentation logic for ports in the CLI a while back (see docker/cli#581)

I also recently discovered that there's a weird discrepancy between services and containers when publishing port-ranges (still to investigate further);

For a service (docker service create);

-p 8080-8090:80 means: map container port 80 to ports 8080 - 8090 on the host

For a container;

-p 8080-8090:80 means: map container port 80 to the first available port within 8080 - 8090

I never realised this discrepancy was there tbh (as mentioned; need to investigate further if this was on purpose or an oversight)

groupMap := make(map[string]*portGroup)
var (
result []string
hostMappings []string
groupMapKeys []string
)

sort.Slice(ports, func(i int, j int) bool {
return comparePorts(ports[i], ports[j])
})

for _, port := range ports {
// Simple case: HOST_IP:PORT1:PORT2
hostIP := "0.0.0.0"
if port.HostIP != "" {
hostIP = port.HostIP
}

if port.HostPort != port.ContainerPort {
hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", hostIP, port.HostPort, port.ContainerPort, port.Protocol))
continue
}

current := port.ContainerPort
portKey := fmt.Sprintf("%s/%s", hostIP, port.Protocol)
group := groupMap[portKey]

if group == nil {
groupMap[portKey] = &portGroup{first: current, last: current}
// record order that groupMap keys are created
groupMapKeys = append(groupMapKeys, portKey)
continue
}

if current == (group.last + 1) {
group.last = current
continue
}

result = append(result, formGroup(portKey, group.first, group.last))
groupMap[portKey] = &portGroup{first: current, last: current}
}

for _, portKey := range groupMapKeys {
g := groupMap[portKey]
result = append(result, formGroup(portKey, g.first, g.last))
}

result = append(result, hostMappings...)

return strings.Join(result, ", ")
}

func formGroup(key string, start uint32, last uint32) string {
parts := strings.Split(key, "/")
protocol := parts[0]
var ip string
if len(parts) > 1 {
ip = parts[0]
protocol = parts[1]
}
group := strconv.Itoa(int(start))

// add range
if start != last {
group = fmt.Sprintf("%s-%d", group, last)
}

// add host ip
if ip != "" {
group = fmt.Sprintf("%s:%s->%s", ip, group, group)
}

// add protocol
return fmt.Sprintf("%s/%s", group, protocol)
}

func comparePorts(i containers.Port, j containers.Port) 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
}
Loading