mirror of https://github.com/docker/compose.git
Implement compose ps on ACI
Signed-off-by: Guillaume Tardif <guillaume.tardif@docker.com>
This commit is contained in:
parent
f80e90caca
commit
b0c50ed6dd
|
@ -164,24 +164,28 @@ func (cs *aciContainerService) List(ctx context.Context, all bool) ([]containers
|
|||
}
|
||||
|
||||
for _, container := range *group.Containers {
|
||||
// don't list sidecar container
|
||||
if *container.Name == convert.ComposeDNSSidecarName {
|
||||
if isContainerVisible(container, group, all) {
|
||||
continue
|
||||
}
|
||||
if !all && convert.GetStatus(container, group) != statusRunning {
|
||||
continue
|
||||
}
|
||||
containerID := *containerGroup.Name + composeContainerSeparator + *container.Name
|
||||
if _, ok := group.Tags[singleContainerTag]; ok {
|
||||
containerID = *containerGroup.Name
|
||||
}
|
||||
c := convert.ContainerGroupToContainer(containerID, group, container)
|
||||
c := convert.ContainerGroupToContainer(getContainerID(group, container), group, container)
|
||||
res = append(res, c)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func getContainerID(group containerinstance.ContainerGroup, container containerinstance.Container) string {
|
||||
containerID := *group.Name + composeContainerSeparator + *container.Name
|
||||
if _, ok := group.Tags[singleContainerTag]; ok {
|
||||
containerID = *group.Name
|
||||
}
|
||||
return containerID
|
||||
}
|
||||
|
||||
func isContainerVisible(container containerinstance.Container, group containerinstance.ContainerGroup, showAll bool) bool {
|
||||
return *container.Name == convert.ComposeDNSSidecarName || (!showAll && convert.GetStatus(container, group) != statusRunning)
|
||||
}
|
||||
|
||||
func (cs *aciContainerService) Run(ctx context.Context, r containers.ContainerConfig) error {
|
||||
if strings.Contains(r.ID, composeContainerSeparator) {
|
||||
return errors.New(fmt.Sprintf("invalid container name. ACI container name cannot include %q", composeContainerSeparator))
|
||||
|
@ -411,7 +415,28 @@ func (cs *aciComposeService) Down(ctx context.Context, project string) error {
|
|||
}
|
||||
|
||||
func (cs *aciComposeService) Ps(ctx context.Context, project string) ([]compose.ServiceStatus, error) {
|
||||
return nil, errdefs.ErrNotImplemented
|
||||
groupsClient, err := login.NewContainerGroupsClient(cs.ctx.SubscriptionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
group, err := groupsClient.Get(ctx, cs.ctx.ResourceGroup, project)
|
||||
if err != nil {
|
||||
return []compose.ServiceStatus{}, err
|
||||
}
|
||||
|
||||
if group.Containers == nil || len(*group.Containers) < 1 {
|
||||
return []compose.ServiceStatus{}, fmt.Errorf("no containers found in ACI container group %s", project)
|
||||
}
|
||||
|
||||
res := []compose.ServiceStatus{}
|
||||
for _, container := range *group.Containers {
|
||||
if isContainerVisible(container, group, false) {
|
||||
continue
|
||||
}
|
||||
res = append(res, convert.ContainerGroupToServiceStatus(getContainerID(group, container), group, container))
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (cs *aciComposeService) Logs(ctx context.Context, project string, w io.Writer) error {
|
||||
|
|
|
@ -26,6 +26,9 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose-cli/compose"
|
||||
"github.com/docker/compose-cli/utils/formatter"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
|
@ -385,6 +388,21 @@ func bytesToGb(b types.UnitBytes) float64 {
|
|||
return math.Round(f*100) / 100
|
||||
}
|
||||
|
||||
// ContainerGroupToServiceStatus convert from an ACI container definition to service status
|
||||
func ContainerGroupToServiceStatus(containerID string, group containerinstance.ContainerGroup, container containerinstance.Container) compose.ServiceStatus {
|
||||
var replicas = 1
|
||||
if GetStatus(container, group) != "Running" {
|
||||
replicas = 0
|
||||
}
|
||||
return compose.ServiceStatus{
|
||||
ID: containerID,
|
||||
Name: *container.Name,
|
||||
Ports: formatter.PortsToStrings(ToPorts(group.IPAddress, *container.Ports)),
|
||||
Replicas: replicas,
|
||||
Desired: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// ContainerGroupToContainer composes a Container from an ACI container definition
|
||||
func ContainerGroupToContainer(containerID string, cg containerinstance.ContainerGroup, cc containerinstance.Container) containers.Container {
|
||||
memLimits := 0.
|
||||
|
|
|
@ -21,6 +21,8 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/compose-cli/compose"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/containerinstance/mgmt/containerinstance"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
|
@ -103,6 +105,44 @@ func TestContainerGroupToContainer(t *testing.T) {
|
|||
assert.DeepEqual(t, container, expectedContainer)
|
||||
}
|
||||
|
||||
func TestContainerGroupToServiceStatus(t *testing.T) {
|
||||
myContainerGroup := containerinstance.ContainerGroup{
|
||||
ContainerGroupProperties: &containerinstance.ContainerGroupProperties{
|
||||
IPAddress: &containerinstance.IPAddress{
|
||||
Ports: &[]containerinstance.Port{{
|
||||
Port: to.Int32Ptr(80),
|
||||
}},
|
||||
IP: to.StringPtr("42.42.42.42"),
|
||||
},
|
||||
},
|
||||
}
|
||||
myContainer := containerinstance.Container{
|
||||
Name: to.StringPtr("myContainerID"),
|
||||
ContainerProperties: &containerinstance.ContainerProperties{
|
||||
Ports: &[]containerinstance.ContainerPort{{
|
||||
Port: to.Int32Ptr(80),
|
||||
}},
|
||||
InstanceView: &containerinstance.ContainerPropertiesInstanceView{
|
||||
RestartCount: nil,
|
||||
CurrentState: &containerinstance.ContainerState{
|
||||
State: to.StringPtr("Running"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var expectedService = compose.ServiceStatus{
|
||||
ID: "myContainerID",
|
||||
Name: "myContainerID",
|
||||
Ports: []string{"42.42.42.42:80->80/tcp"},
|
||||
Replicas: 1,
|
||||
Desired: 1,
|
||||
}
|
||||
|
||||
container := ContainerGroupToServiceStatus("myContainerID", myContainerGroup, myContainer)
|
||||
assert.DeepEqual(t, container, expectedService)
|
||||
}
|
||||
|
||||
func TestComposeContainerGroupToContainerWithDnsSideCarSide(t *testing.T) {
|
||||
project := types.Project{
|
||||
Services: []types.ServiceConfig{
|
||||
|
|
|
@ -20,12 +20,14 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/docker/compose-cli/utils/formatter"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose-cli/cli/formatter"
|
||||
"github.com/docker/compose-cli/client"
|
||||
formatter2 "github.com/docker/compose-cli/formatter"
|
||||
)
|
||||
|
@ -97,7 +99,7 @@ func runPs(ctx context.Context, opts psOpts) error {
|
|||
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, formatter.PortsString(c.Ports))
|
||||
fmt.Fprintf(w, format, c.ID, c.Image, c.Command, c.Status, strings.Join(formatter.PortsToStrings(c.Ports), ", "))
|
||||
}
|
||||
|
||||
return w.Flush()
|
||||
|
|
|
@ -81,10 +81,10 @@ func TestLoginLogout(t *testing.T) {
|
|||
|
||||
t.Run("create context", func(t *testing.T) {
|
||||
sID := getSubscriptionID(t)
|
||||
err := createResourceGroup(sID, rg)
|
||||
err := createResourceGroup(t, sID, rg)
|
||||
assert.Check(t, is.Nil(err))
|
||||
t.Cleanup(func() {
|
||||
_ = deleteResourceGroup(rg)
|
||||
_ = deleteResourceGroup(t, rg)
|
||||
})
|
||||
|
||||
c.RunDockerCmd("context", "create", "aci", contextName, "--subscription-id", sID, "--resource-group", rg, "--location", location)
|
||||
|
@ -388,7 +388,7 @@ func TestContainerRunAttached(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestCompose(t *testing.T) {
|
||||
func TestComposeUpUpdate(t *testing.T) {
|
||||
c := NewParallelE2eCLI(t, binDir)
|
||||
_, _ = setupTestResourceGroup(t, c)
|
||||
|
||||
|
@ -398,6 +398,7 @@ func TestCompose(t *testing.T) {
|
|||
composeProjectName = "acidemo"
|
||||
serverContainer = composeProjectName + "_web"
|
||||
wordsContainer = composeProjectName + "_words"
|
||||
dbContainer = composeProjectName + "_db"
|
||||
)
|
||||
|
||||
t.Run("compose up", func(t *testing.T) {
|
||||
|
@ -431,6 +432,30 @@ func TestCompose(t *testing.T) {
|
|||
assert.Assert(t, strings.Contains(string(b), `"word":`))
|
||||
})
|
||||
|
||||
t.Run("compose ps", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("compose", "ps", "--project-name", composeProjectName)
|
||||
lines := strings.Split(strings.TrimSpace(res.Stdout()), "\n")
|
||||
assert.Assert(t, is.Len(lines, 4))
|
||||
var wordsDisplayed, webDisplayed, dbDisplayed bool
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
containerID := fields[0]
|
||||
switch containerID {
|
||||
case wordsContainer:
|
||||
wordsDisplayed = true
|
||||
assert.DeepEqual(t, fields, []string{containerID, "words", "1/1"})
|
||||
case dbContainer:
|
||||
dbDisplayed = true
|
||||
assert.DeepEqual(t, fields, []string{containerID, "db", "1/1"})
|
||||
case serverContainer:
|
||||
webDisplayed = true
|
||||
assert.Equal(t, fields[1], "web")
|
||||
assert.Check(t, strings.Contains(fields[3], ":80->80/tcp"))
|
||||
}
|
||||
}
|
||||
assert.Check(t, webDisplayed && wordsDisplayed && dbDisplayed, "\n%s\n", res.Stdout())
|
||||
})
|
||||
|
||||
t.Run("logs web", func(t *testing.T) {
|
||||
res := c.RunDockerCmd("logs", serverContainer)
|
||||
res.Assert(t, icmd.Expected{Out: "Listening on port 80"})
|
||||
|
@ -527,10 +552,10 @@ func setupTestResourceGroup(t *testing.T, c *E2eCLI) (string, string) {
|
|||
rg := "E2E-" + t.Name() + "-" + startTime
|
||||
azureLogin(t, c)
|
||||
sID := getSubscriptionID(t)
|
||||
err := createResourceGroup(sID, rg)
|
||||
err := createResourceGroup(t, sID, rg)
|
||||
assert.Check(t, is.Nil(err))
|
||||
t.Cleanup(func() {
|
||||
if err := deleteResourceGroup(rg); err != nil {
|
||||
if err := deleteResourceGroup(t, rg); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
|
@ -541,7 +566,8 @@ func setupTestResourceGroup(t *testing.T, c *E2eCLI) (string, string) {
|
|||
return sID, rg
|
||||
}
|
||||
|
||||
func deleteResourceGroup(rgName string) error {
|
||||
func deleteResourceGroup(t *testing.T, rgName string) error {
|
||||
fmt.Printf(" [%s] deleting resource group %s\n", t.Name(), rgName)
|
||||
ctx := context.TODO()
|
||||
helper := aci.NewACIResourceGroupHelper()
|
||||
models, err := helper.GetSubscriptionIDs(ctx)
|
||||
|
@ -574,7 +600,8 @@ func getSubscriptionID(t *testing.T) string {
|
|||
return *models[0].SubscriptionID
|
||||
}
|
||||
|
||||
func createResourceGroup(sID, rgName string) error {
|
||||
func createResourceGroup(t *testing.T, sID, rgName string) error {
|
||||
fmt.Printf(" [%s] creating resource group %s\n", t.Name(), rgName)
|
||||
helper := aci.NewACIResourceGroupHelper()
|
||||
_, err := helper.CreateOrUpdate(context.TODO(), sID, rgName, resources.Group{Location: to.StringPtr(location)})
|
||||
return err
|
||||
|
|
|
@ -30,8 +30,8 @@ type portGroup struct {
|
|||
last uint32
|
||||
}
|
||||
|
||||
// PortsString returns a human readable published ports
|
||||
func PortsString(ports []containers.Port) string {
|
||||
// PortsToStrings returns a human readable published ports
|
||||
func PortsToStrings(ports []containers.Port) []string {
|
||||
groupMap := make(map[string]*portGroup)
|
||||
var (
|
||||
result []string
|
||||
|
@ -82,7 +82,7 @@ func PortsString(ports []containers.Port) string {
|
|||
|
||||
result = append(result, hostMappings...)
|
||||
|
||||
return strings.Join(result, ", ")
|
||||
return result
|
||||
}
|
||||
|
||||
func formGroup(key string, start uint32, last uint32) string {
|
|
@ -28,37 +28,37 @@ func TestDisplayPorts(t *testing.T) {
|
|||
testCases := []struct {
|
||||
name string
|
||||
in []string
|
||||
expected string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
in: []string{"80"},
|
||||
expected: "0.0.0.0:80->80/tcp",
|
||||
expected: []string{"0.0.0.0:80->80/tcp"},
|
||||
},
|
||||
{
|
||||
name: "different ports",
|
||||
in: []string{"80:90"},
|
||||
expected: "0.0.0.0:80->90/tcp",
|
||||
expected: []string{"0.0.0.0:80->90/tcp"},
|
||||
},
|
||||
{
|
||||
name: "host ip",
|
||||
in: []string{"192.168.0.1:80:90"},
|
||||
expected: "192.168.0.1:80->90/tcp",
|
||||
expected: []string{"192.168.0.1:80->90/tcp"},
|
||||
},
|
||||
{
|
||||
name: "port range",
|
||||
in: []string{"80-90:80-90"},
|
||||
expected: "0.0.0.0:80-90->80-90/tcp",
|
||||
expected: []string{"0.0.0.0:80-90->80-90/tcp"},
|
||||
},
|
||||
{
|
||||
name: "grouping",
|
||||
in: []string{"80:80", "81:81"},
|
||||
expected: "0.0.0.0:80-81->80-81/tcp",
|
||||
expected: []string{"0.0.0.0:80-81->80-81/tcp"},
|
||||
},
|
||||
{
|
||||
name: "groups",
|
||||
in: []string{"80:80", "82:82"},
|
||||
expected: "0.0.0.0:80->80/tcp, 0.0.0.0:82->82/tcp",
|
||||
expected: []string{"0.0.0.0:80->80/tcp", "0.0.0.0:82->82/tcp"},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -70,8 +70,8 @@ func TestDisplayPorts(t *testing.T) {
|
|||
containerConfig, err := runOpts.ToContainerConfig("test")
|
||||
assert.NilError(t, err)
|
||||
|
||||
out := PortsString(containerConfig.Ports)
|
||||
assert.Equal(t, testCase.expected, out)
|
||||
out := PortsToStrings(containerConfig.Ports)
|
||||
assert.DeepEqual(t, testCase.expected, out)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue