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

Signed-off-by: Guillaume Tardif <guillaume.tardif@docker.com>
This commit is contained in:
Guillaume Tardif 2020-09-21 12:42:44 +02:00
parent 7abdb085c0
commit 701d1b834e
12 changed files with 76 additions and 27 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)
}
}
@ -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
}

View File

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

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

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

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

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

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

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

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