Compare commits

...

9 Commits

50 changed files with 873 additions and 871 deletions

View File

@ -1,34 +1,35 @@
package app package app
import ( import (
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var AppCommand = cli.Command{ var AppCommand = cli.Command{
Name: "app", Name: "app",
Aliases: []string{"a"}, Aliases: []string{"a"},
Usage: "Manage apps", Usage: "Manage apps",
ArgsUsage: "<domain>", UsageText: "abra app [command] [options] [arguments]",
Subcommands: []cli.Command{ HideHelpCommand: true,
appBackupCommand, Commands: []*cli.Command{
appCheckCommand, &appBackupCommand,
appCmdCommand, &appCheckCommand,
appConfigCommand, &appCmdCommand,
appCpCommand, &appConfigCommand,
appDeployCommand, &appCpCommand,
appListCommand, &appDeployCommand,
appLogsCommand, &appListCommand,
appNewCommand, &appLogsCommand,
appPsCommand, &appNewCommand,
appRemoveCommand, &appPsCommand,
appRestartCommand, &appRemoveCommand,
appRestoreCommand, &appRestartCommand,
appRollbackCommand, &appRestoreCommand,
appRunCommand, &appRollbackCommand,
appSecretCommand, &appRunCommand,
appServicesCommand, &appSecretCommand,
appUndeployCommand, &appServicesCommand,
appUpgradeCommand, &appUndeployCommand,
appVolumeCommand, &appUpgradeCommand,
&appVolumeCommand,
}, },
} }

View File

@ -1,32 +1,36 @@
package app package app
import ( import (
"context"
"fmt" "fmt"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var snapshot string var snapshot string
var snapshotFlag = &cli.StringFlag{ var snapshotFlag = &cli.StringFlag{
Name: "snapshot, s", Name: "snapshot",
Aliases: []string{"s"},
Usage: "Lists specific snapshot", Usage: "Lists specific snapshot",
Destination: &snapshot, Destination: &snapshot,
} }
var includePath string var includePath string
var includePathFlag = &cli.StringFlag{ var includePathFlag = &cli.StringFlag{
Name: "path, p", Name: "path",
Aliases: []string{"p"},
Usage: "Include path", Usage: "Include path",
Destination: &includePath, Destination: &includePath,
} }
var resticRepo string var resticRepo string
var resticRepoFlag = &cli.StringFlag{ var resticRepoFlag = &cli.StringFlag{
Name: "repo, r", Name: "repo",
Aliases: []string{"r"},
Usage: "Restic repository", Usage: "Restic repository",
Destination: &resticRepo, Destination: &resticRepo,
} }
@ -35,16 +39,17 @@ var appBackupListCommand = cli.Command{
Name: "list", Name: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OfflineFlag,
snapshotFlag, snapshotFlag,
includePathFlag, includePathFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "List all backups", Usage: "List all backups",
BashComplete: autocomplete.AppNameComplete, EnableShellCompletion: true,
Action: func(c *cli.Context) error { HideHelpCommand: true,
app := internal.ValidateApp(c) UsageText: "abra app backup list [options] <domain>",
ShellComplete: autocomplete.AppNameComplete,
Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
@ -82,16 +87,17 @@ var appBackupDownloadCommand = cli.Command{
Name: "download", Name: "download",
Aliases: []string{"d"}, Aliases: []string{"d"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OfflineFlag,
snapshotFlag, snapshotFlag,
includePathFlag, includePathFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "Download a backup", Usage: "Download a backup",
BashComplete: autocomplete.AppNameComplete, UsageText: "abra app backup download [options] <domain>",
Action: func(c *cli.Context) error { HideHelpCommand: true,
app := internal.ValidateApp(c) EnableShellCompletion: true,
ShellComplete: autocomplete.AppNameComplete,
Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.EnsureExists(); err != nil { if err := app.Recipe.EnsureExists(); err != nil {
log.Fatal(err) log.Fatal(err)
@ -153,15 +159,16 @@ var appBackupCreateCommand = cli.Command{
Name: "create", Name: "create",
Aliases: []string{"c"}, Aliases: []string{"c"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OfflineFlag,
resticRepoFlag, resticRepoFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "Create a new backup", Usage: "Create a new backup",
BashComplete: autocomplete.AppNameComplete, HideHelpCommand: true,
Action: func(c *cli.Context) error { UsageText: "abra app backup create [options] <domain>",
app := internal.ValidateApp(c) EnableShellCompletion: true,
ShellComplete: autocomplete.AppNameComplete,
Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.EnsureExists(); err != nil { if err := app.Recipe.EnsureExists(); err != nil {
log.Fatal(err) log.Fatal(err)
@ -211,15 +218,16 @@ var appBackupSnapshotsCommand = cli.Command{
Name: "snapshots", Name: "snapshots",
Aliases: []string{"s"}, Aliases: []string{"s"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OfflineFlag,
snapshotFlag, snapshotFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "List backup snapshots", Usage: "List backup snapshots",
BashComplete: autocomplete.AppNameComplete, UsageText: "abra app backup snapshots [options] <domain>",
Action: func(c *cli.Context) error { HideHelpCommand: true,
app := internal.ValidateApp(c) EnableShellCompletion: true,
ShellComplete: autocomplete.AppNameComplete,
Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.EnsureExists(); err != nil { if err := app.Recipe.EnsureExists(); err != nil {
log.Fatal(err) log.Fatal(err)
@ -266,14 +274,15 @@ var appBackupSnapshotsCommand = cli.Command{
} }
var appBackupCommand = cli.Command{ var appBackupCommand = cli.Command{
Name: "backup", Name: "backup",
Aliases: []string{"b"}, Aliases: []string{"b"},
Usage: "Manage app backups", Usage: "Manage app backups",
ArgsUsage: "<domain>", UsageText: "abra app backup [command] [options] [arguments]",
Subcommands: []cli.Command{ HideHelpCommand: true,
appBackupListCommand, Commands: []*cli.Command{
appBackupSnapshotsCommand, &appBackupListCommand,
appBackupDownloadCommand, &appBackupSnapshotsCommand,
appBackupCreateCommand, &appBackupDownloadCommand,
&appBackupCreateCommand,
}, },
} }

View File

@ -1,21 +1,21 @@
package app package app
import ( import (
"context"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
appPkg "coopcloud.tech/abra/pkg/app" appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var appCheckCommand = cli.Command{ var appCheckCommand = cli.Command{
Name: "check", Name: "check",
Aliases: []string{"chk"}, Aliases: []string{"chk"},
Usage: "Ensure an app is well configured", Usage: "Ensure an app is well configured",
Description: ` Description: `Compare env vars in both the app ".env" and recipe ".env.sample" file.
This command compares env vars in both the app ".env" and recipe ".env.sample"
file.
The goal is to ensure that recipe ".env.sample" env vars are defined in your The goal is to ensure that recipe ".env.sample" env vars are defined in your
app ".env" file. Only env var definitions in the ".env.sample" which are app ".env" file. Only env var definitions in the ".env.sample" which are
@ -25,16 +25,17 @@ these env vars, then "check" will complain.
Recipe maintainers may or may not provide defaults for env vars within their Recipe maintainers may or may not provide defaults for env vars within their
recipes regardless of commenting or not (e.g. through the use of recipes regardless of commenting or not (e.g. through the use of
${FOO:<default>} syntax). "check" does not confirm or deny this for you.`, ${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
ArgsUsage: "<domain>", UsageText: "abra app check [options] <domain>",
HideHelpCommand: true,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.ChaosFlag, internal.ChaosFlag,
internal.OfflineFlag, internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, EnableShellCompletion: true,
Action: func(c *cli.Context) error { ShellComplete: autocomplete.AppNameComplete,
app := internal.ValidateApp(c) Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -14,7 +15,7 @@ import (
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var appCmdCommand = cli.Command{ var appCmdCommand = cli.Command{
@ -26,47 +27,45 @@ var appCmdCommand = cli.Command{
These commands are bash functions, defined in the abra.sh of the recipe itself. These commands are bash functions, defined in the abra.sh of the recipe itself.
They can be run within the context of a service (e.g. app) or locally on your They can be run within the context of a service (e.g. app) or locally on your
work station by passing "--local". Arguments can be passed into these functions work station by passing "--local". Arguments can be passed into these functions
using the "-- <args>" syntax. using the "-- <cmd-args>" syntax.
**WARNING**: options must be passed directly after the sub-command "cmd". **WARNING**: [options] must be passed directly after the "cmd" sub-command.`,
UsageText: "abra app cmd [options] <domain> [<service>] <cmd> [-- <cmd-args>]",
EXAMPLE: HideHelpCommand: true,
abra app cmd --local example.com app create_user -- me@example.com`,
ArgsUsage: "<domain> [<service>] <command> [-- <args>]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.LocalCmdFlag, internal.LocalCmdFlag,
internal.RemoteUserFlag, internal.RemoteUserFlag,
internal.TtyFlag, internal.TtyFlag,
internal.OfflineFlag,
internal.ChaosFlag, internal.ChaosFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Subcommands: []cli.Command{appCmdListCommand}, Commands: []*cli.Command{
BashComplete: func(ctx *cli.Context) { &appCmdListCommand,
args := ctx.Args() },
switch len(args) { EnableShellCompletion: true,
ShellComplete: func(ctx context.Context, cmd *cli.Command) {
args := cmd.Args()
switch args.Len() {
case 0: case 0:
autocomplete.AppNameComplete(ctx) autocomplete.AppNameComplete(ctx, cmd)
case 1: case 1:
autocomplete.ServiceNameComplete(args.Get(0)) autocomplete.ServiceNameComplete(args.Get(0))
case 2: case 2:
cmdNameComplete(args.Get(0)) cmdNameComplete(args.Get(0))
} }
}, },
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if internal.LocalCmd && internal.RemoteUser != "" { if internal.LocalCmd && internal.RemoteUser != "" {
internal.ShowSubcommandHelpAndError(c, errors.New("cannot use --local & --user together")) internal.ShowSubcommandHelpAndError(cmd, errors.New("cannot use --local & --user together"))
} }
hasCmdArgs, parsedCmdArgs := parseCmdArgs(c.Args(), internal.LocalCmd) hasCmdArgs, parsedCmdArgs := parseCmdArgs(cmd.Args().Slice(), internal.LocalCmd)
if _, err := os.Stat(app.Recipe.AbraShPath); err != nil { if _, err := os.Stat(app.Recipe.AbraShPath); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -76,11 +75,11 @@ EXAMPLE:
} }
if internal.LocalCmd { if internal.LocalCmd {
if !(len(c.Args()) >= 2) { if !(cmd.Args().Len() >= 2) {
internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments")) internal.ShowSubcommandHelpAndError(cmd, errors.New("missing arguments"))
} }
cmdName := c.Args().Get(1) cmdName := cmd.Args().Get(1)
if err := internal.EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil { if err := internal.EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -112,13 +111,13 @@ EXAMPLE:
log.Fatal(err) log.Fatal(err)
} }
} else { } else {
if !(len(c.Args()) >= 3) { if !(cmd.Args().Len() >= 3) {
internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments")) internal.ShowSubcommandHelpAndError(cmd, errors.New("missing arguments"))
} }
targetServiceName := c.Args().Get(1) targetServiceName := cmd.Args().Get(1)
cmdName := c.Args().Get(2) cmdName := cmd.Args().Get(2)
if err := internal.EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil { if err := internal.EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -195,19 +194,19 @@ func cmdNameComplete(appName string) {
} }
var appCmdListCommand = cli.Command{ var appCmdListCommand = cli.Command{
Name: "list", Name: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Usage: "List all available commands", Usage: "List all available commands",
ArgsUsage: "<domain>", UsageText: "abra app cmd ls [options] <domain>",
HideHelpCommand: true,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OfflineFlag,
internal.ChaosFlag, internal.ChaosFlag,
}, },
BashComplete: autocomplete.AppNameComplete, EnableShellCompletion: true,
Before: internal.SubCommandBefore, ShellComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Before: internal.SubCommandBefore,
app := internal.ValidateApp(c) Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.EnsureExists(); err != nil { if err := app.Recipe.EnsureExists(); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"errors" "errors"
"os" "os"
"os/exec" "os/exec"
@ -10,24 +11,23 @@ import (
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var appConfigCommand = cli.Command{ var appConfigCommand = cli.Command{
Name: "config", Name: "config",
Aliases: []string{"cfg"}, Aliases: []string{"cfg"},
Usage: "Edit app config", Usage: "Edit app config",
ArgsUsage: "<domain>", HideHelpCommand: true,
Flags: []cli.Flag{ UsageText: "abra app config [options] <domain>",
internal.DebugFlag, Before: internal.SubCommandBefore,
}, EnableShellCompletion: true,
Before: internal.SubCommandBefore, ShellComplete: autocomplete.AppNameComplete,
BashComplete: autocomplete.AppNameComplete, Action: func(ctx context.Context, cmd *cli.Command) error {
Action: func(c *cli.Context) error { appName := cmd.Args().First()
appName := c.Args().First()
if appName == "" { if appName == "" {
internal.ShowSubcommandHelpAndError(c, errors.New("no app provided")) internal.ShowSubcommandHelpAndError(cmd, errors.New("no app provided"))
} }
files, err := appPkg.LoadAppFiles("") files, err := appPkg.LoadAppFiles("")
@ -51,11 +51,11 @@ var appConfigCommand = cli.Command{
} }
} }
cmd := exec.Command(ed, appFile.Path) c := exec.Command(ed, appFile.Path)
cmd.Stdin = os.Stdin c.Stdin = os.Stdin
cmd.Stdout = os.Stdout c.Stdout = os.Stdout
cmd.Stderr = os.Stderr c.Stderr = os.Stderr
if err := cmd.Run(); err != nil { if err := c.Run(); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -22,21 +22,17 @@ import (
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
"github.com/docker/docker/errdefs" "github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/archive"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var appCpCommand = cli.Command{ var appCpCommand = cli.Command{
Name: "cp", Name: "cp",
Aliases: []string{"c"}, Aliases: []string{"c"},
ArgsUsage: "<domain> <src> <dst>", HideHelpCommand: true,
Flags: []cli.Flag{ UsageText: "abra app cp [options] <domain> <src> <dst>",
internal.DebugFlag, Before: internal.SubCommandBefore,
internal.NoInputFlag, Usage: "Copy files to/from a deployed app service",
}, Description: `Copy files to and from any app service file system.
Before: internal.SubCommandBefore,
Usage: "Copy files to/from a deployed app service",
Description: `
Copy files to and from any app service file system.
If you want to copy a myfile.txt to the root of the app service: If you want to copy a myfile.txt to the root of the app service:
@ -44,18 +40,17 @@ If you want to copy a myfile.txt to the root of the app service:
And if you want to copy that file back to your current working directory locally: And if you want to copy that file back to your current working directory locally:
abra app cp <domain> app:/myfile.txt . abra app cp <domain> app:/myfile.txt`,
`, EnableShellCompletion: true,
BashComplete: autocomplete.AppNameComplete, ShellComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
} }
src := c.Args().Get(1) src := cmd.Args().Get(1)
dst := c.Args().Get(2) dst := cmd.Args().Get(2)
if src == "" { if src == "" {
log.Fatal("missing <src> argument") log.Fatal("missing <src> argument")
} }

View File

@ -15,22 +15,21 @@ import (
"coopcloud.tech/abra/pkg/lint" "coopcloud.tech/abra/pkg/lint"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/upstream/stack" "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var appDeployCommand = cli.Command{ var appDeployCommand = cli.Command{
Name: "deploy", Name: "deploy",
Aliases: []string{"d"}, Aliases: []string{"d"},
Usage: "Deploy an app", Usage: "Deploy an app",
ArgsUsage: "<domain> [<version>]", ArgsUsage: "<domain> [<version>]",
HideHelpCommand: true,
UsageText: "abra app deploy [options] <domain> [<version>]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.ForceFlag, internal.ForceFlag,
internal.ChaosFlag, internal.ChaosFlag,
internal.NoDomainChecksFlag, internal.NoDomainChecksFlag,
internal.DontWaitConvergeFlag, internal.DontWaitConvergeFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: `Deploy an app. Description: `Deploy an app.
@ -38,22 +37,18 @@ var appDeployCommand = cli.Command{
This command supports chaos operations. Use "--chaos" to deploy your recipe This command supports chaos operations. Use "--chaos" to deploy your recipe
checkout as-is. Recipe commit hashes are also supported values for checkout as-is. Recipe commit hashes are also supported values for
"[<version>]". Please note, "upgrade"/"rollback" do not support chaos "[<version>]". Please note, "upgrade"/"rollback" do not support chaos
operations. operations.`,
EnableShellCompletion: true,
EXAMPLE: ShellComplete: autocomplete.AppNameComplete,
Action: func(ctx context.Context, cmd *cli.Command) error {
abra app deploy foo.example.com app := internal.ValidateApp(cmd)
abra app deploy foo.example.com 1.2.3+3.2.1
abra app deploy foo.example.com 1e83340e`,
BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error {
app := internal.ValidateApp(c)
stackName := app.StackName() stackName := app.StackName()
specificVersion := c.Args().Get(1) specificVersion := cmd.Args().Get(1)
if specificVersion == "" { if specificVersion == "" {
specificVersion = app.Recipe.Version specificVersion = app.Recipe.Version
} }
if specificVersion != "" && internal.Chaos { if specificVersion != "" && internal.Chaos {
log.Fatal("cannot use <version> and --chaos together") log.Fatal("cannot use <version> and --chaos together")
} }

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"sort" "sort"
@ -12,13 +13,14 @@ import (
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/tagcmp" "coopcloud.tech/tagcmp"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var ( var (
status bool status bool
statusFlag = &cli.BoolFlag{ statusFlag = &cli.BoolFlag{
Name: "status, S", Name: "status",
Aliases: []string{"S"},
Usage: "Show app deployment status", Usage: "Show app deployment status",
Destination: &status, Destination: &status,
} }
@ -27,7 +29,8 @@ var (
var ( var (
recipeFilter string recipeFilter string
recipeFlag = &cli.StringFlag{ recipeFlag = &cli.StringFlag{
Name: "recipe, r", Name: "recipe",
Aliases: []string{"r"},
Value: "", Value: "",
Usage: "Show apps of a specific recipe", Usage: "Show apps of a specific recipe",
Destination: &recipeFilter, Destination: &recipeFilter,
@ -37,7 +40,8 @@ var (
var ( var (
listAppServer string listAppServer string
listAppServerFlag = &cli.StringFlag{ listAppServerFlag = &cli.StringFlag{
Name: "server, s", Name: "server",
Aliases: []string{"s"},
Value: "", Value: "",
Usage: "Show apps of a specific server", Usage: "Show apps of a specific server",
Destination: &listAppServer, Destination: &listAppServer,
@ -67,26 +71,24 @@ type serverStatus struct {
} }
var appListCommand = cli.Command{ var appListCommand = cli.Command{
Name: "list", Name: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Usage: "List all managed apps", Usage: "List all managed apps",
Description: ` HideHelpCommand: true,
Read the local file system listing of apps and servers (e.g. ~/.abra/) to UsageText: "abra app list [options]",
generate a report of all your apps. Description: `Generate a report of all managed apps.
By passing the "--status/-S" flag, you can query all your servers for the By passing the "--status/-S" flag, you can query all your servers for the
actual live deployment status. Depending on how many servers you manage, this actual live deployment status. Depending on how many servers you manage, this
can take some time.`, can take some time.`,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,
statusFlag, statusFlag,
listAppServerFlag, listAppServerFlag,
recipeFlag, recipeFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
appFiles, err := appPkg.LoadAppFiles(listAppServer) appFiles, err := appPkg.LoadAppFiles(listAppServer)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -19,23 +19,24 @@ import (
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var appLogsCommand = cli.Command{ var appLogsCommand = cli.Command{
Name: "logs", Name: "logs",
Aliases: []string{"l"}, Aliases: []string{"l"},
ArgsUsage: "<domain> [<service>]", Usage: "Tail app logs",
Usage: "Tail app logs", HideHelpCommand: true,
UsageText: "abra app logs [options] <domain> [<service>]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.StdErrOnlyFlag, internal.StdErrOnlyFlag,
internal.SinceLogsFlag, internal.SinceLogsFlag,
internal.DebugFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, EnableShellCompletion: true,
Action: func(c *cli.Context) error { ShellComplete: autocomplete.AppNameComplete,
app := internal.ValidateApp(c) Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
stackName := app.StackName() stackName := app.StackName()
if err := app.Recipe.EnsureExists(); err != nil { if err := app.Recipe.EnsureExists(); err != nil {
@ -56,7 +57,7 @@ var appLogsCommand = cli.Command{
log.Fatalf("%s is not deployed?", app.Name) log.Fatalf("%s is not deployed?", app.Name)
} }
serviceName := c.Args().Get(1) serviceName := cmd.Args().Get(1)
serviceNames := []string{} serviceNames := []string{}
if serviceName != "" { if serviceName != "" {
serviceNames = []string{serviceName} serviceNames = []string{serviceName}

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"fmt" "fmt"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
@ -15,12 +16,13 @@ import (
"coopcloud.tech/abra/pkg/secret" "coopcloud.tech/abra/pkg/secret"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var appNewDescription = ` var appNewDescription = `Creates a new app from a default recipe.
Creates a new app from a default recipe. This new app configuration is stored
in your $ABRA_DIR directory under the appropriate server. This new app configuration is stored in your $ABRA_DIR directory under the
appropriate server.
This command does not deploy your app for you. You will need to run "abra app This command does not deploy your app for you. You will need to run "abra app
deploy <domain>" to do so. deploy <domain>" to do so.
@ -43,28 +45,27 @@ var appNewCommand = cli.Command{
Usage: "Create a new app", Usage: "Create a new app",
Description: appNewDescription, Description: appNewDescription,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.NewAppServerFlag, internal.NewAppServerFlag,
internal.DomainFlag, internal.DomainFlag,
internal.PassFlag, internal.PassFlag,
internal.SecretsFlag, internal.SecretsFlag,
internal.OfflineFlag,
internal.ChaosFlag, internal.ChaosFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
ArgsUsage: "[<recipe>] [<version>]", HideHelpCommand: true,
BashComplete: func(ctx *cli.Context) { UsageText: "abra app new [options] [<recipe>] [<version>]",
args := ctx.Args() EnableShellCompletion: true,
switch len(args) { ShellComplete: func(ctx context.Context, cmd *cli.Command) {
args := cmd.Args()
switch args.Len() {
case 0: case 0:
autocomplete.RecipeNameComplete(ctx) autocomplete.RecipeNameComplete(ctx, cmd)
case 1: case 1:
autocomplete.RecipeVersionComplete(ctx.Args().Get(0)) autocomplete.RecipeVersionComplete(cmd.Args().Get(0))
} }
}, },
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
recipe := internal.ValidateRecipe(c) recipe := internal.ValidateRecipe(cmd)
if !internal.Chaos { if !internal.Chaos {
if err := recipe.EnsureIsClean(); err != nil { if err := recipe.EnsureIsClean(); err != nil {
@ -75,7 +76,7 @@ var appNewCommand = cli.Command{
log.Fatal(err) log.Fatal(err)
} }
} }
if c.Args().Get(1) == "" { if cmd.Args().Get(1) == "" {
var version string var version string
recipeVersions, err := recipe.GetRecipeVersions() recipeVersions, err := recipe.GetRecipeVersions()
@ -100,7 +101,7 @@ var appNewCommand = cli.Command{
} }
} }
} else { } else {
if _, err := recipe.EnsureVersion(c.Args().Get(1)); err != nil { if _, err := recipe.EnsureVersion(cmd.Args().Get(1)); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

View File

@ -17,23 +17,24 @@ import (
containerTypes "github.com/docker/docker/api/types/container" containerTypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var appPsCommand = cli.Command{ var appPsCommand = cli.Command{
Name: "ps", Name: "ps",
Aliases: []string{"p"}, Aliases: []string{"p"},
Usage: "Check app status", Usage: "Check app status",
ArgsUsage: "<domain>", HideHelpCommand: true,
Description: "Show status of a deployed app.", UsageText: "abra app ps [options] <domain>",
Description: "Show status of a deployed app.",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.MachineReadableFlag, internal.MachineReadableFlag,
internal.DebugFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, EnableShellCompletion: true,
Action: func(c *cli.Context) error { ShellComplete: autocomplete.AppNameComplete,
app := internal.ValidateApp(c) Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(false, false); err != nil { if err := app.Recipe.Ensure(false, false); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -12,16 +12,16 @@ import (
stack "coopcloud.tech/abra/pkg/upstream/stack" stack "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var appRemoveCommand = cli.Command{ var appRemoveCommand = cli.Command{
Name: "remove", Name: "remove",
Aliases: []string{"rm"}, Aliases: []string{"rm"},
ArgsUsage: "<domain>", HideHelpCommand: true,
Usage: "Remove all app data, locally and remotely", UsageText: "abra app remove [options] <domain>",
Description: ` Usage: "Remove all app data, locally and remotely",
This command removes everything related to an app which is already undeployed. Description: `Remove everything related to an app which is already undeployed.
By default, it will prompt for confirmation before proceeding. All secrets, By default, it will prompt for confirmation before proceeding. All secrets,
volumes and the local app env file will be deleted. volumes and the local app env file will be deleted.
@ -39,14 +39,12 @@ To delete everything without prompt, use the "--force/-f" or the "--no-input/n"
flag.`, flag.`,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.ForceFlag, internal.ForceFlag,
internal.DebugFlag,
internal.NoInputFlag,
internal.OfflineFlag,
}, },
BashComplete: autocomplete.AppNameComplete, EnableShellCompletion: true,
Before: internal.SubCommandBefore, ShellComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Before: internal.SubCommandBefore,
app := internal.ValidateApp(c) Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if !internal.Force && !internal.NoInput { if !internal.Force && !internal.NoInput {
response := false response := false

View File

@ -12,41 +12,36 @@ import (
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
upstream "coopcloud.tech/abra/pkg/upstream/service" upstream "coopcloud.tech/abra/pkg/upstream/service"
stack "coopcloud.tech/abra/pkg/upstream/stack" stack "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var appRestartCommand = cli.Command{ var appRestartCommand = cli.Command{
Name: "restart", Name: "restart",
Aliases: []string{"re"}, Aliases: []string{"re"},
Usage: "Restart an app", Usage: "Restart an app",
ArgsUsage: "<domain> [<service>]", HideHelpCommand: true,
UsageText: "abra app restart [options] <domain> [<service>]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OfflineFlag,
internal.AllServicesFlag, internal.AllServicesFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `This command restarts services within a deployed app.
This command restarts services within a deployed app.
Run "abra app ps <domain>" to see a list of service names. Run "abra app ps <domain>" to see a list of service names.
Pass "--all-services/-a" to restart all services. Pass "--all-services/-a" to restart all services.`,
EnableShellCompletion: true,
EXAMPLE: ShellComplete: autocomplete.AppNameComplete,
Action: func(ctx context.Context, cmd *cli.Command) error {
abra app restart example.com app`, app := internal.ValidateApp(cmd)
BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error {
app := internal.ValidateApp(c)
if err := app.Recipe.Ensure(false, false); err != nil { if err := app.Recipe.Ensure(false, false); err != nil {
log.Fatal(err) log.Fatal(err)
} }
serviceName := c.Args().Get(1) serviceName := cmd.Args().Get(1)
if serviceName == "" && !internal.AllServices { if serviceName == "" && !internal.AllServices {
err := errors.New("missing <service>") err := errors.New("missing <service>")
internal.ShowSubcommandHelpAndError(c, err) internal.ShowSubcommandHelpAndError(cmd, err)
} }
if serviceName != "" && internal.AllServices { if serviceName != "" && internal.AllServices {

View File

@ -1,36 +1,38 @@
package app package app
import ( import (
"context"
"fmt" "fmt"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var targetPath string var targetPath string
var targetPathFlag = &cli.StringFlag{ var targetPathFlag = &cli.StringFlag{
Name: "target, t", Name: "target",
Aliases: []string{"t"},
Usage: "Target path", Usage: "Target path",
Destination: &targetPath, Destination: &targetPath,
} }
var appRestoreCommand = cli.Command{ var appRestoreCommand = cli.Command{
Name: "restore", Name: "restore",
Aliases: []string{"rs"}, Aliases: []string{"rs"},
Usage: "Restore an app backup", Usage: "Restore an app backup",
ArgsUsage: "<domain> <service>", HideHelpCommand: true,
UsageText: "abra app restore [options] <domain> <service>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OfflineFlag,
targetPathFlag, targetPathFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, EnableShellCompletion: true,
Action: func(c *cli.Context) error { ShellComplete: autocomplete.AppNameComplete,
app := internal.ValidateApp(c) Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -15,39 +15,32 @@ import (
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var appRollbackCommand = cli.Command{ var appRollbackCommand = cli.Command{
Name: "rollback", Name: "rollback",
Aliases: []string{"rl"}, Aliases: []string{"rl"},
Usage: "Roll an app back to a previous version", Usage: "Roll an app back to a previous version",
ArgsUsage: "<domain> [<version>]", HideHelpCommand: true,
UsageText: "abra app rollback [options] <domain> [<version>]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.ForceFlag, internal.ForceFlag,
internal.NoDomainChecksFlag, internal.NoDomainChecksFlag,
internal.DontWaitConvergeFlag, internal.DontWaitConvergeFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `This command rolls an app back to a previous version.
This command rolls an app back to a previous version.
Unlike "deploy", chaos operations are not supported here. Only recipe versions Unlike "deploy", chaos operations are not supported here. Only recipe versions
are supported values for "[<version>]". are supported values for "[<version>]".
A rollback can be destructive, please ensure you have a copy of your app data A rollback can be destructive, please ensure you have a copy of your app data
beforehand. beforehand.`,
EnableShellCompletion: true,
EXAMPLE: ShellComplete: autocomplete.AppNameComplete,
Action: func(ctx context.Context, cmd *cli.Command) error {
abra app rollback foo.example.com app := internal.ValidateApp(cmd)
abra app rollback foo.example.com 1.2.3+3.2.1`,
BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error {
app := internal.ValidateApp(c)
stackName := app.StackName() stackName := app.StackName()
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
@ -85,10 +78,11 @@ EXAMPLE:
log.Warnf("failed to determine deployed version of %s", app.Name) log.Warnf("failed to determine deployed version of %s", app.Name)
} }
specificVersion := c.Args().Get(1) specificVersion := cmd.Args().Get(1)
if specificVersion == "" { if specificVersion == "" {
specificVersion = app.Recipe.Version specificVersion = app.Recipe.Version
} }
if specificVersion != "" { if specificVersion != "" {
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version) parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
if err != nil { if err != nil {

View File

@ -14,19 +14,21 @@ import (
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var user string var user string
var userFlag = &cli.StringFlag{ var userFlag = &cli.StringFlag{
Name: "user, u", Name: "user",
Aliases: []string{"u"},
Value: "", Value: "",
Destination: &user, Destination: &user,
} }
var noTTY bool var noTTY bool
var noTTYFlag = &cli.BoolFlag{ var noTTYFlag = &cli.BoolFlag{
Name: "no-tty, t", Name: "no-tty",
Aliases: []string{"t"},
Destination: &noTTY, Destination: &noTTY,
} }
@ -34,23 +36,24 @@ var appRunCommand = cli.Command{
Name: "run", Name: "run",
Aliases: []string{"r"}, Aliases: []string{"r"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
noTTYFlag, noTTYFlag,
userFlag, userFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
ArgsUsage: "<domain> <service> <args>...", UsageText: "abra app run [options] <domain> <service> <args>",
Usage: "Run a command in a service container", Usage: "Run a command in an app service",
BashComplete: autocomplete.AppNameComplete, HideHelpCommand: true,
Action: func(c *cli.Context) error { EnableShellCompletion: true,
app := internal.ValidateApp(c) ShellComplete: autocomplete.AppNameComplete,
Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if len(c.Args()) < 2 { if cmd.Args().Len() < 2 {
internal.ShowSubcommandHelpAndError(c, errors.New("no <service> provided?")) internal.ShowSubcommandHelpAndError(cmd, errors.New("no <service> provided?"))
} }
if len(c.Args()) < 3 { if cmd.Args().Len() < 3 {
internal.ShowSubcommandHelpAndError(c, errors.New("no <args> provided?")) internal.ShowSubcommandHelpAndError(cmd, errors.New("no <args> provided?"))
} }
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
@ -58,7 +61,7 @@ var appRunCommand = cli.Command{
log.Fatal(err) log.Fatal(err)
} }
serviceName := c.Args().Get(1) serviceName := cmd.Args().Get(1)
stackAndServiceName := fmt.Sprintf("^%s_%s", app.StackName(), serviceName) stackAndServiceName := fmt.Sprintf("^%s_%s", app.StackName(), serviceName)
filters := filters.NewArgs() filters := filters.NewArgs()
filters.Add("name", stackAndServiceName) filters.Add("name", stackAndServiceName)
@ -68,12 +71,12 @@ var appRunCommand = cli.Command{
log.Fatal(err) log.Fatal(err)
} }
cmd := c.Args()[2:] c := cmd.Args().Slice()[2:]
execCreateOpts := types.ExecConfig{ execCreateOpts := types.ExecConfig{
AttachStderr: true, AttachStderr: true,
AttachStdin: true, AttachStdin: true,
AttachStdout: true, AttachStdout: true,
Cmd: cmd, Cmd: c,
Detach: false, Detach: false,
Tty: true, Tty: true,
} }

View File

@ -17,13 +17,14 @@ import (
"coopcloud.tech/abra/pkg/secret" "coopcloud.tech/abra/pkg/secret"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var ( var (
allSecrets bool allSecrets bool
allSecretsFlag = &cli.BoolFlag{ allSecretsFlag = &cli.BoolFlag{
Name: "all, a", Name: "all",
Aliases: []string{"a"},
Destination: &allSecrets, Destination: &allSecrets,
Usage: "Generate all secrets", Usage: "Generate all secrets",
} }
@ -32,41 +33,42 @@ var (
var ( var (
rmAllSecrets bool rmAllSecrets bool
rmAllSecretsFlag = &cli.BoolFlag{ rmAllSecretsFlag = &cli.BoolFlag{
Name: "all, a", Name: "all",
Aliases: []string{"a"},
Destination: &rmAllSecrets, Destination: &rmAllSecrets,
Usage: "Remove all secrets", Usage: "Remove all secrets",
} }
) )
var appSecretGenerateCommand = cli.Command{ var appSecretGenerateCommand = cli.Command{
Name: "generate", Name: "generate",
Aliases: []string{"g"}, Aliases: []string{"g"},
Usage: "Generate secrets", Usage: "Generate secrets",
ArgsUsage: "<domain> <secret> <version>", UsageText: "abra app secret generate [options] <domain> <secret> <version>",
HideHelpCommand: true,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
allSecretsFlag, allSecretsFlag,
internal.PassFlag, internal.PassFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,
internal.OfflineFlag,
internal.ChaosFlag, internal.ChaosFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, EnableShellCompletion: true,
Action: func(c *cli.Context) error { ShellComplete: autocomplete.AppNameComplete,
app := internal.ValidateApp(c) Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if len(c.Args()) == 1 && !allSecrets { if cmd.Args().Len() == 1 && !allSecrets {
err := errors.New("missing arguments <secret>/<version> or '--all'") err := errors.New("missing arguments <secret>/<version> or '--all'")
internal.ShowSubcommandHelpAndError(c, err) internal.ShowSubcommandHelpAndError(cmd, err)
} }
if c.Args().Get(1) != "" && allSecrets { if cmd.Args().Get(1) != "" && allSecrets {
err := errors.New("cannot use '<secret> <version>' and '--all' together") err := errors.New("cannot use '<secret> <version>' and '--all' together")
internal.ShowSubcommandHelpAndError(c, err) internal.ShowSubcommandHelpAndError(cmd, err)
} }
composeFiles, err := app.Recipe.GetComposeFiles(app.Env) composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
@ -80,8 +82,8 @@ var appSecretGenerateCommand = cli.Command{
} }
if !allSecrets { if !allSecrets {
secretName := c.Args().Get(1) secretName := cmd.Args().Get(1)
secretVersion := c.Args().Get(2) secretVersion := cmd.Args().Get(2)
s, ok := secrets[secretName] s, ok := secrets[secretName]
if !ok { if !ok {
log.Fatalf("%s doesn't exist in the env config?", secretName) log.Fatalf("%s doesn't exist in the env config?", secretName)
@ -142,26 +144,21 @@ var appSecretInsertCommand = cli.Command{
internal.FileFlag, internal.FileFlag,
internal.TrimFlag, internal.TrimFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
ArgsUsage: "<domain> <secret-name> <version> <data>", UsageText: "abra app secret insert [options] <domain> <secret> <version> <data>",
BashComplete: autocomplete.AppNameComplete, HideHelpCommand: true,
Description: ` EnableShellCompletion: true,
This command inserts a secret into an app environment. ShellComplete: autocomplete.AppNameComplete,
Description: `This command inserts a secret into an app environment.
This can be useful when you want to manually generate secrets for an app This can be useful when you want to manually generate secrets for an app
environment. Typically, you can let Abra generate them for you on app creation environment. Typically, you can let Abra generate them for you on app creation
(see "abra app new --secrets" for more). (see "abra app new --secrets" for more).`,
Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
Example: if cmd.Args().Len() != 4 {
internal.ShowSubcommandHelpAndError(cmd, errors.New("missing arguments?"))
abra app secret insert myapp db_pass v1 mySecretPassword
`,
Action: func(c *cli.Context) error {
app := internal.ValidateApp(c)
if len(c.Args()) != 4 {
internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments?"))
} }
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
@ -169,9 +166,9 @@ Example:
log.Fatal(err) log.Fatal(err)
} }
name := c.Args().Get(1) name := cmd.Args().Get(1)
version := c.Args().Get(2) version := cmd.Args().Get(2)
data := c.Args().Get(3) data := cmd.Args().Get(3)
if internal.File { if internal.File {
raw, err := os.ReadFile(data) raw, err := os.ReadFile(data)
@ -233,9 +230,10 @@ var appSecretRmCommand = cli.Command{
internal.OfflineFlag, internal.OfflineFlag,
internal.ChaosFlag, internal.ChaosFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
ArgsUsage: "<domain> [<secret-name>]", ArgsUsage: "<domain> [<secret-name>]",
BashComplete: autocomplete.AppNameComplete, EnableShellCompletion: true,
ShellComplete: autocomplete.AppNameComplete,
Description: ` Description: `
This command removes app secrets. This command removes app secrets.
@ -243,8 +241,8 @@ Example:
abra app secret remove myapp db_pass abra app secret remove myapp db_pass
`, `,
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -259,12 +257,12 @@ Example:
log.Fatal(err) log.Fatal(err)
} }
if c.Args().Get(1) != "" && rmAllSecrets { if cmd.Args().Get(1) != "" && rmAllSecrets {
internal.ShowSubcommandHelpAndError(c, errors.New("cannot use '<secret-name>' and '--all' together")) internal.ShowSubcommandHelpAndError(cmd, errors.New("cannot use '<secret-name>' and '--all' together"))
} }
if c.Args().Get(1) == "" && !rmAllSecrets { if cmd.Args().Get(1) == "" && !rmAllSecrets {
internal.ShowSubcommandHelpAndError(c, errors.New("no secret(s) specified?")) internal.ShowSubcommandHelpAndError(cmd, errors.New("no secret(s) specified?"))
} }
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
@ -288,7 +286,7 @@ Example:
} }
match := false match := false
secretToRm := c.Args().Get(1) secretToRm := cmd.Args().Get(1)
for secretName, val := range secrets { for secretName, val := range secrets {
secretRemoteName := fmt.Sprintf("%s_%s_%s", app.StackName(), secretName, val.Version) secretRemoteName := fmt.Sprintf("%s_%s_%s", app.StackName(), secretName, val.Version)
if _, ok := remoteSecretNames[secretRemoteName]; ok { if _, ok := remoteSecretNames[secretRemoteName]; ok {
@ -331,11 +329,12 @@ var appSecretLsCommand = cli.Command{
internal.ChaosFlag, internal.ChaosFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "List all secrets", Usage: "List all secrets",
BashComplete: autocomplete.AppNameComplete, EnableShellCompletion: true,
Action: func(c *cli.Context) error { ShellComplete: autocomplete.AppNameComplete,
app := internal.ValidateApp(c) Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -378,14 +377,15 @@ var appSecretLsCommand = cli.Command{
} }
var appSecretCommand = cli.Command{ var appSecretCommand = cli.Command{
Name: "secret", Name: "secret",
Aliases: []string{"s"}, Aliases: []string{"s"},
Usage: "Manage app secrets", Usage: "Manage app secrets",
ArgsUsage: "<domain>", HideHelpCommand: true,
Subcommands: []cli.Command{ UsageText: "abra app secret [command] [options] [arguments]",
appSecretGenerateCommand, Commands: []*cli.Command{
appSecretInsertCommand, &appSecretGenerateCommand,
appSecretRmCommand, &appSecretInsertCommand,
appSecretLsCommand, &appSecretRmCommand,
&appSecretLsCommand,
}, },
} }

View File

@ -13,21 +13,20 @@ import (
"coopcloud.tech/abra/pkg/service" "coopcloud.tech/abra/pkg/service"
stack "coopcloud.tech/abra/pkg/upstream/stack" stack "coopcloud.tech/abra/pkg/upstream/stack"
containerTypes "github.com/docker/docker/api/types/container" containerTypes "github.com/docker/docker/api/types/container"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var appServicesCommand = cli.Command{ var appServicesCommand = cli.Command{
Name: "services", Name: "services",
Aliases: []string{"sr"}, Aliases: []string{"sr"},
Usage: "Display all services of an app", Usage: "Display all services of an app",
ArgsUsage: "<domain>", HideHelpCommand: true,
Flags: []cli.Flag{ UsageText: "abra app services [options] <domain>",
internal.DebugFlag, Before: internal.SubCommandBefore,
}, EnableShellCompletion: true,
Before: internal.SubCommandBefore, ShellComplete: autocomplete.AppNameComplete,
BashComplete: autocomplete.AppNameComplete, Action: func(ctx context.Context, cmd *cli.Command) error {
Action: func(c *cli.Context) error { app := internal.ValidateApp(cmd)
app := internal.ValidateApp(c)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -13,13 +13,14 @@ import (
stack "coopcloud.tech/abra/pkg/upstream/stack" stack "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var prune bool var prune bool
var pruneFlag = &cli.BoolFlag{ var pruneFlag = &cli.BoolFlag{
Name: "prune, p", Name: "prune",
Aliases: []string{"p"},
Destination: &prune, Destination: &prune,
Usage: "Prunes unused containers, networks, and dangling images for an app", Usage: "Prunes unused containers, networks, and dangling images for an app",
} }
@ -61,26 +62,25 @@ func pruneApp(cl *dockerClient.Client, app appPkg.App) error {
} }
var appUndeployCommand = cli.Command{ var appUndeployCommand = cli.Command{
Name: "undeploy", Name: "undeploy",
Aliases: []string{"un"}, Aliases: []string{"un"},
ArgsUsage: "<domain>", UsageText: "abra app undeploy [options] <domain>",
HideHelpCommand: true,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
pruneFlag, pruneFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "Undeploy an app", Usage: "Undeploy an app",
BashComplete: autocomplete.AppNameComplete, EnableShellCompletion: true,
Description: ` ShellComplete: autocomplete.AppNameComplete,
This does not destroy any of the application data. Description: `This does not destroy any of the application data.
However, you should remain vigilant, as your swarm installation will consider However, you should remain vigilant, as your swarm installation will consider
any previously attached volumes as eligible for pruning once undeployed. any previously attached volumes as eligible for pruning once undeployed.
Passing "-p/--prune" does not remove those volumes.`, Passing "-p/--prune" does not remove those volumes.`,
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -14,40 +14,33 @@ import (
stack "coopcloud.tech/abra/pkg/upstream/stack" stack "coopcloud.tech/abra/pkg/upstream/stack"
"coopcloud.tech/tagcmp" "coopcloud.tech/tagcmp"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var appUpgradeCommand = cli.Command{ var appUpgradeCommand = cli.Command{
Name: "upgrade", Name: "upgrade",
Aliases: []string{"up"}, Aliases: []string{"up"},
Usage: "Upgrade an app", Usage: "Upgrade an app",
ArgsUsage: "<domain> [<version>]", UsageText: "abra app upgrade [options] <domain> [<version>]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.ForceFlag, internal.ForceFlag,
internal.NoDomainChecksFlag, internal.NoDomainChecksFlag,
internal.DontWaitConvergeFlag, internal.DontWaitConvergeFlag,
internal.OfflineFlag,
internal.ReleaseNotesFlag, internal.ReleaseNotesFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `Upgrade an app.
Upgrade an app.
Unlike "deploy", chaos operations are not supported here. Only recipe versions Unlike "deploy", chaos operations are not supported here. Only recipe versions
are supported values for "[<version>]". are supported values for "[<version>]".
An upgrade can be destructive, please ensure you have a copy of your app data An upgrade can be destructive, please ensure you have a copy of your app data
beforehand. beforehand.`,
EnableShellCompletion: true,
EXAMPLE: HideHelpCommand: true,
ShellComplete: autocomplete.AppNameComplete,
abra app upgrade foo.example.com Action: func(ctx context.Context, cmd *cli.Command) error {
abra app upgrade foo.example.com 1.2.3+3.2.1`, app := internal.ValidateApp(cmd)
BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error {
app := internal.ValidateApp(c)
stackName := app.StackName() stackName := app.StackName()
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
@ -85,7 +78,7 @@ EXAMPLE:
log.Warnf("failed to determine deployed version of %s", app.Name) log.Warnf("failed to determine deployed version of %s", app.Name)
} }
specificVersion := c.Args().Get(1) specificVersion := cmd.Args().Get(1)
if specificVersion != "" { if specificVersion != "" {
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version) parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
if err != nil { if err != nil {

View File

@ -10,22 +10,20 @@ import (
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/upstream/stack" "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var appVolumeListCommand = cli.Command{ var appVolumeListCommand = cli.Command{
Name: "list", Name: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
ArgsUsage: "<domain>", UsageText: "abra app volume list [options] <domain>",
Flags: []cli.Flag{ Before: internal.SubCommandBefore,
internal.DebugFlag, Usage: "List volumes associated with an app",
internal.NoInputFlag, HideHelpCommand: true,
}, EnableShellCompletion: true,
Before: internal.SubCommandBefore, ShellComplete: autocomplete.AppNameComplete,
Usage: "List volumes associated with an app", Action: func(ctx context.Context, cmd *cli.Command) error {
BashComplete: autocomplete.AppNameComplete, app := internal.ValidateApp(cmd)
Action: func(c *cli.Context) error {
app := internal.ValidateApp(c)
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {
@ -64,27 +62,28 @@ var appVolumeListCommand = cli.Command{
var appVolumeRemoveCommand = cli.Command{ var appVolumeRemoveCommand = cli.Command{
Name: "remove", Name: "remove",
Usage: "Remove volume(s) associated with an app", Usage: "Remove volume(s) associated with an app",
Description: ` Description: `Memove volumes associated with an app.
This command supports removing volumes associated with an app. The app in
question must be undeployed before you try to remove volumes. See "abra app The app in question must be undeployed before you try to remove volumes. See
undeploy <domain>" for more. "abra app undeploy <domain>" for more.
The command is interactive and will show a multiple select input which allows The command is interactive and will show a multiple select input which allows
you to make a seclection. Use the "?" key to see more help on navigating this you to make a seclection. Use the "?" key to see more help on navigating this
interface. interface.
Passing "--force/-f" will select all volumes for removal. Be careful.`, Passing "--force/-f" will select all volumes for removal. Be careful.`,
ArgsUsage: "<domain>", HideHelpCommand: true,
Aliases: []string{"rm"}, UsageText: "abra app volume remove [options] <domain>",
Aliases: []string{"rm"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag, internal.NoInputFlag,
internal.ForceFlag, internal.ForceFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, EnableShellCompletion: true,
Action: func(c *cli.Context) error { ShellComplete: autocomplete.AppNameComplete,
app := internal.ValidateApp(c) Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {
@ -145,12 +144,13 @@ Passing "--force/-f" will select all volumes for removal. Be careful.`,
} }
var appVolumeCommand = cli.Command{ var appVolumeCommand = cli.Command{
Name: "volume", Name: "volume",
Aliases: []string{"vl"}, Aliases: []string{"vl"},
Usage: "Manage app volumes", Usage: "Manage app volumes",
ArgsUsage: "<domain>", UsageText: "abra app volume [command] [options] [arguments]",
Subcommands: []cli.Command{ HideHelpCommand: true,
appVolumeListCommand, Commands: []*cli.Command{
appVolumeRemoveCommand, &appVolumeListCommand,
&appVolumeRemoveCommand,
}, },
} }

View File

@ -1,6 +1,7 @@
package catalogue package catalogue
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -15,25 +16,23 @@ import (
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var catalogueGenerateCommand = cli.Command{ var catalogueGenerateCommand = cli.Command{
Name: "generate", Name: "generate",
Aliases: []string{"g"}, Aliases: []string{"g"},
Usage: "Generate the recipe catalogue", Usage: "Generate the recipe catalogue",
HideHelpCommand: true,
UsageText: "abra catalogue generate [options] [<recipe>]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.PublishFlag, internal.PublishFlag,
internal.DryFlag, internal.DryFlag,
internal.SkipUpdatesFlag, internal.SkipUpdatesFlag,
internal.ChaosFlag, internal.ChaosFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `Generate a new copy of the recipe catalogue.
Generate a new copy of the recipe catalogue.
It is possible to generate new metadata for a single recipe by passing It is possible to generate new metadata for a single recipe by passing
<recipe>. The existing local catalogue will be updated, not overwritten. <recipe>. The existing local catalogue will be updated, not overwritten.
@ -45,14 +44,14 @@ If you have a Hub account you can have Abra log you in to avoid this. Pass
Push your new release to git.coopcloud.tech with "-p/--publish". This requires Push your new release to git.coopcloud.tech with "-p/--publish". This requires
that you have permission to git push to these repositories and have your SSH that you have permission to git push to these repositories and have your SSH
keys configured on your account.`, keys configured on your account.`,
ArgsUsage: "[<recipe>]", EnableShellCompletion: true,
BashComplete: autocomplete.RecipeNameComplete, ShellComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
recipeName := c.Args().First() recipeName := cmd.Args().First()
r := recipe.Get(recipeName) r := recipe.Get(recipeName)
if recipeName != "" { if recipeName != "" {
internal.ValidateRecipe(c) internal.ValidateRecipe(cmd)
} }
if !internal.Chaos { if !internal.Chaos {
@ -205,11 +204,12 @@ keys configured on your account.`,
// CatalogueCommand defines the `abra catalogue` command and sub-commands. // CatalogueCommand defines the `abra catalogue` command and sub-commands.
var CatalogueCommand = cli.Command{ var CatalogueCommand = cli.Command{
Name: "catalogue", Name: "catalogue",
Usage: "Manage the recipe catalogue", Usage: "Manage the recipe catalogue",
Aliases: []string{"c"}, Aliases: []string{"c"},
ArgsUsage: "<recipe>", HideHelpCommand: true,
Subcommands: []cli.Command{ UsageText: "abra catalogue [command] [options] [arguments]",
catalogueGenerateCommand, Commands: []*cli.Command{
&catalogueGenerateCommand,
}, },
} }

View File

@ -2,6 +2,7 @@
package cli package cli
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -18,7 +19,7 @@ import (
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/web" "coopcloud.tech/abra/pkg/web"
charmLog "github.com/charmbracelet/log" charmLog "github.com/charmbracelet/log"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
// AutoCompleteCommand helps people set up auto-complete in their shells // AutoCompleteCommand helps people set up auto-complete in their shells
@ -26,23 +27,15 @@ var AutoCompleteCommand = cli.Command{
Name: "autocomplete", Name: "autocomplete",
Aliases: []string{"ac"}, Aliases: []string{"ac"},
Usage: "Configure shell autocompletion", Usage: "Configure shell autocompletion",
Description: ` Description: `Set up shell auto-completion.
Set up shell auto-completion.
Supported shells are: bash, fish, fizsh & zsh. Supported shells are: bash, fish, fizsh & zsh.`,
EXAMPLE:
abra autocomplete bash`,
ArgsUsage: "<shell>", ArgsUsage: "<shell>",
Flags: []cli.Flag{ Action: func(ctx context.Context, cmd *cli.Command) error {
internal.DebugFlag, shellType := cmd.Args().First()
},
Action: func(c *cli.Context) error {
shellType := c.Args().First()
if shellType == "" { if shellType == "" {
internal.ShowSubcommandHelpAndError(c, errors.New("no shell provided")) internal.ShowSubcommandHelpAndError(cmd, errors.New("no shell provided"))
} }
supportedShells := map[string]bool{ supportedShells := map[string]bool{
@ -113,30 +106,24 @@ var UpgradeCommand = cli.Command{
Name: "upgrade", Name: "upgrade",
Aliases: []string{"u"}, Aliases: []string{"u"},
Usage: "Upgrade abra", Usage: "Upgrade abra",
Description: ` Description: `Upgrade abra in-place with the latest stable or release candidate.
Upgrade abra in-place with the latest stable or release candidate.
Use "-r/--rc" to install the latest release candidate. Please bear in mind that Use "-r/--rc" to install the latest release candidate. Please bear in mind that
it may contain absolutely catastrophic deal-breaker bugs. Thank you very much it may contain absolutely catastrophic deal-breaker bugs. Thank you very much
for the testing efforts 💗 for the testing efforts 💗`,
EXAMPLE:
abra upgrade
abra upgrade --rc`,
Flags: []cli.Flag{internal.RCFlag}, Flags: []cli.Flag{internal.RCFlag},
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
mainURL := "https://install.abra.coopcloud.tech" mainURL := "https://install.abra.coopcloud.tech"
cmd := exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash", mainURL)) c := exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash", mainURL))
if internal.RC { if internal.RC {
releaseCandidateURL := "https://git.coopcloud.tech/coop-cloud/abra/raw/branch/main/scripts/installer/installer" releaseCandidateURL := "https://git.coopcloud.tech/coop-cloud/abra/raw/branch/main/scripts/installer/installer"
cmd = exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash -s -- --rc", releaseCandidateURL)) c = exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash -s -- --rc", releaseCandidateURL))
} }
log.Debugf("attempting to run %s", cmd) log.Debugf("attempting to run %s", c)
if err := internal.RunCmd(cmd); err != nil { if err := internal.RunCmd(c); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -144,32 +131,32 @@ EXAMPLE:
}, },
} }
func newAbraApp(version, commit string) *cli.App { func newAbraApp(version, commit string) *cli.Command {
app := &cli.App{ app := &cli.Command{
Name: "abra", Name: "abra",
Usage: `the Co-op Cloud command-line utility belt 🎩🐇 Usage: "The Co-op Cloud command-line utility belt 🎩🐇",
____ ____ _ _ UsageText: "abra [command] [options] [arguments]",
/ ___|___ ___ _ __ / ___| | ___ _ _ __| | Version: fmt.Sprintf("%s-%s", version, commit[:7]),
| | / _ \ _____ / _ \| '_ \ | | | |/ _ \| | | |/ _' | Flags: []cli.Flag{
| |__| (_) |_____| (_) | |_) | | |___| | (_) | |_| | (_| | internal.DebugFlag,
\____\___/ \___/| .__/ \____|_|\___/ \__,_|\__,_| internal.OfflineFlag,
|_| internal.NoInputFlag,
`,
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
Commands: []cli.Command{
app.AppCommand,
server.ServerCommand,
recipe.RecipeCommand,
catalogue.CatalogueCommand,
UpgradeCommand,
AutoCompleteCommand,
}, },
BashComplete: autocomplete.SubcommandComplete, Commands: []*cli.Command{
&app.AppCommand,
&server.ServerCommand,
&recipe.RecipeCommand,
&catalogue.CatalogueCommand,
&UpgradeCommand,
&AutoCompleteCommand,
},
EnableShellCompletion: true,
UseShortOptionHandling: true,
HideHelpCommand: true,
ShellComplete: autocomplete.SubcommandComplete,
} }
app.EnableBashCompletion = true app.Before = func(ctx context.Context, cmd *cli.Command) error {
app.Before = func(c *cli.Context) error {
paths := []string{ paths := []string{
config.ABRA_DIR, config.ABRA_DIR,
config.SERVERS_DIR, config.SERVERS_DIR,
@ -193,6 +180,13 @@ func newAbraApp(version, commit string) *cli.App {
return nil return nil
} }
cli.HelpFlag = &cli.BoolFlag{
Name: "help",
Aliases: []string{"h, H"},
Usage: "Show help",
Persistent: true,
}
return app return app
} }
@ -200,7 +194,7 @@ func newAbraApp(version, commit string) *cli.App {
func RunApp(version, commit string) { func RunApp(version, commit string) {
app := newAbraApp(version, commit) app := newAbraApp(version, commit)
if err := app.Run(os.Args); err != nil { if err := app.Run(context.Background(), os.Args); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

View File

@ -1,10 +1,11 @@
package internal package internal
import ( import (
"context"
"os" "os"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
// Secrets stores the variable from SecretsFlag // Secrets stores the variable from SecretsFlag
@ -12,7 +13,8 @@ var Secrets bool
// SecretsFlag turns on/off automatically generating secrets // SecretsFlag turns on/off automatically generating secrets
var SecretsFlag = &cli.BoolFlag{ var SecretsFlag = &cli.BoolFlag{
Name: "secrets, S", Name: "secrets",
Aliases: []string{"S"},
Usage: "Automatically generate secrets", Usage: "Automatically generate secrets",
Destination: &Secrets, Destination: &Secrets,
} }
@ -22,7 +24,8 @@ var Pass bool
// PassFlag turns on/off storing generated secrets in pass // PassFlag turns on/off storing generated secrets in pass
var PassFlag = &cli.BoolFlag{ var PassFlag = &cli.BoolFlag{
Name: "pass, p", Name: "pass",
Aliases: []string{"p"},
Usage: "Store the generated secrets in a local pass store", Usage: "Store the generated secrets in a local pass store",
Destination: &Pass, Destination: &Pass,
} }
@ -32,21 +35,24 @@ var PassRemove bool
// PassRemoveFlag turns on/off removing generated secrets from pass // PassRemoveFlag turns on/off removing generated secrets from pass
var PassRemoveFlag = &cli.BoolFlag{ var PassRemoveFlag = &cli.BoolFlag{
Name: "pass, p", Name: "pass",
Aliases: []string{"p"},
Usage: "Remove generated secrets from a local pass store", Usage: "Remove generated secrets from a local pass store",
Destination: &PassRemove, Destination: &PassRemove,
} }
var File bool var File bool
var FileFlag = &cli.BoolFlag{ var FileFlag = &cli.BoolFlag{
Name: "file, f", Name: "file",
Aliases: []string{"f"},
Usage: "Treat input as a file", Usage: "Treat input as a file",
Destination: &File, Destination: &File,
} }
var Trim bool var Trim bool
var TrimFlag = &cli.BoolFlag{ var TrimFlag = &cli.BoolFlag{
Name: "trim, t", Name: "trim",
Aliases: []string{"t"},
Usage: "Trim input", Usage: "Trim input",
Destination: &Trim, Destination: &Trim,
} }
@ -56,7 +62,8 @@ var Force bool
// ForceFlag turns on/off force functionality. // ForceFlag turns on/off force functionality.
var ForceFlag = &cli.BoolFlag{ var ForceFlag = &cli.BoolFlag{
Name: "force, f", Name: "force",
Aliases: []string{"f"},
Usage: "Perform action without further prompt. Use with care!", Usage: "Perform action without further prompt. Use with care!",
Destination: &Force, Destination: &Force,
} }
@ -66,7 +73,8 @@ var Chaos bool
// ChaosFlag turns on/off chaos functionality. // ChaosFlag turns on/off chaos functionality.
var ChaosFlag = &cli.BoolFlag{ var ChaosFlag = &cli.BoolFlag{
Name: "chaos, C", Name: "chaos",
Aliases: []string{"C"},
Usage: "Proceed with uncommitted recipes changes. Use with care!", Usage: "Proceed with uncommitted recipes changes. Use with care!",
Destination: &Chaos, Destination: &Chaos,
} }
@ -76,16 +84,19 @@ var Tty bool
// TtyFlag turns on/off tty mode. // TtyFlag turns on/off tty mode.
var TtyFlag = &cli.BoolFlag{ var TtyFlag = &cli.BoolFlag{
Name: "tty, T", Name: "tty",
Aliases: []string{"T"},
Usage: "Disables TTY mode to run this command from a script.", Usage: "Disables TTY mode to run this command from a script.",
Destination: &Tty, Destination: &Tty,
} }
var NoInput bool var NoInput bool
var NoInputFlag = &cli.BoolFlag{ var NoInputFlag = &cli.BoolFlag{
Name: "no-input, n", Name: "no-input",
Aliases: []string{"n"},
Usage: "Toggle non-interactive mode", Usage: "Toggle non-interactive mode",
Destination: &NoInput, Destination: &NoInput,
Persistent: true,
} }
// Debug stores the variable from DebugFlag. // Debug stores the variable from DebugFlag.
@ -93,8 +104,10 @@ var Debug bool
// DebugFlag turns on/off verbose logging down to the DEBUG level. // DebugFlag turns on/off verbose logging down to the DEBUG level.
var DebugFlag = &cli.BoolFlag{ var DebugFlag = &cli.BoolFlag{
Name: "debug, d", Name: "debug",
Aliases: []string{"d"},
Destination: &Debug, Destination: &Debug,
Persistent: true,
Usage: "Show DEBUG messages", Usage: "Show DEBUG messages",
} }
@ -103,9 +116,11 @@ var Offline bool
// DebugFlag turns on/off offline mode. // DebugFlag turns on/off offline mode.
var OfflineFlag = &cli.BoolFlag{ var OfflineFlag = &cli.BoolFlag{
Name: "offline, o", Name: "offline",
Aliases: []string{"o"},
Destination: &Offline, Destination: &Offline,
Usage: "Prefer offline & filesystem access when possible", Usage: "Prefer offline & filesystem access when possible",
Persistent: true,
} }
// ReleaseNotes stores the variable from ReleaseNotesFlag. // ReleaseNotes stores the variable from ReleaseNotesFlag.
@ -113,7 +128,8 @@ var ReleaseNotes bool
// ReleaseNotesFlag turns on/off printing only release notes when upgrading. // ReleaseNotesFlag turns on/off printing only release notes when upgrading.
var ReleaseNotesFlag = &cli.BoolFlag{ var ReleaseNotesFlag = &cli.BoolFlag{
Name: "releasenotes, r", Name: "releasenotes",
Aliases: []string{"r"},
Destination: &ReleaseNotes, Destination: &ReleaseNotes,
Usage: "Only show release notes", Usage: "Only show release notes",
} }
@ -123,7 +139,8 @@ var MachineReadable bool
// MachineReadableFlag turns on/off machine readable output where supported // MachineReadableFlag turns on/off machine readable output where supported
var MachineReadableFlag = &cli.BoolFlag{ var MachineReadableFlag = &cli.BoolFlag{
Name: "machine, m", Name: "machine",
Aliases: []string{"m"},
Destination: &MachineReadable, Destination: &MachineReadable,
Usage: "Output in a machine-readable format (where supported)", Usage: "Output in a machine-readable format (where supported)",
} }
@ -133,49 +150,56 @@ var RC bool
// RCFlag chooses the latest release candidate for install // RCFlag chooses the latest release candidate for install
var RCFlag = &cli.BoolFlag{ var RCFlag = &cli.BoolFlag{
Name: "rc, r", Name: "rc",
Aliases: []string{"r"},
Destination: &RC, Destination: &RC,
Usage: "Install the latest release candidate", Usage: "Install the latest release candidate",
} }
var Major bool var Major bool
var MajorFlag = &cli.BoolFlag{ var MajorFlag = &cli.BoolFlag{
Name: "major, x", Name: "major",
Aliases: []string{"x"},
Usage: "Increase the major part of the version", Usage: "Increase the major part of the version",
Destination: &Major, Destination: &Major,
} }
var Minor bool var Minor bool
var MinorFlag = &cli.BoolFlag{ var MinorFlag = &cli.BoolFlag{
Name: "minor, y", Name: "minor",
Aliases: []string{"y"},
Usage: "Increase the minor part of the version", Usage: "Increase the minor part of the version",
Destination: &Minor, Destination: &Minor,
} }
var Patch bool var Patch bool
var PatchFlag = &cli.BoolFlag{ var PatchFlag = &cli.BoolFlag{
Name: "patch, z", Name: "patch",
Aliases: []string{"z"},
Usage: "Increase the patch part of the version", Usage: "Increase the patch part of the version",
Destination: &Patch, Destination: &Patch,
} }
var Dry bool var Dry bool
var DryFlag = &cli.BoolFlag{ var DryFlag = &cli.BoolFlag{
Name: "dry-run, r", Name: "dry-run",
Aliases: []string{"r"},
Usage: "Only reports changes that would be made", Usage: "Only reports changes that would be made",
Destination: &Dry, Destination: &Dry,
} }
var Publish bool var Publish bool
var PublishFlag = &cli.BoolFlag{ var PublishFlag = &cli.BoolFlag{
Name: "publish, p", Name: "publish",
Aliases: []string{"p"},
Usage: "Publish changes to git.coopcloud.tech", Usage: "Publish changes to git.coopcloud.tech",
Destination: &Publish, Destination: &Publish,
} }
var Domain string var Domain string
var DomainFlag = &cli.StringFlag{ var DomainFlag = &cli.StringFlag{
Name: "domain, D", Name: "domain",
Aliases: []string{"D"},
Value: "", Value: "",
Usage: "Choose a domain name", Usage: "Choose a domain name",
Destination: &Domain, Destination: &Domain,
@ -183,7 +207,8 @@ var DomainFlag = &cli.StringFlag{
var NewAppServer string var NewAppServer string
var NewAppServerFlag = &cli.StringFlag{ var NewAppServerFlag = &cli.StringFlag{
Name: "server, s", Name: "server",
Aliases: []string{"s"},
Value: "", Value: "",
Usage: "Show apps of a specific server", Usage: "Show apps of a specific server",
Destination: &NewAppServer, Destination: &NewAppServer,
@ -191,21 +216,24 @@ var NewAppServerFlag = &cli.StringFlag{
var NoDomainChecks bool var NoDomainChecks bool
var NoDomainChecksFlag = &cli.BoolFlag{ var NoDomainChecksFlag = &cli.BoolFlag{
Name: "no-domain-checks, D", Name: "no-domain-checks",
Aliases: []string{"D"},
Usage: "Disable public DNS checks", Usage: "Disable public DNS checks",
Destination: &NoDomainChecks, Destination: &NoDomainChecks,
} }
var StdErrOnly bool var StdErrOnly bool
var StdErrOnlyFlag = &cli.BoolFlag{ var StdErrOnlyFlag = &cli.BoolFlag{
Name: "stderr, s", Name: "stderr",
Aliases: []string{"s"},
Usage: "Only tail stderr", Usage: "Only tail stderr",
Destination: &StdErrOnly, Destination: &StdErrOnly,
} }
var SinceLogs string var SinceLogs string
var SinceLogsFlag = &cli.StringFlag{ var SinceLogsFlag = &cli.StringFlag{
Name: "since, S", Name: "since",
Aliases: []string{"S"},
Value: "", Value: "",
Usage: "tail logs since YYYY-MM-DDTHH:MM:SSZ", Usage: "tail logs since YYYY-MM-DDTHH:MM:SSZ",
Destination: &SinceLogs, Destination: &SinceLogs,
@ -213,49 +241,56 @@ var SinceLogsFlag = &cli.StringFlag{
var DontWaitConverge bool var DontWaitConverge bool
var DontWaitConvergeFlag = &cli.BoolFlag{ var DontWaitConvergeFlag = &cli.BoolFlag{
Name: "no-converge-checks, c", Name: "no-converge-checks",
Aliases: []string{"c"},
Usage: "Don't wait for converge logic checks", Usage: "Don't wait for converge logic checks",
Destination: &DontWaitConverge, Destination: &DontWaitConverge,
} }
var Watch bool var Watch bool
var WatchFlag = &cli.BoolFlag{ var WatchFlag = &cli.BoolFlag{
Name: "watch, w", Name: "watch",
Aliases: []string{"w"},
Usage: "Watch status by polling repeatedly", Usage: "Watch status by polling repeatedly",
Destination: &Watch, Destination: &Watch,
} }
var OnlyErrors bool var OnlyErrors bool
var OnlyErrorFlag = &cli.BoolFlag{ var OnlyErrorFlag = &cli.BoolFlag{
Name: "errors, e", Name: "errors",
Aliases: []string{"e"},
Usage: "Only show errors", Usage: "Only show errors",
Destination: &OnlyErrors, Destination: &OnlyErrors,
} }
var SkipUpdates bool var SkipUpdates bool
var SkipUpdatesFlag = &cli.BoolFlag{ var SkipUpdatesFlag = &cli.BoolFlag{
Name: "skip-updates, s", Name: "skip-updates",
Aliases: []string{"s"},
Usage: "Skip updating recipe repositories", Usage: "Skip updating recipe repositories",
Destination: &SkipUpdates, Destination: &SkipUpdates,
} }
var AllTags bool var AllTags bool
var AllTagsFlag = &cli.BoolFlag{ var AllTagsFlag = &cli.BoolFlag{
Name: "all-tags, a", Name: "all-tags",
Aliases: []string{"a"},
Usage: "List all tags, not just upgrades", Usage: "List all tags, not just upgrades",
Destination: &AllTags, Destination: &AllTags,
} }
var LocalCmd bool var LocalCmd bool
var LocalCmdFlag = &cli.BoolFlag{ var LocalCmdFlag = &cli.BoolFlag{
Name: "local, l", Name: "local",
Aliases: []string{"l"},
Usage: "Run command locally", Usage: "Run command locally",
Destination: &LocalCmd, Destination: &LocalCmd,
} }
var RemoteUser string var RemoteUser string
var RemoteUserFlag = &cli.StringFlag{ var RemoteUserFlag = &cli.StringFlag{
Name: "user, u", Name: "user",
Aliases: []string{"u"},
Value: "", Value: "",
Usage: "User to run command within a service context", Usage: "User to run command within a service context",
Destination: &RemoteUser, Destination: &RemoteUser,
@ -263,7 +298,8 @@ var RemoteUserFlag = &cli.StringFlag{
var GitName string var GitName string
var GitNameFlag = &cli.StringFlag{ var GitNameFlag = &cli.StringFlag{
Name: "git-name, gn", Name: "git-name",
Aliases: []string{"gn"},
Value: "", Value: "",
Usage: "Git (user) name to do commits with", Usage: "Git (user) name to do commits with",
Destination: &GitName, Destination: &GitName,
@ -271,7 +307,8 @@ var GitNameFlag = &cli.StringFlag{
var GitEmail string var GitEmail string
var GitEmailFlag = &cli.StringFlag{ var GitEmailFlag = &cli.StringFlag{
Name: "git-email, ge", Name: "git-email",
Aliases: []string{"ge"},
Value: "", Value: "",
Usage: "Git email name to do commits with", Usage: "Git email name to do commits with",
Destination: &GitEmail, Destination: &GitEmail,
@ -279,13 +316,14 @@ var GitEmailFlag = &cli.StringFlag{
var AllServices bool var AllServices bool
var AllServicesFlag = &cli.BoolFlag{ var AllServicesFlag = &cli.BoolFlag{
Name: "all-services, a", Name: "all-services",
Aliases: []string{"a"},
Usage: "Restart all services", Usage: "Restart all services",
Destination: &AllServices, Destination: &AllServices,
} }
// SubCommandBefore wires up pre-action machinery (e.g. --debug handling). // SubCommandBefore wires up pre-action machinery (e.g. --debug handling).
func SubCommandBefore(c *cli.Context) error { func SubCommandBefore(ctx context.Context, cmd *cli.Command) error {
if Debug { if Debug {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
log.SetOutput(os.Stderr) log.SetOutput(os.Stderr)

View File

@ -4,13 +4,13 @@ import (
"os" "os"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
// ShowSubcommandHelpAndError exits the program on error, logs the error to the // ShowSubcommandHelpAndError exits the program on error, logs the error to the
// terminal, and shows the help command. // terminal, and shows the help command.
func ShowSubcommandHelpAndError(c *cli.Context, err interface{}) { func ShowSubcommandHelpAndError(cmd *cli.Command, err interface{}) {
if err2 := cli.ShowSubcommandHelp(c); err2 != nil { if err2 := cli.ShowSubcommandHelp(cmd); err2 != nil {
log.Error(err2) log.Error(err2)
} }
log.Error(err) log.Error(err)

View File

@ -9,12 +9,12 @@ import (
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
// ValidateRecipe ensures the recipe arg is valid. // ValidateRecipe ensures the recipe arg is valid.
func ValidateRecipe(c *cli.Context) recipe.Recipe { func ValidateRecipe(cmd *cli.Command) recipe.Recipe {
recipeName := c.Args().First() recipeName := cmd.Args().First()
if recipeName == "" && !NoInput { if recipeName == "" && !NoInput {
var recipes []string var recipes []string
@ -54,7 +54,7 @@ func ValidateRecipe(c *cli.Context) recipe.Recipe {
} }
if recipeName == "" { if recipeName == "" {
ShowSubcommandHelpAndError(c, errors.New("no recipe name provided")) ShowSubcommandHelpAndError(cmd, errors.New("no recipe name provided"))
} }
chosenRecipe := recipe.Get(recipeName) chosenRecipe := recipe.Get(recipeName)
@ -64,7 +64,7 @@ func ValidateRecipe(c *cli.Context) recipe.Recipe {
} }
_, err = chosenRecipe.GetComposeConfig(nil) _, err = chosenRecipe.GetComposeConfig(nil)
if err != nil { if err != nil {
if c.Command.Name == "generate" { if cmd.Name == "generate" {
if strings.Contains(err.Error(), "missing a compose") { if strings.Contains(err.Error(), "missing a compose") {
log.Fatal(err) log.Fatal(err)
} }
@ -83,11 +83,11 @@ func ValidateRecipe(c *cli.Context) recipe.Recipe {
} }
// ValidateApp ensures the app name arg is valid. // ValidateApp ensures the app name arg is valid.
func ValidateApp(c *cli.Context) app.App { func ValidateApp(cmd *cli.Command) app.App {
appName := c.Args().First() appName := cmd.Args().First()
if appName == "" { if appName == "" {
ShowSubcommandHelpAndError(c, errors.New("no app provided")) ShowSubcommandHelpAndError(cmd, errors.New("no app provided"))
} }
app, err := app.Get(appName) app, err := app.Get(appName)
@ -101,8 +101,8 @@ func ValidateApp(c *cli.Context) app.App {
} }
// ValidateDomain ensures the domain name arg is valid. // ValidateDomain ensures the domain name arg is valid.
func ValidateDomain(c *cli.Context) string { func ValidateDomain(cmd *cli.Command) string {
domainName := c.Args().First() domainName := cmd.Args().First()
if domainName == "" && !NoInput { if domainName == "" && !NoInput {
prompt := &survey.Input{ prompt := &survey.Input{
@ -115,7 +115,7 @@ func ValidateDomain(c *cli.Context) string {
} }
if domainName == "" { if domainName == "" {
ShowSubcommandHelpAndError(c, errors.New("no domain provided")) ShowSubcommandHelpAndError(cmd, errors.New("no domain provided"))
} }
log.Debugf("validated %s as domain argument", domainName) log.Debugf("validated %s as domain argument", domainName)
@ -124,10 +124,10 @@ func ValidateDomain(c *cli.Context) string {
} }
// ValidateSubCmdFlags ensures flag order conforms to correct order // ValidateSubCmdFlags ensures flag order conforms to correct order
func ValidateSubCmdFlags(c *cli.Context) bool { func ValidateSubCmdFlags(cmd *cli.Command) bool {
for argIdx, arg := range c.Args() { for argIdx, arg := range cmd.Args().Slice() {
if !strings.HasPrefix(arg, "--") { if !strings.HasPrefix(arg, "--") {
for _, flag := range c.Args()[argIdx:] { for _, flag := range cmd.Args().Slice()[argIdx:] {
if strings.HasPrefix(flag, "--") { if strings.HasPrefix(flag, "--") {
return false return false
} }
@ -138,8 +138,8 @@ func ValidateSubCmdFlags(c *cli.Context) bool {
} }
// ValidateServer ensures the server name arg is valid. // ValidateServer ensures the server name arg is valid.
func ValidateServer(c *cli.Context) string { func ValidateServer(cmd *cli.Command) string {
serverName := c.Args().First() serverName := cmd.Args().First()
serverNames, err := config.ReadServerNames() serverNames, err := config.ReadServerNames()
if err != nil { if err != nil {
@ -164,11 +164,11 @@ func ValidateServer(c *cli.Context) string {
} }
if serverName == "" { if serverName == "" {
ShowSubcommandHelpAndError(c, errors.New("no server provided")) ShowSubcommandHelpAndError(cmd, errors.New("no server provided"))
} }
if !matched { if !matched {
ShowSubcommandHelpAndError(c, errors.New("server doesn't exist?")) ShowSubcommandHelpAndError(cmd, errors.New("server doesn't exist?"))
} }
log.Debugf("validated %s as server argument", serverName) log.Debugf("validated %s as server argument", serverName)

View File

@ -1,27 +1,27 @@
package recipe package recipe
import ( import (
"context"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
gitPkg "coopcloud.tech/abra/pkg/git" gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var recipeDiffCommand = cli.Command{ var recipeDiffCommand = cli.Command{
Name: "diff", Name: "diff",
Usage: "Show unstaged changes in recipe config", Usage: "Show unstaged changes in recipe config",
Description: "This command requires /usr/bin/git.", Description: "This command requires /usr/bin/git.",
Aliases: []string{"d"}, HideHelpCommand: true,
ArgsUsage: "<recipe>", Aliases: []string{"d"},
Flags: []cli.Flag{ UsageText: "abra recipe diff [options] <recipe>",
internal.DebugFlag, Before: internal.SubCommandBefore,
internal.NoInputFlag, EnableShellCompletion: true,
}, ShellComplete: autocomplete.RecipeNameComplete,
Before: internal.SubCommandBefore, Action: func(ctx context.Context, cmd *cli.Command) error {
BashComplete: autocomplete.RecipeNameComplete, r := internal.ValidateRecipe(cmd)
Action: func(c *cli.Context) error {
r := internal.ValidateRecipe(c)
if err := gitPkg.DiffUnstaged(r.Dir); err != nil { if err := gitPkg.DiffUnstaged(r.Dir); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -1,32 +1,30 @@
package recipe package recipe
import ( import (
"context"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var recipeFetchCommand = cli.Command{ var recipeFetchCommand = cli.Command{
Name: "fetch", Name: "fetch",
Usage: "Fetch recipe(s)", Usage: "Fetch recipe(s)",
Aliases: []string{"f"}, Aliases: []string{"f"},
ArgsUsage: "[<recipe>]", UsageText: "abra recipe fetch [options] [<recipe>]",
Description: "Retrieves all recipes if no <recipe> argument is passed", Description: "Retrieves all recipes if no <recipe> argument is passed",
Flags: []cli.Flag{ Before: internal.SubCommandBefore,
internal.DebugFlag, EnableShellCompletion: true,
internal.NoInputFlag, ShellComplete: autocomplete.RecipeNameComplete,
internal.OfflineFlag, Action: func(ctx context.Context, cmd *cli.Command) error {
}, recipeName := cmd.Args().First()
Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error {
recipeName := c.Args().First()
r := recipe.Get(recipeName) r := recipe.Get(recipeName)
if recipeName != "" { if recipeName != "" {
internal.ValidateRecipe(c) internal.ValidateRecipe(cmd)
if err := r.Ensure(false, false); err != nil { if err := r.Ensure(false, false); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -1,6 +1,7 @@
package recipe package recipe
import ( import (
"context"
"fmt" "fmt"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
@ -8,25 +9,24 @@ import (
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/lint" "coopcloud.tech/abra/pkg/lint"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var recipeLintCommand = cli.Command{ var recipeLintCommand = cli.Command{
Name: "lint", Name: "lint",
Usage: "Lint a recipe", Usage: "Lint a recipe",
Aliases: []string{"l"}, Aliases: []string{"l"},
ArgsUsage: "<recipe>", UsageText: "abra recipe lint [options] <recipe>",
HideHelpCommand: true,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OnlyErrorFlag, internal.OnlyErrorFlag,
internal.OfflineFlag,
internal.NoInputFlag,
internal.ChaosFlag, internal.ChaosFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete, EnableShellCompletion: true,
Action: func(c *cli.Context) error { ShellComplete: autocomplete.RecipeNameComplete,
recipe := internal.ValidateRecipe(c) Action: func(ctx context.Context, cmd *cli.Command) error {
recipe := internal.ValidateRecipe(cmd)
if err := recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -1,6 +1,7 @@
package recipe package recipe
import ( import (
"context"
"fmt" "fmt"
"sort" "sort"
"strconv" "strconv"
@ -10,29 +11,30 @@ import (
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var pattern string var pattern string
var patternFlag = &cli.StringFlag{ var patternFlag = &cli.StringFlag{
Name: "pattern, p", Name: "pattern",
Aliases: []string{"p"},
Value: "", Value: "",
Usage: "Simple string to filter recipes", Usage: "Simple string to filter recipes",
Destination: &pattern, Destination: &pattern,
} }
var recipeListCommand = cli.Command{ var recipeListCommand = cli.Command{
Name: "list", Name: "list",
Usage: "List available recipes", Usage: "List recipes",
Aliases: []string{"ls"}, HideHelpCommand: true,
UsageText: "abra recipe list [options]",
Aliases: []string{"ls"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,
patternFlag, patternFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
catl, err := recipe.ReadRecipeCatalogue(internal.Offline) catl, err := recipe.ReadRecipeCatalogue(internal.Offline)
if err != nil { if err != nil {
log.Fatal(err.Error()) log.Fatal(err.Error())

View File

@ -2,6 +2,7 @@ package recipe
import ( import (
"bytes" "bytes"
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -13,7 +14,7 @@ import (
"coopcloud.tech/abra/pkg/git" "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
// recipeMetadata is the recipe metadata for the README.md // recipeMetadata is the recipe metadata for the README.md
@ -34,27 +35,24 @@ var recipeNewCommand = cli.Command{
Name: "new", Name: "new",
Aliases: []string{"n"}, Aliases: []string{"n"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.OfflineFlag,
internal.GitNameFlag, internal.GitNameFlag,
internal.GitEmailFlag, internal.GitEmailFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "Create a new recipe", Usage: "Create a new recipe",
ArgsUsage: "<recipe>", UsageText: "abra recipe new [options] <recipe>",
Description: ` HideHelpCommand: true,
Create a new recipe. Description: `Create a new recipe.
Abra uses the built-in example repository which is available here: Abra uses the built-in example repository which is available here:
https://git.coopcloud.tech/coop-cloud/example`, https://git.coopcloud.tech/coop-cloud/example`,
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
recipeName := c.Args().First() recipeName := cmd.Args().First()
r := recipe.Get(recipeName) r := recipe.Get(recipeName)
if recipeName == "" { if recipeName == "" {
internal.ShowSubcommandHelpAndError(c, errors.New("no recipe name provided")) internal.ShowSubcommandHelpAndError(cmd, errors.New("no recipe name provided"))
} }
if _, err := os.Stat(r.Dir); !os.IsNotExist(err) { if _, err := os.Stat(r.Dir); !os.IsNotExist(err) {

View File

@ -1,7 +1,7 @@
package recipe package recipe
import ( import (
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
// RecipeCommand defines all recipe related sub-commands. // RecipeCommand defines all recipe related sub-commands.
@ -9,9 +9,8 @@ var RecipeCommand = cli.Command{
Name: "recipe", Name: "recipe",
Aliases: []string{"r"}, Aliases: []string{"r"},
Usage: "Manage recipes", Usage: "Manage recipes",
ArgsUsage: "<recipe>", UsageText: "abra recipe [command] [options] [arguments]",
Description: ` Description: `A recipe is a blueprint for an app. It is a bunch of config files which
A recipe is a blueprint for an app. It is a bunch of config files which
describe how to deploy and maintain an app. Recipes are maintained by the Co-op describe how to deploy and maintain an app. Recipes are maintained by the Co-op
Cloud community and you can use Abra to read them, deploy them and create apps Cloud community and you can use Abra to read them, deploy them and create apps
for you. for you.
@ -19,16 +18,17 @@ for you.
Anyone who uses a recipe can become a maintainer. Maintainers typically make Anyone who uses a recipe can become a maintainer. Maintainers typically make
sure the recipe is in good working order and the config upgraded in a timely sure the recipe is in good working order and the config upgraded in a timely
manner.`, manner.`,
Subcommands: []cli.Command{ HideHelpCommand: true,
recipeFetchCommand, Commands: []*cli.Command{
recipeLintCommand, &recipeFetchCommand,
recipeListCommand, &recipeLintCommand,
recipeNewCommand, &recipeListCommand,
recipeReleaseCommand, &recipeNewCommand,
recipeSyncCommand, &recipeReleaseCommand,
recipeUpgradeCommand, &recipeSyncCommand,
recipeVersionCommand, &recipeUpgradeCommand,
recipeResetCommand, &recipeVersionCommand,
recipeDiffCommand, &recipeResetCommand,
&recipeDiffCommand,
}, },
} }

View File

@ -1,6 +1,7 @@
package recipe package recipe
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -18,17 +19,19 @@ import (
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var recipeReleaseCommand = cli.Command{ var recipeReleaseCommand = cli.Command{
Name: "release", Name: "release",
Aliases: []string{"rl"}, Aliases: []string{"rl"},
Usage: "Release a new recipe version", Usage: "Release a new recipe version",
ArgsUsage: "<recipe> [<version>]", HideHelpCommand: true,
Description: ` UsageText: "abra recipe release [options] <recipe> [<version>]",
Create a new version of a recipe. These versions are then published on the Description: `Create a new version of a recipe.
Co-op Cloud recipe catalogue. These versions take the following form:
These versions are then published on the Co-op Cloud recipe catalogue. These
versions take the following form:
a.b.c+x.y.z a.b.c+x.y.z
@ -46,19 +49,17 @@ Publish your new release to git.coopcloud.tech with "-p/--publish". This
requires that you have permission to git push to these repositories and have requires that you have permission to git push to these repositories and have
your SSH keys configured on your account.`, your SSH keys configured on your account.`,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.DryFlag, internal.DryFlag,
internal.MajorFlag, internal.MajorFlag,
internal.MinorFlag, internal.MinorFlag,
internal.PatchFlag, internal.PatchFlag,
internal.PublishFlag, internal.PublishFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete, EnableShellCompletion: true,
Action: func(c *cli.Context) error { ShellComplete: autocomplete.RecipeNameComplete,
recipe := internal.ValidateRecipe(c) Action: func(ctx context.Context, cmd *cli.Command) error {
recipe := internal.ValidateRecipe(cmd)
imagesTmp, err := getImageVersions(recipe) imagesTmp, err := getImageVersions(recipe)
if err != nil { if err != nil {
@ -75,7 +76,7 @@ your SSH keys configured on your account.`,
log.Fatalf("main app service version for %s is empty?", recipe.Name) log.Fatalf("main app service version for %s is empty?", recipe.Name)
} }
tagString := c.Args().Get(1) tagString := cmd.Args().Get(1)
if tagString != "" { if tagString != "" {
if _, err := tagcmp.Parse(tagString); err != nil { if _, err := tagcmp.Parse(tagString); err != nil {
log.Fatalf("cannot parse %s, invalid tag specified?", tagString) log.Fatalf("cannot parse %s, invalid tag specified?", tagString)

View File

@ -1,32 +1,32 @@
package recipe package recipe
import ( import (
"context"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var recipeResetCommand = cli.Command{ var recipeResetCommand = cli.Command{
Name: "reset", Name: "reset",
Usage: "Remove all unstaged changes from recipe config", Usage: "Remove all unstaged changes from recipe config",
Description: "WARNING: this will delete your changes. Be Careful.", Description: "WARNING: this will delete your changes. Be Careful.",
Aliases: []string{"rs"}, HideHelpCommand: true,
ArgsUsage: "<recipe>", Aliases: []string{"rs"},
Flags: []cli.Flag{ UsageText: "abra recipe reset [options] <recipe>",
internal.DebugFlag, Before: internal.SubCommandBefore,
internal.NoInputFlag, EnableShellCompletion: true,
}, ShellComplete: autocomplete.RecipeNameComplete,
Before: internal.SubCommandBefore, Action: func(ctx context.Context, cmd *cli.Command) error {
BashComplete: autocomplete.RecipeNameComplete, recipeName := cmd.Args().First()
Action: func(c *cli.Context) error {
recipeName := c.Args().First()
r := recipe.Get(recipeName) r := recipe.Get(recipeName)
if recipeName != "" { if recipeName != "" {
internal.ValidateRecipe(c) internal.ValidateRecipe(cmd)
} }
repo, err := git.PlainOpen(r.Dir) repo, err := git.PlainOpen(r.Dir)

View File

@ -1,6 +1,7 @@
package recipe package recipe
import ( import (
"context"
"fmt" "fmt"
"strconv" "strconv"
@ -12,35 +13,35 @@ import (
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var recipeSyncCommand = cli.Command{ var recipeSyncCommand = cli.Command{
Name: "sync", Name: "sync",
Aliases: []string{"s"}, Aliases: []string{"s"},
Usage: "Sync recipe version label", Usage: "Sync recipe version label",
ArgsUsage: "<recipe> [<version>]", HideHelpCommand: true,
UsageText: "abra recipe lint [options] <recipe> [<version>]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.DryFlag, internal.DryFlag,
internal.MajorFlag, internal.MajorFlag,
internal.MinorFlag, internal.MinorFlag,
internal.PatchFlag, internal.PatchFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `Generate labels for the main recipe service.
Generate labels for the main recipe service (i.e. by convention, the service
named "app") which corresponds to the following format: By convention, the service named "app" using the following format:
coop-cloud.${STACK_NAME}.version=<version> coop-cloud.${STACK_NAME}.version=<version>
Where <version> can be specifed on the command-line or Abra can attempt to Where <version> can be specifed on the command-line or Abra can attempt to
auto-generate it for you. The <recipe> configuration will be updated on the auto-generate it for you. The <recipe> configuration will be updated on the
local file system.`, local file system.`,
BashComplete: autocomplete.RecipeNameComplete, EnableShellCompletion: true,
Action: func(c *cli.Context) error { ShellComplete: autocomplete.RecipeNameComplete,
recipe := internal.ValidateRecipe(c) Action: func(ctx context.Context, cmd *cli.Command) error {
recipe := internal.ValidateRecipe(cmd)
mainApp, err := internal.GetMainAppImage(recipe) mainApp, err := internal.GetMainAppImage(recipe)
if err != nil { if err != nil {
@ -59,7 +60,7 @@ local file system.`,
log.Fatal(err) log.Fatal(err)
} }
nextTag := c.Args().Get(1) nextTag := cmd.Args().Get(1)
if len(tags) == 0 && nextTag == "" { if len(tags) == 0 && nextTag == "" {
log.Warnf("no git tags found for %s", recipe.Name) log.Warnf("no git tags found for %s", recipe.Name)
if internal.NoInput { if internal.NoInput {

View File

@ -2,6 +2,7 @@ package recipe
import ( import (
"bufio" "bufio"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
@ -19,7 +20,7 @@ import (
"coopcloud.tech/tagcmp" "coopcloud.tech/tagcmp"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
type imgPin struct { type imgPin struct {
@ -27,8 +28,8 @@ type imgPin struct {
version tagcmp.Tag version tagcmp.Tag
} }
// anUpgrade represents a single service upgrade (as within a recipe), and the list of tags that it can be upgraded to, // anUpgrade represents a single service upgrade (as within a recipe), and the
// for serialization purposes. // list of tags that it can be upgraded to, for serialization purposes.
type anUpgrade struct { type anUpgrade struct {
Service string `json:"service"` Service string `json:"service"`
Image string `json:"image"` Image string `json:"image"`
@ -37,13 +38,13 @@ type anUpgrade struct {
} }
var recipeUpgradeCommand = cli.Command{ var recipeUpgradeCommand = cli.Command{
Name: "upgrade", Name: "upgrade",
Aliases: []string{"u"}, Aliases: []string{"u"},
Usage: "Upgrade recipe image tags", Usage: "Upgrade recipe image tags",
Description: ` HideHelpCommand: true,
Parse all image tags within the given <recipe> configuration and prompt with Description: `Upgrade a given <recipe> configuration.
more recent tags to upgrade to. It will update the relevant compose file tags
on the local file system. It will update the relevant compose file tags on the local file system.
Some image tags cannot be parsed because they do not follow some sort of Some image tags cannot be parsed because they do not follow some sort of
semver-like convention. In this case, all possible tags will be listed and it semver-like convention. In this case, all possible tags will be listed and it
@ -53,25 +54,20 @@ The command is interactive and will show a select input which allows you to
make a seclection. Use the "?" key to see more help on navigating this make a seclection. Use the "?" key to see more help on navigating this
interface. interface.
You may invoke this command in "wizard" mode and be prompted for input. You may invoke this command in "wizard" mode and be prompted for input.`,
UsageText: "abra recipe upgrade [options] [<recipe>]",
EXAMPLE:
abra recipe upgrade`,
ArgsUsage: "<recipe>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.PatchFlag, internal.PatchFlag,
internal.MinorFlag, internal.MinorFlag,
internal.MajorFlag, internal.MajorFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,
internal.AllTagsFlag, internal.AllTagsFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete, EnableShellCompletion: true,
Action: func(c *cli.Context) error { ShellComplete: autocomplete.RecipeNameComplete,
recipe := internal.ValidateRecipe(c) Action: func(ctx context.Context, cmd *cli.Command) error {
recipe := internal.ValidateRecipe(cmd)
if err := recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -1,6 +1,7 @@
package recipe package recipe
import ( import (
"context"
"fmt" "fmt"
"sort" "sort"
@ -10,7 +11,7 @@ import (
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
recipePkg "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
func sortServiceByName(versions [][]string) func(i, j int) bool { func sortServiceByName(versions [][]string) func(i, j int) bool {
@ -24,20 +25,19 @@ func sortServiceByName(versions [][]string) func(i, j int) bool {
} }
var recipeVersionCommand = cli.Command{ var recipeVersionCommand = cli.Command{
Name: "versions", Name: "versions",
Aliases: []string{"v"}, Aliases: []string{"v"},
Usage: "List recipe versions", Usage: "List recipe versions",
ArgsUsage: "<recipe>", UsageText: "abra recipe version [options] <recipe>",
HideHelpCommand: true,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OfflineFlag,
internal.NoInputFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete, EnableShellCompletion: true,
Action: func(c *cli.Context) error { ShellComplete: autocomplete.RecipeNameComplete,
recipe := internal.ValidateRecipe(c) Action: func(ctx context.Context, cmd *cli.Command) error {
recipe := internal.ValidateRecipe(cmd)
catl, err := recipePkg.ReadRecipeCatalogue(internal.Offline) catl, err := recipePkg.ReadRecipeCatalogue(internal.Offline)
if err != nil { if err != nil {

View File

@ -1,6 +1,7 @@
package server package server
import ( import (
"context"
"errors" "errors"
"os" "os"
"path/filepath" "path/filepath"
@ -13,12 +14,13 @@ import (
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/server" "coopcloud.tech/abra/pkg/server"
sshPkg "coopcloud.tech/abra/pkg/ssh" sshPkg "coopcloud.tech/abra/pkg/ssh"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var local bool var local bool
var localFlag = &cli.BoolFlag{ var localFlag = &cli.BoolFlag{
Name: "local, l", Name: "local",
Aliases: []string{"l"},
Usage: "Use local server", Usage: "Use local server",
Destination: &local, Destination: &local,
} }
@ -92,15 +94,16 @@ func createServerDir(name string) (bool, error) {
} }
var serverAddCommand = cli.Command{ var serverAddCommand = cli.Command{
Name: "add", Name: "add",
Aliases: []string{"a"}, Aliases: []string{"a"},
Usage: "Add a new server to your configuration", Usage: "Add a new server",
Description: ` UsageText: "abra server add [options] <domain>",
Add a new server to your configuration so that it can be managed by Abra. HideHelpCommand: true,
Description: `Add a new server to your configuration so that it can be managed by Abra.
Abra relies on the standard SSH command-line and ~/.ssh/config for client Abra relies on the standard SSH command-line and ~/.ssh/config for client
connection details. You must configure an entry per-host in your ~/.ssh/config connection details. You must configure an entry per-host in your ~/.ssh/config
for each server. For example: for each server:
Host example.com example Host example.com example
Hostname example.com Hostname example.com
@ -108,10 +111,6 @@ for each server. For example:
Port 12345 Port 12345
IdentityFile ~/.ssh/example@somewhere IdentityFile ~/.ssh/example@somewhere
You can then add a server like so:
abra server add example.com
If "--local" is passed, then Abra assumes that the current local server is If "--local" is passed, then Abra assumes that the current local server is
intended as the target server. This is useful when you want to have your entire intended as the target server. This is useful when you want to have your entire
Co-op Cloud config located on the server itself, and not on your local Co-op Cloud config located on the server itself, and not on your local
@ -119,28 +118,24 @@ developer machine. The domain is then set to "default".
You can also pass "--no-domain-checks/-D" flag to use any arbitrary name You can also pass "--no-domain-checks/-D" flag to use any arbitrary name
instead of a real domain. The host will be resolved with the "Hostname" entry instead of a real domain. The host will be resolved with the "Hostname" entry
of your ~/.ssh/config. Checks for a valid online domain will be skipped: of your ~/.ssh/config. Checks for a valid online domain will be skipped.`,
abra server add -D example`,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.NoDomainChecksFlag, internal.NoDomainChecksFlag,
localFlag, localFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
ArgsUsage: "<name>", ArgsUsage: "<name>",
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
if len(c.Args()) > 0 && local || !internal.ValidateSubCmdFlags(c) { if cmd.Args().Len() > 0 && local || !internal.ValidateSubCmdFlags(cmd) {
err := errors.New("cannot use <name> and --local together") err := errors.New("cannot use <name> and --local together")
internal.ShowSubcommandHelpAndError(c, err) internal.ShowSubcommandHelpAndError(cmd, err)
} }
var name string var name string
if local { if local {
name = "default" name = "default"
} else { } else {
name = internal.ValidateDomain(c) name = internal.ValidateDomain(cmd)
} }
// NOTE(d1): reasonable 5 second timeout for connections which can't // NOTE(d1): reasonable 5 second timeout for connections which can't

View File

@ -1,29 +1,31 @@
package server package server
import ( import (
"context"
"strings" "strings"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/context" contextPkg "coopcloud.tech/abra/pkg/context"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/docker/cli/cli/connhelper/ssh" "github.com/docker/cli/cli/connhelper/ssh"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var serverListCommand = cli.Command{ var serverListCommand = cli.Command{
Name: "list", Name: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Usage: "List managed servers", Usage: "List managed servers",
UsageText: "abra server list [options]",
HideHelpCommand: true,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
dockerContextStore := context.NewDefaultDockerContextStore() dockerContextStore := contextPkg.NewDefaultDockerContextStore()
contexts, err := dockerContextStore.Store.List() contexts, err := dockerContextStore.Store.List()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -39,14 +41,14 @@ var serverListCommand = cli.Command{
for _, serverName := range serverNames { for _, serverName := range serverNames {
var row []string var row []string
for _, ctx := range contexts { for _, dockerCtx := range contexts {
endpoint, err := context.GetContextEndpoint(ctx) endpoint, err := contextPkg.GetContextEndpoint(dockerCtx)
if err != nil && strings.Contains(err.Error(), "does not exist") { if err != nil && strings.Contains(err.Error(), "does not exist") {
// No local context found, we can continue safely // No local context found, we can continue safely
continue continue
} }
if ctx.Name == serverName { if dockerCtx.Name == serverName {
sp, err := ssh.ParseURL(endpoint) sp, err := ssh.ParseURL(endpoint)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -9,13 +9,14 @@ import (
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var allFilter bool var allFilter bool
var allFilterFlag = &cli.BoolFlag{ var allFilterFlag = &cli.BoolFlag{
Name: "all, a", Name: "all",
Aliases: []string{"a"},
Usage: "Remove all unused images not just dangling ones", Usage: "Remove all unused images not just dangling ones",
Destination: &allFilter, Destination: &allFilter,
} }
@ -23,17 +24,19 @@ var allFilterFlag = &cli.BoolFlag{
var volumesFilter bool var volumesFilter bool
var volumesFilterFlag = &cli.BoolFlag{ var volumesFilterFlag = &cli.BoolFlag{
Name: "volumes, v", Name: "volumes",
Aliases: []string{"v"},
Usage: "Prune volumes. This will remove app data, Be Careful!", Usage: "Prune volumes. This will remove app data, Be Careful!",
Destination: &volumesFilter, Destination: &volumesFilter,
} }
var serverPruneCommand = cli.Command{ var serverPruneCommand = cli.Command{
Name: "prune", Name: "prune",
Aliases: []string{"p"}, Aliases: []string{"p"},
Usage: "Prune resources on a server", Usage: "Prune resources on a server",
Description: ` UsageText: "abra server prune [options] <server>",
Prunes unused containers, networks, and dangling images. HideHelpCommand: true,
Description: `Prunes unused containers, networks, and dangling images.
Use "-v/--volumes" to remove volumes that are not associated with a deployed Use "-v/--volumes" to remove volumes that are not associated with a deployed
app. This can result in unwanted data loss if not used carefully.`, app. This can result in unwanted data loss if not used carefully.`,
@ -41,14 +44,12 @@ app. This can result in unwanted data loss if not used carefully.`,
Flags: []cli.Flag{ Flags: []cli.Flag{
allFilterFlag, allFilterFlag,
volumesFilterFlag, volumesFilterFlag,
internal.DebugFlag,
internal.OfflineFlag,
internal.NoInputFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.ServerNameComplete, EnableShellCompletion: true,
Action: func(c *cli.Context) error { ShellComplete: autocomplete.ServerNameComplete,
serverName := internal.ValidateServer(c) Action: func(ctx context.Context, cmd *cli.Command) error {
serverName := internal.ValidateServer(cmd)
cl, err := client.New(serverName) cl, err := client.New(serverName)
if err != nil { if err != nil {
@ -57,7 +58,6 @@ app. This can result in unwanted data loss if not used carefully.`,
var args filters.Args var args filters.Args
ctx := context.Background()
cr, err := cl.ContainersPrune(ctx, args) cr, err := cl.ContainersPrune(ctx, args)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -1,6 +1,7 @@
package server package server
import ( import (
"context"
"os" "os"
"path/filepath" "path/filepath"
@ -9,29 +10,25 @@ import (
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
var serverRemoveCommand = cli.Command{ var serverRemoveCommand = cli.Command{
Name: "remove", Name: "remove",
Aliases: []string{"rm"}, Aliases: []string{"rm"},
ArgsUsage: "<server>", UsageText: "abra server remove [options] <domain>",
Usage: "Remove a managed server", Usage: "Remove a managed server",
Description: ` HideHelpCommand: true,
Remove a managed server. Description: `Remove a managed server.
Abra will remove the internal bookkeeping (~/.abra/servers/...) and underlying Abra will remove the internal bookkeeping ($ABRA_DIR/servers/...) and
client connection context. This server will then be lost in time, like tears in underlying client connection context. This server will then be lost in time,
rain.`, like tears in rain.`,
Flags: []cli.Flag{ Before: internal.SubCommandBefore,
internal.DebugFlag, EnableShellCompletion: true,
internal.NoInputFlag, ShellComplete: autocomplete.ServerNameComplete,
internal.OfflineFlag, Action: func(ctx context.Context, cmd *cli.Command) error {
}, serverName := internal.ValidateServer(cmd)
Before: internal.SubCommandBefore,
BashComplete: autocomplete.ServerNameComplete,
Action: func(c *cli.Context) error {
serverName := internal.ValidateServer(c)
if err := client.DeleteContext(serverName); err != nil { if err := client.DeleteContext(serverName); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -1,18 +1,20 @@
package server package server
import ( import (
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
// ServerCommand defines the `abra server` command and its subcommands // ServerCommand defines the `abra server` command and its subcommands
var ServerCommand = cli.Command{ var ServerCommand = cli.Command{
Name: "server", Name: "server",
Aliases: []string{"s"}, Aliases: []string{"s"},
Usage: "Manage servers", Usage: "Manage servers",
Subcommands: []cli.Command{ UsageText: "abra server [command] [options] [arguments]",
serverAddCommand, HideHelpCommand: true,
serverListCommand, Commands: []*cli.Command{
serverRemoveCommand, &serverAddCommand,
serverPruneCommand, &serverListCommand,
&serverRemoveCommand,
&serverPruneCommand,
}, },
} }

View File

@ -23,44 +23,44 @@ import (
dockerclient "github.com/docker/docker/client" dockerclient "github.com/docker/docker/client"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
const SERVER = "localhost" const SERVER = "localhost"
var majorUpdate bool var majorUpdate bool
var majorFlag = &cli.BoolFlag{ var majorFlag = &cli.BoolFlag{
Name: "major, m", Name: "major",
Aliases: []string{"m"},
Usage: "Also check for major updates", Usage: "Also check for major updates",
Destination: &majorUpdate, Destination: &majorUpdate,
} }
var updateAll bool var updateAll bool
var allFlag = &cli.BoolFlag{ var allFlag = &cli.BoolFlag{
Name: "all, a", Name: "all",
Aliases: []string{"a"},
Usage: "Update all deployed apps", Usage: "Update all deployed apps",
Destination: &updateAll, Destination: &updateAll,
} }
// Notify checks for available upgrades // Notify checks for available upgrades
var Notify = cli.Command{ var Notify = cli.Command{
Name: "notify", Name: "notify",
Aliases: []string{"n"}, Aliases: []string{"n"},
Usage: "Check for available upgrades", Usage: "Check for available upgrades",
UsageText: "kadabra notify [options]",
HideHelpCommand: true,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
majorFlag, majorFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `Notify on new versions for deployed apps.
Read the deployed app versions and look for new versions in the recipe
catalogue.
If a new patch/minor version is available, a notification is printed. If a new patch/minor version is available, a notification is printed.
Use "--major" to include new major versions.`, Use "--major" to include new major versions.`,
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
cl, err := client.New("default") cl, err := client.New("default")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -92,20 +92,18 @@ Use "--major" to include new major versions.`,
// UpgradeApp upgrades apps. // UpgradeApp upgrades apps.
var UpgradeApp = cli.Command{ var UpgradeApp = cli.Command{
Name: "upgrade", Name: "upgrade",
Aliases: []string{"u"}, Aliases: []string{"u"},
Usage: "Upgrade apps", Usage: "Upgrade apps",
ArgsUsage: "<stack-name> <recipe>", UsageText: "kadabra notify [options] <stack> <recipe>",
HideHelpCommand: true,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.ChaosFlag, internal.ChaosFlag,
majorFlag, majorFlag,
allFlag, allFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `Upgrade an app by specifying stack name and recipe.
Upgrade an app by specifying stack name and recipe.
Use "--all" to upgrade every deployed app. Use "--all" to upgrade every deployed app.
@ -116,15 +114,15 @@ available, the app is upgraded.
To include major versions use the "--major" flag. You probably don't want that To include major versions use the "--major" flag. You probably don't want that
as it will break things. Only apps that are not deployed with "--chaos" are as it will break things. Only apps that are not deployed with "--chaos" are
upgraded, to update chaos deployments use the "--chaos" flag. Use it with care.`, upgraded, to update chaos deployments use the "--chaos" flag. Use it with care.`,
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
cl, err := client.New("default") cl, err := client.New("default")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if !updateAll { if !updateAll {
stackName := c.Args().Get(0) stackName := cmd.Args().Get(0)
recipeName := c.Args().Get(1) recipeName := cmd.Args().Get(1)
err = tryUpgrade(cl, stackName, recipeName) err = tryUpgrade(cl, stackName, recipeName)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -468,25 +466,24 @@ func upgrade(cl *dockerclient.Client, stackName, recipeName, upgradeVersion stri
return err return err
} }
func newAbraApp(version, commit string) *cli.App { func newAbraApp(version, commit string) *cli.Command {
app := &cli.App{ app := &cli.Command{
Name: "kadabra", Name: "kadabra",
Usage: `The Co-op Cloud auto-updater Usage: "The Co-op Cloud auto-updater 🤖🚀",
____ ____ _ _ Version: fmt.Sprintf("%s-%s", version, commit[:7]),
/ ___|___ ___ _ __ / ___| | ___ _ _ __| | UsageText: "kadabra [command] [options] [arguments]",
| | / _ \ _____ / _ \| '_ \ | | | |/ _ \| | | |/ _' | HideHelpCommand: true,
| |__| (_) |_____| (_) | |_) | | |___| | (_) | |_| | (_| | Flags: []cli.Flag{
\____\___/ \___/| .__/ \____|_|\___/ \__,_|\__,_| internal.OfflineFlag,
|_| internal.DebugFlag,
`, },
Version: fmt.Sprintf("%s-%s", version, commit[:7]), Commands: []*cli.Command{
Commands: []cli.Command{ &Notify,
Notify, &UpgradeApp,
UpgradeApp,
}, },
} }
app.Before = func(c *cli.Context) error { app.Before = func(ctx context.Context, cmd *cli.Command) error {
charmLog.SetDefault(log.Logger) charmLog.SetDefault(log.Logger)
log.Debugf("kadabra version %s, commit %s", version, commit) log.Debugf("kadabra version %s, commit %s", version, commit)
return nil return nil
@ -499,7 +496,7 @@ func newAbraApp(version, commit string) *cli.App {
func RunApp(version, commit string) { func RunApp(version, commit string) {
app := newAbraApp(version, commit) app := newAbraApp(version, commit)
if err := app.Run(os.Args); err != nil { if err := app.Run(context.Background(), os.Args); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

6
go.mod
View File

@ -2,6 +2,8 @@ module coopcloud.tech/abra
go 1.21 go 1.21
replace github.com/urfave/cli/v3 => github.com/fiatjaf/cli/v3 v3.0.0-20240704165307-ad0e1925dd42
require ( require (
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb
git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355 git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355
@ -18,6 +20,7 @@ require (
github.com/olekukonko/tablewriter v0.0.5 github.com/olekukonko/tablewriter v0.0.5
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/schollz/progressbar/v3 v3.14.4 github.com/schollz/progressbar/v3 v3.14.4
github.com/urfave/cli/v3 v3.0.0-alpha9
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.5.1 gotest.tools/v3 v3.5.1
) )
@ -36,7 +39,6 @@ require (
github.com/charmbracelet/x/ansi v0.1.2 // indirect github.com/charmbracelet/x/ansi v0.1.2 // indirect
github.com/cloudflare/circl v1.3.9 // indirect github.com/cloudflare/circl v1.3.9 // indirect
github.com/containerd/log v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/cyphar/filepath-securejoin v0.2.5 // indirect github.com/cyphar/filepath-securejoin v0.2.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect
@ -84,7 +86,6 @@ require (
github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect github.com/skeema/knownhosts v1.2.2 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
@ -134,7 +135,6 @@ require (
github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/cobra v1.8.1 // indirect
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
github.com/theupdateframework/notary v0.7.0 // indirect github.com/theupdateframework/notary v0.7.0 // indirect
github.com/urfave/cli v1.22.15
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
golang.org/x/sys v0.22.0 golang.org/x/sys v0.22.0
) )

13
go.sum
View File

@ -49,7 +49,6 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@ -274,7 +273,6 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@ -357,6 +355,8 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fiatjaf/cli/v3 v3.0.0-20240704165307-ad0e1925dd42 h1:yId1d3b2PHJ9vnYCxojs9NslXYVQ1iv7svK/CuDRoU0=
github.com/fiatjaf/cli/v3 v3.0.0-20240704165307-ad0e1925dd42/go.mod h1:Z1ItyMma7t6I7zHG9OpbExhHQOSkFf/96n+mAZ9MtVI=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@ -803,7 +803,6 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
@ -861,9 +860,6 @@ github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@ -871,9 +867,6 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
@ -893,8 +886,6 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.15 h1:nuqt+pdC/KqswQKhETJjo7pvn/k4xMUxgW6liI7XpnM=
github.com/urfave/cli v1.22.15/go.mod h1:wSan1hmo5zeyLGBjRJbzRTNk8gwoYa2B9n4q9dmRIc0=
github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI= github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=

View File

@ -1,22 +1,23 @@
package autocomplete package autocomplete
import ( import (
"context"
"fmt" "fmt"
"coopcloud.tech/abra/pkg/app" "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/urfave/cli" "github.com/urfave/cli/v3"
) )
// AppNameComplete copletes app names. // AppNameComplete copletes app names.
func AppNameComplete(c *cli.Context) { func AppNameComplete(ctx context.Context, cmd *cli.Command) {
appNames, err := app.GetAppNames() appNames, err := app.GetAppNames()
if err != nil { if err != nil {
log.Warn(err) log.Warn(err)
} }
if c.NArg() > 0 { if cmd.NArg() > 0 {
return return
} }
@ -36,13 +37,13 @@ func ServiceNameComplete(appName string) {
} }
// RecipeNameComplete completes recipe names. // RecipeNameComplete completes recipe names.
func RecipeNameComplete(c *cli.Context) { func RecipeNameComplete(ctx context.Context, cmd *cli.Command) {
catl, err := recipe.ReadRecipeCatalogue(false) catl, err := recipe.ReadRecipeCatalogue(false)
if err != nil { if err != nil {
log.Warn(err) log.Warn(err)
} }
if c.NArg() > 0 { if cmd.NArg() > 0 {
return return
} }
@ -66,13 +67,13 @@ func RecipeVersionComplete(recipeName string) {
} }
// ServerNameComplete completes server names. // ServerNameComplete completes server names.
func ServerNameComplete(c *cli.Context) { func ServerNameComplete(ctx context.Context, cmd *cli.Command) {
files, err := app.LoadAppFiles("") files, err := app.LoadAppFiles("")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if c.NArg() > 0 { if cmd.NArg() > 0 {
return return
} }
@ -82,8 +83,8 @@ func ServerNameComplete(c *cli.Context) {
} }
// SubcommandComplete completes sub-commands. // SubcommandComplete completes sub-commands.
func SubcommandComplete(c *cli.Context) { func SubcommandComplete(ctx context.Context, cmd *cli.Command) {
if c.NArg() > 0 { if cmd.NArg() > 0 {
return return
} }

View File

@ -8,9 +8,9 @@ _cli_bash_autocomplete() {
COMPREPLY=() COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == "-"* ]]; then if [[ "$cur" == "-"* ]]; then
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion ) opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-shell-completion )
else else
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-shell-completion )
fi fi
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0 return 0

View File

@ -1,5 +1,5 @@
function complete_abra_args function complete_abra_args
set -l cmd (commandline -poc) --generate-bash-completion set -l cmd (commandline -poc) --generate-shell-completion
$cmd $cmd
end end
complete -c abra -f -n "not __fish_seen_subcommand_from -h --help -v --version complete_abra_args" -a "(complete_abra_args)" complete -c abra -f -n "not __fish_seen_subcommand_from -h --help -v --version complete_abra_args" -a "(complete_abra_args)"

View File

@ -2,7 +2,7 @@ $fn = $($MyInvocation.MyCommand.Name)
$name = $fn -replace "(.*)\.ps1$", '$1' $name = $fn -replace "(.*)\.ps1$", '$1'
Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock { Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock {
param($commandName, $wordToComplete, $cursorPosition) param($commandName, $wordToComplete, $cursorPosition)
$other = "$wordToComplete --generate-bash-completion" $other = "$wordToComplete --generate-shell-completion"
Invoke-Expression $other | ForEach-Object { Invoke-Expression $other | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
} }

View File

@ -6,9 +6,9 @@ _cli_zsh_autocomplete() {
local cur local cur
cur=${words[-1]} cur=${words[-1]}
if [[ "$cur" == "-"* ]]; then if [[ "$cur" == "-"* ]]; then
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-shell-completion)}")
else else
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}") opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-shell-completion)}")
fi fi
if [[ "${opts[1]}" != "" ]]; then if [[ "${opts[1]}" != "" ]]; then