Browse Source

Merge pull request #475 from docker/aci_volume_keys

Get storage account key from azure login, no need to specify it in compose file or run -v option
Guillaume Tardif 5 years ago
parent
commit
27e7a0ced3

+ 8 - 42
aci/aci.go

@@ -24,8 +24,6 @@ import (
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
-	"github.com/docker/api/errdefs"
-
 	"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"
 	"github.com/Azure/go-autorest/autorest"
 	"github.com/Azure/go-autorest/autorest/to"
 	"github.com/Azure/go-autorest/autorest/to"
@@ -39,13 +37,12 @@ import (
 	"github.com/docker/api/aci/login"
 	"github.com/docker/api/aci/login"
 	"github.com/docker/api/containers"
 	"github.com/docker/api/containers"
 	"github.com/docker/api/context/store"
 	"github.com/docker/api/context/store"
+	"github.com/docker/api/errdefs"
 	"github.com/docker/api/progress"
 	"github.com/docker/api/progress"
 )
 )
 
 
-const aciDockerUserAgent = "docker-cli"
-
 func createACIContainers(ctx context.Context, aciContext store.AciContext, groupDefinition containerinstance.ContainerGroup) error {
 func createACIContainers(ctx context.Context, aciContext store.AciContext, groupDefinition containerinstance.ContainerGroup) error {
-	containerGroupsClient, err := getContainerGroupsClient(aciContext.SubscriptionID)
+	containerGroupsClient, err := login.NewContainerGroupsClient(aciContext.SubscriptionID)
 	if err != nil {
 	if err != nil {
 		return errors.Wrapf(err, "cannot get container group client")
 		return errors.Wrapf(err, "cannot get container group client")
 	}
 	}
@@ -69,7 +66,7 @@ func createACIContainers(ctx context.Context, aciContext store.AciContext, group
 
 
 func createOrUpdateACIContainers(ctx context.Context, aciContext store.AciContext, groupDefinition containerinstance.ContainerGroup) error {
 func createOrUpdateACIContainers(ctx context.Context, aciContext store.AciContext, groupDefinition containerinstance.ContainerGroup) error {
 	w := progress.ContextWriter(ctx)
 	w := progress.ContextWriter(ctx)
-	containerGroupsClient, err := getContainerGroupsClient(aciContext.SubscriptionID)
+	containerGroupsClient, err := login.NewContainerGroupsClient(aciContext.SubscriptionID)
 	if err != nil {
 	if err != nil {
 		return errors.Wrapf(err, "cannot get container group client")
 		return errors.Wrapf(err, "cannot get container group client")
 	}
 	}
@@ -124,7 +121,7 @@ func createOrUpdateACIContainers(ctx context.Context, aciContext store.AciContex
 }
 }
 
 
 func getACIContainerGroup(ctx context.Context, aciContext store.AciContext, containerGroupName string) (containerinstance.ContainerGroup, error) {
 func getACIContainerGroup(ctx context.Context, aciContext store.AciContext, containerGroupName string) (containerinstance.ContainerGroup, error) {
-	containerGroupsClient, err := getContainerGroupsClient(aciContext.SubscriptionID)
+	containerGroupsClient, err := login.NewContainerGroupsClient(aciContext.SubscriptionID)
 	if err != nil {
 	if err != nil {
 		return containerinstance.ContainerGroup{}, fmt.Errorf("cannot get container group client: %v", err)
 		return containerinstance.ContainerGroup{}, fmt.Errorf("cannot get container group client: %v", err)
 	}
 	}
@@ -133,7 +130,7 @@ func getACIContainerGroup(ctx context.Context, aciContext store.AciContext, cont
 }
 }
 
 
 func deleteACIContainerGroup(ctx context.Context, aciContext store.AciContext, containerGroupName string) (containerinstance.ContainerGroup, error) {
 func deleteACIContainerGroup(ctx context.Context, aciContext store.AciContext, containerGroupName string) (containerinstance.ContainerGroup, error) {
-	containerGroupsClient, err := getContainerGroupsClient(aciContext.SubscriptionID)
+	containerGroupsClient, err := login.NewContainerGroupsClient(aciContext.SubscriptionID)
 	if err != nil {
 	if err != nil {
 		return containerinstance.ContainerGroup{}, fmt.Errorf("cannot get container group client: %v", err)
 		return containerinstance.ContainerGroup{}, fmt.Errorf("cannot get container group client: %v", err)
 	}
 	}
@@ -142,7 +139,7 @@ func deleteACIContainerGroup(ctx context.Context, aciContext store.AciContext, c
 }
 }
 
 
 func stopACIContainerGroup(ctx context.Context, aciContext store.AciContext, containerGroupName string) error {
 func stopACIContainerGroup(ctx context.Context, aciContext store.AciContext, containerGroupName string) error {
-	containerGroupsClient, err := getContainerGroupsClient(aciContext.SubscriptionID)
+	containerGroupsClient, err := login.NewContainerGroupsClient(aciContext.SubscriptionID)
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("cannot get container group client: %v", err)
 		return fmt.Errorf("cannot get container group client: %v", err)
 	}
 	}
@@ -155,7 +152,7 @@ func stopACIContainerGroup(ctx context.Context, aciContext store.AciContext, con
 }
 }
 
 
 func execACIContainer(ctx context.Context, aciContext store.AciContext, command, containerGroup string, containerName string) (c containerinstance.ContainerExecResponse, err error) {
 func execACIContainer(ctx context.Context, aciContext store.AciContext, command, containerGroup string, containerName string) (c containerinstance.ContainerExecResponse, err error) {
-	containerClient, err := getContainerClient(aciContext.SubscriptionID)
+	containerClient, err := login.NewContainerClient(aciContext.SubscriptionID)
 	if err != nil {
 	if err != nil {
 		return c, errors.Wrapf(err, "cannot get container client")
 		return c, errors.Wrapf(err, "cannot get container client")
 	}
 	}
@@ -248,7 +245,7 @@ func exec(ctx context.Context, address string, password string, request containe
 }
 }
 
 
 func getACIContainerLogs(ctx context.Context, aciContext store.AciContext, containerGroupName, containerName string, tail *int32) (string, error) {
 func getACIContainerLogs(ctx context.Context, aciContext store.AciContext, containerGroupName, containerName string, tail *int32) (string, error) {
-	containerClient, err := getContainerClient(aciContext.SubscriptionID)
+	containerClient, err := login.NewContainerClient(aciContext.SubscriptionID)
 	if err != nil {
 	if err != nil {
 		return "", errors.Wrapf(err, "cannot get container client")
 		return "", errors.Wrapf(err, "cannot get container client")
 	}
 	}
@@ -311,34 +308,3 @@ func getBacktrackLines(lines []string, terminalWidth int) int {
 
 
 	return numLines
 	return numLines
 }
 }
-
-func getContainerGroupsClient(subscriptionID string) (containerinstance.ContainerGroupsClient, error) {
-	containerGroupsClient := containerinstance.NewContainerGroupsClient(subscriptionID)
-	err := setupClient(&containerGroupsClient.Client)
-	if err != nil {
-		return containerinstance.ContainerGroupsClient{}, err
-	}
-	containerGroupsClient.PollingDelay = 5 * time.Second
-	containerGroupsClient.RetryAttempts = 30
-	containerGroupsClient.RetryDuration = 1 * time.Second
-	return containerGroupsClient, nil
-}
-
-func setupClient(aciClient *autorest.Client) error {
-	aciClient.UserAgent = aciDockerUserAgent
-	auth, err := login.NewAuthorizerFromLogin()
-	if err != nil {
-		return err
-	}
-	aciClient.Authorizer = auth
-	return nil
-}
-
-func getContainerClient(subscriptionID string) (containerinstance.ContainerClient, error) {
-	containerClient := containerinstance.NewContainerClient(subscriptionID)
-	err := setupClient(&containerClient.Client)
-	if err != nil {
-		return containerinstance.ContainerClient{}, err
-	}
-	return containerClient, nil
-}

+ 5 - 5
aci/backend.go

@@ -133,7 +133,7 @@ type aciContainerService struct {
 }
 }
 
 
 func (cs *aciContainerService) List(ctx context.Context, all bool) ([]containers.Container, error) {
 func (cs *aciContainerService) List(ctx context.Context, all bool) ([]containers.Container, error) {
-	groupsClient, err := getContainerGroupsClient(cs.ctx.SubscriptionID)
+	groupsClient, err := login.NewContainerGroupsClient(cs.ctx.SubscriptionID)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -209,7 +209,7 @@ func (cs *aciContainerService) Run(ctx context.Context, r containers.ContainerCo
 	}
 	}
 
 
 	logrus.Debugf("Running container %q with name %q\n", r.Image, r.ID)
 	logrus.Debugf("Running container %q with name %q\n", r.Image, r.ID)
-	groupDefinition, err := convert.ToContainerGroup(cs.ctx, project)
+	groupDefinition, err := convert.ToContainerGroup(ctx, cs.ctx, project)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -232,7 +232,7 @@ func (cs *aciContainerService) Start(ctx context.Context, containerID string) er
 		return errors.New(fmt.Sprintf(msg, containerName, groupName, groupName))
 		return errors.New(fmt.Sprintf(msg, containerName, groupName, groupName))
 	}
 	}
 
 
-	containerGroupsClient, err := getContainerGroupsClient(cs.ctx.SubscriptionID)
+	containerGroupsClient, err := login.NewContainerGroupsClient(cs.ctx.SubscriptionID)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -336,7 +336,7 @@ func (cs *aciContainerService) Delete(ctx context.Context, containerID string, r
 	}
 	}
 
 
 	if !request.Force {
 	if !request.Force {
-		containerGroupsClient, err := getContainerGroupsClient(cs.ctx.SubscriptionID)
+		containerGroupsClient, err := login.NewContainerGroupsClient(cs.ctx.SubscriptionID)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -410,7 +410,7 @@ func (cs *aciComposeService) Up(ctx context.Context, opts cli.ProjectOptions) er
 		return err
 		return err
 	}
 	}
 	logrus.Debugf("Up on project with name %q\n", project.Name)
 	logrus.Debugf("Up on project with name %q\n", project.Name)
-	groupDefinition, err := convert.ToContainerGroup(cs.ctx, *project)
+	groupDefinition, err := convert.ToContainerGroup(ctx, cs.ctx, *project)
 	addTag(&groupDefinition, composeContainerTag)
 	addTag(&groupDefinition, composeContainerTag)
 
 
 	if err != nil {
 	if err != nil {

+ 17 - 8
aci/convert/convert.go

@@ -17,8 +17,8 @@
 package convert
 package convert
 
 
 import (
 import (
+	"context"
 	"encoding/base64"
 	"encoding/base64"
-	"errors"
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
 	"math"
 	"math"
@@ -29,7 +29,9 @@ import (
 	"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"
+	"github.com/pkg/errors"
 
 
+	"github.com/docker/api/aci/login"
 	"github.com/docker/api/containers"
 	"github.com/docker/api/containers"
 	"github.com/docker/api/context/store"
 	"github.com/docker/api/context/store"
 )
 )
@@ -42,15 +44,22 @@ const (
 	azureFileDriverName            = "azure_file"
 	azureFileDriverName            = "azure_file"
 	volumeDriveroptsShareNameKey   = "share_name"
 	volumeDriveroptsShareNameKey   = "share_name"
 	volumeDriveroptsAccountNameKey = "storage_account_name"
 	volumeDriveroptsAccountNameKey = "storage_account_name"
-	volumeDriveroptsAccountKeyKey  = "storage_account_key"
 	secretInlineMark               = "inline:"
 	secretInlineMark               = "inline:"
 )
 )
 
 
 // ToContainerGroup converts a compose project into a ACI container group
 // ToContainerGroup converts a compose project into a ACI container group
-func ToContainerGroup(aciContext store.AciContext, p types.Project) (containerinstance.ContainerGroup, error) {
+func ToContainerGroup(ctx context.Context, aciContext store.AciContext, p types.Project) (containerinstance.ContainerGroup, error) {
 	project := projectAciHelper(p)
 	project := projectAciHelper(p)
 	containerGroupName := strings.ToLower(project.Name)
 	containerGroupName := strings.ToLower(project.Name)
-	volumesCache, volumesSlice, err := project.getAciFileVolumes()
+	loginService, err := login.NewAzureLoginService()
+	if err != nil {
+		return containerinstance.ContainerGroup{}, err
+	}
+	storageHelper := login.StorageAccountHelper{
+		LoginService: *loginService,
+		AciContext:   aciContext,
+	}
+	volumesCache, volumesSlice, err := project.getAciFileVolumes(ctx, storageHelper)
 	if err != nil {
 	if err != nil {
 		return containerinstance.ContainerGroup{}, err
 		return containerinstance.ContainerGroup{}, err
 	}
 	}
@@ -191,7 +200,7 @@ func (p projectAciHelper) getAciSecretVolumes() ([]containerinstance.Volume, err
 	return secretVolumes, nil
 	return secretVolumes, nil
 }
 }
 
 
-func (p projectAciHelper) getAciFileVolumes() (map[string]bool, []containerinstance.Volume, error) {
+func (p projectAciHelper) getAciFileVolumes(ctx context.Context, helper login.StorageAccountHelper) (map[string]bool, []containerinstance.Volume, error) {
 	azureFileVolumesMap := make(map[string]bool, len(p.Volumes))
 	azureFileVolumesMap := make(map[string]bool, len(p.Volumes))
 	var azureFileVolumesSlice []containerinstance.Volume
 	var azureFileVolumesSlice []containerinstance.Volume
 	for name, v := range p.Volumes {
 	for name, v := range p.Volumes {
@@ -204,9 +213,9 @@ func (p projectAciHelper) getAciFileVolumes() (map[string]bool, []containerinsta
 			if !ok {
 			if !ok {
 				return nil, nil, fmt.Errorf("cannot retrieve account name for Azurefile")
 				return nil, nil, fmt.Errorf("cannot retrieve account name for Azurefile")
 			}
 			}
-			accountKey, ok := v.DriverOpts[volumeDriveroptsAccountKeyKey]
-			if !ok {
-				return nil, nil, fmt.Errorf("cannot retrieve account key for Azurefile")
+			accountKey, err := helper.GetAzureStorageAccountKey(ctx, accountName)
+			if err != nil {
+				return nil, nil, err
 			}
 			}
 			aciVolume := containerinstance.Volume{
 			aciVolume := containerinstance.Volume{
 				Name: to.StringPtr(name),
 				Name: to.StringPtr(name),

+ 13 - 12
aci/convert/convert_test.go

@@ -17,6 +17,7 @@
 package convert
 package convert
 
 
 import (
 import (
+	"context"
 	"os"
 	"os"
 	"testing"
 	"testing"
 
 
@@ -40,7 +41,7 @@ func TestProjectName(t *testing.T) {
 	project := types.Project{
 	project := types.Project{
 		Name: "TEST",
 		Name: "TEST",
 	}
 	}
-	containerGroup, err := ToContainerGroup(convertCtx, project)
+	containerGroup, err := ToContainerGroup(context.TODO(), convertCtx, project)
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 	assert.Equal(t, *containerGroup.Name, "test")
 	assert.Equal(t, *containerGroup.Name, "test")
 }
 }
@@ -117,7 +118,7 @@ func TestComposeContainerGroupToContainerWithDnsSideCarSide(t *testing.T) {
 		},
 		},
 	}
 	}
 
 
-	group, err := ToContainerGroup(convertCtx, project)
+	group, err := ToContainerGroup(context.TODO(), convertCtx, project)
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 	assert.Assert(t, is.Len(*group.Containers, 3))
 	assert.Assert(t, is.Len(*group.Containers, 3))
 
 
@@ -142,7 +143,7 @@ func TestComposeSingleContainerGroupToContainerNoDnsSideCarSide(t *testing.T) {
 		},
 		},
 	}
 	}
 
 
-	group, err := ToContainerGroup(convertCtx, project)
+	group, err := ToContainerGroup(context.TODO(), convertCtx, project)
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 
 
 	assert.Assert(t, is.Len(*group.Containers, 1))
 	assert.Assert(t, is.Len(*group.Containers, 1))
@@ -165,7 +166,7 @@ func TestComposeSingleContainerRestartPolicy(t *testing.T) {
 		},
 		},
 	}
 	}
 
 
-	group, err := ToContainerGroup(convertCtx, project)
+	group, err := ToContainerGroup(context.TODO(), convertCtx, project)
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 
 
 	assert.Assert(t, is.Len(*group.Containers, 1))
 	assert.Assert(t, is.Len(*group.Containers, 1))
@@ -197,7 +198,7 @@ func TestComposeMultiContainerRestartPolicy(t *testing.T) {
 		},
 		},
 	}
 	}
 
 
-	group, err := ToContainerGroup(convertCtx, project)
+	group, err := ToContainerGroup(context.TODO(), convertCtx, project)
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 
 
 	assert.Assert(t, is.Len(*group.Containers, 3))
 	assert.Assert(t, is.Len(*group.Containers, 3))
@@ -231,7 +232,7 @@ func TestComposeInconsistentMultiContainerRestartPolicy(t *testing.T) {
 		},
 		},
 	}
 	}
 
 
