From c877c7b2bf0ed20ff9054dcbec98362d9e88c145 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 7 Feb 2017 22:51:33 -0800 Subject: [PATCH] Add `PORTS` field for `docker service ls` (`ingress`) This fix is related to 30232 wherw `docker service ls` does not show `PORTS` information like `docker service ps`. This fix adds `PORTS` fields for services that publish ports in ingress mode. Additional unit tests cases have been updated. This fix is related to 30232. Signed-off-by: Yong Tang --- cli/command/formatter/service.go | 23 ++++++- cli/command/formatter/service_test.go | 80 +++++++++++++++++++++--- docs/reference/commandline/service_ls.md | 1 + 3 files changed, 93 insertions(+), 11 deletions(-) diff --git a/cli/command/formatter/service.go b/cli/command/formatter/service.go index 4a4bae2cff..740bd8d53f 100644 --- a/cli/command/formatter/service.go +++ b/cli/command/formatter/service.go @@ -1,6 +1,7 @@ package formatter import ( + "fmt" "strings" "time" @@ -391,7 +392,7 @@ func (ctx *serviceInspectContext) Ports() []swarm.PortConfig { } const ( - defaultServiceTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Mode}}\t{{.Replicas}}\t{{.Image}}" + defaultServiceTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Mode}}\t{{.Replicas}}\t{{.Image}}\t{{.Ports}}" serviceIDHeader = "ID" modeHeader = "MODE" @@ -410,7 +411,7 @@ func NewServiceListFormat(source string, quiet bool) Format { if quiet { return `id: {{.ID}}` } - return `id: {{.ID}}\nname: {{.Name}}\nmode: {{.Mode}}\nreplicas: {{.Replicas}}\nimage: {{.Image}}\n` + return `id: {{.ID}}\nname: {{.Name}}\nmode: {{.Mode}}\nreplicas: {{.Replicas}}\nimage: {{.Image}}\nports: {{.Ports}}\n` } return Format(source) } @@ -439,6 +440,7 @@ func ServiceListWrite(ctx Context, services []swarm.Service, info map[string]Ser "Mode": modeHeader, "Replicas": replicasHeader, "Image": imageHeader, + "Ports": portsHeader, } return ctx.Write(&serviceCtx, render) } @@ -483,3 +485,20 @@ func (c *serviceContext) Image() string { return image } + +func (c *serviceContext) Ports() string { + if c.service.Spec.EndpointSpec == nil || c.service.Spec.EndpointSpec.Ports == nil { + return "" + } + ports := []string{} + for _, pConfig := range c.service.Spec.EndpointSpec.Ports { + if pConfig.PublishMode == swarm.PortConfigPublishModeIngress { + ports = append(ports, fmt.Sprintf("*:%d->%d/%s", + pConfig.PublishedPort, + pConfig.TargetPort, + pConfig.Protocol, + )) + } + } + return strings.Join(ports, ",") +} diff --git a/cli/command/formatter/service_test.go b/cli/command/formatter/service_test.go index d4474297db..93ffc92a3b 100644 --- a/cli/command/formatter/service_test.go +++ b/cli/command/formatter/service_test.go @@ -29,9 +29,9 @@ func TestServiceContextWrite(t *testing.T) { // Table format { Context{Format: NewServiceListFormat("table", false)}, - `ID NAME MODE REPLICAS IMAGE -id_baz baz global 2/4 -id_bar bar replicated 2/4 + `ID NAME MODE REPLICAS IMAGE PORTS +id_baz baz global 2/4 *:80->8080/tcp +id_bar bar replicated 2/4 *:80->8080/tcp `, }, { @@ -62,12 +62,14 @@ name: baz mode: global replicas: 2/4 image: +ports: *:80->8080/tcp id: id_bar name: bar mode: replicated replicas: 2/4 image: +ports: *:80->8080/tcp `, }, @@ -88,8 +90,38 @@ bar for _, testcase := range cases { services := []swarm.Service{ - {ID: "id_baz", Spec: swarm.ServiceSpec{Annotations: swarm.Annotations{Name: "baz"}}}, - {ID: "id_bar", Spec: swarm.ServiceSpec{Annotations: swarm.Annotations{Name: "bar"}}}, + { + ID: "id_baz", + Spec: swarm.ServiceSpec{ + Annotations: swarm.Annotations{Name: "baz"}, + EndpointSpec: &swarm.EndpointSpec{ + Ports: []swarm.PortConfig{ + { + PublishMode: "ingress", + PublishedPort: 80, + TargetPort: 8080, + Protocol: "tcp", + }, + }, + }, + }, + }, + { + ID: "id_bar", + Spec: swarm.ServiceSpec{ + Annotations: swarm.Annotations{Name: "bar"}, + EndpointSpec: &swarm.EndpointSpec{ + Ports: []swarm.PortConfig{ + { + PublishMode: "ingress", + PublishedPort: 80, + TargetPort: 8080, + Protocol: "tcp", + }, + }, + }, + }, + }, } info := map[string]ServiceListInfo{ "id_baz": { @@ -114,8 +146,38 @@ bar func TestServiceContextWriteJSON(t *testing.T) { services := []swarm.Service{ - {ID: "id_baz", Spec: swarm.ServiceSpec{Annotations: swarm.Annotations{Name: "baz"}}}, - {ID: "id_bar", Spec: swarm.ServiceSpec{Annotations: swarm.Annotations{Name: "bar"}}}, + { + ID: "id_baz", + Spec: swarm.ServiceSpec{ + Annotations: swarm.Annotations{Name: "baz"}, + EndpointSpec: &swarm.EndpointSpec{ + Ports: []swarm.PortConfig{ + { + PublishMode: "ingress", + PublishedPort: 80, + TargetPort: 8080, + Protocol: "tcp", + }, + }, + }, + }, + }, + { + ID: "id_bar", + Spec: swarm.ServiceSpec{ + Annotations: swarm.Annotations{Name: "bar"}, + EndpointSpec: &swarm.EndpointSpec{ + Ports: []swarm.PortConfig{ + { + PublishMode: "ingress", + PublishedPort: 80, + TargetPort: 8080, + Protocol: "tcp", + }, + }, + }, + }, + }, } info := map[string]ServiceListInfo{ "id_baz": { @@ -128,8 +190,8 @@ func TestServiceContextWriteJSON(t *testing.T) { }, } expectedJSONs := []map[string]interface{}{ - {"ID": "id_baz", "Name": "baz", "Mode": "global", "Replicas": "2/4", "Image": ""}, - {"ID": "id_bar", "Name": "bar", "Mode": "replicated", "Replicas": "2/4", "Image": ""}, + {"ID": "id_baz", "Name": "baz", "Mode": "global", "Replicas": "2/4", "Image": "", "Ports": "*:80->8080/tcp"}, + {"ID": "id_bar", "Name": "bar", "Mode": "replicated", "Replicas": "2/4", "Image": "", "Ports": "*:80->8080/tcp"}, } out := bytes.NewBufferString("") diff --git a/docs/reference/commandline/service_ls.md b/docs/reference/commandline/service_ls.md index 6b07b84053..c222c04857 100644 --- a/docs/reference/commandline/service_ls.md +++ b/docs/reference/commandline/service_ls.md @@ -137,6 +137,7 @@ Placeholder | Description `.Mode` | Service mode (replicated, global) `.Replicas` | Service replicas `.Image` | Service image +`.Ports` | Service ports published in ingress mode When using the `--format` option, the `service ls` command will either output the data exactly as the template declares or, when using the