Browse Source

ACI: allow users to set DNSLabelName and deploy containers with fqdn like `myapp.eastus.azurecontainers.io`

Signed-off-by: Guillaume Tardif <[email protected]>
Guillaume Tardif 5 years ago
parent
commit
701d1b834e

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

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

+ 13 - 5
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,

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

+ 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

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

+ 1 - 0
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")

+ 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

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

+ 13 - 9
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 {

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