-	_, err := ToContainerGroup(convertCtx, project)
+	_, err := ToContainerGroup(context.TODO(), convertCtx, project)
 	assert.Error(t, err, "ACI integration does not support specifying different restart policies on containers in the same compose application")
 	assert.Error(t, err, "ACI integration does not support specifying different restart policies on containers in the same compose application")
 }
 }
 
 
@@ -248,7 +249,7 @@ func TestLabelsErrorMessage(t *testing.T) {
 		},
 		},
 	}
 	}
 
 
-	_, err := ToContainerGroup(convertCtx, project)
+	_, err := ToContainerGroup(context.TODO(), convertCtx, project)
 	assert.Error(t, err, "ACI integration does not support labels in compose applications")
 	assert.Error(t, err, "ACI integration does not support labels in compose applications")
 }
 }
 
 
@@ -262,7 +263,7 @@ func TestComposeSingleContainerGroupToContainerDefaultRestartPolicy(t *testing.T
 		},
 		},
 	}
 	}
 
 
-	group, err := ToContainerGroup(convertCtx, project)
+	group, err := ToContainerGroup(context.TODO(), convertCtx, project)
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 
 
 	assert.Assert(t, is.Len(*group.Containers, 1))
 	assert.Assert(t, is.Len(*group.Containers, 1))
