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
This commit is contained in:
Guillaume Tardif 2020-08-14 17:55:56 +02:00 committed by GitHub
commit 27e7a0ced3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 190 additions and 195 deletions

View File

@ -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
}

View File

@ -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 {

View File

@ -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] accountKey, err := helper.GetAzureStorageAccountKey(ctx, accountName)
if !ok { if err != nil {
return nil, nil, fmt.Errorf("cannot retrieve account key for Azurefile") return nil, nil, err
} }
aciVolume := containerinstance.Volume{ aciVolume := containerinstance.Volume{
Name: to.StringPtr(name), Name: to.StringPtr(name),

View File

@ -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

View File

@ -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.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.name = name v.name = name
v.target = volumeURL.Path tokens := strings.Split(s, "@")
if v.target == "" { if len(tokens) < 2 || tokens[0] == "" {
// Do not use filepath.Join, on Windows it will replace / by \ return errors.Wrapf(errdefs.ErrParsingFailed, "volume specification %q does not include a storage account before '@'", 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.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

View File

@ -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", "myuser1@myshare1:/my/path/to/target1",
"myuser2:mykey2@myshare2/my/path/to/target2", "myuser2@myshare2:/my/path/to/target2",
"myuser3:mykey3@mydefaultsharename", // Use default placement at '/run/volumes/<share_name>' "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@"}) _, _, err := GetRunVolumes([]string{"myaccount@"})
assert.Assert(t, errdefs.IsErrParsingFailed(err)) assert.ErrorContains(t, err, "does not include a storage file share after '@'")
assert.ErrorContains(t, err, "does not include a storage file share")
} }
func TestGetRunVolumesMissingUser(t *testing.T) { func TestGetRunVolumesMissingUser(t *testing.T) {
_, _, err := GetRunVolumes([]string{":mykey@myshare"}) _, _, err := GetRunVolumes([]string{"@myshare"})
assert.Assert(t, errdefs.IsErrParsingFailed(err)) assert.ErrorContains(t, err, "does not include a storage account before '@'")
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")
} }
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, "does not include a storage account before '@'")
assert.ErrorContains(t, err, "no share specified")
} }

82
aci/login/client.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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",
) )

View File

@ -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 share_name: minecraft-volume
storage_account_name: gtastorageaccount1 storage_account_name: minecraftdocker
storage_account_key: UZyyUyZJA0LYrPrXqvB+HP+gGWD0K54LNmtfV+xwGQ18JufaAQ7vtUhcJoEcFUUrm40mehLKtvi4n58w0ivDtQ==
secrets:
mysecret1:
file: ./my_secret1.txt
mysecret2:
file: ./my_secret2.txt