Bläddra i källkod

Merge pull request #631 from docker/aci_domainname

ACI: allow users to set DNSLabelName and deploy containers with fqdn like `myapp.eastus.azurecontainers.io`
Guillaume Tardif 5 år sedan
förälder
incheckning
d42a931d67

+ 1 - 1
aci/compose.go

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

+ 2 - 2
aci/containers.go

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

+ 1 - 0
aci/convert/container.go

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

+ 12 - 0
aci/convert/container_test.go

@@ -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",

+ 59 - 28
aci/convert/convert.go

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

+ 90 - 4
aci/convert/convert_test.go

@@ -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) {

+ 4 - 0
api/containers/api.go

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

+ 3 - 2
cli/cmd/compose/compose.go

@@ -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(),

+ 13 - 3
cli/cmd/compose/up.go

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

+ 12 - 4
cli/cmd/ps.go

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

+ 7 - 3
cli/cmd/run/run.go

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

+ 11 - 1
cli/cmd/run/run_test.go

@@ -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"))
+}

+ 1 - 0
cli/cmd/run/testdata/run-help.golden

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

+ 5 - 2
cli/main.go

@@ -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())

+ 1 - 1
cli/main_test.go

@@ -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()))

+ 2 - 0
cli/options/run/opts.go

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

+ 23 - 11
tests/aci-e2e/e2e-aci_test.go

@@ -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) {

+ 4 - 1
utils/formatter/container.go

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

+ 13 - 2
utils/formatter/container_test.go

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