Merge pull request #631 from docker/aci_domainname

ACI: allow users to set DNSLabelName and deploy containers with fqdn like `myapp.eastus.azurecontainers.io`
This commit is contained in:
Guillaume Tardif 2020-09-22 15:00:28 +02:00 committed by GitHub
commit d42a931d67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 264 additions and 65 deletions

View File

@ -89,7 +89,7 @@ func (cs *aciComposeService) Ps(ctx context.Context, project string) ([]compose.
if isContainerVisible(container, group, false) {
continue
}
res = append(res, convert.ContainerGroupToServiceStatus(getContainerID(group, container), group, container))
res = append(res, convert.ContainerGroupToServiceStatus(getContainerID(group, container), group, container, cs.ctx.Location))
}
return res, nil
}

View File

@ -63,7 +63,7 @@ func (cs *aciContainerService) List(ctx context.Context, all bool) ([]containers
if isContainerVisible(container, group, all) {
continue
}
c := convert.ContainerGroupToContainer(getContainerID(group, container), group, container)
c := convert.ContainerGroupToContainer(getContainerID(group, container), group, container, cs.ctx.Location)
res = append(res, c)
}
}
@ -257,5 +257,5 @@ func (cs *aciContainerService) Inspect(ctx context.Context, containerID string)
return containers.Container{}, errdefs.ErrNotFound
}
return convert.ContainerGroupToContainer(containerID, cg, cc), nil
return convert.ContainerGroupToContainer(containerID, cg, cc, cs.ctx.Location), nil
}

View File

