Merge pull request #164 from docker/interactive_context_create

Interactive context create
This commit is contained in:
Guillaume Tardif 2020-06-03 18:06:41 +02:00 committed by GitHub
commit c37f666c07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 587 additions and 115 deletions

View File

@ -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,39 +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
}
// GetGroupsClient ...
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) {
c, err := getSubscriptionsClient()
if err != nil {
return "", err
}
res, err := c.List(ctx)
if err != nil {
return "", err
}
subs := res.Values()
if len(subs) == 0 {
return "", errors.New("no subscriptions found")
}
sub := subs[0]
return *sub.SubscriptionID, nil
}

View File

@ -282,3 +282,8 @@ 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) {
contextHelper := newContextCreateHelper()
return contextHelper.createContextData(ctx, params)
}

181
azure/context.go Normal file
View File

@ -0,0 +1,181 @@
/*
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"
"os"
"github.com/AlecAivazis/survey/v2"
"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"
)
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 := helper.resourceGroupHelper.GetSubscriptionIDs(ctx)
if err != nil {
return nil, "", err
}
subscriptionID, err = helper.chooseSub(subs)
if err != nil {
return nil, "", err
}
}
var group resources.Group
var err error
if 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 {
groups, err := helper.resourceGroupHelper.ListGroups(ctx, subscriptionID)
if err != nil {
return nil, "", err
}
group, err = helper.chooseGroup(ctx, subscriptionID, opts, groups)
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 (helper contextCreateACIHelper) createGroup(ctx context.Context, subscriptionID, location string) (resources.Group, error) {
if location == "" {
location = "eastus"
}
gid := uuid.New().String()
g, err := helper.resourceGroupHelper.CreateOrUpdate(ctx, subscriptionID, 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 (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 := helper.selector.userSelect("Select a resource group", groupNames)
if err != nil {
if err == terminal.InterruptErr {
os.Exit(0)
}
return resources.Group{}, err
}
if group == 0 {
return helper.createGroup(ctx, subscriptionID, opts["aciLocation"])
}
return groups[group-1], nil
}
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 + ")")
return *sub.SubscriptionID, nil
}
var options []string
for _, sub := range subs {
options = append(options, *sub.DisplayName+"("+*sub.SubscriptionID+")")
}
selected, err := helper.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
}

210
azure/context_test.go Normal file
View File

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

105
azure/resourcegroup.go Normal file
View File

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

View File

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

View File

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

View File

@ -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
go.mod
View File

@ -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
go.sum
View File

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

View File

@ -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"
@ -15,7 +17,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 +65,10 @@ 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())
helper := azure.NewACIResourceGroupHelper()
models, err := helper.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"))
@ -173,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))
@ -196,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())
}
@ -228,10 +232,10 @@ func TestE2eACI(t *testing.T) {
func setupTestResourceGroup(groupName string) {
log.Println("Creating resource group " + resourceGroupName)
ctx := context.TODO()
subscriptionID, err := azure.GetSubscriptionID(ctx)
helper := azure.NewACIResourceGroupHelper()
models, err := helper.GetSubscriptionIDs(ctx)
Expect(err).To(BeNil())
gc := azure.GetGroupsClient(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())
@ -240,9 +244,9 @@ func setupTestResourceGroup(groupName string) {
func deleteResourceGroup(groupName string) {
log.Println("Deleting resource group " + resourceGroupName)
ctx := context.TODO()
subscriptionID, err := azure.GetSubscriptionID(ctx)
helper := azure.NewACIResourceGroupHelper()
models, err := helper.GetSubscriptionIDs(ctx)
Expect(err).To(BeNil())
gc := azure.GetGroupsClient(subscriptionID)
_, err = gc.Delete(ctx, groupName)
err = helper.Delete(ctx, *models[0].SubscriptionID, groupName)
Expect(err).To(BeNil())
}

View File

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