mirror of
https://github.com/docker/compose.git
synced 2025-07-22 21:24:38 +02:00
Merge pull request #564 from docker/aci_compose_ps
Implement compose ps on ACI
This commit is contained in:
commit
b9153961ab
@ -164,24 +164,28 @@ func (cs *aciContainerService) List(ctx context.Context, all bool) ([]containers
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, container := range *group.Containers {
|
for _, container := range *group.Containers {
|
||||||
// don't list sidecar container
|
if isContainerVisible(container, group, all) {
|
||||||
if *container.Name == convert.ComposeDNSSidecarName {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !all && convert.GetStatus(container, group) != statusRunning {
|
c := convert.ContainerGroupToContainer(getContainerID(group, container), group, container)
|
||||||
continue
|
|
||||||
}
|
|
||||||
containerID := *containerGroup.Name + composeContainerSeparator + *container.Name
|
|
||||||
if _, ok := group.Tags[singleContainerTag]; ok {
|
|
||||||
containerID = *containerGroup.Name
|
|
||||||
}
|
|
||||||
c := convert.ContainerGroupToContainer(containerID, group, container)
|
|
||||||
res = append(res, c)
|
res = append(res, c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res, nil
|
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 {
|
func (cs *aciContainerService) Run(ctx context.Context, r containers.ContainerConfig) error {
|
||||||
if strings.Contains(r.ID, composeContainerSeparator) {
|
if strings.Contains(r.ID, composeContainerSeparator) {
|
||||||
return errors.New(fmt.Sprintf("invalid container name. ACI container name cannot include %q", 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) {
|
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 {
|
func (cs *aciComposeService) Logs(ctx context.Context, project string, w io.Writer) error {
|
||||||
|
@ -26,6 +26,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"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/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance"
|
||||||
"github.com/Azure/go-autorest/autorest/to"
|
"github.com/Azure/go-autorest/autorest/to"
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
@ -385,6 +388,21 @@ func bytesToGb(b types.UnitBytes) float64 {
|
|||||||
return math.Round(f*100) / 100
|
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
|
// 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) containers.Container {
|
||||||
memLimits := 0.
|
memLimits := 0.
|
||||||
|
@ -21,6 +21,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/compose-cli/compose"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/containerinstance/mgmt/containerinstance"
|
"github.com/Azure/azure-sdk-for-go/profiles/latest/containerinstance/mgmt/containerinstance"
|
||||||
"github.com/Azure/go-autorest/autorest/to"
|
"github.com/Azure/go-autorest/autorest/to"
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
@ -103,6 +105,44 @@ func TestContainerGroupToContainer(t *testing.T) {
|
|||||||
assert.DeepEqual(t, container, expectedContainer)
|
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) {
|
func TestComposeContainerGroupToContainerWithDnsSideCarSide(t *testing.T) {
|
||||||
project := types.Project{
|
project := types.Project{
|
||||||
Services: []types.ServiceConfig{
|
Services: []types.ServiceConfig{
|
||||||
|
@ -20,12 +20,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"github.com/docker/compose-cli/utils/formatter"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/cli/formatter"
|
|
||||||
"github.com/docker/compose-cli/client"
|
"github.com/docker/compose-cli/client"
|
||||||
formatter2 "github.com/docker/compose-cli/formatter"
|
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")
|
fmt.Fprintf(w, "CONTAINER ID\tIMAGE\tCOMMAND\tSTATUS\tPORTS\n")
|
||||||
format := "%s\t%s\t%s\t%s\t%s\n"
|
format := "%s\t%s\t%s\t%s\t%s\n"
|
||||||
for _, c := range containers {
|
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()
|
return w.Flush()
|
||||||
|
@ -81,10 +81,10 @@ func TestLoginLogout(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("create context", func(t *testing.T) {
|
t.Run("create context", func(t *testing.T) {
|
||||||
sID := getSubscriptionID(t)
|
sID := getSubscriptionID(t)
|
||||||
err := createResourceGroup(sID, rg)
|
err := createResourceGroup(t, sID, rg)
|
||||||
assert.Check(t, is.Nil(err))
|
assert.Check(t, is.Nil(err))
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
_ = deleteResourceGroup(rg)
|
_ = deleteResourceGroup(t, rg)
|
||||||
})
|
})
|
||||||
|
|
||||||
c.RunDockerCmd("context", "create", "aci", contextName, "--subscription-id", sID, "--resource-group", rg, "--location", location)
|
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)
|
c := NewParallelE2eCLI(t, binDir)
|
||||||
_, _ = setupTestResourceGroup(t, c)
|
_, _ = setupTestResourceGroup(t, c)
|
||||||
|
|
||||||
@ -398,6 +398,7 @@ func TestCompose(t *testing.T) {
|
|||||||
composeProjectName = "acidemo"
|
composeProjectName = "acidemo"
|
||||||
serverContainer = composeProjectName + "_web"
|
serverContainer = composeProjectName + "_web"
|
||||||
wordsContainer = composeProjectName + "_words"
|
wordsContainer = composeProjectName + "_words"
|
||||||
|
dbContainer = composeProjectName + "_db"
|
||||||
)
|
)
|
||||||
|
|
||||||
t.Run("compose up", func(t *testing.T) {
|
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":`))
|
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) {
|
t.Run("logs web", func(t *testing.T) {
|
||||||
res := c.RunDockerCmd("logs", serverContainer)
|
res := c.RunDockerCmd("logs", serverContainer)
|
||||||
res.Assert(t, icmd.Expected{Out: "Listening on port 80"})
|
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
|
rg := "E2E-" + t.Name() + "-" + startTime
|
||||||
azureLogin(t, c)
|
azureLogin(t, c)
|
||||||
sID := getSubscriptionID(t)
|
sID := getSubscriptionID(t)
|
||||||
err := createResourceGroup(sID, rg)
|
err := createResourceGroup(t, sID, rg)
|
||||||
assert.Check(t, is.Nil(err))
|
assert.Check(t, is.Nil(err))
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
if err := deleteResourceGroup(rg); err != nil {
|
if err := deleteResourceGroup(t, rg); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -541,7 +566,8 @@ func setupTestResourceGroup(t *testing.T, c *E2eCLI) (string, string) {
|
|||||||
return sID, rg
|
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()
|
ctx := context.TODO()
|
||||||
helper := aci.NewACIResourceGroupHelper()
|
helper := aci.NewACIResourceGroupHelper()
|
||||||
models, err := helper.GetSubscriptionIDs(ctx)
|
models, err := helper.GetSubscriptionIDs(ctx)
|
||||||
@ -574,7 +600,8 @@ func getSubscriptionID(t *testing.T) string {
|
|||||||
return *models[0].SubscriptionID
|
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()
|
helper := aci.NewACIResourceGroupHelper()
|
||||||
_, err := helper.CreateOrUpdate(context.TODO(), sID, rgName, resources.Group{Location: to.StringPtr(location)})
|
_, err := helper.CreateOrUpdate(context.TODO(), sID, rgName, resources.Group{Location: to.StringPtr(location)})
|
||||||
return err
|
return err
|
||||||
|
@ -30,8 +30,8 @@ type portGroup struct {
|
|||||||
last uint32
|
last uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// PortsString returns a human readable published ports
|
// PortsToStrings returns a human readable published ports
|
||||||
func PortsString(ports []containers.Port) string {
|
func PortsToStrings(ports []containers.Port) []string {
|
||||||
groupMap := make(map[string]*portGroup)
|
groupMap := make(map[string]*portGroup)
|
||||||
var (
|
var (
|
||||||
result []string
|
result []string
|
||||||
@ -82,7 +82,7 @@ func PortsString(ports []containers.Port) string {
|
|||||||
|
|
||||||
result = append(result, hostMappings...)
|
result = append(result, hostMappings...)
|
||||||
|
|
||||||
return strings.Join(result, ", ")
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func formGroup(key string, start uint32, last uint32) string {
|
func formGroup(key string, start uint32, last uint32) string {
|
@ -28,37 +28,37 @@ func TestDisplayPorts(t *testing.T) {
|
|||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
in []string
|
in []string
|
||||||
expected string
|
expected []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "simple",
|
name: "simple",
|
||||||
in: []string{"80"},
|
in: []string{"80"},
|
||||||
expected: "0.0.0.0:80->80/tcp",
|
expected: []string{"0.0.0.0:80->80/tcp"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "different ports",
|
name: "different ports",
|
||||||
in: []string{"80:90"},
|
in: []string{"80:90"},
|
||||||
expected: "0.0.0.0:80->90/tcp",
|
expected: []string{"0.0.0.0:80->90/tcp"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "host ip",
|
name: "host ip",
|
||||||
in: []string{"192.168.0.1:80:90"},
|
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",
|
name: "port range",
|
||||||
in: []string{"80-90:80-90"},
|
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",
|
name: "grouping",
|
||||||
in: []string{"80:80", "81:81"},
|
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",
|
name: "groups",
|
||||||
in: []string{"80:80", "82:82"},
|
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")
|
containerConfig, err := runOpts.ToContainerConfig("test")
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
out := PortsString(containerConfig.Ports)
|
out := PortsToStrings(containerConfig.Ports)
|
||||||
assert.Equal(t, testCase.expected, out)
|
assert.DeepEqual(t, testCase.expected, out)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user