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..0bc75befd 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) } } @@ -86,6 +86,9 @@ func (cs *aciContainerService) Run(ctx context.Context, r containers.ContainerCo return err } addTag(&groupDefinition, singleContainerTag) + if r.DomainName != "" { + groupDefinition.ContainerGroupProperties.IPAddress.DNSNameLabel = &r.DomainName + } return createACIContainers(ctx, cs.ctx, groupDefinition) } @@ -257,5 +260,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/convert.go b/aci/convert/convert.go index 95cd15ad7..d0e2ba231 100644 --- a/aci/convert/convert.go +++ b/aci/convert/convert.go @@ -390,7 +390,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 +398,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 +444,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..092aaad3d 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) } 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/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..5c3f36f58 100644 --- a/cli/cmd/run/run.go +++ b/cli/cmd/run/run.go @@ -46,6 +46,7 @@ func Command() *cobra.Command { cmd.Flags().StringArrayVarP(&opts.Publish, "publish", "p", []string{}, "Publish a container's port(s). [HOST_PORT:]CONTAINER_PORT") cmd.Flags().StringVar(&opts.Name, "name", "", "Assign a name to the container") + cmd.Flags().StringVar(&opts.DomainName, "domainname", "", "Container NIS domain name") cmd.Flags().StringArrayVarP(&opts.Labels, "label", "l", []string{}, "Set meta data on a container") cmd.Flags().StringArrayVarP(&opts.Volumes, "volume", "v", []string{}, "Volume. Ex: storageaccount/my_share[:/absolute/path/to/target][:ro]") cmd.Flags().BoolVarP(&opts.Detach, "detach", "d", false, "Run container in background and print container ID") 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/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..b136f6c36 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 { 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) +}