Connecting it all

Signed-off-by: Guillaume Tardif <guillaume.tardif@docker.com>
This commit is contained in:
Guillaume Tardif 2020-09-07 18:23:28 +02:00
parent 9ed06ece5b
commit 08562b403e
13 changed files with 154 additions and 57 deletions

View File

@ -35,8 +35,8 @@ import (
"github.com/docker/compose-cli/aci/login" "github.com/docker/compose-cli/aci/login"
"github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/containers" "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/secrets"
"github.com/docker/compose-cli/api/volumes"
"github.com/docker/compose-cli/backend" "github.com/docker/compose-cli/backend"
apicontext "github.com/docker/compose-cli/context" apicontext "github.com/docker/compose-cli/context"
"github.com/docker/compose-cli/context/cloud" "github.com/docker/compose-cli/context/cloud"
@ -110,6 +110,9 @@ func getAciAPIService(aciCtx store.AciContext) *aciAPIService {
aciComposeService: &aciComposeService{ aciComposeService: &aciComposeService{
ctx: aciCtx, ctx: aciCtx,
}, },
aciVolumeService: &aciVolumeService{
ctx: aciCtx,
},
} }
} }
@ -169,20 +172,20 @@ func getContainerGroups(ctx context.Context, subscriptionID string, resourceGrou
var containerGroups []containerinstance.ContainerGroup var containerGroups []containerinstance.ContainerGroup
result, err := groupsClient.ListByResourceGroup(ctx, resourceGroup) result, err := groupsClient.ListByResourceGroup(ctx, resourceGroup)
if err != nil { if err != nil {
return []containerinstance.ContainerGroup{}, err return nil, err
} }
for result.NotDone() { for result.NotDone() {
containerGroups = append(containerGroups, result.Values()...) containerGroups = append(containerGroups, result.Values()...)
if err := result.NextWithContext(ctx); err != nil { if err := result.NextWithContext(ctx); err != nil {
return []containerinstance.ContainerGroup{}, err return nil, err
} }
} }
var groups []containerinstance.ContainerGroup var groups []containerinstance.ContainerGroup
for _, group := range containerGroups { for _, group := range containerGroups {
group, err := groupsClient.Get(ctx, resourceGroup, *group.Name) group, err := groupsClient.Get(ctx, resourceGroup, *group.Name)
if err != nil { if err != nil {
return []containerinstance.ContainerGroup{}, err return nil, err
} }
groups = append(groups, group) groups = append(groups, group)
} }
@ -507,11 +510,23 @@ type aciVolumeService struct {
} }
func (cs *aciVolumeService) List(ctx context.Context) ([]volumes.Volume, error) { 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) { 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 { type aciCloudService struct {

View File

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

View File

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

View File

@ -19,8 +19,12 @@ package login
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage" "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/pkg/errors"
"github.com/docker/compose-cli/context/store" "github.com/docker/compose-cli/context/store"
@ -28,8 +32,7 @@ import (
// StorageAccountHelper helper for Azure Storage Account // StorageAccountHelper helper for Azure Storage Account
type StorageAccountHelper struct { type StorageAccountHelper struct {
LoginService AzureLoginService AciContext store.AciContext
AciContext store.AciContext
} }
// GetAzureStorageAccountKey retrieves the storage account ket from the current azure login // 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 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 aciContext := helper.AciContext
accountClient, err := NewStorageAccountsClient(aciContext.SubscriptionID) accountClient, err := NewStorageAccountsClient(aciContext.SubscriptionID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
result, err := accountClient.ListByResourceGroup(ctx, aciContext.ResourceGroup) result, err := accountClient.ListByResourceGroup(ctx, aciContext.ResourceGroup)
if err != nil {
return nil, err
}
accounts := result.Value accounts := result.Value
fileShareClient, err := NewFileShareClient(aciContext.SubscriptionID) fileShareClient, err := NewFileShareClient(aciContext.SubscriptionID)
fileShares := []string{} if err != nil {
return nil, err
}
fileShares := []volumes.Volume{}
for _, account := range *accounts { for _, account := range *accounts {
fileSharePage, err := fileShareClient.List(ctx, aciContext.ResourceGroup, *account.Name, "", "", "") fileSharePage, err := fileShareClient.List(ctx, aciContext.ResourceGroup, *account.Name, "", "", "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
for ; fileSharePage.NotDone() ; fileSharePage.NextWithContext(ctx) {
for fileSharePage.NotDone() {
values := fileSharePage.Values() values := fileSharePage.Values()
for _, fileShare := range 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 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 aciContext := helper.AciContext
accountClient, err := NewStorageAccountsClient(aciContext.SubscriptionID) accountClient, err := NewStorageAccountsClient(aciContext.SubscriptionID)
if err != nil { if err != nil {
return storage.FileShare{}, err return volumes.Volume{}, err
} }
account, err := accountClient.GetProperties(ctx, aciContext.ResourceGroup, accountName, "") account, err := accountClient.GetProperties(ctx, aciContext.ResourceGroup, accountName, "")
if err != nil { if err != nil {
//TODO check err not found if account.StatusCode != 404 {
parameters := storage.AccountCreateParameters{ return volumes.Volume{}, err
Location: &aciContext.Location,
Sku:&storage.Sku{
Name: storage.StandardLRS,
Tier: storage.Standard,
},
} }
//TODO confirm storage account creation
parameters := defaultStorageAccountParams(aciContext)
// TODO progress account creation // TODO progress account creation
future, err := accountClient.Create(ctx, aciContext.ResourceGroup, accountName, parameters) future, err := accountClient.Create(ctx, aciContext.ResourceGroup, accountName, parameters)
if err != nil { if err != nil {
return storage.FileShare{}, err return volumes.Volume{}, err
} }
account, err = future.Result(accountClient) account, err = future.Result(accountClient)
if err != nil {
return volumes.Volume{}, err
}
} }
fileShareClient, err := NewFileShareClient(aciContext.SubscriptionID) fileShareClient, err := NewFileShareClient(aciContext.SubscriptionID)
fileShare, err := fileShareClient.Get(ctx, aciContext.ResourceGroup, *account.Name, fileShareName, "")
if err != nil { if err != nil {
// TODO check err not found return volumes.Volume{}, err
fileShare, err = fileShareClient.Create(ctx, aciContext.ResourceGroup, *account.Name, fileShareName, storage.FileShare{})
} }
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,
},
}
}

View File

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

View File

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

View File

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

View File

@ -18,17 +18,21 @@ package volume
import ( import (
"fmt" "fmt"
"github.com/docker/compose-cli/api/client" "io"
"os"
"strings"
"text/tabwriter"
"github.com/docker/compose-cli/aci"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/volumes"
) )
type createVolumeOptions struct { // Command manage volumes
Account string func Command() *cobra.Command {
Fileshare string
}
// SecretCommand manage secrets
func VolumeCommand() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "volume", Use: "volume",
Short: "Manages volumes", Short: "Manages volumes",
@ -36,16 +40,17 @@ func VolumeCommand() *cobra.Command {
cmd.AddCommand( cmd.AddCommand(
createVolume(), createVolume(),
listVolume(),
) )
return cmd return cmd
} }
func createVolume() *cobra.Command { func createVolume() *cobra.Command {
opts := createVolumeOptions{} opts := aci.VolumeCreateOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "create", Use: "create",
Short: "Creates an Azure file share to use as ACI volume.", 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 { RunE: func(cmd *cobra.Command, args []string) error {
c, err := client.New(cmd.Context()) c, err := client.New(cmd.Context())
if err != nil { 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") cmd.Flags().StringVar(&opts.Fileshare, "fileshare", "", "Fileshare name")
return cmd 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
}

View File

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

View File

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

View File

@ -18,12 +18,14 @@ package local
import ( import (
"context" "context"
"github.com/docker/compose-cli/api/volumes" "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/compose"
"github.com/docker/compose-cli/api/containers" "github.com/docker/compose-cli/api/containers"
"github.com/docker/compose-cli/api/secrets" "github.com/docker/compose-cli/api/secrets"
"github.com/docker/docker/client"
"github.com/docker/compose-cli/backend" "github.com/docker/compose-cli/backend"
"github.com/docker/compose-cli/context/cloud" "github.com/docker/compose-cli/context/cloud"

View File

@ -38,8 +38,8 @@ import (
"github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/containers" "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/secrets"
"github.com/docker/compose-cli/api/volumes"
"github.com/docker/compose-cli/backend" "github.com/docker/compose-cli/backend"
"github.com/docker/compose-cli/context/cloud" "github.com/docker/compose-cli/context/cloud"
"github.com/docker/compose-cli/errdefs" "github.com/docker/compose-cli/errdefs"

View File

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