mirror of
https://github.com/docker/compose.git
synced 2025-07-24 22:24:41 +02:00
commit
daf7061e30
61
azure/aci.go
61
azure/aci.go
@ -20,9 +20,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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"
|
||||||
@ -103,10 +101,6 @@ func createOrUpdateACIContainers(ctx context.Context, aciContext store.AciContex
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
containerGroup, err := future.Result(containerGroupsClient)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, c := range *groupDefinition.Containers {
|
for _, c := range *groupDefinition.Containers {
|
||||||
w.Event(progress.Event{
|
w.Event(progress.Event{
|
||||||
ID: *c.Name,
|
ID: *c.Name,
|
||||||
@ -115,30 +109,6 @@ func createOrUpdateACIContainers(ctx context.Context, aciContext store.AciContex
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(*containerGroup.Containers) > 1 {
|
|
||||||
var commands []string
|
|
||||||
for _, container := range *containerGroup.Containers {
|
|
||||||
commands = append(commands, fmt.Sprintf("echo 127.0.0.1 %s >> /etc/hosts", *container.Name))
|
|
||||||
}
|
|
||||||
commands = append(commands, "exit")
|
|
||||||
|
|
||||||
containers := *containerGroup.Containers
|
|
||||||
container := containers[0]
|
|
||||||
response, err := execACIContainer(ctx, aciContext, "/bin/sh", *containerGroup.Name, *container.Name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = execCommands(
|
|
||||||
ctx,
|
|
||||||
*response.WebSocketURI,
|
|
||||||
*response.Password,
|
|
||||||
commands,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,37 +158,6 @@ func getTermSize() (*int32, *int32) {
|
|||||||
return to.Int32Ptr(int32(rows)), to.Int32Ptr(int32(cols))
|
return to.Int32Ptr(int32(rows)), to.Int32Ptr(int32(cols))
|
||||||
}
|
}
|
||||||
|
|
||||||
type commandSender struct {
|
|
||||||
commands string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *commandSender) Read(p []byte) (int, error) {
|
|
||||||
if len(cs.commands) == 0 {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
var command string
|
|
||||||
if len(p) >= len(cs.commands) {
|
|
||||||
command = cs.commands
|
|
||||||
cs.commands = ""
|
|
||||||
} else {
|
|
||||||
command = cs.commands[:len(p)]
|
|
||||||
cs.commands = cs.commands[len(p):]
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(p, command)
|
|
||||||
|
|
||||||
return len(command), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func execCommands(ctx context.Context, address string, password string, commands []string) error {
|
|
||||||
writer := ioutil.Discard
|
|
||||||
reader := &commandSender{
|
|
||||||
commands: strings.Join(commands, "\n"),
|
|
||||||
}
|
|
||||||
return exec(ctx, address, password, reader, writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func exec(ctx context.Context, address string, password string, reader io.Reader, writer io.Writer) error {
|
func exec(ctx context.Context, address string, password string, reader io.Reader, writer io.Writer) error {
|
||||||
conn, _, _, err := ws.DefaultDialer.Dial(ctx, address)
|
conn, _, _, err := ws.DefaultDialer.Dial(ctx, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -129,6 +129,10 @@ func (cs *aciContainerService) List(ctx context.Context, _ bool) ([]containers.C
|
|||||||
|
|
||||||
for _, container := range *group.Containers {
|
for _, container := range *group.Containers {
|
||||||
var containerID string
|
var containerID string
|
||||||
|
// don't list sidecar container
|
||||||
|
if *container.Name == convert.ComposeDNSSidecarName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if *container.Name == singleContainerName {
|
if *container.Name == singleContainerName {
|
||||||
containerID = *containerGroup.Name
|
containerID = *containerGroup.Name
|
||||||
} else {
|
} else {
|
||||||
|
@ -19,14 +19,9 @@ package azure
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/containerinstance/mgmt/containerinstance"
|
|
||||||
"github.com/Azure/go-autorest/autorest/to"
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
"github.com/docker/api/azure/convert"
|
|
||||||
"github.com/docker/api/containers"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type BackendSuiteTest struct {
|
type BackendSuiteTest struct {
|
||||||
@ -51,56 +46,3 @@ func TestBackendSuite(t *testing.T) {
|
|||||||
RegisterTestingT(t)
|
RegisterTestingT(t)
|
||||||
suite.Run(t, new(BackendSuiteTest))
|
suite.Run(t, new(BackendSuiteTest))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContainerGroupToContainer(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{
|
|
||||||
Image: to.StringPtr("sha256:666"),
|
|
||||||
Command: to.StringSlicePtr([]string{"mycommand"}),
|
|
||||||
Ports: &[]containerinstance.ContainerPort{{
|
|
||||||
Port: to.Int32Ptr(80),
|
|
||||||
}},
|
|
||||||
EnvironmentVariables: nil,
|
|
||||||
InstanceView: &containerinstance.ContainerPropertiesInstanceView{
|
|
||||||
RestartCount: nil,
|
|
||||||
CurrentState: &containerinstance.ContainerState{
|
|
||||||
State: to.StringPtr("Running"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Resources: &containerinstance.ResourceRequirements{
|
|
||||||
Limits: &containerinstance.ResourceLimits{
|
|
||||||
MemoryInGB: to.Float64Ptr(9),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var expectedContainer = containers.Container{
|
|
||||||
ID: "myContainerID",
|
|
||||||
Status: "Running",
|
|
||||||
Image: "sha256:666",
|
|
||||||
Command: "mycommand",
|
|
||||||
MemoryLimit: 9,
|
|
||||||
Ports: []containers.Port{{
|
|
||||||
HostPort: uint32(80),
|
|
||||||
ContainerPort: uint32(80),
|
|
||||||
Protocol: "tcp",
|
|
||||||
HostIP: "42.42.42.42",
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
|
|
||||||
container, err := convert.ContainerGroupToContainer("myContainerID", myContainerGroup, myContainer)
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
Expect(container).To(Equal(expectedContainer))
|
|
||||||
}
|
|
||||||
|
@ -33,6 +33,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// ComposeDNSSidecarName name of the dns sidecar container
|
||||||
|
ComposeDNSSidecarName = "aci--dns--sidecar"
|
||||||
|
dnsSidecarImage = "busybox:1.31.1"
|
||||||
|
|
||||||
azureFileDriverName = "azure_file"
|
azureFileDriverName = "azure_file"
|
||||||
volumeDriveroptsShareNameKey = "share_name"
|
volumeDriveroptsShareNameKey = "share_name"
|
||||||
volumeDriveroptsAccountNameKey = "storage_account_name"
|
volumeDriveroptsAccountNameKey = "storage_account_name"
|
||||||
@ -110,10 +114,44 @@ func ToContainerGroup(aciContext store.AciContext, p compose.Project) (container
|
|||||||
|
|
||||||
containers = append(containers, containerDefinition)
|
containers = append(containers, containerDefinition)
|
||||||
}
|
}
|
||||||
|
if len(containers) > 1 {
|
||||||
|
dnsSideCar := getDNSSidecar(containers)
|
||||||
|
containers = append(containers, dnsSideCar)
|
||||||
|
}
|
||||||
groupDefinition.ContainerGroupProperties.Containers = &containers
|
groupDefinition.ContainerGroupProperties.Containers = &containers
|
||||||
|
|
||||||
return groupDefinition, nil
|
return groupDefinition, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDNSSidecar(containers []containerinstance.Container) containerinstance.Container {
|
||||||
|
var commands []string
|
||||||
|
for _, container := range containers {
|
||||||
|
commands = append(commands, fmt.Sprintf("echo 127.0.0.1 %s >> /etc/hosts", *container.Name))
|
||||||
|
}
|
||||||
|
// ACI restart policy is currently at container group level, cannot let the sidecar terminate quietly once /etc/hosts has been edited
|
||||||
|
// Pricing is done at the container group level so letting the sidecar container "sleep" should not impact the price for the whole group
|
||||||
|
commands = append(commands, "sleep infinity")
|
||||||
|
alpineCmd := []string{"sh", "-c", strings.Join(commands, ";")}
|
||||||
|
dnsSideCar := containerinstance.Container{
|
||||||
|
Name: to.StringPtr(ComposeDNSSidecarName),
|
||||||
|
ContainerProperties: &containerinstance.ContainerProperties{
|
||||||
|
Image: to.StringPtr(dnsSidecarImage),
|
||||||
|
Command: &alpineCmd,
|
||||||
|
Resources: &containerinstance.ResourceRequirements{
|
||||||
|
Limits: &containerinstance.ResourceLimits{
|
||||||
|
MemoryInGB: to.Float64Ptr(0.1), // "The memory requirement should be in incrememts of 0.1 GB."
|
||||||
|
CPU: to.Float64Ptr(0.01), // "The CPU requirement should be in incrememts of 0.01."
|
||||||
|
},
|
||||||
|
Requests: &containerinstance.ResourceRequests{
|
||||||
|
MemoryInGB: to.Float64Ptr(0.1),
|
||||||
|
CPU: to.Float64Ptr(0.01),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return dnsSideCar
|
||||||
|
}
|
||||||
|
|
||||||
type projectAciHelper compose.Project
|
type projectAciHelper compose.Project
|
||||||
|
|
||||||
func (p projectAciHelper) getAciSecretVolumes() ([]containerinstance.Volume, error) {
|
func (p projectAciHelper) getAciSecretVolumes() ([]containerinstance.Volume, error) {
|
||||||
|
@ -19,44 +19,148 @@ package convert
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
|
||||||
"github.com/docker/api/compose"
|
"github.com/docker/api/compose"
|
||||||
|
"github.com/docker/api/containers"
|
||||||
"github.com/docker/api/context/store"
|
"github.com/docker/api/context/store"
|
||||||
|
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
projectName = "TEST"
|
|
||||||
expectedProjectName = "test"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConvertTestSuite struct {
|
type ConvertTestSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
ctx store.AciContext
|
ctx store.AciContext
|
||||||
project compose.Project
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ConvertTestSuite) BeforeTest(suiteName, testName string) {
|
func (suite *ConvertTestSuite) BeforeTest(suiteName, testName string) {
|
||||||
ctx := store.AciContext{
|
suite.ctx = store.AciContext{
|
||||||
SubscriptionID: "subID",
|
SubscriptionID: "subID",
|
||||||
ResourceGroup: "rg",
|
ResourceGroup: "rg",
|
||||||
Location: "eu",
|
Location: "eu",
|
||||||
}
|
}
|
||||||
project := compose.Project{
|
|
||||||
Name: projectName,
|
|
||||||
}
|
|
||||||
|
|
||||||
suite.ctx = ctx
|
|
||||||
suite.project = project
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ConvertTestSuite) TestProjectName() {
|
func (suite *ConvertTestSuite) TestProjectName() {
|
||||||
containerGroup, err := ToContainerGroup(suite.ctx, suite.project)
|
project := compose.Project{
|
||||||
|
Name: "TEST",
|
||||||
|
}
|
||||||
|
containerGroup, err := ToContainerGroup(suite.ctx, project)
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err)
|
||||||
require.Equal(suite.T(), *containerGroup.Name, expectedProjectName)
|
require.Equal(suite.T(), *containerGroup.Name, "test")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConvertTestSuite) TestContainerGroupToContainer() {
|
||||||
|
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{
|
||||||
|
Image: to.StringPtr("sha256:666"),
|
||||||
|
Command: to.StringSlicePtr([]string{"mycommand"}),
|
||||||
|
Ports: &[]containerinstance.ContainerPort{{
|
||||||
|
Port: to.Int32Ptr(80),
|
||||||
|
}},
|
||||||
|
EnvironmentVariables: nil,
|
||||||
|
InstanceView: &containerinstance.ContainerPropertiesInstanceView{
|
||||||
|
RestartCount: nil,
|
||||||
|
CurrentState: &containerinstance.ContainerState{
|
||||||
|
State: to.StringPtr("Running"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resources: &containerinstance.ResourceRequirements{
|
||||||
|
Limits: &containerinstance.ResourceLimits{
|
||||||
|
MemoryInGB: to.Float64Ptr(9),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var expectedContainer = containers.Container{
|
||||||
|
ID: "myContainerID",
|
||||||
|
Status: "Running",
|
||||||
|
Image: "sha256:666",
|
||||||
|
Command: "mycommand",
|
||||||
|
MemoryLimit: 9,
|
||||||
|
Ports: []containers.Port{{
|
||||||
|
HostPort: uint32(80),
|
||||||
|
ContainerPort: uint32(80),
|
||||||
|
Protocol: "tcp",
|
||||||
|
HostIP: "42.42.42.42",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
container, err := ContainerGroupToContainer("myContainerID", myContainerGroup, myContainer)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(container).To(Equal(expectedContainer))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerWithDnsSideCarSide() {
|
||||||
|
project := compose.Project{
|
||||||
|
Name: "",
|
||||||
|
Config: types.Config{
|
||||||
|
Services: []types.ServiceConfig{
|
||||||
|
{
|
||||||
|
Name: "service1",
|
||||||
|
Image: "image1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "service2",
|
||||||
|
Image: "image2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
group, err := ToContainerGroup(suite.ctx, project)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(len(*group.Containers)).To(Equal(3))
|
||||||
|
|
||||||
|
Expect(*(*group.Containers)[0].Name).To(Equal("service1"))
|
||||||
|
Expect(*(*group.Containers)[1].Name).To(Equal("service2"))
|
||||||
|
Expect(*(*group.Containers)[2].Name).To(Equal(ComposeDNSSidecarName))
|
||||||
|
|
||||||
|
Expect(*(*group.Containers)[2].Command).To(Equal([]string{"sh", "-c", "echo 127.0.0.1 service1 >> /etc/hosts;echo 127.0.0.1 service2 >> /etc/hosts;sleep infinity"}))
|
||||||
|
|
||||||
|
Expect(*(*group.Containers)[0].Image).To(Equal("image1"))
|
||||||
|
Expect(*(*group.Containers)[1].Image).To(Equal("image2"))
|
||||||
|
Expect(*(*group.Containers)[2].Image).To(Equal(dnsSidecarImage))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ConvertTestSuite) TestComposeSingleContainerGroupToContainerNoDnsSideCarSide() {
|
||||||
|
project := compose.Project{
|
||||||
|
Name: "",
|
||||||
|
Config: types.Config{
|
||||||
|
Services: []types.ServiceConfig{
|
||||||
|
{
|
||||||
|
Name: "service1",
|
||||||
|
Image: "image1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
group, err := ToContainerGroup(suite.ctx, project)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
Expect(len(*group.Containers)).To(Equal(1))
|
||||||
|
Expect(*(*group.Containers)[0].Name).To(Equal("service1"))
|
||||||
|
Expect(*(*group.Containers)[0].Image).To(Equal("image1"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConvertTestSuite(t *testing.T) {
|
func TestConvertTestSuite(t *testing.T) {
|
||||||
|
RegisterTestingT(t)
|
||||||
suite.Run(t, new(ConvertTestSuite))
|
suite.Run(t, new(ConvertTestSuite))
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,7 @@ func (s *E2eACISuite) TestACIBackend() {
|
|||||||
testStorageAccountName, firstKey, testShareName, mountTarget),
|
testStorageAccountName, firstKey, testShareName, mountTarget),
|
||||||
"-p", "80:80",
|
"-p", "80:80",
|
||||||
"--name", testContainerName).ExecOrDie()
|
"--name", testContainerName).ExecOrDie()
|
||||||
Expect(output).To(Equal(testContainerName + "\n"))
|
Expect(output).To(ContainSubstring(testContainerName))
|
||||||
output = s.NewDockerCommand("ps").ExecOrDie()
|
output = s.NewDockerCommand("ps").ExecOrDie()
|
||||||
lines := Lines(output)
|
lines := Lines(output)
|
||||||
Expect(len(lines)).To(Equal(2))
|
Expect(len(lines)).To(Equal(2))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user