@@ -296,7 +297,7 @@ func TestComposeContainerGroupToContainerMultiplePorts(t *testing.T) {
 		},
 		},
 	}
 	}
 
 
-	group, err := ToContainerGroup(convertCtx, project)
+	group, err := ToContainerGroup(context.TODO(), convertCtx, project)
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 	assert.Assert(t, is.Len(*group.Containers, 3))
 	assert.Assert(t, is.Len(*group.Containers, 3))
 
 
@@ -335,7 +336,7 @@ func TestComposeContainerGroupToContainerResourceLimits(t *testing.T) {
 		},
 		},
 	}
 	}
 
 
-	group, err := ToContainerGroup(convertCtx, project)
+	group, err := ToContainerGroup(context.TODO(), convertCtx, project)
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 
 
 	limits := *((*group.Containers)[0]).Resources.Limits
 	limits := *((*group.Containers)[0]).Resources.Limits
@@ -361,7 +362,7 @@ func TestComposeContainerGroupToContainerResourceLimitsDefaults(t *testing.T) {
 		},
 		},
 	}
 	}
 
 
-	group, err := ToContainerGroup(convertCtx, project)
+	group, err := ToContainerGroup(context.TODO(), convertCtx, project)
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 
 
 	limits := *((*group.Containers)[0]).Resources.Limits
 	limits := *((*group.Containers)[0]).Resources.Limits
