Prechádzať zdrojové kódy

introduce `port` command for parity with docker-compose

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 4 rokov pred
rodič
commit
8b38874aba

+ 4 - 0
aci/compose.go

@@ -233,3 +233,7 @@ func (cs *aciComposeService) Top(ctx context.Context, projectName string, servic
 func (cs *aciComposeService) Events(ctx context.Context, project string, options compose.EventsOptions) error {
 	return errdefs.ErrNotImplemented
 }
+
+func (cs *aciComposeService) Port(ctx context.Context, project string, service string, port int, options compose.PortOptions) (string, int, error) {
+	return "", 0, errdefs.ErrNotImplemented
+}

+ 4 - 0
api/client/compose.go

@@ -107,3 +107,7 @@ func (c *composeService) Top(ctx context.Context, projectName string, services [
 func (c *composeService) Events(ctx context.Context, project string, options compose.EventsOptions) error {
 	return errdefs.ErrNotImplemented
 }
+
+func (c *composeService) Port(ctx context.Context, project string, service string, port int, options compose.PortOptions) (string, int, error) {
+	return "", 0, errdefs.ErrNotImplemented
+}

+ 8 - 0
api/compose/api.go

@@ -68,6 +68,8 @@ type Service interface {
 	Top(ctx context.Context, projectName string, services []string) ([]ContainerProcSummary, error)
 	// Events executes the equivalent to a `compose events`
 	Events(ctx context.Context, project string, options EventsOptions) error
+	// Port executes the equivalent to a `compose port`
+	Port(ctx context.Context, project string, service string, port int, options PortOptions) (string, int, error)
 }
 
 // BuildOptions group options of the Build API
@@ -199,6 +201,12 @@ type Event struct {
 	Attributes map[string]string
 }
 
+// PortOptions group options of the Port API
+type PortOptions struct {
+	Protocol string
+	Index    int
+}
+
 func (e Event) String() string {
 	t := e.Timestamp.Format("2006-01-02 15:04:05.000000")
 	var attr []string

+ 1 - 0
cli/cmd/compose/compose.go

@@ -140,6 +140,7 @@ func Command(contextType string) *cobra.Command {
 		unpauseCommand(&opts),
 		topCommand(&opts),
 		eventsCommand(&opts),
+		portCommand(&opts),
 	)
 
 	if contextType == store.LocalContextType || contextType == store.DefaultContextType {

+ 77 - 0
cli/cmd/compose/port.go

@@ -0,0 +1,77 @@
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package compose
+
+import (
+	"context"
+	"fmt"
+	"strconv"
+
+	"github.com/spf13/cobra"
+
+	"github.com/docker/compose-cli/api/client"
+	"github.com/docker/compose-cli/api/compose"
+)
+
+type portOptions struct {
+	*projectOptions
+	protocol string
+	index    int
+}
+
+func portCommand(p *projectOptions) *cobra.Command {
+	opts := portOptions{
+		projectOptions: p,
+	}
+	cmd := &cobra.Command{
+		Use:   "port [options] [--] SERVICE PRIVATE_PORT",
+		Short: "Print the public port for a port binding.",
+		Args:  cobra.MinimumNArgs(2),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			port, err := strconv.Atoi(args[1])
+			if err != nil {
+				return err
+			}
+			return runPort(cmd.Context(), opts, args[0], port)
+		},
+	}
+	cmd.Flags().StringVar(&opts.protocol, "protocol", "tcp", "tcp or udp")
+	cmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if service has multiple replicas")
+	return cmd
+}
+
+func runPort(ctx context.Context, opts portOptions, service string, port int) error {
+	c, err := client.New(ctx)
+	if err != nil {
+		return err
+	}
+
+	projectName, err := opts.toProjectName()
+	if err != nil {
+		return err
+	}
+	ip, port, err := c.ComposeService().Port(ctx, projectName, service, port, compose.PortOptions{
+		Protocol: opts.protocol,
+		Index:    opts.index,
+	})
+	if err != nil {
+		return err
+	}
+
+	fmt.Printf("%s:%d\n", ip, port)
+	return nil
+}

+ 2 - 10
cli/cmd/compose/ps.go

@@ -29,6 +29,7 @@ import (
 	"github.com/docker/compose-cli/api/client"
 	"github.com/docker/compose-cli/api/compose"
 	"github.com/docker/compose-cli/cli/formatter"
+	"github.com/docker/compose-cli/utils"
 )
 
 type psOptions struct {
@@ -77,7 +78,7 @@ func runPs(ctx context.Context, opts psOptions) error {
 	if opts.Services {
 		services := []string{}
 		for _, s := range containers {
-			if !contains(services, s.Service) {
+			if !utils.StringContains(services, s.Service) {
 				services = append(services, s.Service)
 			}
 		}
@@ -115,12 +116,3 @@ func runPs(ctx context.Context, opts psOptions) error {
 		},
 		"NAME", "SERVICE", "STATUS", "PORTS")
 }
-
-func contains(slice []string, item string) bool {
-	for _, v := range slice {
-		if v == item {
-			return true
-		}
-	}
-	return false
-}

+ 2 - 1
cli/cmd/compose/pull.go

@@ -23,6 +23,7 @@ import (
 
 	"github.com/docker/compose-cli/api/client"
 	"github.com/docker/compose-cli/api/progress"
+	"github.com/docker/compose-cli/utils"
 )
 
 type pullOptions struct {
@@ -65,7 +66,7 @@ func runPull(ctx context.Context, opts pullOptions, services []string) error {
 			return err
 		}
 		for _, s := range project.Services {
-			if !contains(services, s.Name) {
+			if !utils.StringContains(services, s.Name) {
 				project.DisabledServices = append(project.DisabledServices, s)
 			}
 		}

+ 2 - 1
cli/cmd/compose/up.go

@@ -28,6 +28,7 @@ import (
 	"time"
 
 	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/compose-cli/utils"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 	"golang.org/x/sync/errgroup"
@@ -106,7 +107,7 @@ func (opts upOptions) apply(project *types.Project, services []string) error {
 			return err
 		}
 		for _, s := range project.Services {
-			if !contains(services, s.Name) {
+			if !utils.StringContains(services, s.Name) {
 				project.DisabledServices = append(project.DisabledServices, s)
 			}
 		}

+ 4 - 0
ecs/local/compose.go

@@ -199,3 +199,7 @@ func (e ecsLocalSimulation) Top(ctx context.Context, projectName string, service
 func (e ecsLocalSimulation) Events(ctx context.Context, project string, options compose.EventsOptions) error {
 	return e.compose.Events(ctx, project, options)
 }
+
+func (e ecsLocalSimulation) Port(ctx context.Context, project string, service string, port int, options compose.PortOptions) (string, int, error) {
+	return "", 0, errdefs.ErrNotImplemented
+}

+ 4 - 0
ecs/up.go

@@ -67,6 +67,10 @@ func (b *ecsAPIService) Events(ctx context.Context, project string, options comp
 	return errdefs.ErrNotImplemented
 }
 
+func (b *ecsAPIService) Port(ctx context.Context, project string, service string, port int, options compose.PortOptions) (string, int, error) {
+	return "", 0, errdefs.ErrNotImplemented
+}
+
 func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
 	logrus.Debugf("deploying on AWS with region=%q", b.Region)
 	err := b.aws.CheckRequirements(ctx, b.Region)

+ 4 - 0
kube/compose.go

@@ -262,3 +262,7 @@ func (s *composeService) Top(ctx context.Context, projectName string, services [
 func (s *composeService) Events(ctx context.Context, project string, options compose.EventsOptions) error {
 	return errdefs.ErrNotImplemented
 }
+
+func (s *composeService) Port(ctx context.Context, project string, service string, port int, options compose.PortOptions) (string, int, error) {
+	return "", 0, errdefs.ErrNotImplemented
+}

+ 50 - 0
local/compose/port.go

@@ -0,0 +1,50 @@
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package compose
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/docker/compose-cli/api/compose"
+
+	moby "github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
+)
+
+func (s *composeService) Port(ctx context.Context, project string, service string, port int, options compose.PortOptions) (string, int, error) {
+	list, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
+		Filters: filters.NewArgs(
+			projectFilter(project),
+			serviceFilter(service),
+			filters.Arg("label", fmt.Sprintf("%s=%d", containerNumberLabel, options.Index)),
+		),
+	})
+	if err != nil {
+		return "", 0, err
+	}
+	if len(list) == 0 {
+		return "", 0, fmt.Errorf("no container found for %s_%d", service, options.Index)
+	}
+	container := list[0]
+	for _, p := range container.Ports {
+		if p.PrivatePort == uint16(port) && p.Type == options.Protocol {
+			return p.IP, int(p.PublicPort), nil
+		}
+	}
+	return "", 0, err
+}

+ 5 - 0
local/e2e/compose/networks_test.go

@@ -55,6 +55,11 @@ func TestNetworks(t *testing.T) {
 		res.Assert(t, icmd.Expected{Out: "microservices"})
 	})
 
+	t.Run("port", func(t *testing.T) {
+		res := c.RunDockerCmd("compose", "--project-name", projectName, "port", "words", "8080")
+		res.Assert(t, icmd.Expected{Out: `0.0.0.0:8080`})
+	})
+
 	t.Run("down", func(t *testing.T) {
 		_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
 	})