From 5675763856c3f04e854e811aada3a2c1117625a0 Mon Sep 17 00:00:00 2001 From: Guillaume Tardif Date: Tue, 2 Jun 2020 09:49:30 +0200 Subject: [PATCH 1/3] Interactive context create, adding method CreateContextData to CloudService interface, so Cloud Backends can provide a custom context creation method. --- azure/aci.go | 23 +++-- azure/backend.go | 4 + azure/context.go | 175 ++++++++++++++++++++++++++++++++++ cli/cmd/context/create.go | 44 +++++++-- cli/cmd/context/createaci.go | 55 ----------- context/cloud/api.go | 6 ++ go.mod | 3 + go.sum | 24 +++++ tests/aci-e2e/e2e-aci_test.go | 13 ++- 9 files changed, 268 insertions(+), 79 deletions(-) create mode 100644 azure/context.go delete mode 100644 cli/cmd/context/createaci.go diff --git a/azure/aci.go b/azure/aci.go index 9d2cf58fd..20aef8412 100644 --- a/azure/aci.go +++ b/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") } - sub := subs[0] - return *sub.SubscriptionID, nil + for res.NotDone() { + err = res.NextWithContext(ctx) + if err != nil { + return nil, err + } + subs = append(subs, res.Values()...) + } + return subs, nil } diff --git a/azure/backend.go b/azure/backend.go index 99e34a888..073120e19 100644 --- a/azure/backend.go +++ b/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{}) +} diff --git a/azure/context.go b/azure/context.go new file mode 100644 index 000000000..640b77b22 --- /dev/null +++ b/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 +} diff --git a/cli/cmd/context/create.go b/cli/cmd/context/create.go index 25ffd56c2..c04ecf9e8 100644 --- a/cli/cmd/context/create.go +++ b/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, + ) } diff --git a/cli/cmd/context/createaci.go b/cli/cmd/context/createaci.go deleted file mode 100644 index ceb23f3c6..000000000 --- a/cli/cmd/context/createaci.go +++ /dev/null @@ -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, - }, - ) -} diff --git a/context/cloud/api.go b/context/cloud/api.go index a00d3db7a..cae625d2f 100644 --- a/context/cloud/api.go +++ b/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 +} diff --git a/go.mod b/go.mod index 3f7c826b9..6fe82548c 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index ea77a4a4b..1465ba723 100644 --- a/go.sum +++ b/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= diff --git a/tests/aci-e2e/e2e-aci_test.go b/tests/aci-e2e/e2e-aci_test.go index 8e217ab7e..c0678e28e 100644 --- a/tests/aci-e2e/e2e-aci_test.go +++ b/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()) } From 774bfea341cac675ccf3075d5b86007e69fb3163 Mon Sep 17 00:00:00 2001 From: Guillaume Tardif Date: Tue, 2 Jun 2020 23:33:41 +0200 Subject: [PATCH 2/3] Refactoring, add unit test or various interactive context creation --- azure/aci.go | 45 ------- azure/aciResourceGroupHelper.go | 105 ++++++++++++++++ azure/backend.go | 3 +- azure/context.go | 52 ++++---- azure/context_test.go | 210 +++++++++++++++++++++++++++++++ tests/aci-e2e/e2e-aci_test.go | 23 ++-- tests/aci-e2e/storage/storage.go | 3 +- 7 files changed, 362 insertions(+), 79 deletions(-) create mode 100644 azure/aciResourceGroupHelper.go create mode 100644 azure/context_test.go diff --git a/azure/aci.go b/azure/aci.go index 20aef8412..ebce23044 100644 --- a/azure/aci.go +++ b/azure/aci.go @@ -9,8 +9,6 @@ import ( "strings" "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/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/to" @@ -20,8 +18,6 @@ import ( "github.com/pkg/errors" "github.com/docker/api/azure/login" - "github.com/docker/api/errdefs" - "github.com/docker/api/context/store" ) @@ -257,44 +253,3 @@ func getContainerClient(subscriptionID string) (containerinstance.ContainerClien containerClient.Authorizer = auth return containerClient, nil } - -func getSubscriptionsClient() (subscription.SubscriptionsClient, error) { - subc := subscription.NewSubscriptionsClient() - authorizer, err := login.NewAuthorizerFromLogin() - if err != nil { - return subscription.SubscriptionsClient{}, errors.Wrap(errdefs.ErrLoginFailed, err.Error()) - } - subc.Authorizer = authorizer - return subc, nil -} - -func getGroupsClient(subscriptionID string) resources.GroupsClient { - groupsClient := resources.NewGroupsClient(subscriptionID) - authorizer, _ := login.NewAuthorizerFromLogin() - groupsClient.Authorizer = authorizer - return groupsClient -} - -func getSubscriptionIDs(ctx context.Context) ([]subscription.Model, error) { - c, err := getSubscriptionsClient() - if err != nil { - return nil, err - } - res, err := c.List(ctx) - if err != nil { - return nil, err - } - subs := res.Values() - - if len(subs) == 0 { - 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()...) - } - return subs, nil -} diff --git a/azure/aciResourceGroupHelper.go b/azure/aciResourceGroupHelper.go new file mode 100644 index 000000000..d63770706 --- /dev/null +++ b/azure/aciResourceGroupHelper.go @@ -0,0 +1,105 @@ +package azure + +import ( + "context" + + "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/pkg/errors" + + "github.com/docker/api/azure/login" + "github.com/docker/api/errdefs" +) + +// ACIResourceGroupHelper interface to manage resource groups and subscription IDs +type ACIResourceGroupHelper interface { + GetSubscriptionIDs(ctx context.Context) ([]subscription.Model, error) + ListGroups(ctx context.Context, subscriptionID string) ([]resources.Group, error) + GetGroup(ctx context.Context, subscriptionID string, groupName string) (resources.Group, error) + CreateOrUpdate(ctx context.Context, subscriptionID string, resourceGroupName string, parameters resources.Group) (result resources.Group, err error) + Delete(ctx context.Context, subscriptionID string, resourceGroupName string) error +} + +type aciResourceGroupHelperImpl struct { +} + +// NewACIResourceGroupHelper create a new ACIResourceGroupHelper +func NewACIResourceGroupHelper() ACIResourceGroupHelper { + return aciResourceGroupHelperImpl{} +} + +// GetGroup get a resource group from its name +func (mgt aciResourceGroupHelperImpl) GetGroup(ctx context.Context, subscriptionID string, groupName string) (resources.Group, error) { + gc := getGroupsClient(subscriptionID) + return gc.Get(ctx, groupName) +} + +// ListGroups list resource groups +func (mgt aciResourceGroupHelperImpl) ListGroups(ctx context.Context, subscriptionID string) ([]resources.Group, error) { + gc := getGroupsClient(subscriptionID) + groupResponse, err := gc.List(ctx, "", nil) + if err != nil { + return nil, err + } + + groups := groupResponse.Values() + return groups, nil +} + +// 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) { + gc := getGroupsClient(subscriptionID) + return gc.CreateOrUpdate(ctx, resourceGroupName, parameters) +} + +// Delete deletes a resource group +func (mgt aciResourceGroupHelperImpl) Delete(ctx context.Context, subscriptionID string, resourceGroupName string) (err error) { + gc := getGroupsClient(subscriptionID) + future, err := gc.Delete(ctx, resourceGroupName) + if err != nil { + return err + } + return future.WaitForCompletionRef(ctx, gc.Client) +} + +// GetSubscriptionIDs Return available subscription IDs based on azure login +func (mgt aciResourceGroupHelperImpl) GetSubscriptionIDs(ctx context.Context) ([]subscription.Model, error) { + c, err := getSubscriptionsClient() + if err != nil { + return nil, err + } + res, err := c.List(ctx) + if err != nil { + return nil, err + } + subs := res.Values() + + if len(subs) == 0 { + 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()...) + } + return subs, nil +} + +func getSubscriptionsClient() (subscription.SubscriptionsClient, error) { + subc := subscription.NewSubscriptionsClient() + authorizer, err := login.NewAuthorizerFromLogin() + if err != nil { + return subscription.SubscriptionsClient{}, errors.Wrap(errdefs.ErrLoginFailed, err.Error()) + } + subc.Authorizer = authorizer + return subc, nil +} + +func getGroupsClient(subscriptionID string) resources.GroupsClient { + groupsClient := resources.NewGroupsClient(subscriptionID) + authorizer, _ := login.NewAuthorizerFromLogin() + groupsClient.Authorizer = authorizer + return groupsClient +} diff --git a/azure/backend.go b/azure/backend.go index 073120e19..b990424dc 100644 --- a/azure/backend.go +++ b/azure/backend.go @@ -284,5 +284,6 @@ func (cs *aciCloudService) Login(ctx context.Context, params map[string]string) } func (cs *aciCloudService) CreateContextData(ctx context.Context, params map[string]string) (interface{}, string, error) { - return createContextData(ctx, params, cliUserSelector{}) + contextHelper := newContextCreateHelper() + return contextHelper.createContextData(ctx, params) } diff --git a/azure/context.go b/azure/context.go index 640b77b22..9c8d03ac1 100644 --- a/azure/context.go +++ b/azure/context.go @@ -30,53 +30,59 @@ 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/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" "github.com/google/uuid" "github.com/pkg/errors" "github.com/tj/survey/terminal" + + "github.com/docker/api/context/store" ) -func createContextData(ctx context.Context, opts map[string]string, selector userSelector) (interface{}, string, error) { +type contextCreateACIHelper struct { + selector userSelector + resourceGroupHelper ACIResourceGroupHelper +} + +func newContextCreateHelper() contextCreateACIHelper { + return contextCreateACIHelper{ + selector: cliUserSelector{}, + resourceGroupHelper: aciResourceGroupHelperImpl{}, + } +} + +func (helper contextCreateACIHelper) createContextData(ctx context.Context, opts map[string]string) (interface{}, string, error) { var subscriptionID string if opts["aciSubscriptionID"] != "" { subscriptionID = opts["aciSubscriptionID"] } else { - subs, err := getSubscriptionIDs(ctx) + subs, err := helper.resourceGroupHelper.GetSubscriptionIDs(ctx) if err != nil { return nil, "", err } - subscriptionID, err = chooseSub(subs, selector) + subscriptionID, err = helper.chooseSub(subs) 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"]) + group, err = helper.resourceGroupHelper.GetGroup(ctx, subscriptionID, 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) + groups, err := helper.resourceGroupHelper.ListGroups(ctx, subscriptionID) if err != nil { return nil, "", err } - - groups := groupResponse.Values() - group, err = chooseGroup(ctx, gc, opts, groups, selector) + group, err = helper.chooseGroup(ctx, subscriptionID, opts, groups) if err != nil { return nil, "", err } @@ -99,12 +105,12 @@ func createContextData(ctx context.Context, opts map[string]string, selector use }, description, nil } -func createGroup(ctx context.Context, gc resources.GroupsClient, location string) (resources.Group, error) { +func (helper contextCreateACIHelper) createGroup(ctx context.Context, subscriptionID, location string) (resources.Group, error) { if location == "" { location = "eastus" } gid := uuid.New().String() - g, err := gc.CreateOrUpdate(ctx, gid, resources.Group{ + g, err := helper.resourceGroupHelper.CreateOrUpdate(ctx, subscriptionID, gid, resources.Group{ Location: &location, }) if err != nil { @@ -116,13 +122,13 @@ func createGroup(ctx context.Context, gc resources.GroupsClient, location string return g, nil } -func chooseGroup(ctx context.Context, gc resources.GroupsClient, opts map[string]string, groups []resources.Group, selector userSelector) (resources.Group, error) { +func (helper contextCreateACIHelper) chooseGroup(ctx context.Context, subscriptionID string, opts map[string]string, groups []resources.Group) (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) + group, err := helper.selector.userSelect("Select a resource group", groupNames) if err != nil { if err == terminal.InterruptErr { os.Exit(0) @@ -132,13 +138,13 @@ func chooseGroup(ctx context.Context, gc resources.GroupsClient, opts map[string } if group == 0 { - return createGroup(ctx, gc, opts["aciLocation"]) + return helper.createGroup(ctx, subscriptionID, opts["aciLocation"]) } return groups[group-1], nil } -func chooseSub(subs []subscription.Model, selector userSelector) (string, error) { +func (helper contextCreateACIHelper) chooseSub(subs []subscription.Model) (string, error) { if len(subs) == 1 { sub := subs[0] fmt.Println("Using only available subscription : " + *sub.DisplayName + "(" + *sub.SubscriptionID + ")") @@ -148,7 +154,7 @@ func chooseSub(subs []subscription.Model, selector userSelector) (string, error) for _, sub := range subs { options = append(options, *sub.DisplayName+"("+*sub.SubscriptionID+")") } - selected, err := selector.userSelect("Select a subscription ID", options) + selected, err := helper.selector.userSelect("Select a subscription ID", options) if err != nil { if err == terminal.InterruptErr { os.Exit(0) diff --git a/azure/context_test.go b/azure/context_test.go new file mode 100644 index 000000000..a927e6157 --- /dev/null +++ b/azure/context_test.go @@ -0,0 +1,210 @@ +package azure + +import ( + "context" + "testing" + + "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/go-autorest/autorest/to" + "github.com/pkg/errors" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" + + "github.com/docker/api/context/store" + + . "github.com/onsi/gomega" +) + +type ContextSuiteTest struct { + suite.Suite + mockUserSelector *MockUserSelector + mockResourceGroupHeper *MockResourceGroupHelper + contextCreateHelper contextCreateACIHelper +} + +func (suite *ContextSuiteTest) BeforeTest(suiteName, testName string) { + suite.mockUserSelector = &MockUserSelector{} + suite.mockResourceGroupHeper = &MockResourceGroupHelper{} + suite.contextCreateHelper = contextCreateACIHelper{ + suite.mockUserSelector, + suite.mockResourceGroupHeper, + } +} + +func (suite *ContextSuiteTest) TestCreateSpecifiedSubscriptionAndGroup() { + ctx := context.TODO() + opts := options("1234", "myResourceGroup") + suite.mockResourceGroupHeper.On("GetGroup", ctx, "1234", "myResourceGroup").Return(group("myResourceGroup", "eastus"), nil) + + data, description, err := suite.contextCreateHelper.createContextData(ctx, opts) + + Expect(err).To(BeNil()) + Expect(description).To(Equal("myResourceGroup@eastus")) + Expect(data).To(Equal(aciContext("1234", "myResourceGroup", "eastus"))) +} + +func (suite *ContextSuiteTest) TestErrorOnNonExistentResourceGroup() { + ctx := context.TODO() + opts := options("1234", "myResourceGroup") + notFoundError := errors.New(`Not Found: "myResourceGroup"`) + suite.mockResourceGroupHeper.On("GetGroup", ctx, "1234", "myResourceGroup").Return(resources.Group{}, notFoundError) + + data, description, err := suite.contextCreateHelper.createContextData(ctx, opts) + + Expect(data).To(BeNil()) + Expect(description).To(Equal("")) + Expect(err.Error()).To(Equal("Could not find resource group \"myResourceGroup\": Not Found: \"myResourceGroup\"")) +} + +func (suite *ContextSuiteTest) TestCreateNewResourceGroup() { + ctx := context.TODO() + opts := options("1234", "") + suite.mockResourceGroupHeper.On("GetGroup", ctx, "1234", "myResourceGroup").Return(group("myResourceGroup", "eastus"), nil) + + selectOptions := []string{"create a new resource group", "group1 (eastus)", "group2 (westeurope)"} + suite.mockUserSelector.On("userSelect", "Select a resource group", selectOptions).Return(0, nil) + suite.mockResourceGroupHeper.On("CreateOrUpdate", ctx, "1234", mock.AnythingOfType("string"), mock.AnythingOfType("resources.Group")).Return(group("newResourceGroup", "eastus"), nil) + suite.mockResourceGroupHeper.On("ListGroups", ctx, "1234").Return([]resources.Group{ + group("group1", "eastus"), + group("group2", "westeurope"), + }, nil) + + data, description, err := suite.contextCreateHelper.createContextData(ctx, opts) + + Expect(err).To(BeNil()) + Expect(description).To(Equal("newResourceGroup@eastus")) + Expect(data).To(Equal(aciContext("1234", "newResourceGroup", "eastus"))) +} + +func (suite *ContextSuiteTest) TestSelectExistingResourceGroup() { + ctx := context.TODO() + opts := options("1234", "") + selectOptions := []string{"create a new resource group", "group1 (eastus)", "group2 (westeurope)"} + suite.mockUserSelector.On("userSelect", "Select a resource group", selectOptions).Return(2, nil) + suite.mockResourceGroupHeper.On("ListGroups", ctx, "1234").Return([]resources.Group{ + group("group1", "eastus"), + group("group2", "westeurope"), + }, nil) + + data, description, err := suite.contextCreateHelper.createContextData(ctx, opts) + + Expect(err).To(BeNil()) + Expect(description).To(Equal("group2@westeurope")) + Expect(data).To(Equal(aciContext("1234", "group2", "westeurope"))) +} + +func (suite *ContextSuiteTest) TestSelectSingleSubscriptionIdAndExistingResourceGroup() { + ctx := context.TODO() + opts := options("", "") + suite.mockResourceGroupHeper.On("GetSubscriptionIDs", ctx).Return([]subscription.Model{subModel("123456", "Subscription1")}, nil) + + selectOptions := []string{"create a new resource group", "group1 (eastus)", "group2 (westeurope)"} + suite.mockUserSelector.On("userSelect", "Select a resource group", selectOptions).Return(2, nil) + suite.mockResourceGroupHeper.On("ListGroups", ctx, "123456").Return([]resources.Group{ + group("group1", "eastus"), + group("group2", "westeurope"), + }, nil) + + data, description, err := suite.contextCreateHelper.createContextData(ctx, opts) + + Expect(err).To(BeNil()) + Expect(description).To(Equal("group2@westeurope")) + Expect(data).To(Equal(aciContext("123456", "group2", "westeurope"))) +} + +func (suite *ContextSuiteTest) TestSelectSubscriptionIdAndExistingResourceGroup() { + ctx := context.TODO() + opts := options("", "") + sub1 := subModel("1234", "Subscription1") + sub2 := subModel("5678", "Subscription2") + + suite.mockResourceGroupHeper.On("GetSubscriptionIDs", ctx).Return([]subscription.Model{sub1, sub2}, nil) + + selectOptions := []string{"Subscription1(1234)", "Subscription2(5678)"} + suite.mockUserSelector.On("userSelect", "Select a subscription ID", selectOptions).Return(1, nil) + selectOptions = []string{"create a new resource group", "group1 (eastus)", "group2 (westeurope)"} + suite.mockUserSelector.On("userSelect", "Select a resource group", selectOptions).Return(2, nil) + suite.mockResourceGroupHeper.On("ListGroups", ctx, "5678").Return([]resources.Group{ + group("group1", "eastus"), + group("group2", "westeurope"), + }, nil) + + data, description, err := suite.contextCreateHelper.createContextData(ctx, opts) + + Expect(err).To(BeNil()) + Expect(description).To(Equal("group2@westeurope")) + Expect(data).To(Equal(aciContext("5678", "group2", "westeurope"))) +} + +func subModel(subID string, display string) subscription.Model { + return subscription.Model{ + SubscriptionID: to.StringPtr(subID), + DisplayName: to.StringPtr(display), + } +} + +func group(groupName string, location string) resources.Group { + return resources.Group{ + Name: to.StringPtr(groupName), + Location: to.StringPtr(location), + } +} + +func aciContext(subscriptionID string, resourceGroupName string, location string) store.AciContext { + return store.AciContext{ + SubscriptionID: subscriptionID, + Location: location, + ResourceGroup: resourceGroupName, + } +} + +func options(subscriptionID string, resourceGroupName string) map[string]string { + return map[string]string{ + "aciSubscriptionID": subscriptionID, + "aciResourceGroup": resourceGroupName, + } +} + +func TestContextSuite(t *testing.T) { + RegisterTestingT(t) + suite.Run(t, new(ContextSuiteTest)) +} + +type MockUserSelector struct { + mock.Mock +} + +func (s *MockUserSelector) userSelect(message string, options []string) (int, error) { + args := s.Called(message, options) + return args.Int(0), args.Error(1) +} + +type MockResourceGroupHelper struct { + mock.Mock +} + +func (s *MockResourceGroupHelper) GetSubscriptionIDs(ctx context.Context) ([]subscription.Model, error) { + args := s.Called(ctx) + return args.Get(0).([]subscription.Model), args.Error(1) +} + +func (s *MockResourceGroupHelper) ListGroups(ctx context.Context, subscriptionID string) ([]resources.Group, error) { + args := s.Called(ctx, subscriptionID) + return args.Get(0).([]resources.Group), args.Error(1) +} + +func (s *MockResourceGroupHelper) GetGroup(ctx context.Context, subscriptionID string, groupName string) (resources.Group, error) { + args := s.Called(ctx, subscriptionID, groupName) + return args.Get(0).(resources.Group), args.Error(1) +} + +func (s *MockResourceGroupHelper) CreateOrUpdate(ctx context.Context, subscriptionID string, resourceGroupName string, parameters resources.Group) (result resources.Group, err error) { + args := s.Called(ctx, subscriptionID, resourceGroupName, parameters) + return args.Get(0).(resources.Group), args.Error(1) +} + +func (s *MockResourceGroupHelper) Delete(ctx context.Context, subscriptionID string, resourceGroupName string) (err error) { + args := s.Called(ctx, subscriptionID, resourceGroupName) + return args.Error(0) +} diff --git a/tests/aci-e2e/e2e-aci_test.go b/tests/aci-e2e/e2e-aci_test.go index c0678e28e..69141a1b3 100644 --- a/tests/aci-e2e/e2e-aci_test.go +++ b/tests/aci-e2e/e2e-aci_test.go @@ -7,6 +7,8 @@ import ( "strings" "testing" + "github.com/docker/api/azure" + "github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/resources/mgmt/resources" azure_storage "github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/storage/mgmt/storage" "github.com/Azure/azure-storage-file-go/azfile" @@ -63,7 +65,8 @@ func (s *E2eACISuite) TestContextDefault() { func (s *E2eACISuite) TestACIBackend() { It("creates a new aci context for tests", func() { setupTestResourceGroup(resourceGroupName) - models, err := azure.getSubscriptionIDs(context.TODO()) + helper := azure.NewACIResourceGroupHelper() + models, err := helper.GetSubscriptionIDs(context.TODO()) Expect(err).To(BeNil()) subscriptionID = *models[0].SubscriptionID @@ -172,13 +175,14 @@ func (s *E2eACISuite) TestACIBackend() { } const ( - testStorageAccountName = "dockertestaccountname" - testShareName = "dockertestsharename" + testStorageAccountName = "dockertestaccount" + testShareName = "dockertestshare" testFileContent = "Volume mounted with success!" testFileName = "index.html" ) func createStorageAccount(aciContext store.AciContext, accountName string) azure_storage.Account { + log.Println("Creating storage account " + accountName) storageAccount, err := storage.CreateStorageAccount(context.TODO(), aciContext, accountName) Expect(err).To(BeNil()) Expect(*storageAccount.Name).To(Equal(accountName)) @@ -195,6 +199,7 @@ func getStorageKeys(aciContext store.AciContext, storageAccountName string) []az } func deleteStorageAccount(aciContext store.AciContext) { + log.Println("Deleting storage account " + testStorageAccountName) _, err := storage.DeleteStorageAccount(context.TODO(), aciContext, testStorageAccountName) Expect(err).To(BeNil()) } @@ -227,10 +232,10 @@ func TestE2eACI(t *testing.T) { func setupTestResourceGroup(groupName string) { log.Println("Creating resource group " + resourceGroupName) ctx := context.TODO() - models, err := azure.getSubscriptionIDs(ctx) + helper := azure.NewACIResourceGroupHelper() + models, err := helper.GetSubscriptionIDs(ctx) Expect(err).To(BeNil()) - gc := azure.getGroupsClient(*models[0].SubscriptionID) - _, err = gc.CreateOrUpdate(ctx, groupName, resources.Group{ + _, err = helper.CreateOrUpdate(ctx, *models[0].SubscriptionID, groupName, resources.Group{ Location: to.StringPtr(location), }) Expect(err).To(BeNil()) @@ -239,9 +244,9 @@ func setupTestResourceGroup(groupName string) { func deleteResourceGroup(groupName string) { log.Println("Deleting resource group " + resourceGroupName) ctx := context.TODO() - models, err := azure.getSubscriptionIDs(ctx) + helper := azure.NewACIResourceGroupHelper() + models, err := helper.GetSubscriptionIDs(ctx) Expect(err).To(BeNil()) - gc := azure.getGroupsClient(*models[0].SubscriptionID) - _, err = gc.Delete(ctx, groupName) + err = helper.Delete(ctx, *models[0].SubscriptionID, groupName) Expect(err).To(BeNil()) } diff --git a/tests/aci-e2e/storage/storage.go b/tests/aci-e2e/storage/storage.go index 8d616db95..d729d8501 100644 --- a/tests/aci-e2e/storage/storage.go +++ b/tests/aci-e2e/storage/storage.go @@ -2,6 +2,7 @@ package storage import ( "context" + "errors" "github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/storage/mgmt/storage" "github.com/Azure/go-autorest/autorest" @@ -25,7 +26,7 @@ func CreateStorageAccount(ctx context.Context, aciContext store.AciContext, acco return storage.Account{}, err } if !*result.NameAvailable { - return storage.Account{}, err + return storage.Account{}, errors.New("storage account name already exists" + accountName) } future, err := storageAccountsClient.Create( From 39140c8ea0a6cb75cd2a97f13238c8493c3efb3f Mon Sep 17 00:00:00 2001 From: Guillaume Tardif Date: Wed, 3 Jun 2020 12:05:58 +0200 Subject: [PATCH 3/3] Refactoring, add unit test or various interactive context creation --- azure/{aciResourceGroupHelper.go => resourcegroup.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename azure/{aciResourceGroupHelper.go => resourcegroup.go} (100%) diff --git a/azure/aciResourceGroupHelper.go b/azure/resourcegroup.go similarity index 100% rename from azure/aciResourceGroupHelper.go rename to azure/resourcegroup.go