@@ -385,7 +386,7 @@ func TestComposeContainerGroupToContainerenvVar(t *testing.T) {
 		},
 		},
 	}
 	}
 
 
-	group, err := ToContainerGroup(convertCtx, project)
+	group, err := ToContainerGroup(context.TODO(), convertCtx, project)
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 
 
 	envVars := *((*group.Containers)[0]).EnvironmentVariables
 	envVars := *((*group.Containers)[0]).EnvironmentVariables

+ 13 - 62
aci/convert/volume.go

@@ -18,7 +18,6 @@ package convert
 
 
 import (
 import (
 	"fmt"
 	"fmt"
-	"net/url"
 	"strings"
 	"strings"
 
 
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
@@ -44,7 +43,6 @@ func GetRunVolumes(volumes []string) (map[string]types.VolumeConfig, []types.Ser
 			Driver: azureFileDriverName,
 			Driver: azureFileDriverName,
 			DriverOpts: map[string]string{
 			DriverOpts: map[string]string{
 				volumeDriveroptsAccountNameKey: vi.username,
 				volumeDriveroptsAccountNameKey: vi.username,
-				volumeDriveroptsAccountKeyKey:  vi.key,
 				volumeDriveroptsShareNameKey:   vi.share,
 				volumeDriveroptsShareNameKey:   vi.share,
 			},
 			},
 		}
 		}
@@ -62,73 +60,26 @@ func GetRunVolumes(volumes []string) (map[string]types.VolumeConfig, []types.Ser
 type volumeInput struct {
 type volumeInput struct {
 	name     string
 	name     string
 	username string
 	username string
-	key      string
 	share    string
 	share    string
 	target   string
 	target   string
 }
 }
 
 
-func escapeKeySlashes(rawURL string) (string, error) {
-	urlSplit := strings.Split(rawURL, "@")
-	if len(urlSplit) < 1 {
-		return "", fmt.Errorf("invalid URL format: %s", rawURL)
-	}
-	userPasswd := strings.ReplaceAll(urlSplit[0], "/", "_")
-
-	atIndex := strings.Index(rawURL, "@")
-	if atIndex < 0 {
-		return "", fmt.Errorf("no share specified in: %s", rawURL)
-	}
-
-	scaped := userPasswd + rawURL[atIndex:]
-
-	return scaped, nil
-}
-
-func unescapeKey(key string) string {
-	return strings.ReplaceAll(key, "_", "/")
-}
-
-// Removes the second ':' that separates the source from target
-func volumeURL(pathURL string) (*url.URL, error) {
-	scapedURL, err := escapeKeySlashes(pathURL)
-	if err != nil {
-		return nil, err
-	}
-	pathURL = "//" + scapedURL
-
-	count := strings.Count(pathURL, ":")
-	if count > 2 {
-		return nil, fmt.Errorf("invalid path URL: %s", pathURL)
-	}
-	if count == 2 {
-		tokens := strings.Split(pathURL, ":")
-		pathURL = fmt.Sprintf("%s:%s%s", tokens[0], tokens[1], tokens[2])
-	}
-	return url.Parse(pathURL)
-}
-
 func (v *volumeInput) parse(name string, s string) error {
 func (v *volumeInput) parse(name string, s string) error {
-	volumeURL, err := volumeURL(s)
-	if err != nil {
-		return errors.Wrapf(errdefs.ErrParsingFailed, "unable to parse volume specification: %s", err.Error())
-	}
-	v.username = volumeURL.User.Username()
-	if v.username == "" {
-		return errors.Wrapf(errdefs.ErrParsingFailed, "volume specification %q does not include a storage username", v)
-	}
-	key, ok := volumeURL.User.Password()
-	if !ok || key == "" {
-		return errors.Wrapf(errdefs.ErrParsingFailed, "volume specification %q does not include a storage key", v)
+	v.name = name
+	tokens := strings.Split(s, "@")
+	if len(tokens) < 2 || tokens[0] == "" {
+		return errors.Wrapf(errdefs.ErrParsingFailed, "volume specification %q does not include a storage account before '@'", v)
 	}
 	}
-	v.key = unescapeKey(key)
-	v.share = volumeURL.Host
-	if v.share == "" {
-		return errors.Wrapf(errdefs.ErrParsingFailed, "volume specification %q does not include a storage file share", v)
+	v.username = tokens[0]
+	remaining := tokens[1]
+	tokens = strings.Split(remaining, ":")
+	if tokens[0] == "" {
+		return errors.Wrapf(errdefs.ErrParsingFailed, "volume specification %q does not include a storage file share after '@'", v)
 	}
 	}
-	v.name = name
-	v.target = volumeURL.Path
-	if v.target == "" {
-		// Do not use filepath.Join, on Windows it will replace / by \
+	v.share = tokens[0]
+	if len(tokens) > 1 {
+		v.target = tokens[1]
+	} else {
 		v.target = "/run/volumes/" + v.share
 		v.target = "/run/volumes/" + v.share
 	}
 	}
 	return nil
 	return nil

+ 8 - 27
aci/convert/volume_test.go

@@ -21,21 +21,18 @@ import (
 
 
 	"github.com/compose-spec/compose-go/types"
 	"github.com/compose-spec/compose-go/types"
 	"gotest.tools/v3/assert"
 	"gotest.tools/v3/assert"
-
-	"github.com/docker/api/errdefs"
 )
 )
 
 
 const (
 const (
 	storageAccountNameKey = "storage_account_name"
 	storageAccountNameKey = "storage_account_name"
-	storageAccountKeyKey  = "storage_account_key"
 	shareNameKey          = "share_name"
 	shareNameKey          = "share_name"
 )
 )
 
 
 func TestGetRunVolumes(t *testing.T) {
 func TestGetRunVolumes(t *testing.T) {
 	volumeStrings := []string{
 	volumeStrings := []string{
-		"myuser1:mykey1@myshare1/my/path/to/target1",
-		"myuser2:mykey2@myshare2/my/path/to/target2",
-		"myuser3:mykey3@mydefaultsharename", // Use default placement at '/run/volumes/<share_name>'
+		"myuser1@myshare1:/my/path/to/target1",
+		"myuser2@myshare2:/my/path/to/target2",
+		"myuser3@mydefaultsharename", // Use default placement at '/run/volumes/<share_name>'
 	}
 	}
 	var goldenVolumeConfigs = map[string]types.VolumeConfig{
 	var goldenVolumeConfigs = map[string]types.VolumeConfig{
 		"volume-0": {
 		"volume-0": {
@@ -43,7 +40,6 @@ func TestGetRunVolumes(t *testing.T) {
 			Driver: "azure_file",
 			Driver: "azure_file",
 			DriverOpts: map[string]string{
 			DriverOpts: map[string]string{
 				storageAccountNameKey: "myuser1",
 				storageAccountNameKey: "myuser1",
-				storageAccountKeyKey:  "mykey1",
 				shareNameKey:          "myshare1",
 				shareNameKey:          "myshare1",
 			},
 			},
 		},
 		},
@@ -52,7 +48,6 @@ func TestGetRunVolumes(t *testing.T) {
 			Driver: "azure_file",
 			Driver: "azure_file",
 			DriverOpts: map[string]string{
 			DriverOpts: map[string]string{
 				storageAccountNameKey: "myuser2",
 				storageAccountNameKey: "myuser2",
-				storageAccountKeyKey:  "mykey2",
 				shareNameKey:          "myshare2",
 				shareNameKey:          "myshare2",
 			},
 			},
 		},
 		},
@@ -61,7 +56,6 @@ func TestGetRunVolumes(t *testing.T) {
 			Driver: "azure_file",
 			Driver: "azure_file",
 			DriverOpts: map[string]string{
 			DriverOpts: map[string]string{
 				storageAccountNameKey: "myuser3",
 				storageAccountNameKey: "myuser3",
-				storageAccountKeyKey:  "mykey3",
 				shareNameKey:          "mydefaultsharename",
 				shareNameKey:          "mydefaultsharename",
 			},
 			},
 		},
 		},
@@ -95,29 +89,16 @@ func TestGetRunVolumes(t *testing.T) {
 }
 }
 
 
 func TestGetRunVolumesMissingFileShare(t *testing.T) {
 func TestGetRunVolumesMissingFileShare(t *testing.T) {
-	_, _, err := GetRunVolumes([]string{"myuser:mykey@"})
-	assert.Assert(t, errdefs.IsErrParsingFailed(err))
-	assert.ErrorContains(t, err, "does not include a storage file share")
+	_, _, err := GetRunVolumes([]string{"myaccount@"})
+	assert.ErrorContains(t, err, "does not include a storage file share after '@'")
 }
 }
 
 
 func TestGetRunVolumesMissingUser(t *testing.T) {
 func TestGetRunVolumesMissingUser(t *testing.T) {
-	_, _, err := GetRunVolumes([]string{":mykey@myshare"})
-	assert.Assert(t, errdefs.IsErrParsingFailed(err))
-	assert.ErrorContains(t, err, "does not include a storage username")
-}
-
-func TestGetRunVolumesMissingKey(t *testing.T) {
-	_, _, err := GetRunVolumes([]string{"userwithnokey:@myshare"})
-	assert.Assert(t, errdefs.IsErrParsingFailed(err))
-	assert.ErrorContains(t, err, "does not include a storage key")
-
-	_, _, err = GetRunVolumes([]string{"userwithnokeytoo@myshare"})
-	assert.Assert(t, errdefs.IsErrParsingFailed(err))
-	assert.ErrorContains(t, err, "does not include a storage key")
+	_, _, err := GetRunVolumes([]string{"@myshare"})
+	assert.ErrorContains(t, err, "does not include a storage account before '@'")
 }
 }
 
 
 func TestGetRunVolumesNoShare(t *testing.T) {
 func TestGetRunVolumesNoShare(t *testing.T) {
 	_, _, err := GetRunVolumes([]string{"noshare"})
 	_, _, err := GetRunVolumes([]string{"noshare"})
-	assert.Assert(t, errdefs.IsErrParsingFailed(err))
-	assert.ErrorContains(t, err, "no share specified")
+	assert.ErrorContains(t, err, "does not include a storage account before '@'")
 }
 }

+ 82 - 0
aci/login/client.go

@@ -0,0 +1,82 @@
+package login
+
+import (
+	"time"
+
+	"github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/resources/mgmt/resources"
+	"github.com/Azure/azure-sdk-for-go/profiles/preview/preview/subscription/mgmt/subscription"
+	"github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance"
+	"github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage"
+	"github.com/Azure/go-autorest/autorest"
+	"github.com/pkg/errors"
+
+	"github.com/docker/api/errdefs"
+)
+
+const userAgent = "docker-cli"
+
+// NewContainerGroupsClient get client toi manipulate containerGrouos
+func NewContainerGroupsClient(subscriptionID string) (containerinstance.ContainerGroupsClient, error) {
+	containerGroupsClient := containerinstance.NewContainerGroupsClient(subscriptionID)
+	err := setupClient(&containerGroupsClient.Client)
+	if err != nil {
+		return containerinstance.ContainerGroupsClient{}, err
+	}
+	containerGroupsClient.PollingDelay = 5 * time.Second
+	containerGroupsClient.RetryAttempts = 30
+	containerGroupsClient.RetryDuration = 1 * time.Second
+	return containerGroupsClient, nil
+}
+
+func setupClient(aciClient *autorest.Client) error {
+	aciClient.UserAgent = userAgent
+	auth, err := NewAuthorizerFromLogin()
+	if err != nil {
+		return err
+	}
+	aciClient.Authorizer = auth
+	return nil
+}
+
+// NewStorageAccountsClient get client to manipulate storage accounts
+func NewStorageAccountsClient(subscriptionID string) (storage.AccountsClient, error) {
+	containerGroupsClient := storage.NewAccountsClient(subscriptionID)
+	err := setupClient(&containerGroupsClient.Client)
+	if err != nil {
+		return storage.AccountsClient{}, err
+	}
+	containerGroupsClient.PollingDelay = 5 * time.Second
+	containerGroupsClient.RetryAttempts = 30
+	containerGroupsClient.RetryDuration = 1 * time.Second
+	return containerGroupsClient, nil
+}
+
+// NewSubscriptionsClient get subscription client
+func NewSubscriptionsClient() (subscription.SubscriptionsClient, error) {
+	subc := subscription.NewSubscriptionsClient()
+	err := setupClient(&subc.Client)
+	if err != nil {
+		return subscription.SubscriptionsClient{}, errors.Wrap(errdefs.ErrLoginRequired, err.Error())
+	}
+	return subc, nil
+}
+
+// NewGroupsClient get client to manipulate groups
+func NewGroupsClient(subscriptionID string) (resources.GroupsClient, error) {
+	groupsClient := resources.NewGroupsClient(subscriptionID)
+	err := setupClient(&groupsClient.Client)
+	if err != nil {
+		return resources.GroupsClient{}, err
+	}
+	return groupsClient, nil
+}
+
+// NewContainerClient get client to manipulate containers
+func NewContainerClient(subscriptionID string) (containerinstance.ContainerClient, error) {
+	containerClient := containerinstance.NewContainerClient(subscriptionID)
+	err := setupClient(&containerClient.Client)
+	if err != nil {
+		return containerinstance.ContainerClient{}, err
+	}
+	return containerClient, nil
+}

+ 34 - 0
aci/login/storage_helper.go

@@ -0,0 +1,34 @@
+package login
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/pkg/errors"
+
+	"github.com/docker/api/context/store"
+)
+
+// StorageAccountHelper helper for Azure Storage Account
+type StorageAccountHelper struct {
+	LoginService AzureLoginService
+	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
+}

+ 6 - 24
aci/resource_group.go

@@ -23,7 +23,7 @@ import (
 	"github.com/Azure/azure-sdk-for-go/profiles/preview/preview/subscription/mgmt/subscription"
 	"github.com/Azure/azure-sdk-for-go/profiles/preview/preview/subscription/mgmt/subscription"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 
 
-	"github.com/docker/api/errdefs"
+	"github.com/docker/api/aci/login"
 )
 )
 
 
 // ResourceGroupHelper interface to manage resource groups and subscription IDs
 // ResourceGroupHelper interface to manage resource groups and subscription IDs
@@ -45,7 +45,7 @@ func NewACIResourceGroupHelper() ResourceGroupHelper {
 
 
 // GetGroup get a resource group from its name
 // GetGroup get a resource group from its name
 func (mgt aciResourceGroupHelperImpl) GetGroup(ctx context.Context, subscriptionID string, groupName string) (resources.Group, error) {
 func (mgt aciResourceGroupHelperImpl) GetGroup(ctx context.Context, subscriptionID string, groupName string) (resources.Group, error) {
-	gc, err := getGroupsClient(subscriptionID)
+	gc, err := login.NewGroupsClient(subscriptionID)
 	if err != nil {
 	if err != nil {
 		return resources.Group{}, err
 		return resources.Group{}, err
 	}
 	}
@@ -54,7 +54,7 @@ func (mgt aciResourceGroupHelperImpl) GetGroup(ctx context.Context, subscription
 
 
 // ListGroups list resource groups
 // ListGroups list resource groups
 func (mgt aciResourceGroupHelperImpl) ListGroups(ctx context.Context, subscriptionID string) ([]resources.Group, error) {
 func (mgt aciResourceGroupHelperImpl) ListGroups(ctx context.Context, subscriptionID string) ([]resources.Group, error) {
-	gc, err := getGroupsClient(subscriptionID)
+	gc, err := login.NewGroupsClient(subscriptionID)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -80,7 +80,7 @@ func (mgt aciResourceGroupHelperImpl) ListGroups(ctx context.Context, subscripti
 
 
 // CreateOrUpdate create or update a resource group
 // CreateOrUpdate create or update a resource group
 func (mgt aciResourceGroupHelperImpl) CreateOrUpdate(ctx context.Context, subscriptionID string, resourceGroupName string, parameters resources.Group) (result resources.Group, err error) {
 func (mgt aciResourceGroupHelperImpl) CreateOrUpdate(ctx context.Context, subscriptionID string, resourceGroupName string, parameters resources.Group) (result resources.Group, err error) {
-	gc, err := getGroupsClient(subscriptionID)
+	gc, err := login.NewGroupsClient(subscriptionID)
 	if err != nil {
 	if err != nil {
 		return resources.Group{}, err
 		return resources.Group{}, err
 	}
 	}
@@ -89,7 +89,7 @@ func (mgt aciResourceGroupHelperImpl) CreateOrUpdate(ctx context.Context, subscr
 
 
 // DeleteAsync deletes a resource group. Does not wait for full deletion to return (long operation)
 // DeleteAsync deletes a resource group. Does not wait for full deletion to return (long operation)
 func (mgt aciResourceGroupHelperImpl) DeleteAsync(ctx context.Context, subscriptionID string, resourceGroupName string) (err error) {
 func (mgt aciResourceGroupHelperImpl) DeleteAsync(ctx context.Context, subscriptionID string, resourceGroupName string) (err error) {
-	gc, err := getGroupsClient(subscriptionID)
+	gc, err := login.NewGroupsClient(subscriptionID)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -100,7 +100,7 @@ func (mgt aciResourceGroupHelperImpl) DeleteAsync(ctx context.Context, subscript
 
 
 // GetSubscriptionIDs Return available subscription IDs based on azure login
 // GetSubscriptionIDs Return available subscription IDs based on azure login
 func (mgt aciResourceGroupHelperImpl) GetSubscriptionIDs(ctx context.Context) ([]subscription.Model, error) {
 func (mgt aciResourceGroupHelperImpl) GetSubscriptionIDs(ctx context.Context) ([]subscription.Model, error) {
-	c, err := getSubscriptionsClient()
+	c, err := login.NewSubscriptionsClient()
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -122,21 +122,3 @@ func (mgt aciResourceGroupHelperImpl) GetSubscriptionIDs(ctx context.Context) ([
 	}
 	}
 	return subs, nil
 	return subs, nil
 }
 }
-
-func getSubscriptionsClient() (subscription.SubscriptionsClient, error) {
-	subc := subscription.NewSubscriptionsClient()
-	err := setupClient(&subc.Client)
-	if err != nil {
-		return subscription.SubscriptionsClient{}, errors.Wrap(errdefs.ErrLoginRequired, err.Error())
-	}
-	return subc, nil
-}
-
-func getGroupsClient(subscriptionID string) (resources.GroupsClient, error) {
-	groupsClient := resources.NewGroupsClient(subscriptionID)
-	err := setupClient(&groupsClient.Client)
-	if err != nil {
-		return resources.GroupsClient{}, err
-	}
-	return groupsClient, nil
-}

+ 1 - 1
tests/aci-e2e/e2e-aci_test.go

@@ -165,7 +165,7 @@ func TestContainerRun(t *testing.T) {
 		mountTarget := "/usr/share/nginx/html"
 		mountTarget := "/usr/share/nginx/html"
 		res := c.RunDockerCmd(
 		res := c.RunDockerCmd(
 			"run", "-d",
 			"run", "-d",
-			"-v", fmt.Sprintf("%s:%s@%s:%s", saName, k, testShareName, mountTarget),
+			"-v", fmt.Sprintf("%s@%s:%s", saName, testShareName, mountTarget),
 			"-p", "80:80",
 			"-p", "80:80",
 			"nginx",
 			"nginx",
 		)
 		)

+ 2 - 13
tests/composefiles/aci-demo/aci_demo_port_secrets_volumes.yaml

@@ -14,10 +14,6 @@ services:
     image: gtardif/sentences-web
     image: gtardif/sentences-web
     ports:
     ports:
       - "80:80"
       - "80:80"
-    secrets:
-      - source: mysecret1
-        target: mytarget1
-      - mysecret2
     volumes:
     volumes:
       - mydata:/mount/testvolumes
       - mydata:/mount/testvolumes
 
 
@@ -25,12 +21,5 @@ volumes:
   mydata:
   mydata:
     driver: azure_file
     driver: azure_file
     driver_opts:
     driver_opts:
-      share_name: gtashare1      
-      storage_account_name: gtastorageaccount1       
-      storage_account_key: UZyyUyZJA0LYrPrXqvB+HP+gGWD0K54LNmtfV+xwGQ18JufaAQ7vtUhcJoEcFUUrm40mehLKtvi4n58w0ivDtQ==
-
-secrets:
-  mysecret1:
-    file: ./my_secret1.txt
-  mysecret2:
-    file: ./my_secret2.txt
+      share_name: minecraft-volume
+      storage_account_name: minecraftdocker