@ -49,6 +49,7 @@ func ContainerToComposeProject(r containers.ContainerConfig) (types.Project, err
Ports: ports,
Labels: r.Labels,
Volumes: serviceConfigVolumes,
DomainName: r.DomainName,
Environment: toComposeEnvs(r.Environment),
Deploy: &types.DeployConfig{
Resources: types.Resources{

View File

@ -54,6 +54,18 @@ func TestConvertRestartPolicy(t *testing.T) {
assert.Equal(t, service1.Deploy.RestartPolicy.Condition, "none")
}
func TestConvertDomainName(t *testing.T) {
container := containers.ContainerConfig{
ID: "container1",
DomainName: "myapp",
}
project, err := ContainerToComposeProject(container)
assert.NilError(t, err)
service1 := project.Services[0]
assert.Equal(t, service1.Name, container.ID)
assert.Equal(t, service1.DomainName, "myapp")
}
func TestConvertEnvVariables(t *testing.T) {
container := containers.ContainerConfig{
ID: "container1",

View File

@ -43,8 +43,8 @@ const (
StatusRunning = "Running"
// ComposeDNSSidecarName name of the dns sidecar container
ComposeDNSSidecarName = "aci--dns--sidecar"
dnsSidecarImage = "busybox:1.31.1"
dnsSidecarImage = "busybox:1.31.1"
azureFileDriverName = "azure_file"
volumeDriveroptsShareNameKey = "share_name"
volumeDriveroptsAccountNameKey = "storage_account_name"
@ -93,6 +93,7 @@ func ToContainerGroup(ctx context.Context, aciContext store.AciContext, p types.
}
var groupPorts []containerinstance.Port
var dnsLabelName *string
for _, s := range project.Services {
service := serviceConfigAciHelper(s)
containerDefinition, err := service.getAciContainer(volumesCache)
@ -102,32 +103,29 @@ func ToContainerGroup(ctx context.Context, aciContext store.AciContext, p types.
if service.Labels != nil && len(service.Labels) > 0 {
return containerinstance.ContainerGroup{}, errors.New("ACI integration does not support labels in compose applications")
}
if service.Ports != nil {
var containerPorts []containerinstance.ContainerPort
for _, portConfig := range service.Ports {
if portConfig.Published != 0 && portConfig.Published != portConfig.Target {
msg := fmt.Sprintf("Port mapping is not supported with ACI, cannot map port %d to %d for container %s",
portConfig.Published, portConfig.Target, service.Name)
return groupDefinition, errors.New(msg)
}
portNumber := int32(portConfig.Target)
containerPorts = append(containerPorts, containerinstance.ContainerPort{
Port: to.Int32Ptr(portNumber),
})
groupPorts = append(groupPorts, containerinstance.Port{
Port: to.Int32Ptr(portNumber),
Protocol: containerinstance.TCP,
})
}
containerDefinition.ContainerProperties.Ports = &containerPorts
groupDefinition.ContainerGroupProperties.IPAddress = &containerinstance.IPAddress{
Type: containerinstance.Public,
Ports: &groupPorts,
containerPorts, serviceGroupPorts, serviceDomainName, err := convertPortsToAci(service)
if err != nil {
return groupDefinition, err
}
containerDefinition.ContainerProperties.Ports = &containerPorts
groupPorts = append(groupPorts, serviceGroupPorts...)
if serviceDomainName != nil {
if dnsLabelName != nil && *serviceDomainName != *dnsLabelName {
return containerinstance.ContainerGroup{}, fmt.Errorf("ACI integration does not support specifying different domain names on services in the same compose application")
}
dnsLabelName = serviceDomainName
}
containers = append(containers, containerDefinition)
}
if len(groupPorts) > 0 {
groupDefinition.ContainerGroupProperties.IPAddress = &containerinstance.IPAddress{
Type: containerinstance.Public,
Ports: &groupPorts,
DNSNameLabel: dnsLabelName,
}
}
if len(containers) > 1 {
dnsSideCar := getDNSSidecar(containers)
containers = append(containers, dnsSideCar)
@ -137,6 +135,31 @@ func ToContainerGroup(ctx context.Context, aciContext store.AciContext, p types.
return groupDefinition, nil
}
func convertPortsToAci(service serviceConfigAciHelper) ([]containerinstance.ContainerPort, []containerinstance.Port, *string, error) {
var groupPorts []containerinstance.Port
var containerPorts []containerinstance.ContainerPort
for _, portConfig := range service.Ports {
if portConfig.Published != 0 && portConfig.Published != portConfig.Target {
msg := fmt.Sprintf("Port mapping is not supported with ACI, cannot map port %d to %d for container %s",
portConfig.Published, portConfig.Target, service.Name)
return nil, nil, nil, errors.New(msg)
}
portNumber := int32(portConfig.Target)
containerPorts = append(containerPorts, containerinstance.ContainerPort{
Port: to.Int32Ptr(portNumber),
})
groupPorts = append(groupPorts, containerinstance.Port{
Port: to.Int32Ptr(portNumber),
Protocol: containerinstance.TCP,
})
}
var dnsLabelName *string = nil
if service.DomainName != "" {
dnsLabelName = &service.DomainName
}
return containerPorts, groupPorts, dnsLabelName, nil
}
func getDNSSidecar(containers []containerinstance.Container) containerinstance.Container {
var commands []string
for _, container := range containers {
@ -249,7 +272,7 @@ func (p projectAciHelper) getRestartPolicy() (containerinstance.ContainerGroupRe
restartPolicyCondition = toAciRestartPolicy(service.Deploy.RestartPolicy.Condition)
}
if alreadySpecified && restartPolicyCondition != toAciRestartPolicy(service.Deploy.RestartPolicy.Condition) {
return "", errors.New("ACI integration does not support specifying different restart policies on containers in the same compose application")
return "", errors.New("ACI integration does not support specifying different restart policies on services in the same compose application")
}
}
@ -390,7 +413,7 @@ func bytesToGb(b types.UnitBytes) float64 {
}
// ContainerGroupToServiceStatus convert from an ACI container definition to service status
func ContainerGroupToServiceStatus(containerID string, group containerinstance.ContainerGroup, container containerinstance.Container) compose.ServiceStatus {
func ContainerGroupToServiceStatus(containerID string, group containerinstance.ContainerGroup, container containerinstance.Container, region string) compose.ServiceStatus {
var replicas = 1
if GetStatus(container, group) != StatusRunning {
replicas = 0
@ -398,14 +421,22 @@ func ContainerGroupToServiceStatus(containerID string, group containerinstance.C
return compose.ServiceStatus{
ID: containerID,
Name: *container.Name,
Ports: formatter.PortsToStrings(ToPorts(group.IPAddress, *container.Ports)),
Ports: formatter.PortsToStrings(ToPorts(group.IPAddress, *container.Ports), fqdn(group, region)),
Replicas: replicas,
Desired: 1,
}
}
func fqdn(group containerinstance.ContainerGroup, region string) string {
fqdn := ""
if group.IPAddress != nil && group.IPAddress.DNSNameLabel != nil && *group.IPAddress.DNSNameLabel != "" {
fqdn = *group.IPAddress.DNSNameLabel + "." + region + ".azurecontainer.io"
}
return fqdn
}
// ContainerGroupToContainer composes a Container from an ACI container definition
func ContainerGroupToContainer(containerID string, cg containerinstance.ContainerGroup, cc containerinstance.Container) containers.Container {
func ContainerGroupToContainer(containerID string, cg containerinstance.ContainerGroup, cc containerinstance.Container, region string) containers.Container {
memLimits := 0.
if cc.Resources != nil &&
cc.Resources.Limits != nil &&
@ -436,9 +467,9 @@ func ContainerGroupToContainer(containerID string, cg containerinstance.Containe
}
}
var config *containers.RuntimeConfig = nil
var config *containers.RuntimeConfig = &containers.RuntimeConfig{FQDN: fqdn(cg, region)}
if envVars != nil {
config = &containers.RuntimeConfig{Env: envVars}
config.Env = envVars
}
c := containers.Container{
ID: containerID,

View File

@ -59,7 +59,8 @@ func TestContainerGroupToContainer(t *testing.T) {
Ports: &[]containerinstance.Port{{
Port: to.Int32Ptr(80),
}},
IP: to.StringPtr("42.42.42.42"),
IP: to.StringPtr("42.42.42.42"),
DNSNameLabel: to.StringPtr("myapp"),
},
OsType: "Linux",
},
@ -102,10 +103,13 @@ func TestContainerGroupToContainer(t *testing.T) {
Protocol: "tcp",
HostIP: "42.42.42.42",
}},
Config: &containers.RuntimeConfig{
FQDN: "myapp.eastus.azurecontainer.io",
},
RestartPolicyCondition: "any",
}
container := ContainerGroupToContainer("myContainerID", myContainerGroup, myContainer)
container := ContainerGroupToContainer("myContainerID", myContainerGroup, myContainer, "eastus")
assert.DeepEqual(t, container, expectedContainer)
}
@ -143,7 +147,7 @@ func TestContainerGroupToServiceStatus(t *testing.T) {
Desired: 1,
}
container := ContainerGroupToServiceStatus("myContainerID", myContainerGroup, myContainer)
container := ContainerGroupToServiceStatus("myContainerID", myContainerGroup, myContainer, "eastus")
assert.DeepEqual(t, container, expectedService)
}
@ -366,7 +370,7 @@ func TestComposeInconsistentMultiContainerRestartPolicy(t *testing.T) {
}
_, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
assert.Error(t, err, "ACI integration does not support specifying different restart policies on containers in the same compose application")
assert.Error(t, err, "ACI integration does not support specifying different restart policies on services in the same compose application")
}
func TestLabelsErrorMessage(t *testing.T) {
@ -448,6 +452,88 @@ func TestComposeContainerGroupToContainerMultiplePorts(t *testing.T) {
assert.Assert(t, is.Len(groupPorts, 2))
assert.Equal(t, *groupPorts[0].Port, int32(80))
assert.Equal(t, *groupPorts[1].Port, int32(8080))
assert.Assert(t, group.IPAddress.DNSNameLabel == nil)
}
func TestComposeContainerGroupToContainerWithDomainName(t *testing.T) {
project := types.Project{
Services: []types.ServiceConfig{
{
Name: "service1",
Image: "image1",
Ports: []types.ServicePortConfig{
{
Published: 80,
Target: 80,
},
},
DomainName: "myApp",
},
{
Name: "service2",
Image: "image2",
Ports: []types.ServicePortConfig{
{
Published: 8080,
Target: 8080,
},
},
},
},
}
group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
assert.NilError(t, err)
assert.Assert(t, is.Len(*group.Containers, 3))
groupPorts := *group.IPAddress.Ports
assert.Assert(t, is.Len(groupPorts, 2))
assert.Equal(t, *groupPorts[0].Port, int32(80))
assert.Equal(t, *groupPorts[1].Port, int32(8080))
assert.Equal(t, *group.IPAddress.DNSNameLabel, "myApp")
}
func TestComposeContainerGroupToContainerErrorWhenSeveralDomainNames(t *testing.T) {
project := types.Project{
Services: []types.ServiceConfig{
{
Name: "service1",
Image: "image1",
DomainName: "myApp",
},
{
Name: "service2",
Image: "image2",
DomainName: "myApp2",
},
},
}
_, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
assert.Error(t, err, "ACI integration does not support specifying different domain names on services in the same compose application")
}
// ACI fails if group definition IPAddress has no ports
func TestComposeContainerGroupToContainerIgnoreDomainNameWithoutPorts(t *testing.T) {
project := types.Project{
Services: []types.ServiceConfig{
{
Name: "service1",
Image: "image1",
DomainName: "myApp",
},
{
Name: "service2",
Image: "image2",
DomainName: "myApp",
},
},
}
group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
assert.NilError(t, err)
assert.Assert(t, is.Len(*group.Containers, 3))
assert.Assert(t, group.IPAddress == nil)
}
func TestComposeContainerGroupToContainerResourceLimits(t *testing.T) {

View File

@ -57,6 +57,8 @@ type Container struct {
// RuntimeConfig config of a created container
type RuntimeConfig struct {
Env map[string]string `json:",omitempty"`
// FQDN is the fqdn to use
FQDN string `json:"fqdn,omitempty"`
}
// Port represents a published port of a container
@ -91,6 +93,8 @@ type ContainerConfig struct {
Environment []string
// Restart policy condition
RestartPolicyCondition string
// DomainName Container NIS domain name
DomainName string
}
// ExecRequest contaiens configuration about an exec request

View File

@ -29,6 +29,7 @@ import (
type composeOptions struct {
Name string
DomainName string
WorkingDir string
ConfigPaths []string
Environment []string
@ -60,7 +61,7 @@ func (o *composeOptions) toProjectOptions() (*cli.ProjectOptions, error) {
}
// Command returns the compose command with its child commands
func Command() *cobra.Command {
func Command(contextType string) *cobra.Command {
command := &cobra.Command{
Short: "Docker Compose",
Use: "compose",
@ -70,7 +71,7 @@ func Command() *cobra.Command {
}
command.AddCommand(
upCommand(),
upCommand(contextType),
downCommand(),
psCommand(),
listCommand(),

View File

@ -19,15 +19,16 @@ package compose
import (
"context"
"github.com/compose-spec/compose-go/cli"
"github.com/spf13/cobra"
"github.com/compose-spec/compose-go/cli"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/context/store"
"github.com/docker/compose-cli/progress"
)
func upCommand() *cobra.Command {
func upCommand(contextType string) *cobra.Command {
opts := composeOptions{}
upCmd := &cobra.Command{
Use: "up",
@ -40,6 +41,11 @@ func upCommand() *cobra.Command {
upCmd.Flags().StringArrayVarP(&opts.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
upCmd.Flags().StringArrayVarP(&opts.Environment, "environment", "e", []string{}, "Environment variables")
upCmd.Flags().BoolP("detach", "d", true, " Detached mode: Run containers in the background")
if contextType == store.AciContextType {
upCmd.Flags().StringVar(&opts.DomainName, "domainname", "", "Container NIS domain name")
}
return upCmd
}
@ -55,6 +61,10 @@ func runUp(ctx context.Context, opts composeOptions) error {
return "", err
}
project, err := cli.ProjectFromOptions(options)
if opts.DomainName != "" {
//arbitrarily set the domain name on the first service ; ACI backend will expose the entire project
project.Services[0].DomainName = opts.DomainName
}
if err != nil {
return "", err
}

View File

@ -23,13 +23,13 @@ import (
"strings"
"text/tabwriter"
"github.com/docker/compose-cli/utils/formatter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/containers"
formatter2 "github.com/docker/compose-cli/formatter"
"github.com/docker/compose-cli/utils/formatter"
)
type psOpts struct {
@ -98,9 +98,17 @@ func runPs(ctx context.Context, opts psOpts) error {
w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "CONTAINER ID\tIMAGE\tCOMMAND\tSTATUS\tPORTS\n")
format := "%s\t%s\t%s\t%s\t%s\n"
for _, c := range containers {
fmt.Fprintf(w, format, c.ID, c.Image, c.Command, c.Status, strings.Join(formatter.PortsToStrings(c.Ports), ", "))
for _, container := range containers {
fmt.Fprintf(w, format, container.ID, container.Image, container.Command, container.Status, strings.Join(formatter.PortsToStrings(container.Ports, fqdn(container)), ", "))
}
return w.Flush()
}
func fqdn(container containers.Container) string {
fqdn := ""
if container.Config != nil {
fqdn = container.Config.FQDN
}
return fqdn
}

View File

@ -25,15 +25,15 @@ import (
"github.com/containerd/console"
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/containers"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/containers"
"github.com/docker/compose-cli/cli/options/run"
"github.com/docker/compose-cli/context/store"
"github.com/docker/compose-cli/progress"
)
// Command runs a container
func Command() *cobra.Command {
func Command(contextType string) *cobra.Command {
var opts run.Opts
cmd := &cobra.Command{
Use: "run",
@ -54,6 +54,10 @@ func Command() *cobra.Command {
cmd.Flags().StringArrayVarP(&opts.Environment, "env", "e", []string{}, "Set environment variables")
cmd.Flags().StringVarP(&opts.RestartPolicyCondition, "restart", "", containers.RestartPolicyNone, "Restart policy to apply when a container exits")
if contextType == store.AciContextType {
cmd.Flags().StringVar(&opts.DomainName, "domainname", "", "Container NIS domain name")
}
return cmd
}

View File

@ -18,15 +18,25 @@ package run
import (
"bytes"
"strings"
"testing"
"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
)
func TestHelp(t *testing.T) {
var b bytes.Buffer
c := Command()
c := Command("aci")
c.SetOutput(&b)
_ = c.Help()
golden.Assert(t, b.String(), "run-help.golden")
}
func TestHelpNoDomainFlag(t *testing.T) {
var b bytes.Buffer
c := Command("default")
c.SetOutput(&b)
_ = c.Help()
assert.Assert(t, !strings.Contains(b.String(), "domainname"))
}

View File

@ -6,6 +6,7 @@ Usage:
Flags:
--cpus float Number of CPUs (default 1)
-d, --detach Run container in background and print container ID
--domainname string Container NIS domain name
-e, --env stringArray Set environment variables
-l, --label stringArray Set meta data on a container
-m, --memory bytes Memory limit

View File

@ -115,7 +115,6 @@ func main() {
contextcmd.Command(),
cmd.PsCommand(),
cmd.ServeCommand(),
run.Command(),
cmd.ExecCommand(),
cmd.LogsCommand(),
cmd.RmCommand(),
@ -127,7 +126,6 @@ func main() {
cmd.StopCommand(),
cmd.KillCommand(),
cmd.SecretCommand(),
compose.Command(),
// Place holders
cmd.EcsCommand(),
@ -180,6 +178,11 @@ func main() {
ctype = cc.Type()
}
root.AddCommand(
run.Command(ctype),
compose.Command(ctype),
)
if ctype == store.AciContextType {
// we can also pass ctype as a parameter to the volume command and customize subcommands, flags, etc. when we have other backend implementations
root.AddCommand(volume.ACICommand())

View File

@ -64,7 +64,7 @@ func TestCheckOwnCommand(t *testing.T) {
assert.Assert(t, isContextAgnosticCommand(login.Command()))
assert.Assert(t, isContextAgnosticCommand(context.Command()))
assert.Assert(t, isContextAgnosticCommand(cmd.ServeCommand()))
assert.Assert(t, !isContextAgnosticCommand(run.Command()))
assert.Assert(t, !isContextAgnosticCommand(run.Command("default")))
assert.Assert(t, !isContextAgnosticCommand(cmd.ExecCommand()))
assert.Assert(t, !isContextAgnosticCommand(cmd.LogsCommand()))
assert.Assert(t, !isContextAgnosticCommand(cmd.PsCommand()))

View File

@ -41,6 +41,7 @@ type Opts struct {
Detach bool
Environment []string
RestartPolicyCondition string
DomainName string
}
// ToContainerConfig convert run options to a container configuration
@ -74,6 +75,7 @@ func (r *Opts) ToContainerConfig(image string) (containers.ContainerConfig, erro
CPULimit: r.Cpus,
Environment: r.Environment,
RestartPolicyCondition: restartPolicy,
DomainName: r.DomainName,
}, nil
}

View File

@ -335,18 +335,19 @@ func lines(output string) []string {
func TestContainerRunAttached(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
_, _ = setupTestResourceGroup(t, c)
_, groupID := setupTestResourceGroup(t, c)
// Used in subtests
var (
container string
endpoint string
container string = "test-container"
endpoint string
followLogsProcess *icmd.Result
)
container = "test-container"
var followLogsProcess *icmd.Result
t.Run("run attached limits", func(t *testing.T) {
dnsLabelName := "nginx-" + groupID
fqdn := dnsLabelName + "." + location + ".azurecontainer.io"
cmd := c.NewDockerCmd(
"run",
"--name", container,
@ -354,15 +355,17 @@ func TestContainerRunAttached(t *testing.T) {
"--memory", "0.1G", "--cpus", "0.1",
"-p", "80:80",
"nginx",
"--domainname",
dnsLabelName,
)
followLogsProcess = icmd.StartCmd(cmd)
checkRunning := func(t poll.LogT) poll.Result {
res := c.RunDockerOrExitError("inspect", container)
if res.ExitCode == 0 {
if res.ExitCode == 0 && strings.Contains(res.Stdout(), `"Status": "Running"`) {
return poll.Success()
}
return poll.Continue("waiting for container to be running")
return poll.Continue("waiting for container to be running, current inspect result: \n%s", res.Combined())
}
poll.WaitOn(t, checkRunning, poll.WithDelay(5*time.Second), poll.WithTimeout(60*time.Second))
@ -380,7 +383,8 @@ func TestContainerRunAttached(t *testing.T) {
assert.Assert(t, len(port.HostIP) > 0)
assert.Equal(t, port.ContainerPort, uint32(80))
assert.Equal(t, port.HostPort, uint32(80))
endpoint = fmt.Sprintf("http://%s:%d", port.HostIP, port.HostPort)
assert.Equal(t, containerInspect.Config.FQDN, fqdn)
endpoint = fmt.Sprintf("http://%s:%d", fqdn, port.HostPort)
assert.Assert(t, !strings.Contains(followLogsProcess.Stdout(), "/test"))
checkRequest := func(t poll.LogT) poll.Result {
@ -449,7 +453,7 @@ func TestContainerRunAttached(t *testing.T) {
func TestComposeUpUpdate(t *testing.T) {
c := NewParallelE2eCLI(t, binDir)
_, _ = setupTestResourceGroup(t, c)
_, groupID := setupTestResourceGroup(t, c)
const (
composeFile = "../composefiles/aci-demo/aci_demo_port.yaml"
@ -461,8 +465,11 @@ func TestComposeUpUpdate(t *testing.T) {
)
t.Run("compose up", func(t *testing.T) {
dnsLabelName := "nginx-" + groupID
fqdn := dnsLabelName + "." + location + ".azurecontainer.io"
// Name of Compose project is taken from current folder "acie2e"
c.RunDockerCmd("compose", "up", "-f", composeFile)
c.RunDockerCmd("compose", "up", "-f", composeFile, "--domainname", dnsLabelName)
res := c.RunDockerCmd("ps")
out := lines(res.Stdout())
// Check three containers are running
@ -489,6 +496,11 @@ func TestComposeUpUpdate(t *testing.T) {
b, err := ioutil.ReadAll(r.Body)
assert.NilError(t, err)
assert.Assert(t, strings.Contains(string(b), `"word":`))
endpoint = fmt.Sprintf("http://%s:%d", fqdn, containerInspect.Ports[0].HostPort)
r, err = HTTPGetWithRetry(endpoint+"/words/noun", 3)
assert.NilError(t, err)
assert.Equal(t, r.StatusCode, http.StatusOK)
})
t.Run("compose ps", func(t *testing.T) {

View File

@ -31,7 +31,7 @@ type portGroup struct {
}
// PortsToStrings returns a human readable published ports
func PortsToStrings(ports []containers.Port) []string {
func PortsToStrings(ports []containers.Port, fqdn string) []string {
groupMap := make(map[string]*portGroup)
var (
result []string
@ -49,6 +49,9 @@ func PortsToStrings(ports []containers.Port) []string {
if port.HostIP != "" {
hostIP = port.HostIP
}
if fqdn != "" {
hostIP = fqdn
}
if port.HostPort != port.ContainerPort {
hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", hostIP, port.HostPort, port.ContainerPort, port.Protocol))

View File

@ -24,7 +24,7 @@ import (
"github.com/docker/compose-cli/cli/options/run"
)
func TestDisplayPorts(t *testing.T) {
func TestDisplayPortsNoDomainname(t *testing.T) {
testCases := []struct {
name string
in []string
@ -70,8 +70,19 @@ func TestDisplayPorts(t *testing.T) {
containerConfig, err := runOpts.ToContainerConfig("test")
assert.NilError(t, err)
out := PortsToStrings(containerConfig.Ports)
out := PortsToStrings(containerConfig.Ports, "")
assert.DeepEqual(t, testCase.expected, out)
})
}
}
func TestDisplayPortsWithDomainname(t *testing.T) {
runOpts := run.Opts{
Publish: []string{"80"},
}
containerConfig, err := runOpts.ToContainerConfig("test")
assert.NilError(t, err)
out := PortsToStrings(containerConfig.Ports, "mydomain.westus.azurecontainner.io")
assert.DeepEqual(t, []string{"mydomain.westus.azurecontainner.io:80->80/tcp"}, out)
}