1
0
Эх сурвалжийг харах

Interactive context create, adding method CreateContextData to CloudService interface, so Cloud Backends can provide a custom context creation method.

Guillaume Tardif 5 жил өмнө
parent
commit
5675763856

+ 14 - 9
azure/aci.go

@@ -268,28 +268,33 @@ func getSubscriptionsClient() (subscription.SubscriptionsClient, error) {
 	return subc, nil
 }
 
-// GetGroupsClient ...
-func GetGroupsClient(subscriptionID string) resources.GroupsClient {
+func getGroupsClient(subscriptionID string) resources.GroupsClient {
 	groupsClient := resources.NewGroupsClient(subscriptionID)
 	authorizer, _ := login.NewAuthorizerFromLogin()
 	groupsClient.Authorizer = authorizer
 	return groupsClient
 }
 
-// GetSubscriptionID ...
-func GetSubscriptionID(ctx context.Context) (string, error) {
+func getSubscriptionIDs(ctx context.Context) ([]subscription.Model, error) {
 	c, err := getSubscriptionsClient()
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 	res, err := c.List(ctx)
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 	subs := res.Values()
+
 	if len(subs) == 0 {
-		return "", errors.New("no subscriptions found")
+		return nil, errors.New("no subscriptions found")
+	}
+	for res.NotDone() {
+		err = res.NextWithContext(ctx)
+		if err != nil {
+			return nil, err
+		}
+		subs = append(subs, res.Values()...)
 	}
-	sub := subs[0]
-	return *sub.SubscriptionID, nil
+	return subs, nil
 }

+ 4 - 0
azure/backend.go

@@ -282,3 +282,7 @@ type aciCloudService struct {
 func (cs *aciCloudService) Login(ctx context.Context, params map[string]string) error {
 	return cs.loginService.Login(ctx)
 }
+
+func (cs *aciCloudService) CreateContextData(ctx context.Context, params map[string]string) (interface{}, string, error) {
+	return createContextData(ctx, params, cliUserSelector{})
+}

+ 175 - 0
azure/context.go

@@ -0,0 +1,175 @@
+/*
+	Copyright (c) 2020 Docker Inc.
+
+	Permission is hereby granted, free of charge, to any person
+	obtaining a copy of this software and associated documentation
+	files (the "Software"), to deal in the Software without
+	restriction, including without limitation the rights to use, copy,
+	modify, merge, publish, distribute, sublicense, and/or sell copies
+	of the Software, and to permit persons to whom the Software is
+	furnished to do so, subject to the following conditions:
+
+	The above copyright notice and this permission notice shall be
+	included in all copies or substantial portions of the Software.
+
+	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+	EXPRESS OR IMPLIED,
+	INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+	HOLDERS BE LIABLE FOR ANY CLAIM,
+	DAMAGES OR OTHER LIABILITY,
+	WHETHER IN AN ACTION OF CONTRACT,
+	TORT OR OTHERWISE,
+	ARISING FROM, OUT OF OR IN CONNECTION WITH
+	THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+package azure
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/Azure/azure-sdk-for-go/profiles/preview/preview/subscription/mgmt/subscription"
+	"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-05-01/resources"
+
+	"os"
+
+	"github.com/AlecAivazis/survey/v2"
+
+	"github.com/docker/api/context/store"
+
+	"github.com/google/uuid"
+	"github.com/pkg/errors"
+	"github.com/tj/survey/terminal"
+)
+
+func createContextData(ctx context.Context, opts map[string]string, selector userSelector) (interface{}, string, error) {
+	var subscriptionID string
+	if opts["aciSubscriptionID"] != "" {
+		subscriptionID = opts["aciSubscriptionID"]
+	} else {
+		subs, err := getSubscriptionIDs(ctx)
+		if err != nil {
+			return nil, "", err
+		}
+		subscriptionID, err = chooseSub(subs, selector)
+		if err != nil {
+			return nil, "", err
+		}
+	}
+
+	gc := getGroupsClient(subscriptionID)
+	var group resources.Group
+	var err error
+
+	if opts["aciResourceGroup"] != "" {
+		group, err = gc.Get(ctx, opts["aciResourceGroup"])
+		if err != nil {
+			return nil, "", errors.Wrapf(err, "Could not find resource group %q", opts["aciResourceGroup"])
+		}
+	} else {
+		groupResponse, err := gc.List(ctx, "", nil)
+		if err != nil {
+			return nil, "", err
+		}
+
+		groups := groupResponse.Values()
+		group, err = chooseGroup(ctx, gc, opts, groups, selector)
+		if err != nil {
+			return nil, "", err
+		}
+	}
+
+	location := opts["aciLocation"]
+	if location == "" {
+		location = *group.Location
+	}
+
+	description := fmt.Sprintf("%s@%s", *group.Name, location)
+	if opts["description"] != "" {
+		description = fmt.Sprintf("%s (%s)", opts["description"], description)
+	}
+
+	return store.AciContext{
+		SubscriptionID: subscriptionID,
+		Location:       location,
+		ResourceGroup:  *group.Name,
+	}, description, nil
+}
+
+func createGroup(ctx context.Context, gc resources.GroupsClient, location string) (resources.Group, error) {
+	if location == "" {
+		location = "eastus"
+	}
+	gid := uuid.New().String()
+	g, err := gc.CreateOrUpdate(ctx, gid, resources.Group{
+		Location: &location,
+	})
+	if err != nil {
+		return resources.Group{}, err
+	}
+
+	fmt.Printf("Resource group %q (%s) created\n", *g.Name, *g.Location)
+
+	return g, nil
+}
+
+func chooseGroup(ctx context.Context, gc resources.GroupsClient, opts map[string]string, groups []resources.Group, selector userSelector) (resources.Group, error) {
+	groupNames := []string{"create a new resource group"}
+	for _, g := range groups {
+		groupNames = append(groupNames, fmt.Sprintf("%s (%s)", *g.Name, *g.Location))
+	}
+
+	group, err := selector.userSelect("Choose a resource group", groupNames)
+	if err != nil {
+		if err == terminal.InterruptErr {
+			os.Exit(0)
+		}
+
+		return resources.Group{}, err
+	}
+
+	if group == 0 {
+		return createGroup(ctx, gc, opts["aciLocation"])
+	}
+
+	return groups[group-1], nil
+}
+
+func chooseSub(subs []subscription.Model, selector userSelector) (string, error) {
+	if len(subs) == 1 {
+		sub := subs[0]
+		fmt.Println("Using only available subscription : " + *sub.DisplayName + "(" + *sub.SubscriptionID + ")")
+		return *sub.SubscriptionID, nil
+	}
+	var options []string
+	for _, sub := range subs {
+		options = append(options, *sub.DisplayName+"("+*sub.SubscriptionID+")")
+	}
+	selected, err := selector.userSelect("Select a subscription ID", options)
+	if err != nil {
+		if err == terminal.InterruptErr {
+			os.Exit(0)
+		}
+		return "", err
+	}
+	return *subs[selected].SubscriptionID, nil
+}
+
+type userSelector interface {
+	userSelect(message string, options []string) (int, error)
+}
+
+type cliUserSelector struct{}
+
+func (us cliUserSelector) userSelect(message string, options []string) (int, error) {
+	qs := &survey.Select{
+		Message: message,
+		Options: options,
+	}
+	var selected int
+	err := survey.AskOne(qs, &selected, nil)
+	return selected, err
+}

+ 36 - 8
cli/cmd/context/create.go

@@ -30,12 +30,17 @@ package context
 import (
 	"context"
 
+	"github.com/pkg/errors"
+
+	"github.com/docker/api/client"
+
 	"github.com/spf13/cobra"
 
 	"github.com/docker/api/context/store"
 )
 
-type createOpts struct {
+// AciCreateOpts Options for ACI context create
+type AciCreateOpts struct {
 	description       string
 	aciLocation       string
 	aciSubscriptionID string
@@ -43,7 +48,7 @@ type createOpts struct {
 }
 
 func createCommand() *cobra.Command {
-	var opts createOpts
+	var opts AciCreateOpts
 	cmd := &cobra.Command{
 		Use:   "create CONTEXT BACKEND [OPTIONS]",
 		Short: "Create a context",
@@ -61,13 +66,36 @@ func createCommand() *cobra.Command {
 	return cmd
 }
 
-func runCreate(ctx context.Context, opts createOpts, name string, contextType string) error {
+func runCreate(ctx context.Context, opts AciCreateOpts, name string, contextType string) error {
+	var description string
+	var contextData interface{}
+
 	switch contextType {
 	case "aci":
-		return createACIContext(ctx, name, opts)
-	default:
-		s := store.ContextStore(ctx)
-		// TODO: we need to implement different contexts for known backends
-		return s.Create(name, contextType, opts.description, store.ExampleContext{})
+		cs, err := client.GetCloudService(ctx, "aci")
+		if err != nil {
+			return errors.Wrap(err, "cannot connect to backend")
+		}
+		params := map[string]string{
+			"aciSubscriptionId": opts.aciSubscriptionID,
+			"aciResourceGroup":  opts.aciResourceGroup,
+			"aciLocation":       opts.aciLocation,
+			"description":       opts.description,
+		}
+		contextData, description, err = cs.CreateContextData(ctx, params)
+		if err != nil {
+			return errors.Wrap(err, "cannot create context")
+		}
+	default: // TODO: we need to implement different contexts for known backends
+		description = opts.description
+		contextData = store.ExampleContext{}
 	}
+
+	s := store.ContextStore(ctx)
+	return s.Create(
+		name,
+		contextType,
+		description,
+		contextData,
+	)
 }

+ 0 - 55
cli/cmd/context/createaci.go

@@ -1,55 +0,0 @@
-/*
-	Copyright (c) 2020 Docker Inc.
-
-	Permission is hereby granted, free of charge, to any person
-	obtaining a copy of this software and associated documentation
-	files (the "Software"), to deal in the Software without
-	restriction, including without limitation the rights to use, copy,
-	modify, merge, publish, distribute, sublicense, and/or sell copies
-	of the Software, and to permit persons to whom the Software is
-	furnished to do so, subject to the following conditions:
-
-	The above copyright notice and this permission notice shall be
-	included in all copies or substantial portions of the Software.
-
-	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-	EXPRESS OR IMPLIED,
-	INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-	HOLDERS BE LIABLE FOR ANY CLAIM,
-	DAMAGES OR OTHER LIABILITY,
-	WHETHER IN AN ACTION OF CONTRACT,
-	TORT OR OTHERWISE,
-	ARISING FROM, OUT OF OR IN CONNECTION WITH
-	THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-*/
-
-package context
-
-import (
-	"context"
-	"fmt"
-
-	"github.com/docker/api/context/store"
-)
-
-func createACIContext(ctx context.Context, name string, opts createOpts) error {
-	s := store.ContextStore(ctx)
-
-	description := fmt.Sprintf("%s@%s", opts.aciResourceGroup, opts.aciLocation)
-	if opts.description != "" {
-		description = fmt.Sprintf("%s (%s)", opts.description, description)
-	}
-
-	return s.Create(
-		name,
-		store.AciContextType,
-		description,
-		store.AciContext{
-			SubscriptionID: opts.aciSubscriptionID,
-			Location:       opts.aciLocation,
-			ResourceGroup:  opts.aciResourceGroup,
-		},
-	)
-}

+ 6 - 0
context/cloud/api.go

@@ -10,6 +10,8 @@ import (
 type Service interface {
 	// Login login to cloud provider
 	Login(ctx context.Context, params map[string]string) error
+	// Login login to cloud provider
+	CreateContextData(ctx context.Context, params map[string]string) (contextData interface{}, description string, err error)
 }
 
 // NotImplementedCloudService to use for backend that don't provide cloud services
@@ -23,3 +25,7 @@ type notImplementedCloudService struct {
 func (cs notImplementedCloudService) Login(ctx context.Context, params map[string]string) error {
 	return errdefs.ErrNotImplemented
 }
+
+func (cs notImplementedCloudService) CreateContextData(ctx context.Context, params map[string]string) (interface{}, string, error) {
+	return nil, "", errdefs.ErrNotImplemented
+}

+ 3 - 0
go.mod

@@ -3,6 +3,7 @@ module github.com/docker/api
 go 1.14
 
 require (
+	github.com/AlecAivazis/survey/v2 v2.0.7
 	github.com/Azure/azure-sdk-for-go v42.0.0+incompatible
 	github.com/Azure/azure-storage-file-go v0.7.0
 	github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
@@ -24,6 +25,7 @@ require (
 	github.com/gobwas/pool v0.2.0 // indirect
 	github.com/gobwas/ws v1.0.3
 	github.com/golang/protobuf v1.4.1
+	github.com/google/uuid v1.1.1
 	github.com/gorilla/mux v1.7.4 // indirect
 	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
 	github.com/hashicorp/go-multierror v1.1.0
@@ -38,6 +40,7 @@ require (
 	github.com/spf13/cobra v1.0.0
 	github.com/spf13/pflag v1.0.5
 	github.com/stretchr/testify v1.5.1
+	github.com/tj/survey v2.0.6+incompatible
 	golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0
 	golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
 	golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 // indirect

+ 24 - 0
go.sum

@@ -1,4 +1,6 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/AlecAivazis/survey/v2 v2.0.7 h1:+f825XHLse/hWd2tE/V5df04WFGimk34Eyg/z35w/rc=
+github.com/AlecAivazis/survey/v2 v2.0.7/go.mod h1:mlizQTaPjnR4jcpwRSaSlkbsRfYFEyKgLQvYTzxxiHA=
 github.com/Azure/azure-pipeline-go v0.2.1 h1:OLBdZJ3yvOn2MezlWvbrBMTEUQC72zAftRZOMdj5HYo=
 github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4=
 github.com/Azure/azure-sdk-for-go v42.0.0+incompatible h1:yz6sFf5bHZ+gEOQVuK5JhPqTTAmv+OvSLSaqgzqaCwY=
@@ -34,6 +36,8 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
+github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
+github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
@@ -126,6 +130,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
 github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
@@ -138,6 +144,8 @@ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv
 github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
 github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
+github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
@@ -148,6 +156,8 @@ github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
@@ -158,16 +168,24 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.5 h1:hyz3dwM5QLc1Rfoz4FuWJQG5BN7tc6K1MndAUnGpQr4=
 github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149 h1:HfxbT6/JcvIljmERptWhwa8XzP7H3T+Z2N26gTsaDaA=
 github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
+github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw=
 github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
+github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@@ -245,11 +263,14 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/tj/survey v2.0.6+incompatible h1:tVgc1+kmYX9R0CEoHaTczapjdc4GaJla0VAB7O+w1So=
+github.com/tj/survey v2.0.6+incompatible/go.mod h1:vLPzQYAOKWgXqr5jV9luQXJuoXKHOg0ltn5FEw1Nz0c=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
@@ -266,6 +287,7 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
 golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -298,9 +320,11 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

+ 6 - 7
tests/aci-e2e/e2e-aci_test.go

@@ -15,7 +15,6 @@ import (
 	log "github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/suite"
 
-	"github.com/docker/api/azure"
 	"github.com/docker/api/context/store"
 	"github.com/docker/api/tests/aci-e2e/storage"
 	. "github.com/docker/api/tests/framework"
@@ -64,9 +63,9 @@ func (s *E2eACISuite) TestContextDefault() {
 func (s *E2eACISuite) TestACIBackend() {
 	It("creates a new aci context for tests", func() {
 		setupTestResourceGroup(resourceGroupName)
-		var err error
-		subscriptionID, err = azure.GetSubscriptionID(context.TODO())
+		models, err := azure.getSubscriptionIDs(context.TODO())
 		Expect(err).To(BeNil())
+		subscriptionID = *models[0].SubscriptionID
 
 		s.NewDockerCommand("context", "create", contextName, "aci", "--aci-subscription-id", subscriptionID, "--aci-resource-group", resourceGroupName, "--aci-location", location).ExecOrDie()
 		// Expect(output).To(ContainSubstring("ACI context acitest created"))
@@ -228,9 +227,9 @@ func TestE2eACI(t *testing.T) {
 func setupTestResourceGroup(groupName string) {
 	log.Println("Creating resource group " + resourceGroupName)
 	ctx := context.TODO()
-	subscriptionID, err := azure.GetSubscriptionID(ctx)
+	models, err := azure.getSubscriptionIDs(ctx)
 	Expect(err).To(BeNil())
-	gc := azure.GetGroupsClient(subscriptionID)
+	gc := azure.getGroupsClient(*models[0].SubscriptionID)
 	_, err = gc.CreateOrUpdate(ctx, groupName, resources.Group{
 		Location: to.StringPtr(location),
 	})
@@ -240,9 +239,9 @@ func setupTestResourceGroup(groupName string) {
 func deleteResourceGroup(groupName string) {
 	log.Println("Deleting resource group " + resourceGroupName)
 	ctx := context.TODO()
-	subscriptionID, err := azure.GetSubscriptionID(ctx)
+	models, err := azure.getSubscriptionIDs(ctx)
 	Expect(err).To(BeNil())
-	gc := azure.GetGroupsClient(subscriptionID)
+	gc := azure.getGroupsClient(*models[0].SubscriptionID)
 	_, err = gc.Delete(ctx, groupName)
 	Expect(err).To(BeNil())
 }