introduce `port` command for parity with docker-compose

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2021-03-19 09:49:14 +01:00
parent 3366131096
commit 8b38874aba
No known key found for this signature in database
GPG Key ID: 9858809D6F8F6E7E
13 changed files with 167 additions and 12 deletions

View File

@ -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 { func (cs *aciComposeService) Events(ctx context.Context, project string, options compose.EventsOptions) error {
return errdefs.ErrNotImplemented 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
}

View File

@ -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 { func (c *composeService) Events(ctx context.Context, project string, options compose.EventsOptions) error {
return errdefs.ErrNotImplemented 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
}

View File

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

View File

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

77
cli/cmd/compose/port.go Normal file
View File

@ -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
}

View File

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

View File

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

View File

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

View File

@ -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 { func (e ecsLocalSimulation) Events(ctx context.Context, project string, options compose.EventsOptions) error {
return e.compose.Events(ctx, project, options) 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
}

View File

@ -67,6 +67,10 @@ func (b *ecsAPIService) Events(ctx context.Context, project string, options comp
return errdefs.ErrNotImplemented 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 { func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
logrus.Debugf("deploying on AWS with region=%q", b.Region) logrus.Debugf("deploying on AWS with region=%q", b.Region)
err := b.aws.CheckRequirements(ctx, b.Region) err := b.aws.CheckRequirements(ctx, b.Region)

View File

@ -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 { func (s *composeService) Events(ctx context.Context, project string, options compose.EventsOptions) error {
return errdefs.ErrNotImplemented 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
local/compose/port.go Normal file
View File

@ -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
}

View File

@ -55,6 +55,11 @@ func TestNetworks(t *testing.T) {
res.Assert(t, icmd.Expected{Out: "microservices"}) 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) { t.Run("down", func(t *testing.T) {
_ = c.RunDockerCmd("compose", "--project-name", projectName, "down") _ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
}) })