diff --git a/aci/compose.go b/aci/compose.go index 63606f02c..50f05deb8 100644 --- a/aci/compose.go +++ b/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 } diff --git a/aci/containers.go b/aci/containers.go index 2d0c01e8d..570f6afd7 100644 --- a/aci/containers.go +++ b/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 } diff --git a/aci/convert/container.go b/aci/convert/container.go index f2cbc3ba5..f2456039a 100644 --- a/aci/convert/container.go +++ b/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{ diff --git a/aci/convert/container_test.go b/aci/convert/container_test.go index 1fd0a7f0e..6bb63e4a7 100644 --- a/aci/convert/container_test.go +++ b/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", diff --git a/aci/convert/convert.go b/aci/convert/convert.go index 95cd15ad7..b2fd1973c 100644 --- a/aci/convert/convert.go +++ b/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, diff --git a/aci/convert/convert_test.go b/aci/convert/convert_test.go index 66cc56f5a..18f229b3f 100644 --- a/aci/convert/convert_test.go +++ b/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) { diff --git a/api/containers/api.go b/api/containers/api.go index 0185f80b7..e9772d6b5 100644 --- a/api/containers/api.go +++ b/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 diff --git a/cli/cmd/compose/compose.go b/cli/cmd/compose/compose.go index e58243d64..8e8211208 100644 --- a/cli/cmd/compose/compose.go +++ b/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(), diff --git a/cli/cmd/compose/up.go b/cli/cmd/compose/up.go index 3c5ec616f..b47395ffc 100644 --- a/cli/cmd/compose/up.go +++ b/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 } diff --git a/cli/cmd/ps.go b/cli/cmd/ps.go index ea55862f1..815284dec 100644 --- a/cli/cmd/ps.go +++ b/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 +} diff --git a/cli/cmd/run/run.go b/cli/cmd/run/run.go index 350b7e146..0c1559b3b 100644 --- a/cli/cmd/run/run.go +++ b/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 } diff --git a/cli/cmd/run/run_test.go b/cli/cmd/run/run_test.go index b781179e0..78b23c540 100644 --- a/cli/cmd/run/run_test.go +++ b/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")) +} diff --git a/cli/cmd/run/testdata/run-help.golden b/cli/cmd/run/testdata/run-help.golden index 1b499106e..178c63b08 100644 --- a/cli/cmd/run/testdata/run-help.golden +++ b/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 diff --git a/cli/main.go b/cli/main.go index 56abe671f..eb8a64f15 100644 --- a/cli/main.go +++ b/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()) diff --git a/cli/main_test.go b/cli/main_test.go index f634b4672..c151ce0d0 100644 --- a/cli/main_test.go +++ b/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())) diff --git a/cli/options/run/opts.go b/cli/options/run/opts.go index 8694aa418..9cdd2ebe2 100644 --- a/cli/options/run/opts.go +++ b/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 } diff --git a/tests/aci-e2e/e2e-aci_test.go b/tests/aci-e2e/e2e-aci_test.go index f27dccc2a..3456ffc6e 100644 --- a/tests/aci-e2e/e2e-aci_test.go +++ b/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) { diff --git a/utils/formatter/container.go b/utils/formatter/container.go index 4ed7a3854..dc5c3fdb2 100644 --- a/utils/formatter/container.go +++ b/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)) diff --git a/utils/formatter/container_test.go b/utils/formatter/container_test.go index dcfaf8106..d590d24ff 100644 --- a/utils/formatter/container_test.go +++ b/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) +}