diff --git a/cli/app/app.go b/cli/app/app.go index 28c1fa56..db075ab4 100644 --- a/cli/app/app.go +++ b/cli/app/app.go @@ -29,5 +29,6 @@ scaling apps up and spinning them down. appRunCommand, appRollbackCommand, appSecretCommand, + appVolumeCommand, }, } diff --git a/cli/app/volume.go b/cli/app/volume.go new file mode 100644 index 00000000..72a683ea --- /dev/null +++ b/cli/app/volume.go @@ -0,0 +1,113 @@ +package app + +import ( + "context" + "errors" + + abraFormatter "coopcloud.tech/abra/cli/formatter" + "coopcloud.tech/abra/cli/internal" + "coopcloud.tech/abra/client" + "coopcloud.tech/abra/config" + "github.com/AlecAivazis/survey/v2" + "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" +) + +func getAppsHost(appName string) string { + appFiles, err := config.LoadAppFiles("") + if err != nil { + logrus.Fatal(err) + } + + var host string + if app, ok := appFiles[appName]; ok { + host = app.Server + } else { + logrus.Fatalf(`app "%s" does not exist`, appName) + } + return host +} + +var appVolumeListCommand = &cli.Command{ + Name: "list", + Usage: "list volumes associated with an app", + Aliases: []string{"ls"}, + Action: func(c *cli.Context) error { + appName := c.Args().First() + if appName == "" { + internal.ShowSubcommandHelpAndError(c, errors.New("no app name provided!")) + } + host := getAppsHost(appName) + + ctx := context.Background() + volumeList, err := client.GetVolumes(ctx, host, appName) + if err != nil { + logrus.Fatal(err) + } + table := abraFormatter.CreateTable([]string{"DRIVER", "VOLUME NAME"}) + var volTable [][]string + for _, volume := range volumeList { + volRow := []string{ + volume.Driver, + volume.Name, + } + volTable = append(volTable, volRow) + } + table.AppendBulk(volTable) + table.Render() + return nil + }, +} + +var appVolumeRemoveCommand = &cli.Command{ + Name: "remove", + Usage: "remove volume(s) associated with an app", + Aliases: []string{"rm", "delete"}, + Flags: []cli.Flag{ + internal.ForceFlag, + }, + Action: func(c *cli.Context) error { + appName := c.Args().First() + if appName == "" { + internal.ShowSubcommandHelpAndError(c, errors.New("no app name provided!")) + } + host := getAppsHost(appName) + ctx := context.Background() + volumeList, err := client.GetVolumes(ctx, host, appName) + if err != nil { + logrus.Fatal(err) + } + volumeNames := client.GetVolumeNames(volumeList) + + var volumesToRemove []string + if !internal.Force { + volumesPrompt := &survey.MultiSelect{ + Message: "Which volumes do you want to remove?", + Options: volumeNames, + Default: volumeNames, + } + if err := survey.AskOne(volumesPrompt, &volumesToRemove); err != nil { + logrus.Fatal(err) + } + } else { + volumesToRemove = volumeNames + } + + err = client.RemoveVolumes(ctx, host, volumesToRemove, internal.Force) + if err != nil { + logrus.Fatal(err) + } + logrus.Info("Volumes removed successfully.") + return nil + }, +} + +var appVolumeCommand = &cli.Command{ + Name: "volume", + Usage: "List or remove volumes associated with app", + ArgsUsage: "", + Subcommands: []*cli.Command{ + appVolumeListCommand, + appVolumeRemoveCommand, + }, +} diff --git a/client/volumes.go b/client/volumes.go new file mode 100644 index 00000000..9b7dc13e --- /dev/null +++ b/client/volumes.go @@ -0,0 +1,51 @@ +package client + +import ( + "context" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/sirupsen/logrus" +) + +func GetVolumes(ctx context.Context, server string, appName string) ([]*types.Volume, error) { + + cl, err := NewClientWithContext(server) + if err != nil { + return nil, err + } + + fs := filters.NewArgs() + fs.Add("name", appName) + + volumeListOKBody, err := cl.VolumeList(ctx, fs) + volumeList := volumeListOKBody.Volumes + if err != nil { + logrus.Fatal(err) + } + + return volumeList, nil +} + +func GetVolumeNames(volumes []*types.Volume) []string { + var volumeNames []string + for _, vol := range volumes { + volumeNames = append(volumeNames, vol.Name) + } + return volumeNames +} + +func RemoveVolumes(ctx context.Context, server string, volumeNames []string, force bool) error { + cl, err := NewClientWithContext(server) + if err != nil { + return err + } + for _, volName := range volumeNames { + err := cl.VolumeRemove(ctx, volName, force) + if err != nil { + return err + } + } + return nil + +}