mirror of https://github.com/docker/compose.git
Removed NAME from `volume ls` output, allow `volume delete <ID>` using IDs from `volume ls`.
Signed-off-by: Guillaume Tardif <guillaume.tardif@docker.com>
This commit is contained in:
parent
38a8f5310b
commit
80d23a6097
|
@ -19,6 +19,7 @@ package aci
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/progress"
|
"github.com/docker/compose-cli/progress"
|
||||||
|
|
||||||
|
@ -80,17 +81,10 @@ type VolumeCreateOptions struct {
|
||||||
Fileshare string
|
Fileshare string
|
||||||
}
|
}
|
||||||
|
|
||||||
//VolumeDeleteOptions options to create a new ACI volume
|
|
||||||
type VolumeDeleteOptions struct {
|
|
||||||
Account string
|
|
||||||
Fileshare string
|
|
||||||
DeleteAccount bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *aciVolumeService) Create(ctx context.Context, options interface{}) (volumes.Volume, error) {
|
func (cs *aciVolumeService) Create(ctx context.Context, options interface{}) (volumes.Volume, error) {
|
||||||
opts, ok := options.(VolumeCreateOptions)
|
opts, ok := options.(VolumeCreateOptions)
|
||||||
if !ok {
|
if !ok {
|
||||||
return volumes.Volume{}, errors.New("Could not read azure LoginParams struct from generic parameter")
|
return volumes.Volume{}, errors.New("Could not read azure VolumeCreateOptions struct from generic parameter")
|
||||||
}
|
}
|
||||||
w := progress.ContextWriter(ctx)
|
w := progress.ContextWriter(ctx)
|
||||||
w.Event(event(opts.Account, progress.Working, "Validating"))
|
w.Event(event(opts.Account, progress.Working, "Validating"))
|
||||||
|
@ -105,7 +99,6 @@ func (cs *aciVolumeService) Create(ctx context.Context, options interface{}) (vo
|
||||||
if account.StatusCode != 404 {
|
if account.StatusCode != 404 {
|
||||||
return volumes.Volume{}, err
|
return volumes.Volume{}, err
|
||||||
}
|
}
|
||||||
//TODO confirm storage account creation
|
|
||||||
result, err := accountClient.CheckNameAvailability(ctx, storage.AccountCheckNameAvailabilityParameters{
|
result, err := accountClient.CheckNameAvailability(ctx, storage.AccountCheckNameAvailabilityParameters{
|
||||||
Name: to.StringPtr(opts.Account),
|
Name: to.StringPtr(opts.Account),
|
||||||
Type: to.StringPtr("Microsoft.Storage/storageAccounts"),
|
Type: to.StringPtr("Microsoft.Storage/storageAccounts"),
|
||||||
|
@ -177,49 +170,62 @@ func errorEvent(resource string) progress.Event {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *aciVolumeService) Delete(ctx context.Context, options interface{}) error {
|
func (cs *aciVolumeService) Delete(ctx context.Context, id string, options interface{}) error {
|
||||||
opts, ok := options.(VolumeDeleteOptions)
|
tokens := strings.Split(id, "@")
|
||||||
if !ok {
|
if len(tokens) != 2 {
|
||||||
return errors.New("Could not read azure VolumeDeleteOptions struct from generic parameter")
|
return errors.New("wrong format for volume ID : should be storageaccount@fileshare")
|
||||||
}
|
|
||||||
if opts.DeleteAccount {
|
|
||||||
//TODO check if there are other fileshares on this account
|
|
||||||
storageAccountsClient, err := login.NewStorageAccountsClient(cs.aciContext.SubscriptionID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := storageAccountsClient.Delete(ctx, cs.aciContext.ResourceGroup, opts.Account)
|
|
||||||
if result.StatusCode == 204 {
|
|
||||||
return errors.Wrapf(errdefs.ErrNotFound, "storage account %s does not exist", opts.Account)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
storageAccount := tokens[0]
|
||||||
|
fileshare := tokens[1]
|
||||||
|
|
||||||
fileShareClient, err := login.NewFileShareClient(cs.aciContext.SubscriptionID)
|
fileShareClient, err := login.NewFileShareClient(cs.aciContext.SubscriptionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
fileShareItemsPage, err := fileShareClient.List(ctx, cs.aciContext.ResourceGroup, storageAccount, "", "", "")
|
||||||
result, err := fileShareClient.Delete(ctx, cs.aciContext.ResourceGroup, opts.Account, opts.Fileshare)
|
if err != nil {
|
||||||
if result.StatusCode == 204 {
|
return err
|
||||||
return errors.Wrapf(errdefs.ErrNotFound, "fileshare %s does not exist", opts.Fileshare)
|
|
||||||
}
|
}
|
||||||
if result.StatusCode == 404 {
|
fileshares := fileShareItemsPage.Values()
|
||||||
return errors.Wrapf(errdefs.ErrNotFound, "storage account %s does not exist", opts.Account)
|
if len(fileshares) == 1 && *fileshares[0].Name == fileshare {
|
||||||
|
storageAccountsClient, err := login.NewStorageAccountsClient(cs.aciContext.SubscriptionID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
account, err := storageAccountsClient.GetProperties(ctx, cs.aciContext.ResourceGroup, storageAccount, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
if _, ok := account.Tags[dockerVolumeTag]; ok {
|
||||||
|
result, err := storageAccountsClient.Delete(ctx, cs.aciContext.ResourceGroup, storageAccount)
|
||||||
|
if result.StatusCode == 204 {
|
||||||
|
return errors.Wrapf(errdefs.ErrNotFound, "storage account %s does not exist", storageAccount)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := fileShareClient.Delete(ctx, cs.aciContext.ResourceGroup, storageAccount, fileshare)
|
||||||
|
if result.StatusCode == 204 {
|
||||||
|
return errors.Wrapf(errdefs.ErrNotFound, "fileshare %s does not exist", fileshare)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func toVolume(account storage.Account, fileShareName string) volumes.Volume {
|
func toVolume(account storage.Account, fileShareName string) volumes.Volume {
|
||||||
return volumes.Volume{
|
return volumes.Volume{
|
||||||
ID: fmt.Sprintf("%s@%s", *account.Name, fileShareName),
|
ID: VolumeID(*account.Name, fileShareName),
|
||||||
Name: fileShareName,
|
|
||||||
Description: fmt.Sprintf("Fileshare %s in %s storage account", fileShareName, *account.Name),
|
Description: fmt.Sprintf("Fileshare %s in %s storage account", fileShareName, *account.Name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VolumeID generate volume ID from azure storage accoun & fileshare
|
||||||
|
func VolumeID(storageAccount string, fileShareName string) string {
|
||||||
|
return fmt.Sprintf("%s@%s", storageAccount, fileShareName)
|
||||||
|
}
|
||||||
|
|
||||||
func defaultStorageAccountParams(aciContext store.AciContext) storage.AccountCreateParameters {
|
func defaultStorageAccountParams(aciContext store.AciContext) storage.AccountCreateParameters {
|
||||||
tags := map[string]*string{dockerVolumeTag: to.StringPtr(dockerVolumeTag)}
|
tags := map[string]*string{dockerVolumeTag: to.StringPtr(dockerVolumeTag)}
|
||||||
return storage.AccountCreateParameters{
|
return storage.AccountCreateParameters{
|
||||||
|
|
|
@ -34,6 +34,6 @@ func (c *volumeService) Create(ctx context.Context, options interface{}) (volume
|
||||||
return volumes.Volume{}, errdefs.ErrNotImplemented
|
return volumes.Volume{}, errdefs.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *volumeService) Delete(ctx context.Context, options interface{}) error {
|
func (c *volumeService) Delete(ctx context.Context, id string, options interface{}) error {
|
||||||
return errdefs.ErrNotImplemented
|
return errdefs.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ import (
|
||||||
// Volume volume info
|
// Volume volume info
|
||||||
type Volume struct {
|
type Volume struct {
|
||||||
ID string
|
ID string
|
||||||
Name string
|
|
||||||
Description string
|
Description string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,5 +33,5 @@ type Service interface {
|
||||||
// Create creates a new volume
|
// Create creates a new volume
|
||||||
Create(ctx context.Context, options interface{}) (Volume, error)
|
Create(ctx context.Context, options interface{}) (Volume, error)
|
||||||
// Delete deletes an existing volume
|
// Delete deletes an existing volume
|
||||||
Delete(ctx context.Context, options interface{}) error
|
Delete(ctx context.Context, volumeID string, options interface{}) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,9 +53,9 @@ func listVolume() *cobra.Command {
|
||||||
func printList(out io.Writer, volumes []volumes.Volume) {
|
func printList(out io.Writer, volumes []volumes.Volume) {
|
||||||
printSection(out, func(w io.Writer) {
|
printSection(out, func(w io.Writer) {
|
||||||
for _, vol := range volumes {
|
for _, vol := range volumes {
|
||||||
fmt.Fprintf(w, "%s\t%s\t%s\n", vol.ID, vol.Name, vol.Description) // nolint:errcheck
|
fmt.Fprintf(w, "%s\t%s\n", vol.ID, vol.Description) // nolint:errcheck
|
||||||
}
|
}
|
||||||
}, "ID", "NAME", "DESCRIPTION")
|
}, "ID", "DESCRIPTION")
|
||||||
}
|
}
|
||||||
|
|
||||||
func printSection(out io.Writer, printer func(io.Writer), headers ...string) {
|
func printSection(out io.Writer, printer func(io.Writer), headers ...string) {
|
||||||
|
|
|
@ -29,7 +29,6 @@ func TestPrintList(t *testing.T) {
|
||||||
secrets := []volumes.Volume{
|
secrets := []volumes.Volume{
|
||||||
{
|
{
|
||||||
ID: "volume@123",
|
ID: "volume@123",
|
||||||
Name: "123",
|
|
||||||
Description: "volume 123",
|
Description: "volume 123",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,9 @@ package volume
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
@ -64,8 +67,8 @@ func createVolume() *cobra.Command {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf("volume successfully created\n")
|
fmt.Println(aci.VolumeID(aciOpts.Account, aciOpts.Fileshare))
|
||||||
return err
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,22 +78,37 @@ func createVolume() *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
func rmVolume() *cobra.Command {
|
func rmVolume() *cobra.Command {
|
||||||
aciOpts := aci.VolumeDeleteOptions{}
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "rm",
|
Use: "rm [OPTIONS] VOLUME [VOLUME...]",
|
||||||
Short: "Deletes an Azure file share and/or the Azure storage account.",
|
Short: "Remove one or more volumes.",
|
||||||
Args: cobra.ExactArgs(0),
|
Args: cobra.MinimumNArgs(1),
|
||||||
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 {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return c.VolumeService().Delete(cmd.Context(), aciOpts)
|
var errs *multierror.Error
|
||||||
|
for _, id := range args {
|
||||||
|
err = c.VolumeService().Delete(cmd.Context(), id, nil)
|
||||||
|
if err != nil {
|
||||||
|
errs = multierror.Append(errs, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Println(id)
|
||||||
|
}
|
||||||
|
if errs != nil {
|
||||||
|
errs.ErrorFormat = formatErrors
|
||||||
|
}
|
||||||
|
return errs.ErrorOrNil()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().StringVar(&aciOpts.Account, "storage-account", "", "Storage account name")
|
|
||||||
cmd.Flags().StringVar(&aciOpts.Fileshare, "fileshare", "", "Fileshare name")
|
|
||||||
cmd.Flags().BoolVar(&aciOpts.DeleteAccount, "delete-storage-account", false, "Also delete storage account")
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatErrors(errs []error) string {
|
||||||
|
messages := make([]string, len(errs))
|
||||||
|
for i, err := range errs {
|
||||||
|
messages[i] = "Error: " + err.Error()
|
||||||
|
}
|
||||||
|
return strings.Join(messages, "\n")
|
||||||
|
}
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
ID NAME DESCRIPTION
|
ID DESCRIPTION
|
||||||
volume@123 123 volume 123
|
volume@123 volume 123
|
||||||
|
|
|
@ -162,9 +162,10 @@ func TestContainerRunVolume(t *testing.T) {
|
||||||
t.Run("create volumes", func(t *testing.T) {
|
t.Run("create volumes", func(t *testing.T) {
|
||||||
c.RunDockerCmd("volume", "create", "--storage-account", accountName, "--fileshare", fileshareName)
|
c.RunDockerCmd("volume", "create", "--storage-account", accountName, "--fileshare", fileshareName)
|
||||||
})
|
})
|
||||||
|
volumeID = accountName + "@" + fileshareName
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
c.RunDockerCmd("volume", "rm", "--storage-account", accountName, "--delete-storage-account")
|
c.RunDockerCmd("volume", "rm", volumeID)
|
||||||
res := c.RunDockerCmd("volume", "ls")
|
res := c.RunDockerCmd("volume", "ls")
|
||||||
lines := lines(res.Stdout())
|
lines := lines(res.Stdout())
|
||||||
assert.Equal(t, len(lines), 1)
|
assert.Equal(t, len(lines), 1)
|
||||||
|
@ -173,6 +174,7 @@ func TestContainerRunVolume(t *testing.T) {
|
||||||
t.Run("create second fileshare", func(t *testing.T) {
|
t.Run("create second fileshare", func(t *testing.T) {
|
||||||
c.RunDockerCmd("volume", "create", "--storage-account", accountName, "--fileshare", "dockertestshare2")
|
c.RunDockerCmd("volume", "create", "--storage-account", accountName, "--fileshare", "dockertestshare2")
|
||||||
})
|
})
|
||||||
|
volumeID2 := accountName + "@dockertestshare2"
|
||||||
|
|
||||||
t.Run("list volumes", func(t *testing.T) {
|
t.Run("list volumes", func(t *testing.T) {
|
||||||
res := c.RunDockerCmd("volume", "ls")
|
res := c.RunDockerCmd("volume", "ls")
|
||||||
|
@ -180,18 +182,14 @@ func TestContainerRunVolume(t *testing.T) {
|
||||||
assert.Equal(t, len(lines), 3)
|
assert.Equal(t, len(lines), 3)
|
||||||
firstAccount := lines[1]
|
firstAccount := lines[1]
|
||||||
fields := strings.Fields(firstAccount)
|
fields := strings.Fields(firstAccount)
|
||||||
volumeID = accountName + "@" + fileshareName
|
|
||||||
assert.Equal(t, fields[0], volumeID)
|
assert.Equal(t, fields[0], volumeID)
|
||||||
assert.Equal(t, fields[1], fileshareName)
|
|
||||||
secondAccount := lines[2]
|
secondAccount := lines[2]
|
||||||
fields = strings.Fields(secondAccount)
|
fields = strings.Fields(secondAccount)
|
||||||
assert.Equal(t, fields[0], accountName+"@dockertestshare2")
|
assert.Equal(t, fields[0], volumeID2)
|
||||||
assert.Equal(t, fields[1], "dockertestshare2")
|
|
||||||
//assert.Assert(t, fields[2], strings.Contains(firstAccount, fmt.Sprintf("Fileshare %s in %s storage account", fileshareName, accountName)))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("delete only fileshare", func(t *testing.T) {
|
t.Run("delete only fileshare", func(t *testing.T) {
|
||||||
c.RunDockerCmd("volume", "rm", "--storage-account", accountName, "--fileshare", "dockertestshare2")
|
c.RunDockerCmd("volume", "rm", volumeID2)
|
||||||
res := c.RunDockerCmd("volume", "ls")
|
res := c.RunDockerCmd("volume", "ls")
|
||||||
lines := lines(res.Stdout())
|
lines := lines(res.Stdout())
|
||||||
assert.Equal(t, len(lines), 2)
|
assert.Equal(t, len(lines), 2)
|
||||||
|
|
Loading…
Reference in New Issue