Break out aci backend.go into several files for each service

Signed-off-by: Guillaume Tardif <guillaume.tardif@docker.com>
This commit is contained in:
Guillaume Tardif 2020-09-08 10:11:02 +02:00
parent dcbc1c3fb2
commit 15addf5c22
8 changed files with 544 additions and 441 deletions

View File

@ -129,6 +129,34 @@ func getACIContainerGroup(ctx context.Context, aciContext store.AciContext, cont
return containerGroupsClient.Get(ctx, aciContext.ResourceGroup, containerGroupName)
}
func getACIContainerGroups(ctx context.Context, subscriptionID string, resourceGroup string) ([]containerinstance.ContainerGroup, error) {
groupsClient, err := login.NewContainerGroupsClient(subscriptionID)
if err != nil {
return nil, err
}
var containerGroups []containerinstance.ContainerGroup
result, err := groupsClient.ListByResourceGroup(ctx, resourceGroup)
if err != nil {
return nil, err
}
for result.NotDone() {
containerGroups = append(containerGroups, result.Values()...)
if err := result.NextWithContext(ctx); err != nil {
return nil, err
}
}
var groups []containerinstance.ContainerGroup
for _, group := range containerGroups {
group, err := groupsClient.Get(ctx, resourceGroup, *group.Name)
if err != nil {
return nil, err
}
groups = append(groups, group)
}
return groups, nil
}
func deleteACIContainerGroup(ctx context.Context, aciContext store.AciContext, containerGroupName string) (containerinstance.ContainerGroup, error) {
containerGroupsClient, err := login.NewContainerGroupsClient(aciContext.SubscriptionID)
if err != nil {

View File

@ -18,18 +18,11 @@ package aci
import (
"context"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/to"
"github.com/compose-spec/compose-go/types"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/docker/compose-cli/aci/convert"
"github.com/docker/compose-cli/aci/login"
@ -41,7 +34,6 @@ import (
apicontext "github.com/docker/compose-cli/context"
"github.com/docker/compose-cli/context/cloud"
"github.com/docker/compose-cli/context/store"
"github.com/docker/compose-cli/errdefs"
)
const (
@ -111,7 +103,7 @@ func getAciAPIService(aciCtx store.AciContext) *aciAPIService {
ctx: aciCtx,
},
aciVolumeService: &aciVolumeService{
ctx: aciCtx,
aciContext: aciCtx,
},
}
}
@ -138,60 +130,6 @@ func (a *aciAPIService) VolumeService() volumes.Service {
return a.aciVolumeService
}
type aciContainerService struct {
ctx store.AciContext
}
func (cs *aciContainerService) List(ctx context.Context, all bool) ([]containers.Container, error) {
containerGroups, err := getContainerGroups(ctx, cs.ctx.SubscriptionID, cs.ctx.ResourceGroup)
if err != nil {
return []containers.Container{}, err
}
var res []containers.Container
for _, group := range containerGroups {
if group.Containers == nil || len(*group.Containers) < 1 {
return []containers.Container{}, fmt.Errorf("no containers found in ACI container group %s", *group.Name)
}
for _, container := range *group.Containers {
if isContainerVisible(container, group, all) {
continue
}
c := convert.ContainerGroupToContainer(getContainerID(group, container), group, container)
res = append(res, c)
}
}
return res, nil
}
func getContainerGroups(ctx context.Context, subscriptionID string, resourceGroup string) ([]containerinstance.ContainerGroup, error) {
groupsClient, err := login.NewContainerGroupsClient(subscriptionID)
if err != nil {
return nil, err
}
var containerGroups []containerinstance.ContainerGroup
result, err := groupsClient.ListByResourceGroup(ctx, resourceGroup)
if err != nil {
return nil, err
}
for result.NotDone() {
containerGroups = append(containerGroups, result.Values()...)
if err := result.NextWithContext(ctx); err != nil {
return nil, err
}
}
var groups []containerinstance.ContainerGroup
for _, group := range containerGroups {
group, err := groupsClient.Get(ctx, resourceGroup, *group.Name)
if err != nil {
return nil, err
}
groups = append(groups, group)
}
return groups, nil
}
func getContainerID(group containerinstance.ContainerGroup, container containerinstance.Container) string {
containerID := *group.Name + composeContainerSeparator + *container.Name
if _, ok := group.Tags[singleContainerTag]; ok {
@ -204,26 +142,6 @@ func isContainerVisible(container containerinstance.Container, group containerin
return *container.Name == convert.ComposeDNSSidecarName || (!showAll && convert.GetStatus(container, group) != convert.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))
}
project, err := convert.ContainerToComposeProject(r)
if err != nil {
return err
}
logrus.Debugf("Running container %q with name %q\n", r.Image, r.ID)
groupDefinition, err := convert.ToContainerGroup(ctx, cs.ctx, project)
if err != nil {
return err
}
addTag(&groupDefinition, singleContainerTag)
return createACIContainers(ctx, cs.ctx, groupDefinition)
}
func addTag(groupDefinition *containerinstance.ContainerGroup, tagName string) {
if groupDefinition.Tags == nil {
groupDefinition.Tags = make(map[string]*string, 1)
@ -231,52 +149,6 @@ func addTag(groupDefinition *containerinstance.ContainerGroup, tagName string) {
groupDefinition.Tags[tagName] = to.StringPtr(tagName)
}
func (cs *aciContainerService) Start(ctx context.Context, containerID string) error {
groupName, containerName := getGroupAndContainerName(containerID)
if groupName != containerID {
msg := "cannot start specified service %q from compose application %q, you can update and restart the entire compose app with docker compose up --project-name %s"
return errors.New(fmt.Sprintf(msg, containerName, groupName, groupName))
}
containerGroupsClient, err := login.NewContainerGroupsClient(cs.ctx.SubscriptionID)
if err != nil {
return err
}
future, err := containerGroupsClient.Start(ctx, cs.ctx.ResourceGroup, containerName)
if err != nil {
var aerr autorest.DetailedError
if ok := errors.As(err, &aerr); ok {
if aerr.StatusCode == http.StatusNotFound {
return errdefs.ErrNotFound
}
}
return err
}
return future.WaitForCompletionRef(ctx, containerGroupsClient.Client)
}
func (cs *aciContainerService) Stop(ctx context.Context, containerID string, timeout *uint32) error {
if timeout != nil && *timeout != uint32(0) {
return errors.Errorf("ACI integration does not support setting a timeout to stop a container before killing it.")
}
groupName, containerName := getGroupAndContainerName(containerID)
if groupName != containerID {
msg := "cannot stop service %q from compose application %q, you can stop the entire compose app with docker stop %s"
return errors.New(fmt.Sprintf(msg, containerName, groupName, groupName))
}
return stopACIContainerGroup(ctx, cs.ctx, groupName)
}
func (cs *aciContainerService) Kill(ctx context.Context, containerID string, _ string) error {
groupName, containerName := getGroupAndContainerName(containerID)
if groupName != containerID {
msg := "cannot kill service %q from compose application %q, you can kill the entire compose app with docker kill %s"
return errors.New(fmt.Sprintf(msg, containerName, groupName, groupName))
}
return stopACIContainerGroup(ctx, cs.ctx, groupName) // As ACI doesn't have a kill command, we are using the stop implementation instead
}
func getGroupAndContainerName(containerID string) (string, string) {
tokens := strings.Split(containerID, composeContainerSeparator)
@ -289,267 +161,3 @@ func getGroupAndContainerName(containerID string) (string, string) {
return groupName, containerName
}
func (cs *aciContainerService) Exec(ctx context.Context, name string, request containers.ExecRequest) error {
err := verifyExecCommand(request.Command)
if err != nil {
return err
}
groupName, containerAciName := getGroupAndContainerName(name)
containerExecResponse, err := execACIContainer(ctx, cs.ctx, request.Command, groupName, containerAciName)
if err != nil {
return err
}
return exec(
context.Background(),
*containerExecResponse.WebSocketURI,
*containerExecResponse.Password,
request,
)
}
func verifyExecCommand(command string) error {
tokens := strings.Split(command, " ")
if len(tokens) > 1 {
return errors.New("ACI exec command does not accept arguments to the command. " +
"Only the binary should be specified")
}
return nil
}
func (cs *aciContainerService) Logs(ctx context.Context, containerName string, req containers.LogsRequest) error {
groupName, containerAciName := getGroupAndContainerName(containerName)
var tail *int32
if req.Follow {
return streamLogs(ctx, cs.ctx, groupName, containerAciName, req)
}
if req.Tail != "all" {
reqTail, err := strconv.Atoi(req.Tail)
if err != nil {
return err
}
i32 := int32(reqTail)
tail = &i32
}
logs, err := getACIContainerLogs(ctx, cs.ctx, groupName, containerAciName, tail)
if err != nil {
return err
}
_, err = fmt.Fprint(req.Writer, logs)
return err
}
func (cs *aciContainerService) Delete(ctx context.Context, containerID string, request containers.DeleteRequest) error {
groupName, containerName := getGroupAndContainerName(containerID)
if groupName != containerID {
msg := "cannot delete service %q from compose application %q, you can delete the entire compose app with docker compose down --project-name %s"
return errors.New(fmt.Sprintf(msg, containerName, groupName, groupName))
}
if !request.Force {
containerGroupsClient, err := login.NewContainerGroupsClient(cs.ctx.SubscriptionID)
if err != nil {
return err
}
cg, err := containerGroupsClient.Get(ctx, cs.ctx.ResourceGroup, groupName)
if err != nil {
if cg.StatusCode == http.StatusNotFound {
return errdefs.ErrNotFound
}
return err
}
for _, container := range *cg.Containers {
status := convert.GetStatus(container, cg)
if status == convert.StatusRunning {
return errdefs.ErrForbidden
}
}
}
cg, err := deleteACIContainerGroup(ctx, cs.ctx, groupName)
// Delete returns `StatusNoContent` if the group is not found
if cg.StatusCode == http.StatusNoContent {
return errdefs.ErrNotFound
}
if err != nil {
return err
}
return err
}
func (cs *aciContainerService) Inspect(ctx context.Context, containerID string) (containers.Container, error) {
groupName, containerName := getGroupAndContainerName(containerID)
cg, err := getACIContainerGroup(ctx, cs.ctx, groupName)
if err != nil {
return containers.Container{}, err
}
if cg.StatusCode == http.StatusNoContent {
return containers.Container{}, errdefs.ErrNotFound
}
var cc containerinstance.Container
var found = false
for _, c := range *cg.Containers {
if to.String(c.Name) == containerName {
cc = c
found = true
break
}
}
if !found {
return containers.Container{}, errdefs.ErrNotFound
}
return convert.ContainerGroupToContainer(containerID, cg, cc), nil
}
type aciComposeService struct {
ctx store.AciContext
}
func (cs *aciComposeService) Up(ctx context.Context, project *types.Project) error {
logrus.Debugf("Up on project with name %q\n", project.Name)
groupDefinition, err := convert.ToContainerGroup(ctx, cs.ctx, *project)
addTag(&groupDefinition, composeContainerTag)
if err != nil {
return err
}
return createOrUpdateACIContainers(ctx, cs.ctx, groupDefinition)
}
func (cs *aciComposeService) Down(ctx context.Context, project string) error {
logrus.Debugf("Down on project with name %q\n", project)
cg, err := deleteACIContainerGroup(ctx, cs.ctx, project)
if err != nil {
return err
}
if cg.StatusCode == http.StatusNoContent {
return errdefs.ErrNotFound
}
return err
}
func (cs *aciComposeService) Ps(ctx context.Context, project string) ([]compose.ServiceStatus, error) {
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) List(ctx context.Context, project string) ([]compose.Stack, error) {
containerGroups, err := getContainerGroups(ctx, cs.ctx.SubscriptionID, cs.ctx.ResourceGroup)
if err != nil {
return []compose.Stack{}, err
}
stacks := []compose.Stack{}
for _, group := range containerGroups {
if _, found := group.Tags[composeContainerTag]; !found {
continue
}
if project != "" && *group.Name != project {
continue
}
state := compose.RUNNING
for _, container := range *group.ContainerGroupProperties.Containers {
containerState := convert.GetStatus(container, group)
if containerState != compose.RUNNING {
state = containerState
break
}
}
stacks = append(stacks, compose.Stack{
ID: *group.ID,
Name: *group.Name,
Status: state,
})
}
return stacks, nil
}
func (cs *aciComposeService) Logs(ctx context.Context, project string, w io.Writer) error {
return errdefs.ErrNotImplemented
}
func (cs *aciComposeService) Convert(ctx context.Context, project *types.Project) ([]byte, error) {
return nil, errdefs.ErrNotImplemented
}
type aciVolumeService struct {
ctx store.AciContext
}
func (cs *aciVolumeService) List(ctx context.Context) ([]volumes.Volume, error) {
storageHelper := login.StorageAccountHelper{AciContext: cs.ctx}
return storageHelper.ListFileShare(ctx)
}
//VolumeCreateOptions options to create a new ACI volume
type VolumeCreateOptions struct {
Account string
Fileshare string
}
func (cs *aciVolumeService) Create(ctx context.Context, options interface{}) (volumes.Volume, error) {
opts, ok := options.(VolumeCreateOptions)
if !ok {
return volumes.Volume{}, errors.New("Could not read azure LoginParams struct from generic parameter")
}
storageHelper := login.StorageAccountHelper{AciContext: cs.ctx}
return storageHelper.CreateFileShare(ctx, opts.Account, opts.Fileshare)
}
type aciCloudService struct {
loginService login.AzureLoginServiceAPI
}
func (cs *aciCloudService) Login(ctx context.Context, params interface{}) error {
opts, ok := params.(LoginParams)
if !ok {
return errors.New("Could not read azure LoginParams struct from generic parameter")
}
if opts.ClientID != "" {
return cs.loginService.LoginServicePrincipal(opts.ClientID, opts.ClientSecret, opts.TenantID)
}
return cs.loginService.Login(ctx, opts.TenantID)
}
func (cs *aciCloudService) Logout(ctx context.Context) error {
return cs.loginService.Logout(ctx)
}
func (cs *aciCloudService) CreateContextData(ctx context.Context, params interface{}) (interface{}, string, error) {
contextHelper := newContextCreateHelper()
createOpts := params.(ContextParams)
return contextHelper.createContextData(ctx, createOpts)
}

48
aci/cloud.go Normal file
View File

@ -0,0 +1,48 @@
/*
Copyright 2020 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package aci
import (
"context"
"github.com/pkg/errors"
"github.com/docker/compose-cli/aci/login"
)
type aciCloudService struct {
loginService login.AzureLoginServiceAPI
}
func (cs *aciCloudService) Login(ctx context.Context, params interface{}) error {
opts, ok := params.(LoginParams)
if !ok {
return errors.New("Could not read azure LoginParams struct from generic parameter")
}
if opts.ClientID != "" {
return cs.loginService.LoginServicePrincipal(opts.ClientID, opts.ClientSecret, opts.TenantID)
}
return cs.loginService.Login(ctx, opts.TenantID)
}
func (cs *aciCloudService) Logout(ctx context.Context) error {
return cs.loginService.Logout(ctx)
}
func (cs *aciCloudService) CreateContextData(ctx context.Context, params interface{}) (interface{}, string, error) {
contextHelper := newContextCreateHelper()
createOpts := params.(ContextParams)
return contextHelper.createContextData(ctx, createOpts)
}

128
aci/compose.go Normal file
View File

@ -0,0 +1,128 @@
/*
Copyright 2020 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package aci
import (
"context"
"fmt"
"io"
"net/http"
"github.com/compose-spec/compose-go/types"
"github.com/sirupsen/logrus"
"github.com/docker/compose-cli/aci/convert"
"github.com/docker/compose-cli/aci/login"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/context/store"
"github.com/docker/compose-cli/errdefs"
)
type aciComposeService struct {
ctx store.AciContext
}
func (cs *aciComposeService) Up(ctx context.Context, project *types.Project) error {
logrus.Debugf("Up on project with name %q\n", project.Name)
groupDefinition, err := convert.ToContainerGroup(ctx, cs.ctx, *project)
addTag(&groupDefinition, composeContainerTag)
if err != nil {
return err
}
return createOrUpdateACIContainers(ctx, cs.ctx, groupDefinition)
}
func (cs *aciComposeService) Down(ctx context.Context, project string) error {
logrus.Debugf("Down on project with name %q\n", project)
cg, err := deleteACIContainerGroup(ctx, cs.ctx, project)
if err != nil {
return err
}
if cg.StatusCode == http.StatusNoContent {
return errdefs.ErrNotFound
}
return err
}
func (cs *aciComposeService) Ps(ctx context.Context, project string) ([]compose.ServiceStatus, error) {
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) List(ctx context.Context, project string) ([]compose.Stack, error) {
containerGroups, err := getACIContainerGroups(ctx, cs.ctx.SubscriptionID, cs.ctx.ResourceGroup)
if err != nil {
return []compose.Stack{}, err
}
stacks := []compose.Stack{}
for _, group := range containerGroups {
if _, found := group.Tags[composeContainerTag]; !found {
continue
}
if project != "" && *group.Name != project {
continue
}
state := compose.RUNNING
for _, container := range *group.ContainerGroupProperties.Containers {
containerState := convert.GetStatus(container, group)
if containerState != compose.RUNNING {
state = containerState
break
}
}
stacks = append(stacks, compose.Stack{
ID: *group.ID,
Name: *group.Name,
Status: state,
})
}
return stacks, nil
}
func (cs *aciComposeService) Logs(ctx context.Context, project string, w io.Writer) error {
return errdefs.ErrNotImplemented
}
func (cs *aciComposeService) Convert(ctx context.Context, project *types.Project) ([]byte, error) {
return nil, errdefs.ErrNotImplemented
}

254
aci/containers.go Normal file
View File

@ -0,0 +1,254 @@
/*
Copyright 2020 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package aci
import (
"context"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/to"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/docker/compose-cli/aci/convert"
"github.com/docker/compose-cli/aci/login"
"github.com/docker/compose-cli/api/containers"
"github.com/docker/compose-cli/context/store"
"github.com/docker/compose-cli/errdefs"
)
type aciContainerService struct {
ctx store.AciContext
}
func (cs *aciContainerService) List(ctx context.Context, all bool) ([]containers.Container, error) {
containerGroups, err := getACIContainerGroups(ctx, cs.ctx.SubscriptionID, cs.ctx.ResourceGroup)
if err != nil {
return []containers.Container{}, err
}
var res []containers.Container
for _, group := range containerGroups {
if group.Containers == nil || len(*group.Containers) < 1 {
return []containers.Container{}, fmt.Errorf("no containers found in ACI container group %s", *group.Name)
}
for _, container := range *group.Containers {
if isContainerVisible(container, group, all) {
continue
}
c := convert.ContainerGroupToContainer(getContainerID(group, container), group, container)
res = append(res, c)
}
}
return res, nil
}
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))
}
project, err := convert.ContainerToComposeProject(r)
if err != nil {
return err
}
logrus.Debugf("Running container %q with name %q\n", r.Image, r.ID)
groupDefinition, err := convert.ToContainerGroup(ctx, cs.ctx, project)
if err != nil {
return err
}
addTag(&groupDefinition, singleContainerTag)
return createACIContainers(ctx, cs.ctx, groupDefinition)
}
func (cs *aciContainerService) Start(ctx context.Context, containerID string) error {
groupName, containerName := getGroupAndContainerName(containerID)
if groupName != containerID {
msg := "cannot start specified service %q from compose application %q, you can update and restart the entire compose app with docker compose up --project-name %s"
return errors.New(fmt.Sprintf(msg, containerName, groupName, groupName))
}
containerGroupsClient, err := login.NewContainerGroupsClient(cs.ctx.SubscriptionID)
if err != nil {
return err
}
future, err := containerGroupsClient.Start(ctx, cs.ctx.ResourceGroup, containerName)
if err != nil {
var aerr autorest.DetailedError
if ok := errors.As(err, &aerr); ok {
if aerr.StatusCode == http.StatusNotFound {
return errdefs.ErrNotFound
}
}
return err
}
return future.WaitForCompletionRef(ctx, containerGroupsClient.Client)
}
func (cs *aciContainerService) Stop(ctx context.Context, containerID string, timeout *uint32) error {
if timeout != nil && *timeout != uint32(0) {
return errors.Errorf("ACI integration does not support setting a timeout to stop a container before killing it.")
}
groupName, containerName := getGroupAndContainerName(containerID)
if groupName != containerID {
msg := "cannot stop service %q from compose application %q, you can stop the entire compose app with docker stop %s"
return errors.New(fmt.Sprintf(msg, containerName, groupName, groupName))
}
return stopACIContainerGroup(ctx, cs.ctx, groupName)
}
func (cs *aciContainerService) Kill(ctx context.Context, containerID string, _ string) error {
groupName, containerName := getGroupAndContainerName(containerID)
if groupName != containerID {
msg := "cannot kill service %q from compose application %q, you can kill the entire compose app with docker kill %s"
return errors.New(fmt.Sprintf(msg, containerName, groupName, groupName))
}
return stopACIContainerGroup(ctx, cs.ctx, groupName) // As ACI doesn't have a kill command, we are using the stop implementation instead
}
func (cs *aciContainerService) Exec(ctx context.Context, name string, request containers.ExecRequest) error {
err := verifyExecCommand(request.Command)
if err != nil {
return err
}
groupName, containerAciName := getGroupAndContainerName(name)
containerExecResponse, err := execACIContainer(ctx, cs.ctx, request.Command, groupName, containerAciName)
if err != nil {
return err
}
return exec(
context.Background(),
*containerExecResponse.WebSocketURI,
*containerExecResponse.Password,
request,
)
}
func verifyExecCommand(command string) error {
tokens := strings.Split(command, " ")
if len(tokens) > 1 {
return errors.New("ACI exec command does not accept arguments to the command. " +
"Only the binary should be specified")
}
return nil
}
func (cs *aciContainerService) Logs(ctx context.Context, containerName string, req containers.LogsRequest) error {
groupName, containerAciName := getGroupAndContainerName(containerName)
var tail *int32
if req.Follow {
return streamLogs(ctx, cs.ctx, groupName, containerAciName, req)
}
if req.Tail != "all" {
reqTail, err := strconv.Atoi(req.Tail)
if err != nil {
return err
}
i32 := int32(reqTail)
tail = &i32
}
logs, err := getACIContainerLogs(ctx, cs.ctx, groupName, containerAciName, tail)
if err != nil {
return err
}
_, err = fmt.Fprint(req.Writer, logs)
return err
}
func (cs *aciContainerService) Delete(ctx context.Context, containerID string, request containers.DeleteRequest) error {
groupName, containerName := getGroupAndContainerName(containerID)
if groupName != containerID {
msg := "cannot delete service %q from compose application %q, you can delete the entire compose app with docker compose down --project-name %s"
return errors.New(fmt.Sprintf(msg, containerName, groupName, groupName))
}
if !request.Force {
containerGroupsClient, err := login.NewContainerGroupsClient(cs.ctx.SubscriptionID)
if err != nil {
return err
}
cg, err := containerGroupsClient.Get(ctx, cs.ctx.ResourceGroup, groupName)
if err != nil {
if cg.StatusCode == http.StatusNotFound {
return errdefs.ErrNotFound
}
return err
}
for _, container := range *cg.Containers {
status := convert.GetStatus(container, cg)
if status == convert.StatusRunning {
return errdefs.ErrForbidden
}
}
}
cg, err := deleteACIContainerGroup(ctx, cs.ctx, groupName)
// Delete returns `StatusNoContent` if the group is not found
if cg.StatusCode == http.StatusNoContent {
return errdefs.ErrNotFound
}
if err != nil {
return err
}
return err
}
func (cs *aciContainerService) Inspect(ctx context.Context, containerID string) (containers.Container, error) {
groupName, containerName := getGroupAndContainerName(containerID)
cg, err := getACIContainerGroup(ctx, cs.ctx, groupName)
if err != nil {
return containers.Container{}, err
}
if cg.StatusCode == http.StatusNoContent {
return containers.Container{}, errdefs.ErrNotFound
}
var cc containerinstance.Container
var found = false
for _, c := range *cg.Containers {
if to.String(c.Name) == containerName {
cc = c
found = true
break
}
}
if !found {
return containers.Container{}, errdefs.ErrNotFound
}
return convert.ContainerGroupToContainer(containerID, cg, cc), nil
}

View File

@ -56,7 +56,7 @@ const (
func ToContainerGroup(ctx context.Context, aciContext store.AciContext, p types.Project) (containerinstance.ContainerGroup, error) {
project := projectAciHelper(p)
containerGroupName := strings.ToLower(project.Name)
storageHelper := login.StorageAccountHelper{
storageHelper := login.StorageLogin{
AciContext: aciContext,
}
volumesCache, volumesSlice, err := project.getAciFileVolumes(ctx, storageHelper)
@ -200,7 +200,7 @@ func (p projectAciHelper) getAciSecretVolumes() ([]containerinstance.Volume, err
return secretVolumes, nil
}
func (p projectAciHelper) getAciFileVolumes(ctx context.Context, helper login.StorageAccountHelper) (map[string]bool, []containerinstance.Volume, error) {
func (p projectAciHelper) getAciFileVolumes(ctx context.Context, helper login.StorageLogin) (map[string]bool, []containerinstance.Volume, error) {
azureFileVolumesMap := make(map[string]bool, len(p.Volumes))
var azureFileVolumesSlice []containerinstance.Volume
for name, v := range p.Volumes {

49
aci/login/storagelogin.go Normal file
View File

@ -0,0 +1,49 @@
/*
Copyright 2020 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package login
import (
"context"
"fmt"
"github.com/pkg/errors"
"github.com/docker/compose-cli/context/store"
)
// StorageLogin helper for Azure Storage Login
type StorageLogin struct {
AciContext store.AciContext
}
// GetAzureStorageAccountKey retrieves the storage account ket from the current azure login
func (helper StorageLogin) GetAzureStorageAccountKey(ctx context.Context, accountName string) (string, error) {
client, err := NewStorageAccountsClient(helper.AciContext.SubscriptionID)
if err != nil {
return "", err
}
result, err := client.ListKeys(ctx, helper.AciContext.ResourceGroup, accountName, "")
if err != nil {
return "", errors.Wrap(err, fmt.Sprintf("could not access storage account acountKeys for %s, using the azure login", accountName))
}
if result.Keys != nil && len((*result.Keys)) < 1 {
return "", fmt.Errorf("no key could be obtained for storage account %s from your azure login", accountName)
}
key := (*result.Keys)[0]
return *key.Value, nil
}

View File

@ -14,11 +14,12 @@
limitations under the License.
*/
package login
package aci
import (
"context"
"fmt"
"github.com/docker/compose-cli/aci/login"
"github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage"
@ -30,48 +31,27 @@ import (
"github.com/docker/compose-cli/context/store"
)
// StorageAccountHelper helper for Azure Storage Account
type StorageAccountHelper struct {
AciContext store.AciContext
type aciVolumeService struct {
aciContext store.AciContext
}
// GetAzureStorageAccountKey retrieves the storage account ket from the current azure login
func (helper StorageAccountHelper) GetAzureStorageAccountKey(ctx context.Context, accountName string) (string, error) {
client, err := NewStorageAccountsClient(helper.AciContext.SubscriptionID)
if err != nil {
return "", err
}
result, err := client.ListKeys(ctx, helper.AciContext.ResourceGroup, accountName, "")
if err != nil {
return "", errors.Wrap(err, fmt.Sprintf("could not access storage account acountKeys for %s, using the azure login", accountName))
}
if result.Keys != nil && len((*result.Keys)) < 1 {
return "", fmt.Errorf("no key could be obtained for storage account %s from your azure login", accountName)
}
key := (*result.Keys)[0]
return *key.Value, nil
}
// ListFileShare list file shares in all visible storage accounts
func (helper StorageAccountHelper) ListFileShare(ctx context.Context) ([]volumes.Volume, error) {
aciContext := helper.AciContext
accountClient, err := NewStorageAccountsClient(aciContext.SubscriptionID)
func (cs *aciVolumeService) List(ctx context.Context) ([]volumes.Volume, error) {
accountClient, err := login.NewStorageAccountsClient(cs.aciContext.SubscriptionID)
if err != nil {
return nil, err
}
result, err := accountClient.ListByResourceGroup(ctx, aciContext.ResourceGroup)
result, err := accountClient.ListByResourceGroup(ctx, cs.aciContext.ResourceGroup)
if err != nil {
return nil, err
}
accounts := result.Value
fileShareClient, err := NewFileShareClient(aciContext.SubscriptionID)
fileShareClient, err := login.NewFileShareClient(cs.aciContext.SubscriptionID)
if err != nil {
return nil, err
}
fileShares := []volumes.Volume{}
for _, account := range *accounts {
fileSharePage, err := fileShareClient.List(ctx, aciContext.ResourceGroup, *account.Name, "", "", "")
fileSharePage, err := fileShareClient.List(ctx, cs.aciContext.ResourceGroup, *account.Name, "", "", "")
if err != nil {
return nil, err
}
@ -89,30 +69,30 @@ func (helper StorageAccountHelper) ListFileShare(ctx context.Context) ([]volumes
return fileShares, nil
}
func toVolume(account storage.Account, fileShareName string) volumes.Volume {
return volumes.Volume{
ID: fmt.Sprintf("%s@%s", *account.Name, fileShareName),
Name: fileShareName,
Description: fmt.Sprintf("Fileshare %s in %s storage account", fileShareName, *account.Name),
}
//VolumeCreateOptions options to create a new ACI volume
type VolumeCreateOptions struct {
Account string
Fileshare string
}
// CreateFileShare create a new fileshare
func (helper StorageAccountHelper) CreateFileShare(ctx context.Context, accountName string, fileShareName string) (volumes.Volume, error) {
aciContext := helper.AciContext
accountClient, err := NewStorageAccountsClient(aciContext.SubscriptionID)
func (cs *aciVolumeService) Create(ctx context.Context, options interface{}) (volumes.Volume, error) {
opts, ok := options.(VolumeCreateOptions)
if !ok {
return volumes.Volume{}, errors.New("Could not read azure LoginParams struct from generic parameter")
}
accountClient, err := login.NewStorageAccountsClient(cs.aciContext.SubscriptionID)
if err != nil {
return volumes.Volume{}, err
}
account, err := accountClient.GetProperties(ctx, aciContext.ResourceGroup, accountName, "")
account, err := accountClient.GetProperties(ctx, cs.aciContext.ResourceGroup, opts.Account, "")
if err != nil {
if account.StatusCode != 404 {
return volumes.Volume{}, err
}
//TODO confirm storage account creation
parameters := defaultStorageAccountParams(aciContext)
parameters := defaultStorageAccountParams(cs.aciContext)
// TODO progress account creation
future, err := accountClient.Create(ctx, aciContext.ResourceGroup, accountName, parameters)
future, err := accountClient.Create(ctx, cs.aciContext.ResourceGroup, opts.Account, parameters)
if err != nil {
return volumes.Volume{}, err
}
@ -121,25 +101,33 @@ func (helper StorageAccountHelper) CreateFileShare(ctx context.Context, accountN
return volumes.Volume{}, err
}
}
fileShareClient, err := NewFileShareClient(aciContext.SubscriptionID)
fileShareClient, err := login.NewFileShareClient(cs.aciContext.SubscriptionID)
if err != nil {
return volumes.Volume{}, err
}
fileShare, err := fileShareClient.Get(ctx, aciContext.ResourceGroup, *account.Name, fileShareName, "")
fileShare, err := fileShareClient.Get(ctx, cs.aciContext.ResourceGroup, *account.Name, opts.Fileshare, "")
if err == nil {
return volumes.Volume{}, errors.Wrapf(errdefs.ErrAlreadyExists, "Azure fileshare %q already exists", fileShareName)
return volumes.Volume{}, errors.Wrapf(errdefs.ErrAlreadyExists, "Azure fileshare %q already exists", opts.Fileshare)
}
if fileShare.StatusCode != 404 {
return volumes.Volume{}, err
}
fileShare, err = fileShareClient.Create(ctx, aciContext.ResourceGroup, *account.Name, fileShareName, storage.FileShare{})
fileShare, err = fileShareClient.Create(ctx, cs.aciContext.ResourceGroup, *account.Name, opts.Fileshare, storage.FileShare{})
if err != nil {
return volumes.Volume{}, err
}
return toVolume(account, *fileShare.Name), nil
}
func toVolume(account storage.Account, fileShareName string) volumes.Volume {
return volumes.Volume{
ID: fmt.Sprintf("%s@%s", *account.Name, fileShareName),
Name: fileShareName,
Description: fmt.Sprintf("Fileshare %s in %s storage account", fileShareName, *account.Name),
}
}
func defaultStorageAccountParams(aciContext store.AciContext) storage.AccountCreateParameters {
return storage.AccountCreateParameters{
Location: &aciContext.Location,