Browse Source

Connecting it all

Signed-off-by: Guillaume Tardif <[email protected]>
Guillaume Tardif 5 years ago
parent
commit
08562b403e

+ 21 - 6
aci/backend.go

@@ -35,8 +35,8 @@ import (
 	"github.com/docker/compose-cli/aci/login"
 	"github.com/docker/compose-cli/api/compose"
 	"github.com/docker/compose-cli/api/containers"
-	"github.com/docker/compose-cli/api/volumes"
 	"github.com/docker/compose-cli/api/secrets"
+	"github.com/docker/compose-cli/api/volumes"
 	"github.com/docker/compose-cli/backend"
 	apicontext "github.com/docker/compose-cli/context"
 	"github.com/docker/compose-cli/context/cloud"
@@ -110,6 +110,9 @@ func getAciAPIService(aciCtx store.AciContext) *aciAPIService {
 		aciComposeService: &aciComposeService{
 			ctx: aciCtx,
 		},
+		aciVolumeService: &aciVolumeService{
+			ctx: aciCtx,
+		},
 	}
 }
 
@@ -169,20 +172,20 @@ func getContainerGroups(ctx context.Context, subscriptionID string, resourceGrou
 	var containerGroups []containerinstance.ContainerGroup
 	result, err := groupsClient.ListByResourceGroup(ctx, resourceGroup)
 	if err != nil {
-		return []containerinstance.ContainerGroup{}, err
+		return nil, err
 	}
 
 	for result.NotDone() {
 		containerGroups = append(containerGroups, result.Values()...)
 		if err := result.NextWithContext(ctx); err != nil {
-			return []containerinstance.ContainerGroup{}, err
+			return nil, err
 		}
 	}
 	var groups []containerinstance.ContainerGroup
 	for _, group := range containerGroups {
 		group, err := groupsClient.Get(ctx, resourceGroup, *group.Name)
 		if err != nil {
-			return []containerinstance.ContainerGroup{}, err
+			return nil, err
 		}
 		groups = append(groups, group)
 	}
@@ -507,11 +510,23 @@ type aciVolumeService struct {
 }
 
 func (cs *aciVolumeService) List(ctx context.Context) ([]volumes.Volume, error) {
-	return nil, nil
+	storageHelper := login.StorageAccountHelper{AciContext: cs.ctx}
+	return storageHelper.ListFileShare(ctx)
+}
+
+//VolumeCreateOptions options to create a new ACI volume
+type VolumeCreateOptions struct {
+	Account   string
+	Fileshare string
 }
 
 func (cs *aciVolumeService) Create(ctx context.Context, options interface{}) (volumes.Volume, error) {
-	return volumes.Volume{}, nil
+	opts, ok := options.(VolumeCreateOptions)
+	if !ok {
+		return volumes.Volume{}, errors.New("Could not read azure LoginParams struct from generic parameter")
+	}
+	storageHelper := login.StorageAccountHelper{AciContext: cs.ctx}
+	return storageHelper.CreateFileShare(ctx, opts.Account, opts.Fileshare)
 }
 
 type aciCloudService struct {

+ 1 - 6
aci/convert/convert.go

@@ -56,13 +56,8 @@ const (
 func ToContainerGroup(ctx context.Context, aciContext store.AciContext, p types.Project) (containerinstance.ContainerGroup, error) {
 	project := projectAciHelper(p)
 	containerGroupName := strings.ToLower(project.Name)
-	loginService, err := login.NewAzureLoginService()
-	if err != nil {
-		return containerinstance.ContainerGroup{}, err
-	}
 	storageHelper := login.StorageAccountHelper{
-		LoginService: *loginService,
-		AciContext:   aciContext,
+		AciContext: aciContext,
 	}
 	volumesCache, volumesSlice, err := project.getAciFileVolumes(ctx, storageHelper)
 	if err != nil {

+ 1 - 2
aci/login/client.go

@@ -67,7 +67,7 @@ func NewStorageAccountsClient(subscriptionID string) (storage.AccountsClient, er
 	return containerGroupsClient, nil
 }
 
-// NewStorageAccountsClient get client to manipulate storage accounts
+// NewFileShareClient get client to manipulate file shares
 func NewFileShareClient(subscriptionID string) (storage.FileSharesClient, error) {
 	containerGroupsClient := storage.NewFileSharesClient(subscriptionID)
 	err := setupClient(&containerGroupsClient.Client)
@@ -80,7 +80,6 @@ func NewFileShareClient(subscriptionID string) (storage.FileSharesClient, error)
 	return containerGroupsClient, nil
 }
 
-
 // NewSubscriptionsClient get subscription client
 func NewSubscriptionsClient() (subscription.SubscriptionsClient, error) {
 	subc := subscription.NewSubscriptionsClient()

+ 61 - 20
aci/login/storage_helper.go

@@ -19,8 +19,12 @@ package login
 import (
 	"context"
 	"fmt"
+
 	"github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage"
 
+	"github.com/docker/compose-cli/api/volumes"
+	"github.com/docker/compose-cli/errdefs"
+
 	"github.com/pkg/errors"
 
 	"github.com/docker/compose-cli/context/store"
@@ -28,8 +32,7 @@ import (
 
 // StorageAccountHelper helper for Azure Storage Account
 type StorageAccountHelper struct {
-	LoginService AzureLoginService
-	AciContext   store.AciContext
+	AciContext store.AciContext
 }
 
 // GetAzureStorageAccountKey retrieves the storage account ket from the current azure login
@@ -50,61 +53,99 @@ func (helper StorageAccountHelper) GetAzureStorageAccountKey(ctx context.Context
 	return *key.Value, nil
 }
 
-func (helper StorageAccountHelper) ListFileShare(ctx context.Context) ([]string, error) {
+// ListFileShare list file shares in all visible storage accounts
+func (helper StorageAccountHelper) ListFileShare(ctx context.Context) ([]volumes.Volume, error) {
 	aciContext := helper.AciContext
 	accountClient, err := NewStorageAccountsClient(aciContext.SubscriptionID)
 	if err != nil {
 		return nil, err
 	}
 	result, err := accountClient.ListByResourceGroup(ctx, aciContext.ResourceGroup)
+	if err != nil {
+		return nil, err
+	}
 	accounts := result.Value
 	fileShareClient, err := NewFileShareClient(aciContext.SubscriptionID)
-	fileShares := []string{}
+	if err != nil {
+		return nil, err
+	}
+	fileShares := []volumes.Volume{}
 	for _, account := range *accounts {
 		fileSharePage, err := fileShareClient.List(ctx, aciContext.ResourceGroup, *account.Name, "", "", "")
 		if err != nil {
 			return nil, err
 		}
-		for ; fileSharePage.NotDone() ; fileSharePage.NextWithContext(ctx) {
+
+		for fileSharePage.NotDone() {
 			values := fileSharePage.Values()
 			for _, fileShare := range values {
-				fileShares = append(fileShares, *fileShare.Name)
+				fileShares = append(fileShares, toVolume(account, *fileShare.Name))
+			}
+			if err := fileSharePage.NextWithContext(ctx); err != nil {
+				return nil, err
 			}
 		}
 	}
 	return fileShares, nil
 }
 
-func (helper StorageAccountHelper) CreateFileShare(ctx context.Context, accountName string, fileShareName string) (storage.FileShare, error) {
+func toVolume(account storage.Account, fileShareName string) volumes.Volume {
+	return volumes.Volume{
+		ID:          fmt.Sprintf("%s@%s", *account.Name, fileShareName),
+		Name:        fileShareName,
+		Description: fmt.Sprintf("Fileshare %s in %s storage account", fileShareName, *account.Name),
+	}
+}
+
+// CreateFileShare create a new fileshare
+func (helper StorageAccountHelper) CreateFileShare(ctx context.Context, accountName string, fileShareName string) (volumes.Volume, error) {
 	aciContext := helper.AciContext
 	accountClient, err := NewStorageAccountsClient(aciContext.SubscriptionID)
 	if err != nil {
-		return storage.FileShare{}, err
+		return volumes.Volume{}, err
 	}
 	account, err := accountClient.GetProperties(ctx, aciContext.ResourceGroup, accountName, "")
 	if err != nil {
-		//TODO check err not found
-		parameters := storage.AccountCreateParameters{
-			Location: &aciContext.Location,
-			Sku:&storage.Sku{
-				Name: storage.StandardLRS,
-				Tier: storage.Standard,
-			},
+		if account.StatusCode != 404 {
+			return volumes.Volume{}, err
 		}
+		//TODO confirm storage account creation
+		parameters := defaultStorageAccountParams(aciContext)
 		// TODO progress account creation
 		future, err := accountClient.Create(ctx, aciContext.ResourceGroup, accountName, parameters)
 		if err != nil {
-			return storage.FileShare{}, err
+			return volumes.Volume{}, err
 		}
 		account, err = future.Result(accountClient)
+		if err != nil {
+			return volumes.Volume{}, err
+		}
 	}
 	fileShareClient, err := NewFileShareClient(aciContext.SubscriptionID)
-	fileShare, err := fileShareClient.Get(ctx, aciContext.ResourceGroup, *account.Name, fileShareName, "")
 	if err != nil {
-		// TODO check err not found
-		fileShare, err = fileShareClient.Create(ctx, aciContext.ResourceGroup, *account.Name, fileShareName, storage.FileShare{})
+		return volumes.Volume{}, err
 	}
 
-	return fileShare, nil
+	fileShare, err := fileShareClient.Get(ctx, aciContext.ResourceGroup, *account.Name, fileShareName, "")
+	if err == nil {
+		return volumes.Volume{}, errors.Wrapf(errdefs.ErrAlreadyExists, "Azure fileshare %q already exists", fileShareName)
+	}
+	if fileShare.StatusCode != 404 {
+		return volumes.Volume{}, err
+	}
+	fileShare, err = fileShareClient.Create(ctx, aciContext.ResourceGroup, *account.Name, fileShareName, storage.FileShare{})
+	if err != nil {
+		return volumes.Volume{}, err
+	}
+	return toVolume(account, *fileShare.Name), nil
 }
 
+func defaultStorageAccountParams(aciContext store.AciContext) storage.AccountCreateParameters {
+	return storage.AccountCreateParameters{
+		Location: &aciContext.Location,
+		Sku: &storage.Sku{
+			Name: storage.StandardLRS,
+			Tier: storage.Standard,
+		},
+	}
+}

+ 2 - 0
api/client/client.go

@@ -18,6 +18,7 @@ package client
 
 import (
 	"context"
+
 	"github.com/docker/compose-cli/api/volumes"
 
 	"github.com/docker/compose-cli/api/compose"
@@ -87,6 +88,7 @@ func (c *Client) SecretsService() secrets.Service {
 
 	return &secretsService{}
 }
+
 // VolumeService returns the backend service for the current context
 func (c *Client) VolumeService() volumes.Service {
 	if vs := c.bs.VolumeService(); vs != nil {

+ 2 - 1
api/client/volume.go

@@ -18,6 +18,7 @@ package client
 
 import (
 	"context"
+
 	"github.com/docker/compose-cli/api/volumes"
 	"github.com/docker/compose-cli/errdefs"
 )
@@ -31,6 +32,6 @@ func (c *volumeService) List(ctx context.Context) ([]volumes.Volume, error) {
 }
 
 // Create creates a volume
-func (c *volumeService) Create(ctx context.Context, options interface {}) (volumes.Volume, error) {
+func (c *volumeService) Create(ctx context.Context, options interface{}) (volumes.Volume, error) {
 	return volumes.Volume{}, errdefs.ErrNotImplemented
 }

+ 4 - 6
api/volumes/api.go

@@ -20,13 +20,11 @@ import (
 	"context"
 )
 
+// Volume volume info
 type Volume struct {
-	Name string
-}
-
-type VolumeCreateOptions struct {
-	account string
-	fileshare string
+	ID          string
+	Name        string
+	Description string
 }
 
 // Service interacts with the underlying container backend

+ 52 - 11
cli/cmd/volume/create.go

@@ -18,17 +18,21 @@ package volume
 
 import (
 	"fmt"
-	"github.com/docker/compose-cli/api/client"
+	"io"
+	"os"
+	"strings"
+	"text/tabwriter"
+
+	"github.com/docker/compose-cli/aci"
+
 	"github.com/spf13/cobra"
-)
 
-type createVolumeOptions struct {
-	Account string
-	Fileshare    string
-}
+	"github.com/docker/compose-cli/api/client"
+	"github.com/docker/compose-cli/api/volumes"
+)
 
-// SecretCommand manage secrets
-func VolumeCommand() *cobra.Command {
+// Command manage volumes
+func Command() *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "volume",
 		Short: "Manages volumes",
@@ -36,16 +40,17 @@ func VolumeCommand() *cobra.Command {
 
 	cmd.AddCommand(
 		createVolume(),
+		listVolume(),
 	)
 	return cmd
 }
 
 func createVolume() *cobra.Command {
-	opts := createVolumeOptions{}
+	opts := aci.VolumeCreateOptions{}
 	cmd := &cobra.Command{
 		Use:   "create",
 		Short: "Creates an Azure file share to use as ACI volume.",
-		Args:  cobra.ExactArgs(1),
+		Args:  cobra.ExactArgs(0),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			c, err := client.New(cmd.Context())
 			if err != nil {
@@ -60,7 +65,43 @@ func createVolume() *cobra.Command {
 		},
 	}
 
-	cmd.Flags().StringVar(&opts.Account, "storage-account",  "", "Storage account name")
+	cmd.Flags().StringVar(&opts.Account, "storage-account", "", "Storage account name")
 	cmd.Flags().StringVar(&opts.Fileshare, "fileshare", "", "Fileshare name")
 	return cmd
 }
+
+func listVolume() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "ls",
+		Short: "list Azure file shares usable as ACI volumes.",
+		Args:  cobra.ExactArgs(0),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			c, err := client.New(cmd.Context())
+			if err != nil {
+				return err
+			}
+			vols, err := c.VolumeService().List(cmd.Context())
+			if err != nil {
+				return err
+			}
+			printList(os.Stdout, vols)
+			return nil
+		},
+	}
+	return cmd
+}
+
+func printList(out io.Writer, volumes []volumes.Volume) {
+	printSection(out, func(w io.Writer) {
+		for _, vol := range volumes {
+			fmt.Fprintf(w, "%s\t%s\t%s\n", vol.ID, vol.Name, vol.Description) // nolint:errcheck
+		}
+	}, "ID", "NAME", "DESCRIPTION")
+}
+
+func printSection(out io.Writer, printer func(io.Writer), headers ...string) {
+	w := tabwriter.NewWriter(out, 20, 1, 3, ' ', 0)
+	fmt.Fprintln(w, strings.Join(headers, "\t")) // nolint:errcheck
+	printer(w)
+	w.Flush() // nolint:errcheck
+}

+ 3 - 2
cli/main.go

@@ -19,7 +19,6 @@ package main
 import (
 	"context"
 	"fmt"
-	volume "github.com/docker/compose-cli/cli/cmd/volume"
 	"math/rand"
 	"os"
 	"os/signal"
@@ -28,6 +27,8 @@ import (
 	"syscall"
 	"time"
 
+	volume "github.com/docker/compose-cli/cli/cmd/volume"
+
 	"github.com/docker/compose-cli/cli/cmd/compose"
 
 	"github.com/docker/compose-cli/cli/cmd/logout"
@@ -134,7 +135,7 @@ func main() {
 
 		// Place holders
 		cmd.EcsCommand(),
-		volume.VolumeCommand(),
+		volume.Command(),
 	)
 
 	helpFunc := root.HelpFunc()

+ 1 - 0
ecs/backend.go

@@ -18,6 +18,7 @@ package ecs
 
 import (
 	"context"
+
 	"github.com/docker/compose-cli/api/volumes"
 
 	"github.com/aws/aws-sdk-go/aws"

+ 3 - 1
ecs/local/backend.go

@@ -18,12 +18,14 @@ package local
 
 import (
 	"context"
+
 	"github.com/docker/compose-cli/api/volumes"
 
+	"github.com/docker/docker/client"
+
 	"github.com/docker/compose-cli/api/compose"
 	"github.com/docker/compose-cli/api/containers"
 	"github.com/docker/compose-cli/api/secrets"
-	"github.com/docker/docker/client"
 
 	"github.com/docker/compose-cli/backend"
 	"github.com/docker/compose-cli/context/cloud"

+ 1 - 1
local/backend.go

@@ -38,8 +38,8 @@ import (
 
 	"github.com/docker/compose-cli/api/compose"
 	"github.com/docker/compose-cli/api/containers"
-	"github.com/docker/compose-cli/api/volumes"
 	"github.com/docker/compose-cli/api/secrets"
+	"github.com/docker/compose-cli/api/volumes"
 	"github.com/docker/compose-cli/backend"
 	"github.com/docker/compose-cli/context/cloud"
 	"github.com/docker/compose-cli/errdefs"

+ 2 - 1
tests/ecs-local-e2e/context_test.go

@@ -21,8 +21,9 @@ import (
 	"os"
 	"testing"
 
-	. "github.com/docker/compose-cli/tests/framework"
 	"gotest.tools/v3/icmd"
+
+	. "github.com/docker/compose-cli/tests/framework"
 )
 
 const (