forked from toolshed/abra
refactor!: cobra migrate
This commit is contained in:
@ -1,34 +1,11 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var AppCommand = cli.Command{
|
||||
Name: "app",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "Manage apps",
|
||||
UsageText: "abra app [command] [arguments] [options]",
|
||||
Commands: []*cli.Command{
|
||||
&appBackupCommand,
|
||||
&appCheckCommand,
|
||||
&appCmdCommand,
|
||||
&appConfigCommand,
|
||||
&appCpCommand,
|
||||
&appDeployCommand,
|
||||
&appListCommand,
|
||||
&appLogsCommand,
|
||||
&appNewCommand,
|
||||
&appPsCommand,
|
||||
&appRemoveCommand,
|
||||
&appRestartCommand,
|
||||
&appRestoreCommand,
|
||||
&appRollbackCommand,
|
||||
&appRunCommand,
|
||||
&appSecretCommand,
|
||||
&appServicesCommand,
|
||||
&appUndeployCommand,
|
||||
&appUpgradeCommand,
|
||||
&appVolumeCommand,
|
||||
},
|
||||
var AppCommand = &cobra.Command{
|
||||
Use: "app [cmd] [args] [flags]",
|
||||
Aliases: []string{"a"},
|
||||
Short: "Manage apps",
|
||||
}
|
||||
|
@ -1,283 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/log"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var snapshot string
|
||||
var snapshotFlag = &cli.StringFlag{
|
||||
Name: "snapshot",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "Lists specific snapshot",
|
||||
Destination: &snapshot,
|
||||
}
|
||||
|
||||
var includePath string
|
||||
var includePathFlag = &cli.StringFlag{
|
||||
Name: "path",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "Include path",
|
||||
Destination: &includePath,
|
||||
}
|
||||
|
||||
var resticRepo string
|
||||
var resticRepoFlag = &cli.StringFlag{
|
||||
Name: "repo",
|
||||
Aliases: []string{"r"},
|
||||
Usage: "Restic repository",
|
||||
Destination: &resticRepo,
|
||||
}
|
||||
|
||||
var appBackupListCommand = cli.Command{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Flags: []cli.Flag{
|
||||
snapshotFlag,
|
||||
includePathFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
Usage: "List all backups",
|
||||
UsageText: "abra app backup list <domain> [options]",
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
|
||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
targetContainer, err := internal.RetrieveBackupBotContainer(cl)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
execEnv := []string{fmt.Sprintf("SERVICE=%s", app.Domain)}
|
||||
if snapshot != "" {
|
||||
log.Debugf("including SNAPSHOT=%s in backupbot exec invocation", snapshot)
|
||||
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
|
||||
}
|
||||
if includePath != "" {
|
||||
log.Debugf("including INCLUDE_PATH=%s in backupbot exec invocation", includePath)
|
||||
execEnv = append(execEnv, fmt.Sprintf("INCLUDE_PATH=%s", includePath))
|
||||
}
|
||||
|
||||
if err := internal.RunBackupCmdRemote(cl, "ls", targetContainer.ID, execEnv); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var appBackupDownloadCommand = cli.Command{
|
||||
Name: "download",
|
||||
Aliases: []string{"d"},
|
||||
Flags: []cli.Flag{
|
||||
snapshotFlag,
|
||||
includePathFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
Usage: "Download a backup",
|
||||
UsageText: "abra app backup download <domain> [options]",
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
|
||||
if err := app.Recipe.EnsureExists(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := app.Recipe.EnsureIsClean(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := app.Recipe.EnsureUpToDate(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := app.Recipe.EnsureLatest(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
targetContainer, err := internal.RetrieveBackupBotContainer(cl)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
execEnv := []string{fmt.Sprintf("SERVICE=%s", app.Domain)}
|
||||
if snapshot != "" {
|
||||
log.Debugf("including SNAPSHOT=%s in backupbot exec invocation", snapshot)
|
||||
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
|
||||
}
|
||||
if includePath != "" {
|
||||
log.Debugf("including INCLUDE_PATH=%s in backupbot exec invocation", includePath)
|
||||
execEnv = append(execEnv, fmt.Sprintf("INCLUDE_PATH=%s", includePath))
|
||||
}
|
||||
|
||||
if err := internal.RunBackupCmdRemote(cl, "download", targetContainer.ID, execEnv); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
remoteBackupDir := "/tmp/backup.tar.gz"
|
||||
currentWorkingDir := "."
|
||||
if err = CopyFromContainer(cl, targetContainer.ID, remoteBackupDir, currentWorkingDir); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("backup successfully downloaded to current working directory")
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var appBackupCreateCommand = cli.Command{
|
||||
Name: "create",
|
||||
Aliases: []string{"c"},
|
||||
Flags: []cli.Flag{
|
||||
resticRepoFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
Usage: "Create a new backup",
|
||||
UsageText: "abra app backup create <domain> [options]",
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
|
||||
if err := app.Recipe.EnsureExists(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := app.Recipe.EnsureIsClean(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := app.Recipe.EnsureUpToDate(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := app.Recipe.EnsureLatest(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
targetContainer, err := internal.RetrieveBackupBotContainer(cl)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
execEnv := []string{fmt.Sprintf("SERVICE=%s", app.Domain)}
|
||||
if resticRepo != "" {
|
||||
log.Debugf("including RESTIC_REPO=%s in backupbot exec invocation", resticRepo)
|
||||
execEnv = append(execEnv, fmt.Sprintf("RESTIC_REPO=%s", resticRepo))
|
||||
}
|
||||
|
||||
if err := internal.RunBackupCmdRemote(cl, "create", targetContainer.ID, execEnv); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var appBackupSnapshotsCommand = cli.Command{
|
||||
Name: "snapshots",
|
||||
Aliases: []string{"s"},
|
||||
Flags: []cli.Flag{
|
||||
snapshotFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
Usage: "List backup snapshots",
|
||||
UsageText: "abra app backup snapshots <domain> [options]",
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
|
||||
if err := app.Recipe.EnsureExists(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := app.Recipe.EnsureIsClean(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := app.Recipe.EnsureUpToDate(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := app.Recipe.EnsureLatest(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
targetContainer, err := internal.RetrieveBackupBotContainer(cl)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
execEnv := []string{fmt.Sprintf("SERVICE=%s", app.Domain)}
|
||||
if snapshot != "" {
|
||||
log.Debugf("including SNAPSHOT=%s in backupbot exec invocation", snapshot)
|
||||
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
|
||||
}
|
||||
|
||||
if err := internal.RunBackupCmdRemote(cl, "snapshots", targetContainer.ID, execEnv); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var appBackupCommand = cli.Command{
|
||||
Name: "backup",
|
||||
Aliases: []string{"b"},
|
||||
Usage: "Manage app backups",
|
||||
UsageText: "abra app backup [command] [arguments] [options]",
|
||||
Commands: []*cli.Command{
|
||||
&appBackupListCommand,
|
||||
&appBackupSnapshotsCommand,
|
||||
&appBackupDownloadCommand,
|
||||
&appBackupCreateCommand,
|
||||
},
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
@ -10,15 +9,14 @@ import (
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"coopcloud.tech/abra/pkg/log"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var appCheckCommand = cli.Command{
|
||||
Name: "check",
|
||||
Aliases: []string{"chk"},
|
||||
UsageText: "abra app check <domain> [options]",
|
||||
Usage: "Ensure an app is well configured",
|
||||
Description: `Compare env vars in both the app ".env" and recipe ".env.sample" file.
|
||||
var AppCheckCommand = &cobra.Command{
|
||||
Use: "check <app> [flags]",
|
||||
Aliases: []string{"chk"},
|
||||
Short: "Ensure an app is well configured",
|
||||
Long: `Compare 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
|
||||
app ".env" file. Only env var definitions in the ".env.sample" which are
|
||||
@ -28,15 +26,15 @@ these env vars, then "check" will complain.
|
||||
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
|
||||
${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
|
||||
Flags: []cli.Flag{
|
||||
internal.ChaosFlag,
|
||||
internal.OfflineFlag,
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return autocomplete.AppNameComplete()
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app := internal.ValidateApp(args)
|
||||
|
||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||
log.Fatal(err)
|
||||
@ -74,7 +72,15 @@ ${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
|
||||
}
|
||||
|
||||
fmt.Println(table)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
AppCheckCommand.Flags().BoolVarP(
|
||||
&internal.Chaos,
|
||||
"chaos",
|
||||
"C",
|
||||
false,
|
||||
"ignore uncommitted recipes changes",
|
||||
)
|
||||
}
|
||||
|
320
cli/app/cmd.go
320
cli/app/cmd.go
@ -1,67 +1,105 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
"coopcloud.tech/abra/pkg/app"
|
||||
appPkg "coopcloud.tech/abra/pkg/app"
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/log"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var appCmdCommand = cli.Command{
|
||||
Name: "command",
|
||||
Aliases: []string{"cmd"},
|
||||
Usage: "Run app commands",
|
||||
UsageText: "abra app cmd <domain> [<service>] <cmd> [<cmd-args>] [options]",
|
||||
Description: `Run an app specific command.
|
||||
var AppCmdCommand = &cobra.Command{
|
||||
Use: "command <app> [service | --local] <cmd> [[args] [flags] | [flags] -- [args]]",
|
||||
Aliases: []string{"cmd"},
|
||||
Short: "Run app commands",
|
||||
Long: `Run an app specific command.
|
||||
|
||||
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
|
||||
work station by passing "--local".`,
|
||||
Flags: []cli.Flag{
|
||||
internal.LocalCmdFlag,
|
||||
internal.RemoteUserFlag,
|
||||
internal.TtyFlag,
|
||||
internal.ChaosFlag,
|
||||
internal.NoInputFlag,
|
||||
work station by passing "--local/-l".
|
||||
|
||||
N.B. If using the "--" style to pass arguments, flags (e.g. "--local/-l") must
|
||||
be passed *before* the "--". It is possible to pass arguments without the "--"
|
||||
as long as no dashes are present (i.e. "foo" works without "--", "-foo"
|
||||
does not).`,
|
||||
Example: ` # pass <cmd> args/flags without "--"
|
||||
abra app cmd 1312.net app my_cmd_arg foo --user bar
|
||||
|
||||
# pass <cmd> args/flags with "--"
|
||||
abra app cmd 1312.net app my_cmd_args --user bar -- foo -vvv
|
||||
|
||||
# drop the [service] arg if using "--local/-l"
|
||||
abra app cmd 1312.net my_cmd --local`,
|
||||
Args: func(cmd *cobra.Command, args []string) error {
|
||||
if local {
|
||||
if !(len(args) >= 2) {
|
||||
return errors.New("requires at least 2 arguments with --local/-l")
|
||||
}
|
||||
|
||||
if slices.Contains(os.Args, "--") {
|
||||
if cmd.ArgsLenAtDash() > 2 {
|
||||
return errors.New("accepts at most 2 args with --local/-l")
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(d1): it is unclear how to correctly validate this case
|
||||
//
|
||||
// abra app cmd 1312.net app test_cmd_args foo --local
|
||||
// FATAL <recipe> doesn't have a app function
|
||||
//
|
||||
// "app" should not be there, but there is no reliable way to detect arg
|
||||
// count when the user can pass an arbitrary amount of recipe command
|
||||
// arguments
|
||||
}
|
||||
|
||||
if !(len(args) >= 3) {
|
||||
return errors.New("requires at least 3 arguments")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
Commands: []*cli.Command{
|
||||
&appCmdListCommand,
|
||||
},
|
||||
ShellComplete: func(ctx context.Context, cmd *cli.Command) {
|
||||
args := cmd.Args()
|
||||
switch args.Len() {
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
switch l := len(args); l {
|
||||
case 0:
|
||||
autocomplete.AppNameComplete(ctx, cmd)
|
||||
return autocomplete.AppNameComplete()
|
||||
case 1:
|
||||
autocomplete.ServiceNameComplete(args.Get(0))
|
||||
if !local {
|
||||
return autocomplete.ServiceNameComplete(args[0])
|
||||
}
|
||||
return autocomplete.CommandNameComplete(args[0])
|
||||
case 2:
|
||||
cmdNameComplete(args.Get(0))
|
||||
if !local {
|
||||
return autocomplete.CommandNameComplete(args[0])
|
||||
}
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
default:
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
},
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app := internal.ValidateApp(args)
|
||||
|
||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if internal.LocalCmd && internal.RemoteUser != "" {
|
||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("cannot use --local & --user together"))
|
||||
if local && remoteUser != "" {
|
||||
log.Fatal("cannot use --local & --user together")
|
||||
}
|
||||
|
||||
hasCmdArgs, parsedCmdArgs := parseCmdArgs(cmd.Args().Slice(), internal.LocalCmd)
|
||||
hasCmdArgs, parsedCmdArgs := parseCmdArgs(args, local)
|
||||
|
||||
if _, err := os.Stat(app.Recipe.AbraShPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
@ -70,12 +108,8 @@ work station by passing "--local".`,
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if internal.LocalCmd {
|
||||
if !(cmd.Args().Len() >= 2) {
|
||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("missing arguments"))
|
||||
}
|
||||
|
||||
cmdName := cmd.Args().Get(1)
|
||||
if local {
|
||||
cmdName := args[1]
|
||||
if err := internal.EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@ -106,53 +140,78 @@ work station by passing "--local".`,
|
||||
if err := internal.RunCmd(cmd); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
if !(cmd.Args().Len() >= 3) {
|
||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("missing arguments"))
|
||||
}
|
||||
|
||||
targetServiceName := cmd.Args().Get(1)
|
||||
return
|
||||
}
|
||||
|
||||
cmdName := cmd.Args().Get(2)
|
||||
if err := internal.EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cmdName := args[2]
|
||||
if err := internal.EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
matchingServiceName := false
|
||||
for _, serviceName := range serviceNames {
|
||||
if serviceName == targetServiceName {
|
||||
matchingServiceName = true
|
||||
}
|
||||
}
|
||||
|
||||
if !matchingServiceName {
|
||||
log.Fatalf("no service %s for %s?", targetServiceName, app.Name)
|
||||
}
|
||||
|
||||
log.Debugf("running command %s within the context of %s_%s", cmdName, app.StackName(), targetServiceName)
|
||||
|
||||
if hasCmdArgs {
|
||||
log.Debugf("parsed following command arguments: %s", parsedCmdArgs)
|
||||
} else {
|
||||
log.Debug("did not detect any command arguments")
|
||||
}
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := internal.RunCmdRemote(cl, app, app.Recipe.AbraShPath, targetServiceName, cmdName, parsedCmdArgs); err != nil {
|
||||
log.Fatal(err)
|
||||
matchingServiceName := false
|
||||
targetServiceName := args[1]
|
||||
for _, serviceName := range serviceNames {
|
||||
if serviceName == targetServiceName {
|
||||
matchingServiceName = true
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
if !matchingServiceName {
|
||||
log.Fatalf("no service %s for %s?", targetServiceName, app.Name)
|
||||
}
|
||||
|
||||
log.Debugf("running command %s within the context of %s_%s", cmdName, app.StackName(), targetServiceName)
|
||||
|
||||
if hasCmdArgs {
|
||||
log.Debugf("parsed following command arguments: %s", parsedCmdArgs)
|
||||
} else {
|
||||
log.Debug("did not detect any command arguments")
|
||||
}
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := internal.RunCmdRemote(
|
||||
cl,
|
||||
app,
|
||||
requestTTY,
|
||||
app.Recipe.AbraShPath,
|
||||
targetServiceName, cmdName, parsedCmdArgs, remoteUser); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var AppCmdListCommand = &cobra.Command{
|
||||
Use: "list <app> [flags]",
|
||||
Aliases: []string{"ls"},
|
||||
Short: "List all available commands",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app := internal.ValidateApp(args)
|
||||
|
||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
cmdNames, err := appPkg.ReadAbraShCmdNames(app.Recipe.AbraShPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
sort.Strings(cmdNames)
|
||||
|
||||
for _, cmdName := range cmdNames {
|
||||
fmt.Println(cmdName)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@ -175,73 +234,42 @@ func parseCmdArgs(args []string, isLocal bool) (bool, string) {
|
||||
return hasCmdArgs, parsedCmdArgs
|
||||
}
|
||||
|
||||
func cmdNameComplete(appName string) {
|
||||
app, err := app.Get(appName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cmdNames, _ := getShCmdNames(app)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, n := range cmdNames {
|
||||
fmt.Println(n)
|
||||
}
|
||||
}
|
||||
|
||||
var appCmdListCommand = cli.Command{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Usage: "List all available commands",
|
||||
UsageText: "abra app cmd ls <domain> [options]",
|
||||
Flags: []cli.Flag{
|
||||
internal.ChaosFlag,
|
||||
},
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
Before: internal.SubCommandBefore,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
|
||||
if err := app.Recipe.EnsureExists(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := app.Recipe.EnsureIsClean(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := app.Recipe.EnsureUpToDate(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := app.Recipe.EnsureLatest(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
cmdNames, err := getShCmdNames(app)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, cmdName := range cmdNames {
|
||||
fmt.Println(cmdName)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func getShCmdNames(app appPkg.App) ([]string, error) {
|
||||
cmdNames, err := appPkg.ReadAbraShCmdNames(app.Recipe.AbraShPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.Strings(cmdNames)
|
||||
return cmdNames, nil
|
||||
var (
|
||||
local bool
|
||||
remoteUser string
|
||||
requestTTY bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
AppCmdCommand.Flags().BoolVarP(
|
||||
&local,
|
||||
"local",
|
||||
"l",
|
||||
false,
|
||||
"run command locally",
|
||||
)
|
||||
|
||||
AppCmdCommand.Flags().StringVarP(
|
||||
&remoteUser,
|
||||
"user",
|
||||
"u",
|
||||
"",
|
||||
"request remote user",
|
||||
)
|
||||
|
||||
AppCmdCommand.Flags().BoolVarP(
|
||||
&requestTTY,
|
||||
"tty",
|
||||
"t",
|
||||
false,
|
||||
"request remote TTY",
|
||||
)
|
||||
|
||||
AppCmdCommand.Flags().BoolVarP(
|
||||
&internal.Chaos,
|
||||
"chaos",
|
||||
"C",
|
||||
false,
|
||||
"ignore uncommitted recipes changes",
|
||||
)
|
||||
}
|
||||
|
@ -1,39 +1,35 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
appPkg "coopcloud.tech/abra/pkg/app"
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
"coopcloud.tech/abra/pkg/log"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var appConfigCommand = cli.Command{
|
||||
Name: "config",
|
||||
Aliases: []string{"cfg"},
|
||||
Usage: "Edit app config",
|
||||
UsageText: "abra app config <domain> [options]",
|
||||
Before: internal.SubCommandBefore,
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
appName := cmd.Args().First()
|
||||
|
||||
if appName == "" {
|
||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("no app provided"))
|
||||
}
|
||||
|
||||
var AppConfigCommand = &cobra.Command{
|
||||
Use: "config <app> [flags]",
|
||||
Aliases: []string{"cfg"},
|
||||
Short: "Edit app config",
|
||||
Example: " abra config 1312.net",
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return autocomplete.AppNameComplete()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
files, err := appPkg.LoadAppFiles("")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
appName := args[0]
|
||||
appFile, exists := files[appName]
|
||||
if !exists {
|
||||
log.Fatalf("cannot find app with name %s", appName)
|
||||
@ -57,7 +53,5 @@ var appConfigCommand = cli.Command{
|
||||
if err := c.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@ -22,41 +22,39 @@ import (
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var appCpCommand = cli.Command{
|
||||
Name: "cp",
|
||||
Aliases: []string{"c"},
|
||||
Before: internal.SubCommandBefore,
|
||||
Usage: "Copy files to/from a deployed app service",
|
||||
UsageText: "abra app cp <domain> <src> <dst> [options]",
|
||||
Description: `Copy files to and from any app service file system.
|
||||
var AppCpCommand = &cobra.Command{
|
||||
Use: "cp <app> <src> <dst> [flags]",
|
||||
Aliases: []string{"c"},
|
||||
Short: "Copy files to/from a deployed app service",
|
||||
Example: ` # copy myfile.txt to the root of the app service
|
||||
abra app cp 1312.net myfile.txt app:/
|
||||
|
||||
If you want to copy a myfile.txt to the root of the app service:
|
||||
# copy that file back to your current working directory locally
|
||||
abra app cp 1312.net app:/myfile.txt`,
|
||||
Args: cobra.ExactArgs(3),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
switch l := len(args); l {
|
||||
case 0:
|
||||
return autocomplete.AppNameComplete()
|
||||
default:
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app := internal.ValidateApp(args)
|
||||
|
||||
abra app cp <domain> myfile.txt app:/
|
||||
|
||||
And if you want to copy that file back to your current working directory locally:
|
||||
|
||||
abra app cp <domain> app:/myfile.txt`,
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
src := cmd.Args().Get(1)
|
||||
dst := cmd.Args().Get(2)
|
||||
if src == "" {
|
||||
log.Fatal("missing <src> argument")
|
||||
}
|
||||
if dst == "" {
|
||||
log.Fatal("missing <dest> argument")
|
||||
}
|
||||
|
||||
src := args[1]
|
||||
dst := args[2]
|
||||
srcPath, dstPath, service, toContainer, err := parseSrcAndDst(src, dst)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@ -81,8 +79,6 @@ And if you want to copy that file back to your current working directory locally
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -18,41 +18,60 @@ import (
|
||||
"coopcloud.tech/abra/pkg/lint"
|
||||
"coopcloud.tech/abra/pkg/log"
|
||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var appDeployCommand = cli.Command{
|
||||
Name: "deploy",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Deploy an app",
|
||||
UsageText: "abra app deploy <domain> [<version>] [options]",
|
||||
Flags: []cli.Flag{
|
||||
internal.ForceFlag,
|
||||
internal.ChaosFlag,
|
||||
internal.NoDomainChecksFlag,
|
||||
internal.DontWaitConvergeFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
Description: `Deploy an app.
|
||||
var AppDeployCommand = &cobra.Command{
|
||||
Use: "deploy <app> [version] [flags]",
|
||||
Aliases: []string{"d"},
|
||||
Short: "Deploy an app",
|
||||
Long: `Deploy an app.
|
||||
|
||||
This command supports chaos operations. Use "--chaos" to deploy your recipe
|
||||
checkout as-is. Recipe commit hashes are also supported values for
|
||||
"[<version>]". Please note, "upgrade"/"rollback" do not support chaos
|
||||
operations.`,
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
This command supports chaos operations. Use "--chaos/-c" to deploy your recipe
|
||||
checkout as-is. Recipe commit hashes are also supported values for "[version]".
|
||||
Please note, "upgrade"/"rollback" do not support chaos operations.`,
|
||||
Example: ` # standard deployment
|
||||
abra app deploy 1312.net
|
||||
|
||||
# chaos deployment
|
||||
abra app deploy 1312.net --chaos
|
||||
|
||||
# deploy specific version
|
||||
abra app deploy 1312.net 2.0.0+1.2.3
|
||||
|
||||
# deploy a specific git hash
|
||||
abra app deploy 1312.net 886db76d`,
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
switch l := len(args); l {
|
||||
case 0:
|
||||
return autocomplete.AppNameComplete()
|
||||
case 1:
|
||||
app, err := appPkg.Get(args[0])
|
||||
if err != nil {
|
||||
log.Debugf("autocomplete failed: %s", err)
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
return autocomplete.RecipeVersionComplete(app.Recipe.Name)
|
||||
default:
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var warnMessages []string
|
||||
|
||||
app := internal.ValidateApp(cmd)
|
||||
app := internal.ValidateApp(args)
|
||||
stackName := app.StackName()
|
||||
|
||||
ok, err := validateChaosXORVersion(cmd.Args())
|
||||
ok, err := validateChaosXORVersion(args)
|
||||
if !ok {
|
||||
log.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
specificVersion := getSpecifiedVersion(cmd.Args())
|
||||
specificVersion := getSpecifiedVersion(args)
|
||||
|
||||
if specificVersion != "" {
|
||||
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
|
||||
@ -256,21 +275,54 @@ operations.`,
|
||||
if err := app.WriteRecipeVersion(app.Recipe.Version, false); err != nil {
|
||||
log.Fatalf("writing new recipe version in env file: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// This is not really xor since both can be absent
|
||||
//
|
||||
// but, I say we let it slide this time!
|
||||
func validateChaosXORVersion(args cli.Args) (bool, error) {
|
||||
// validateChaosXORVersion xor checks version/chaos mode
|
||||
func validateChaosXORVersion(args []string) (bool, error) {
|
||||
if getSpecifiedVersion(args) != "" && internal.Chaos {
|
||||
return false, errors.New("cannot use <version> and --chaos together")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func getSpecifiedVersion(args cli.Args) string {
|
||||
return args.Get(1)
|
||||
// getSpecifiedVersion retrieves the specific version if available
|
||||
func getSpecifiedVersion(args []string) string {
|
||||
if len(args) >= 2 {
|
||||
return args[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
AppDeployCommand.Flags().BoolVarP(
|
||||
&internal.Chaos,
|
||||
"chaos",
|
||||
"C",
|
||||
false,
|
||||
"ignore uncommitted recipes changes",
|
||||
)
|
||||
|
||||
AppDeployCommand.Flags().BoolVarP(
|
||||
&internal.Force,
|
||||
"force",
|
||||
"f",
|
||||
false,
|
||||
"perform action without further prompt",
|
||||
)
|
||||
|
||||
AppDeployCommand.Flags().BoolVarP(
|
||||
&internal.NoDomainChecks,
|
||||
"no-domain-checks",
|
||||
"D",
|
||||
false,
|
||||
"disable public DNS checks",
|
||||
)
|
||||
|
||||
AppDeployCommand.Flags().BoolVarP(
|
||||
&internal.DontWaitConverge, "no-converge-checks",
|
||||
"c",
|
||||
false,
|
||||
"do not wait for converge logic checks",
|
||||
)
|
||||
}
|
||||
|
@ -8,19 +8,19 @@ import (
|
||||
|
||||
func TestGetSpecificVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
input mockArgs
|
||||
input []string
|
||||
expectedOutput string
|
||||
}{
|
||||
// No specified version when command has one or less args
|
||||
{mockArgs{}, ""},
|
||||
{mockArgs{[]string{"arg0"}}, ""},
|
||||
{[]string{}, ""},
|
||||
{[]string{"arg0"}, ""},
|
||||
// Second in arg (index-1) is the specified result when command has more than 1 args
|
||||
{mockArgs{[]string{"arg0", "arg1"}}, "arg1"},
|
||||
{mockArgs{[]string{"arg0", "arg1", "arg2"}}, "arg1"},
|
||||
{[]string{"arg0", "arg1"}, "arg1"},
|
||||
{[]string{"arg0", "arg1", "arg2"}, "arg1"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if test.expectedOutput != getSpecifiedVersion(&test.input) {
|
||||
if test.expectedOutput != getSpecifiedVersion(test.input) {
|
||||
t.Fatalf("result for %s should be %s", test.input, test.expectedOutput)
|
||||
}
|
||||
}
|
||||
@ -28,23 +28,23 @@ func TestGetSpecificVersion(t *testing.T) {
|
||||
|
||||
func TestValidateChaosXORVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
input mockArgs
|
||||
input []string
|
||||
isChaos bool
|
||||
expectedResult bool
|
||||
}{
|
||||
// Chaos = true, Specified Version absent
|
||||
{mockArgs{}, true, true},
|
||||
{[]string{}, true, true},
|
||||
// Chaos = false, Specified Version absent
|
||||
{mockArgs{}, false, true},
|
||||
{[]string{}, false, true},
|
||||
// Chaos = true, Specified Version present
|
||||
{mockArgs{[]string{"arg0", "arg1"}}, true, false},
|
||||
{[]string{"arg0", "arg1"}, true, false},
|
||||
// Chaos = false, Specified Version present
|
||||
{mockArgs{[]string{"arg0", "arg1", "arg2"}}, false, true},
|
||||
{[]string{"arg0", "arg1", "arg2"}, false, true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
internal.Chaos = test.isChaos
|
||||
res, _ := validateChaosXORVersion(&test.input)
|
||||
res, _ := validateChaosXORVersion(test.input)
|
||||
if res != test.expectedResult {
|
||||
t.Fatalf(
|
||||
"When args are %s and Chaos mode is %t result needs to be %t",
|
||||
@ -55,43 +55,3 @@ func TestValidateChaosXORVersion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type mockArgs struct {
|
||||
v []string
|
||||
}
|
||||
|
||||
func (a *mockArgs) Get(n int) string {
|
||||
if len(a.v) > n {
|
||||
return a.v[n]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a *mockArgs) First() string {
|
||||
return a.Get(0)
|
||||
}
|
||||
|
||||
func (a *mockArgs) Tail() []string {
|
||||
if a.Len() >= 2 {
|
||||
tail := a.v[1:]
|
||||
ret := make([]string, len(tail))
|
||||
copy(ret, tail)
|
||||
return ret
|
||||
}
|
||||
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (a *mockArgs) Len() int {
|
||||
return len(a.v)
|
||||
}
|
||||
|
||||
func (a *mockArgs) Present() bool {
|
||||
return a.Len() != 0
|
||||
}
|
||||
|
||||
func (a *mockArgs) Slice() []string {
|
||||
ret := make([]string, len(a.v))
|
||||
copy(ret, a.v)
|
||||
return ret
|
||||
}
|
||||
|
129
cli/app/list.go
129
cli/app/list.go
@ -1,7 +1,6 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
@ -10,42 +9,11 @@ import (
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
appPkg "coopcloud.tech/abra/pkg/app"
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"coopcloud.tech/abra/pkg/log"
|
||||
"coopcloud.tech/tagcmp"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var (
|
||||
status bool
|
||||
statusFlag = &cli.BoolFlag{
|
||||
Name: "status",
|
||||
Aliases: []string{"S"},
|
||||
Usage: "Show app deployment status",
|
||||
Destination: &status,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
recipeFilter string
|
||||
recipeFlag = &cli.StringFlag{
|
||||
Name: "recipe",
|
||||
Aliases: []string{"r"},
|
||||
Value: "",
|
||||
Usage: "Show apps of a specific recipe",
|
||||
Destination: &recipeFilter,
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
listAppServer string
|
||||
listAppServerFlag = &cli.StringFlag{
|
||||
Name: "server",
|
||||
Aliases: []string{"s"},
|
||||
Value: "",
|
||||
Usage: "Show apps of a specific server",
|
||||
Destination: &listAppServer,
|
||||
}
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type appStatus struct {
|
||||
@ -70,25 +38,23 @@ type serverStatus struct {
|
||||
UpgradeCount int `json:"upgradeCount"`
|
||||
}
|
||||
|
||||
var appListCommand = cli.Command{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Usage: "List all managed apps",
|
||||
UsageText: "abra app list [options]",
|
||||
Description: `Generate a report of all managed apps.
|
||||
var AppListCommand = &cobra.Command{
|
||||
Use: "list [flags]",
|
||||
Aliases: []string{"ls"},
|
||||
Short: "List all managed apps",
|
||||
Long: `Generate a report of all managed apps.
|
||||
|
||||
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
|
||||
can take some time.`,
|
||||
Flags: []cli.Flag{
|
||||
internal.MachineReadableFlag,
|
||||
statusFlag,
|
||||
listAppServerFlag,
|
||||
recipeFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
Use "--status/-S" flag to query all servers for the live deployment status.`,
|
||||
Example: ` # list apps of all servers without live status
|
||||
abra app ls
|
||||
|
||||
# list apps of a specific server with live status
|
||||
abra app ls -s 1312.net -S
|
||||
|
||||
# list apps of all servers which match a specific recipe
|
||||
abra app ls -r gitea`,
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
appFiles, err := appPkg.LoadAppFiles(listAppServer)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@ -230,7 +196,8 @@ can take some time.`,
|
||||
} else {
|
||||
fmt.Println(string(jsonstring))
|
||||
}
|
||||
return nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
alreadySeen := make(map[string]bool)
|
||||
@ -318,7 +285,59 @@ can take some time.`,
|
||||
totalApps := formatter.BoldStyle.Render("TOTAL APPS")
|
||||
log.Infof("%s: %v | %s: %v ", totalServers, totalServersCount, totalApps, totalAppsCount)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
status bool
|
||||
recipeFilter string
|
||||
listAppServer string
|
||||
)
|
||||
|
||||
func init() {
|
||||
AppListCommand.Flags().BoolVarP(
|
||||
&status,
|
||||
"status",
|
||||
"S",
|
||||
false,
|
||||
"show app deployment status",
|
||||
)
|
||||
|
||||
AppListCommand.Flags().StringVarP(
|
||||
&recipeFilter,
|
||||
"recipe",
|
||||
"r",
|
||||
"",
|
||||
"show apps of a specific recipe",
|
||||
)
|
||||
|
||||
AppListCommand.RegisterFlagCompletionFunc(
|
||||
"recipe",
|
||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return autocomplete.RecipeNameComplete()
|
||||
},
|
||||
)
|
||||
|
||||
AppListCommand.Flags().BoolVarP(
|
||||
&internal.MachineReadable,
|
||||
"machine",
|
||||
"m",
|
||||
false,
|
||||
"print machine-readable output",
|
||||
)
|
||||
|
||||
AppListCommand.Flags().StringVarP(
|
||||
&listAppServer,
|
||||
"server",
|
||||
"s",
|
||||
"",
|
||||
"show apps of a specific server",
|
||||
)
|
||||
|
||||
AppListCommand.RegisterFlagCompletionFunc(
|
||||
"server",
|
||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return autocomplete.ServerNameComplete()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -19,23 +19,34 @@ import (
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var appLogsCommand = cli.Command{
|
||||
Name: "logs",
|
||||
Aliases: []string{"l"},
|
||||
Usage: "Tail app logs",
|
||||
UsageText: "abra app logs <domain> [<service>] [options]",
|
||||
Flags: []cli.Flag{
|
||||
internal.StdErrOnlyFlag,
|
||||
internal.SinceLogsFlag,
|
||||
var AppLogsCommand = &cobra.Command{
|
||||
Use: "logs <app> [service] [flags]",
|
||||
Aliases: []string{"l"},
|
||||
Short: "Tail app logs",
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
switch l := len(args); l {
|
||||
case 0:
|
||||
return autocomplete.AppNameComplete()
|
||||
case 1:
|
||||
app, err := appPkg.Get(args[0])
|
||||
if err != nil {
|
||||
log.Debugf("autocomplete failed: %s", err)
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
return autocomplete.ServiceNameComplete(app.Name)
|
||||
default:
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app := internal.ValidateApp(args)
|
||||
stackName := app.StackName()
|
||||
|
||||
if err := app.Recipe.EnsureExists(); err != nil {
|
||||
@ -56,17 +67,14 @@ var appLogsCommand = cli.Command{
|
||||
log.Fatalf("%s is not deployed?", app.Name)
|
||||
}
|
||||
|
||||
serviceName := cmd.Args().Get(1)
|
||||
serviceNames := []string{}
|
||||
if serviceName != "" {
|
||||
serviceNames = []string{serviceName}
|
||||
}
|
||||
err = tailLogs(cl, app, serviceNames)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
var serviceNames []string
|
||||
if len(args) == 2 {
|
||||
serviceNames = []string{args[1]}
|
||||
}
|
||||
|
||||
return nil
|
||||
if err = tailLogs(cl, app, serviceNames); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@ -112,8 +120,8 @@ func tailLogs(cl *dockerClient.Client, app appPkg.App, serviceNames []string) er
|
||||
go func(serviceID string) {
|
||||
logs, err := cl.ServiceLogs(context.Background(), serviceID, containerTypes.LogsOptions{
|
||||
ShowStderr: true,
|
||||
ShowStdout: !internal.StdErrOnly,
|
||||
Since: internal.SinceLogs,
|
||||
ShowStdout: !stdErr,
|
||||
Since: sinceLogs,
|
||||
Until: "",
|
||||
Timestamps: true,
|
||||
Follow: true,
|
||||
@ -137,3 +145,26 @@ func tailLogs(cl *dockerClient.Client, app appPkg.App, serviceNames []string) er
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
stdErr bool
|
||||
sinceLogs string
|
||||
)
|
||||
|
||||
func init() {
|
||||
AppLogsCommand.Flags().BoolVarP(
|
||||
&stdErr,
|
||||
"stderr",
|
||||
"s",
|
||||
false,
|
||||
"only tail stderr",
|
||||
)
|
||||
|
||||
AppLogsCommand.Flags().StringVarP(
|
||||
&sinceLogs,
|
||||
"since",
|
||||
"S",
|
||||
"",
|
||||
"tail logs since YYYY-MM-DDTHH:MM:SSZ",
|
||||
)
|
||||
}
|
||||
|
186
cli/app/new.go
186
cli/app/new.go
@ -1,7 +1,6 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
@ -17,7 +16,7 @@ import (
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/charmbracelet/lipgloss/table"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var appNewDescription = `Creates a new app from a default recipe.
|
||||
@ -26,12 +25,12 @@ 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
|
||||
deploy <domain>" to do so.
|
||||
deploy <app>" to do so.
|
||||
|
||||
You can see what recipes are available (i.e. values for the <recipe> argument)
|
||||
You can see what recipes are available (i.e. values for the [recipe] argument)
|
||||
by running "abra recipe ls".
|
||||
|
||||
Recipe commit hashes are supported values for "[<version>]".
|
||||
Recipe commit hashes are supported values for "[version]".
|
||||
|
||||
Passing the "--secrets/-S" flag will automatically generate secrets for your
|
||||
app and store them encrypted at rest on the chosen target server. These
|
||||
@ -42,32 +41,28 @@ You can use the "--pass/-P" to store these generated passwords locally in a
|
||||
pass store (see passwordstore.org for more). The pass command must be available
|
||||
on your $PATH.`
|
||||
|
||||
var appNewCommand = cli.Command{
|
||||
Name: "new",
|
||||
Aliases: []string{"n"},
|
||||
Usage: "Create a new app",
|
||||
UsageText: "abra app new [<recipe>] [<version>] [options]",
|
||||
Description: appNewDescription,
|
||||
Flags: []cli.Flag{
|
||||
internal.NewAppServerFlag,
|
||||
internal.DomainFlag,
|
||||
internal.PassFlag,
|
||||
internal.SecretsFlag,
|
||||
internal.ChaosFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
HideHelp: true,
|
||||
ShellComplete: func(ctx context.Context, cmd *cli.Command) {
|
||||
args := cmd.Args()
|
||||
switch args.Len() {
|
||||
var AppNewCommand = &cobra.Command{
|
||||
Use: "new [recipe] [version] [flags]",
|
||||
Aliases: []string{"n"},
|
||||
Short: "Create a new app",
|
||||
Long: appNewDescription,
|
||||
Args: cobra.RangeArgs(0, 2),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
switch l := len(args); l {
|
||||
case 0:
|
||||
autocomplete.RecipeNameComplete(ctx, cmd)
|
||||
return autocomplete.RecipeNameComplete()
|
||||
case 1:
|
||||
autocomplete.RecipeVersionComplete(cmd.Args().Get(0))
|
||||
recipe := internal.ValidateRecipe(args, cmd.Name())
|
||||
return autocomplete.RecipeVersionComplete(recipe.Name)
|
||||
default:
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
},
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
recipe := internal.ValidateRecipe(cmd)
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
recipe := internal.ValidateRecipe(args, cmd.Name())
|
||||
|
||||
var version string
|
||||
if !internal.Chaos {
|
||||
@ -80,7 +75,12 @@ var appNewCommand = cli.Command{
|
||||
}
|
||||
}
|
||||
|
||||
if cmd.Args().Get(1) == "" {
|
||||
var recipeVersion string
|
||||
if len(args) == 2 {
|
||||
recipeVersion = args[1]
|
||||
}
|
||||
|
||||
if recipeVersion == "" {
|
||||
recipeVersions, err := recipe.GetRecipeVersions()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@ -101,8 +101,7 @@ var appNewCommand = cli.Command{
|
||||
}
|
||||
}
|
||||
} else {
|
||||
version = cmd.Args().Get(1)
|
||||
if _, err := recipe.EnsureVersion(version); err != nil {
|
||||
if _, err := recipe.EnsureVersion(recipeVersion); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@ -112,25 +111,25 @@ var appNewCommand = cli.Command{
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := ensureDomainFlag(recipe, internal.NewAppServer); err != nil {
|
||||
if err := ensureDomainFlag(recipe, newAppServer); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
sanitisedAppName := appPkg.SanitiseAppName(internal.Domain)
|
||||
log.Debugf("%s sanitised as %s for new app", internal.Domain, sanitisedAppName)
|
||||
sanitisedAppName := appPkg.SanitiseAppName(appDomain)
|
||||
log.Debugf("%s sanitised as %s for new app", appDomain, sanitisedAppName)
|
||||
|
||||
if err := appPkg.TemplateAppEnvSample(
|
||||
recipe,
|
||||
internal.Domain,
|
||||
internal.NewAppServer,
|
||||
internal.Domain,
|
||||
appDomain,
|
||||
newAppServer,
|
||||
appDomain,
|
||||
); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var secrets AppSecrets
|
||||
var appSecrets AppSecrets
|
||||
var secretsTable *table.Table
|
||||
if internal.Secrets {
|
||||
if generateSecrets {
|
||||
sampleEnv, err := recipe.SampleEnv()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@ -141,21 +140,25 @@ var appNewCommand = cli.Command{
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
secretsConfig, err := secret.ReadSecretsConfig(recipe.SampleEnvPath, composeFiles, appPkg.StackName(internal.Domain))
|
||||
secretsConfig, err := secret.ReadSecretsConfig(
|
||||
recipe.SampleEnvPath,
|
||||
composeFiles,
|
||||
appPkg.StackName(appDomain),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := promptForSecrets(recipe.Name, secretsConfig); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
cl, err := client.New(internal.NewAppServer)
|
||||
cl, err := client.New(newAppServer)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
secrets, err = createSecrets(cl, secretsConfig, sanitisedAppName)
|
||||
appSecrets, err = createSecrets(cl, secretsConfig, sanitisedAppName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@ -168,13 +171,13 @@ var appNewCommand = cli.Command{
|
||||
headers := []string{"NAME", "VALUE"}
|
||||
secretsTable.Headers(headers...)
|
||||
|
||||
for name, val := range secrets {
|
||||
for name, val := range appSecrets {
|
||||
secretsTable.Row(name, val)
|
||||
}
|
||||
}
|
||||
|
||||
if internal.NewAppServer == "default" {
|
||||
internal.NewAppServer = "local"
|
||||
if newAppServer == "default" {
|
||||
newAppServer = "local"
|
||||
}
|
||||
|
||||
table, err := formatter.CreateTable()
|
||||
@ -185,7 +188,7 @@ var appNewCommand = cli.Command{
|
||||
headers := []string{"SERVER", "DOMAIN", "RECIPE", "VERSION"}
|
||||
table.Headers(headers...)
|
||||
|
||||
table.Row(internal.NewAppServer, internal.Domain, recipe.Name, version)
|
||||
table.Row(newAppServer, appDomain, recipe.Name, version)
|
||||
|
||||
log.Infof("new app '%s' created 🌞", recipe.Name)
|
||||
|
||||
@ -194,13 +197,13 @@ var appNewCommand = cli.Command{
|
||||
fmt.Println("")
|
||||
|
||||
fmt.Println("Configure this app:")
|
||||
fmt.Println(fmt.Sprintf("\n abra app config %s", internal.Domain))
|
||||
fmt.Println(fmt.Sprintf("\n abra app config %s", appDomain))
|
||||
|
||||
fmt.Println("")
|
||||
fmt.Println("Deploy this app:")
|
||||
fmt.Println(fmt.Sprintf("\n abra app deploy %s", internal.Domain))
|
||||
fmt.Println(fmt.Sprintf("\n abra app deploy %s", appDomain))
|
||||
|
||||
if len(secrets) > 0 {
|
||||
if len(appSecrets) > 0 {
|
||||
fmt.Println("")
|
||||
fmt.Println("Generated secrets:")
|
||||
fmt.Println("")
|
||||
@ -213,7 +216,7 @@ var appNewCommand = cli.Command{
|
||||
)
|
||||
}
|
||||
|
||||
app, err := app.Get(internal.Domain)
|
||||
app, err := app.Get(appDomain)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@ -222,8 +225,6 @@ var appNewCommand = cli.Command{
|
||||
if err := app.WriteRecipeVersion(version, false); err != nil {
|
||||
log.Fatalf("writing new recipe version in env file: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@ -238,19 +239,19 @@ func createSecrets(cl *dockerClient.Client, secretsConfig map[string]secret.Secr
|
||||
sanitisedAppName = sanitisedAppName[:config.MAX_SANITISED_APP_NAME_LENGTH]
|
||||
}
|
||||
|
||||
secrets, err := secret.GenerateSecrets(cl, secretsConfig, internal.NewAppServer)
|
||||
secrets, err := secret.GenerateSecrets(cl, secretsConfig, newAppServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if internal.Pass {
|
||||
if saveInPass {
|
||||
for secretName := range secrets {
|
||||
secretValue := secrets[secretName]
|
||||
if err := secret.PassInsertSecret(
|
||||
secretValue,
|
||||
secretName,
|
||||
internal.Domain,
|
||||
internal.NewAppServer,
|
||||
appDomain,
|
||||
newAppServer,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -262,17 +263,17 @@ func createSecrets(cl *dockerClient.Client, secretsConfig map[string]secret.Secr
|
||||
|
||||
// ensureDomainFlag checks if the domain flag was used. if not, asks the user for it/
|
||||
func ensureDomainFlag(recipe recipePkg.Recipe, server string) error {
|
||||
if internal.Domain == "" && !internal.NoInput {
|
||||
if appDomain == "" && !internal.NoInput {
|
||||
prompt := &survey.Input{
|
||||
Message: "Specify app domain",
|
||||
Default: fmt.Sprintf("%s.%s", recipe.Name, server),
|
||||
}
|
||||
if err := survey.AskOne(prompt, &internal.Domain); err != nil {
|
||||
if err := survey.AskOne(prompt, &appDomain); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if internal.Domain == "" {
|
||||
if appDomain == "" {
|
||||
return fmt.Errorf("no domain provided")
|
||||
}
|
||||
|
||||
@ -286,11 +287,11 @@ func promptForSecrets(recipeName string, secretsConfig map[string]secret.Secret)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !internal.Secrets && !internal.NoInput {
|
||||
if !generateSecrets && !internal.NoInput {
|
||||
prompt := &survey.Confirm{
|
||||
Message: "Generate app secrets?",
|
||||
}
|
||||
if err := survey.AskOne(prompt, &internal.Secrets); err != nil {
|
||||
if err := survey.AskOne(prompt, &generateSecrets); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -305,19 +306,76 @@ func ensureServerFlag() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if internal.NewAppServer == "" && !internal.NoInput {
|
||||
if newAppServer == "" && !internal.NoInput {
|
||||
prompt := &survey.Select{
|
||||
Message: "Select app server:",
|
||||
Options: servers,
|
||||
}
|
||||
if err := survey.AskOne(prompt, &internal.NewAppServer); err != nil {
|
||||
if err := survey.AskOne(prompt, &newAppServer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if internal.NewAppServer == "" {
|
||||
if newAppServer == "" {
|
||||
return fmt.Errorf("no server provided")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
newAppServer string
|
||||
appDomain string
|
||||
saveInPass bool
|
||||
generateSecrets bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
AppNewCommand.Flags().StringVarP(
|
||||
&newAppServer,
|
||||
"server",
|
||||
"s",
|
||||
"",
|
||||
"specify server for new app",
|
||||
)
|
||||
|
||||
AppNewCommand.RegisterFlagCompletionFunc(
|
||||
"server",
|
||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return autocomplete.ServerNameComplete()
|
||||
},
|
||||
)
|
||||
|
||||
AppNewCommand.Flags().StringVarP(
|
||||
&appDomain,
|
||||
"domain",
|
||||
"D",
|
||||
"",
|
||||
"domain name for app",
|
||||
)
|
||||
|
||||
AppNewCommand.Flags().BoolVarP(
|
||||
&saveInPass,
|
||||
"pass",
|
||||
"p",
|
||||
false,
|
||||
"store secrets in a local pass store",
|
||||
)
|
||||
|
||||
AppNewCommand.Flags().BoolVarP(
|
||||
&generateSecrets,
|
||||
"secrets",
|
||||
"S",
|
||||
false,
|
||||
"automatically generate secrets",
|
||||
)
|
||||
|
||||
AppNewCommand.Flags().BoolVarP(
|
||||
&internal.Chaos,
|
||||
"chaos",
|
||||
"C",
|
||||
false,
|
||||
"ignore uncommitted recipes changes",
|
||||
)
|
||||
|
||||
}
|
||||
|
@ -18,25 +18,23 @@ import (
|
||||
containerTypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var appPsCommand = cli.Command{
|
||||
Name: "ps",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "Check app status",
|
||||
UsageText: "abra app ps <domain> [options]",
|
||||
Description: "Show status of a deployed app.",
|
||||
Flags: []cli.Flag{
|
||||
internal.MachineReadableFlag,
|
||||
internal.ChaosFlag,
|
||||
internal.OfflineFlag,
|
||||
var AppPsCommand = &cobra.Command{
|
||||
Use: "ps <app> [flags]",
|
||||
Aliases: []string{"p"},
|
||||
Short: "Check app status",
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return autocomplete.AppNameComplete()
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app := internal.ValidateApp(args)
|
||||
|
||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@ -67,8 +65,6 @@ var appPsCommand = cli.Command{
|
||||
}
|
||||
|
||||
showPSOutput(app, cl, deployMeta.Version, chaosVersion)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@ -175,3 +171,21 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
||||
|
||||
log.Infof("VERSION: %s CHAOS: %s", deployedVersion, chaosVersion)
|
||||
}
|
||||
|
||||
func init() {
|
||||
AppPsCommand.Flags().BoolVarP(
|
||||
&internal.MachineReadable,
|
||||
"machine",
|
||||
"m",
|
||||
false,
|
||||
"print machine-readable output",
|
||||
)
|
||||
|
||||
AppPsCommand.Flags().BoolVarP(
|
||||
&internal.Chaos,
|
||||
"chaos",
|
||||
"C",
|
||||
false,
|
||||
"ignore uncommitted recipes changes",
|
||||
)
|
||||
}
|
||||
|
@ -12,15 +12,14 @@ import (
|
||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var appRemoveCommand = cli.Command{
|
||||
Name: "remove",
|
||||
Aliases: []string{"rm"},
|
||||
UsageText: "abra app remove <domain> [options]",
|
||||
Usage: "Remove all app data, locally and remotely",
|
||||
Description: `Remove everything related to an app which is already undeployed.
|
||||
var AppRemoveCommand = &cobra.Command{
|
||||
Use: "remove <app> [flags]",
|
||||
Aliases: []string{"rm"},
|
||||
Short: "Remove all app data, locally and remotely",
|
||||
Long: `Remove everything related to an app which is already undeployed.
|
||||
|
||||
By default, it will prompt for confirmation before proceeding. All secrets,
|
||||
volumes and the local app env file will be deleted.
|
||||
@ -36,17 +35,19 @@ secrets first, Abra will *not* be able to help you remove them afterwards.
|
||||
|
||||
To delete everything without prompt, use the "--force/-f" or the "--no-input/n"
|
||||
flag.`,
|
||||
Flags: []cli.Flag{
|
||||
internal.ForceFlag,
|
||||
Example: " abra app remove 1312.net",
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return autocomplete.AppNameComplete()
|
||||
},
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
Before: internal.SubCommandBefore,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app := internal.ValidateApp(args)
|
||||
|
||||
if !internal.Force && !internal.NoInput {
|
||||
log.Warnf("ALERTA ALERTA: this will completely remove %s data and config locally and remotely", app.Name)
|
||||
log.Warnf("ALERTA ALERTA: deleting %s data and config (local/remote)", app.Name)
|
||||
|
||||
response := false
|
||||
prompt := &survey.Confirm{Message: "are you sure?"}
|
||||
@ -129,7 +130,15 @@ flag.`,
|
||||
}
|
||||
|
||||
log.Info(fmt.Sprintf("file: %s removed", app.Path))
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
AppRemoveCommand.Flags().BoolVarP(
|
||||
&internal.Force,
|
||||
"force",
|
||||
"f",
|
||||
false,
|
||||
"perform action without further prompt",
|
||||
)
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
@ -12,43 +11,62 @@ import (
|
||||
"coopcloud.tech/abra/pkg/log"
|
||||
upstream "coopcloud.tech/abra/pkg/upstream/service"
|
||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var appRestartCommand = cli.Command{
|
||||
Name: "restart",
|
||||
Aliases: []string{"re"},
|
||||
Usage: "Restart an app",
|
||||
UsageText: "abra app restart <domain> [<service>] [options]",
|
||||
Flags: []cli.Flag{
|
||||
internal.AllServicesFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
Description: `This command restarts services within a deployed app.
|
||||
var AppRestartCommand = &cobra.Command{
|
||||
Use: "restart <app> [[service] | --all-services] [flags]",
|
||||
Aliases: []string{"re"},
|
||||
Short: "Restart an app",
|
||||
Long: `This command restarts services within a deployed app.
|
||||
|
||||
Run "abra app ps <domain>" to see a list of service names.
|
||||
Run "abra app ps <app>" to see a list of service names.
|
||||
|
||||
Pass "--all-services/-a" to restart all services.`,
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
Example: ` # restart a single app service
|
||||
abra app restart 1312.net app
|
||||
|
||||
# restart all app services
|
||||
abra app restart 1312.net -a`,
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
switch l := len(args); l {
|
||||
case 0:
|
||||
return autocomplete.AppNameComplete()
|
||||
case 1:
|
||||
if !allServices {
|
||||
return autocomplete.ServiceNameComplete(args[0])
|
||||
}
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
default:
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app := internal.ValidateApp(args)
|
||||
|
||||
if err := app.Recipe.Ensure(false, false); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
serviceName := cmd.Args().Get(1)
|
||||
if serviceName == "" && !internal.AllServices {
|
||||
err := errors.New("missing <service>")
|
||||
internal.ShowSubcommandHelpAndError(cmd, err)
|
||||
var serviceName string
|
||||
if len(args) == 2 {
|
||||
serviceName = args[1]
|
||||
}
|
||||
|
||||
if serviceName != "" && internal.AllServices {
|
||||
log.Fatal("cannot use <service> and --all-services together")
|
||||
if serviceName == "" && !allServices {
|
||||
log.Fatal("missing [service]")
|
||||
}
|
||||
|
||||
if serviceName != "" && allServices {
|
||||
log.Fatal("cannot use [service] and --all-services/-a together")
|
||||
}
|
||||
|
||||
var serviceNames []string
|
||||
if internal.AllServices {
|
||||
if allServices {
|
||||
var err error
|
||||
serviceNames, err = appPkg.GetAppServiceNames(app.Name)
|
||||
if err != nil {
|
||||
@ -99,7 +117,17 @@ Pass "--all-services/-a" to restart all services.`,
|
||||
log.Debugf("%s has been scaled to 1", stackServiceName)
|
||||
log.Infof("%s service successfully restarted", serviceName)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var allServices bool
|
||||
|
||||
func init() {
|
||||
AppRestartCommand.Flags().BoolVarP(
|
||||
&allServices,
|
||||
"all-services",
|
||||
"a",
|
||||
false,
|
||||
"restart all services",
|
||||
)
|
||||
}
|
||||
|
@ -1,65 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/log"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var targetPath string
|
||||
var targetPathFlag = &cli.StringFlag{
|
||||
Name: "target",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "Target path",
|
||||
Destination: &targetPath,
|
||||
}
|
||||
|
||||
var appRestoreCommand = cli.Command{
|
||||
Name: "restore",
|
||||
Aliases: []string{"rs"},
|
||||
Usage: "Restore an app backup",
|
||||
UsageText: "abra app restore <domain> <service> [options]",
|
||||
Flags: []cli.Flag{
|
||||
targetPathFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
targetContainer, err := internal.RetrieveBackupBotContainer(cl)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
execEnv := []string{fmt.Sprintf("SERVICE=%s", app.Domain)}
|
||||
if snapshot != "" {
|
||||
log.Debugf("including SNAPSHOT=%s in backupbot exec invocation", snapshot)
|
||||
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
|
||||
}
|
||||
if targetPath != "" {
|
||||
log.Debugf("including TARGET=%s in backupbot exec invocation", targetPath)
|
||||
execEnv = append(execEnv, fmt.Sprintf("TARGET=%s", targetPath))
|
||||
}
|
||||
|
||||
if err := internal.RunBackupCmdRemote(cl, "restore", targetContainer.ID, execEnv); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
@ -16,36 +16,55 @@ import (
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/log"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var appRollbackCommand = cli.Command{
|
||||
Name: "rollback",
|
||||
Aliases: []string{"rl"},
|
||||
Usage: "Roll an app back to a previous version",
|
||||
UsageText: "abra app rollback <domain> [<version>] [options]",
|
||||
Flags: []cli.Flag{
|
||||
internal.ForceFlag,
|
||||
internal.NoDomainChecksFlag,
|
||||
internal.DontWaitConvergeFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
Description: `This command rolls an app back to a previous version.
|
||||
var AppRollbackCommand = &cobra.Command{
|
||||
Use: "rollback <app> [version] [flags]",
|
||||
Aliases: []string{"rl"},
|
||||
Short: "Roll an app back to a previous version",
|
||||
Long: `This command rolls an app back to a previous version.
|
||||
|
||||
Unlike "deploy", chaos operations are not supported here. Only recipe versions
|
||||
are supported values for "[<version>]".
|
||||
|
||||
A rollback can be destructive, please ensure you have a copy of your app data
|
||||
beforehand.`,
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
Example: ` # standard rollback
|
||||
abra app rollback 1312.net
|
||||
|
||||
# rollback to specific version
|
||||
abra app rollback 1312.net 2.0.0+1.2.3`,
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
switch l := len(args); l {
|
||||
case 0:
|
||||
return autocomplete.AppNameComplete()
|
||||
case 1:
|
||||
app, err := appPkg.Get(args[0])
|
||||
if err != nil {
|
||||
log.Debugf("autocomplete failed: %s", err)
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
return autocomplete.RecipeVersionComplete(app.Recipe.Name)
|
||||
default:
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var warnMessages []string
|
||||
|
||||
app := internal.ValidateApp(cmd)
|
||||
app := internal.ValidateApp(args)
|
||||
stackName := app.StackName()
|
||||
|
||||
specificVersion := cmd.Args().Get(1)
|
||||
var specificVersion string
|
||||
if len(args) == 2 {
|
||||
specificVersion = args[1]
|
||||
}
|
||||
|
||||
if specificVersion != "" {
|
||||
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
|
||||
app.Recipe.Version = specificVersion
|
||||
@ -131,7 +150,7 @@ beforehand.`,
|
||||
|
||||
if len(availableDowngrades) == 0 && !internal.Force {
|
||||
log.Info("no available downgrades")
|
||||
return nil
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,7 +171,7 @@ beforehand.`,
|
||||
}
|
||||
|
||||
if err := survey.AskOne(prompt, &chosenDowngrade); err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -220,7 +239,30 @@ beforehand.`,
|
||||
if err := app.WriteRecipeVersion(app.Recipe.Version, false); err != nil {
|
||||
log.Fatalf("writing new recipe version in env file: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
AppRollbackCommand.Flags().BoolVarP(
|
||||
&internal.Force,
|
||||
"force",
|
||||
"f",
|
||||
false,
|
||||
"perform action without further prompt",
|
||||
)
|
||||
|
||||
AppRollbackCommand.Flags().BoolVarP(
|
||||
&internal.NoDomainChecks,
|
||||
"no-domain-checks",
|
||||
"D",
|
||||
false,
|
||||
"disable public DNS checks",
|
||||
)
|
||||
|
||||
AppRollbackCommand.Flags().BoolVarP(
|
||||
&internal.DontWaitConverge, "no-converge-checks",
|
||||
"c",
|
||||
false,
|
||||
"do not wait for converge logic checks",
|
||||
)
|
||||
}
|
||||
|
102
cli/app/run.go
102
cli/app/run.go
@ -2,7 +2,6 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
@ -14,54 +13,48 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var user string
|
||||
var userFlag = &cli.StringFlag{
|
||||
Name: "user",
|
||||
Aliases: []string{"u"},
|
||||
Value: "",
|
||||
Destination: &user,
|
||||
}
|
||||
|
||||
var noTTY bool
|
||||
var noTTYFlag = &cli.BoolFlag{
|
||||
Name: "no-tty",
|
||||
Aliases: []string{"t"},
|
||||
Destination: &noTTY,
|
||||
}
|
||||
|
||||
var appRunCommand = cli.Command{
|
||||
Name: "run",
|
||||
var AppRunCommand = &cobra.Command{
|
||||
Use: "run <app> <service> <cmd> [[args] [flags] | [flags] -- [args]]",
|
||||
Aliases: []string{"r"},
|
||||
Flags: []cli.Flag{
|
||||
noTTYFlag,
|
||||
userFlag,
|
||||
Short: "Run a command inside a service container",
|
||||
Example: ` # run <cmd> with args/flags
|
||||
abra app run 1312.net app -- ls -lha
|
||||
|
||||
# run <cmd> without args/flags
|
||||
abra app run 1312.net app bash --user nobody
|
||||
|
||||
# run <cmd> with both kinds of args/flags
|
||||
abra app run 1312.net app --user nobody -- ls -lha`,
|
||||
Args: cobra.MinimumNArgs(3),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
switch l := len(args); l {
|
||||
case 0:
|
||||
return autocomplete.AppNameComplete()
|
||||
case 1:
|
||||
return autocomplete.ServiceNameComplete(args[0])
|
||||
case 2:
|
||||
return autocomplete.CommandNameComplete(args[0])
|
||||
default:
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
Usage: "Run a command in an app service",
|
||||
UsageText: "abra app run <domain> <service> <args> [options]",
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
|
||||
if cmd.Args().Len() < 2 {
|
||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("no <service> provided?"))
|
||||
}
|
||||
|
||||
if cmd.Args().Len() < 3 {
|
||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("no <args> provided?"))
|
||||
}
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app := internal.ValidateApp(args)
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
serviceName := cmd.Args().Get(1)
|
||||
serviceName := args[1]
|
||||
stackAndServiceName := fmt.Sprintf("^%s_%s", app.StackName(), serviceName)
|
||||
|
||||
filters := filters.NewArgs()
|
||||
filters.Add("name", stackAndServiceName)
|
||||
|
||||
@ -70,24 +63,23 @@ var appRunCommand = cli.Command{
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
c := cmd.Args().Slice()[2:]
|
||||
userCmd := args[2:]
|
||||
execCreateOpts := types.ExecConfig{
|
||||
AttachStderr: true,
|
||||
AttachStdin: true,
|
||||
AttachStdout: true,
|
||||
Cmd: c,
|
||||
Cmd: userCmd,
|
||||
Detach: false,
|
||||
Tty: true,
|
||||
}
|
||||
|
||||
if user != "" {
|
||||
execCreateOpts.User = user
|
||||
if runAsUser != "" {
|
||||
execCreateOpts.User = runAsUser
|
||||
}
|
||||
if noTTY {
|
||||
execCreateOpts.Tty = false
|
||||
}
|
||||
|
||||
// FIXME: avoid instantiating a new CLI
|
||||
dcli, err := command.NewDockerCli()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@ -96,7 +88,27 @@ var appRunCommand = cli.Command{
|
||||
if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
noTTY bool
|
||||
runAsUser string
|
||||
)
|
||||
|
||||
func init() {
|
||||
AppRunCommand.Flags().BoolVarP(&noTTY,
|
||||
"no-tty",
|
||||
"t",
|
||||
false,
|
||||
"do not request a TTY",
|
||||
)
|
||||
|
||||
AppRunCommand.Flags().StringVarP(
|
||||
&runAsUser,
|
||||
"user",
|
||||
"u",
|
||||
"",
|
||||
"run command as user",
|
||||
)
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
@ -17,57 +16,45 @@ import (
|
||||
"coopcloud.tech/abra/pkg/secret"
|
||||
"github.com/docker/docker/api/types"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
allSecrets bool
|
||||
allSecretsFlag = &cli.BoolFlag{
|
||||
Name: "all",
|
||||
Aliases: []string{"a"},
|
||||
Destination: &allSecrets,
|
||||
Usage: "Generate all secrets",
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
rmAllSecrets bool
|
||||
rmAllSecretsFlag = &cli.BoolFlag{
|
||||
Name: "all",
|
||||
Aliases: []string{"a"},
|
||||
Destination: &rmAllSecrets,
|
||||
Usage: "Remove all secrets",
|
||||
}
|
||||
)
|
||||
|
||||
var appSecretGenerateCommand = cli.Command{
|
||||
Name: "generate",
|
||||
Aliases: []string{"g"},
|
||||
Usage: "Generate secrets",
|
||||
UsageText: "abra app secret generate <domain> <secret> <version> [options]",
|
||||
Flags: []cli.Flag{
|
||||
allSecretsFlag,
|
||||
internal.PassFlag,
|
||||
internal.MachineReadableFlag,
|
||||
internal.ChaosFlag,
|
||||
var AppSecretGenerateCommand = &cobra.Command{
|
||||
Use: "generate <app> [[secret] [version] | --all] [flags]",
|
||||
Aliases: []string{"g"},
|
||||
Short: "Generate secrets",
|
||||
Args: cobra.RangeArgs(1, 3),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
switch l := len(args); l {
|
||||
case 0:
|
||||
return autocomplete.AppNameComplete()
|
||||
case 1:
|
||||
app, err := appPkg.Get(args[0])
|
||||
if err != nil {
|
||||
log.Debugf("autocomplete failed: %s", err)
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
return autocomplete.SecretComplete(app.Recipe.Name)
|
||||
default:
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app := internal.ValidateApp(args)
|
||||
|
||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if cmd.Args().Len() == 1 && !allSecrets {
|
||||
err := errors.New("missing arguments <secret>/<version> or '--all'")
|
||||
internal.ShowSubcommandHelpAndError(cmd, err)
|
||||
if len(args) == 1 && !generateAllSecrets {
|
||||
log.Fatal("missing arguments [secret]/[version] or '--all'")
|
||||
}
|
||||
|
||||
if cmd.Args().Get(1) != "" && allSecrets {
|
||||
err := errors.New("cannot use '<secret> <version>' and '--all' together")
|
||||
internal.ShowSubcommandHelpAndError(cmd, err)
|
||||
if len(args) > 1 && generateAllSecrets {
|
||||
log.Fatal("cannot use '[secret] [version]' and '--all' together")
|
||||
}
|
||||
|
||||
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
||||
@ -80,9 +67,9 @@ var appSecretGenerateCommand = cli.Command{
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if !allSecrets {
|
||||
secretName := cmd.Args().Get(1)
|
||||
secretVersion := cmd.Args().Get(2)
|
||||
if !generateAllSecrets {
|
||||
secretName := args[1]
|
||||
secretVersion := args[2]
|
||||
s, ok := secrets[secretName]
|
||||
if !ok {
|
||||
log.Fatalf("%s doesn't exist in the env config?", secretName)
|
||||
@ -103,7 +90,7 @@ var appSecretGenerateCommand = cli.Command{
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if internal.Pass {
|
||||
if storeInPass {
|
||||
for name, data := range secretVals {
|
||||
if err := secret.PassInsertSecret(data, name, app.Name, app.Server); err != nil {
|
||||
log.Fatal(err)
|
||||
@ -137,7 +124,7 @@ var appSecretGenerateCommand = cli.Command{
|
||||
log.Fatal("unable to render to JSON: %s", err)
|
||||
}
|
||||
fmt.Println(out)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(table)
|
||||
@ -147,50 +134,54 @@ var appSecretGenerateCommand = cli.Command{
|
||||
formatter.BoldStyle.Render("NOT"),
|
||||
formatter.BoldStyle.Render("NOW"),
|
||||
)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var appSecretInsertCommand = cli.Command{
|
||||
Name: "insert",
|
||||
Aliases: []string{"i"},
|
||||
Usage: "Insert secret",
|
||||
UsageText: "abra app secret insert <domain> <secret> <version> <data> [options]",
|
||||
Flags: []cli.Flag{
|
||||
internal.PassFlag,
|
||||
internal.FileFlag,
|
||||
internal.TrimFlag,
|
||||
internal.ChaosFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelpCommand: true,
|
||||
Description: `This command inserts a secret into an app environment.
|
||||
var AppSecretInsertCommand = &cobra.Command{
|
||||
Use: "insert <app> <secret> <version> <data> [flags]",
|
||||
Aliases: []string{"i"},
|
||||
Short: "Insert secret",
|
||||
Long: `This command inserts a secret into an app environment.
|
||||
|
||||
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
|
||||
(see "abra app new --secrets" for more).`,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
(see "abra app new --secrets/-S" for more).`,
|
||||
Args: cobra.MinimumNArgs(4),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
switch l := len(args); l {
|
||||
case 0:
|
||||
return autocomplete.AppNameComplete()
|
||||
case 1:
|
||||
app, err := appPkg.Get(args[0])
|
||||
if err != nil {
|
||||
log.Debugf("autocomplete failed: %s", err)
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
return autocomplete.SecretComplete(app.Recipe.Name)
|
||||
default:
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app := internal.ValidateApp(args)
|
||||
|
||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if cmd.Args().Len() != 4 {
|
||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("missing arguments?"))
|
||||
}
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
name := cmd.Args().Get(1)
|
||||
version := cmd.Args().Get(2)
|
||||
data := cmd.Args().Get(3)
|
||||
name := args[1]
|
||||
version := args[2]
|
||||
data := args[3]
|
||||
|
||||
if internal.File {
|
||||
if insertFromFile {
|
||||
raw, err := os.ReadFile(data)
|
||||
if err != nil {
|
||||
log.Fatalf("reading secret from file: %s", err)
|
||||
@ -198,7 +189,7 @@ environment. Typically, you can let Abra generate them for you on app creation
|
||||
data = string(raw)
|
||||
}
|
||||
|
||||
if internal.Trim {
|
||||
if trimInput {
|
||||
data = strings.TrimSpace(data)
|
||||
}
|
||||
|
||||
@ -209,13 +200,11 @@ environment. Typically, you can let Abra generate them for you on app creation
|
||||
|
||||
log.Infof("%s successfully stored on server", secretName)
|
||||
|
||||
if internal.Pass {
|
||||
if storeInPass {
|
||||
if err := secret.PassInsertSecret(data, name, app.Name, app.Server); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@ -227,7 +216,7 @@ func secretRm(cl *dockerClient.Client, app appPkg.App, secretName, parsed string
|
||||
|
||||
log.Infof("deleted %s successfully from server", secretName)
|
||||
|
||||
if internal.PassRemove {
|
||||
if removeFromPass {
|
||||
if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -238,29 +227,35 @@ func secretRm(cl *dockerClient.Client, app appPkg.App, secretName, parsed string
|
||||
return nil
|
||||
}
|
||||
|
||||
var appSecretRmCommand = cli.Command{
|
||||
Name: "remove",
|
||||
Aliases: []string{"rm"},
|
||||
Usage: "Remove a secret",
|
||||
UsageText: "abra app remove <domainabra app remove <domain> [options]",
|
||||
Flags: []cli.Flag{
|
||||
internal.NoInputFlag,
|
||||
rmAllSecretsFlag,
|
||||
internal.PassRemoveFlag,
|
||||
internal.OfflineFlag,
|
||||
internal.ChaosFlag,
|
||||
var AppSecretRmCommand = &cobra.Command{
|
||||
Use: "remove <app> [[secret] | --all] [flags]",
|
||||
Aliases: []string{"rm"},
|
||||
Short: "Remove a secret",
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
switch l := len(args); l {
|
||||
case 0:
|
||||
return autocomplete.AppNameComplete()
|
||||
case 1:
|
||||
if !rmAllSecrets {
|
||||
app, err := appPkg.Get(args[0])
|
||||
if err != nil {
|
||||
log.Debugf("autocomplete failed: %s", err)
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
return autocomplete.SecretComplete(app.Recipe.Name)
|
||||
}
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
default:
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
Description: `
|
||||
This command removes app secrets.
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app := internal.ValidateApp(args)
|
||||
|
||||
Example:
|
||||
|
||||
abra app secret remove myapp db_pass`,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@ -275,12 +270,12 @@ Example:
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if cmd.Args().Get(1) != "" && rmAllSecrets {
|
||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("cannot use '<secret-name>' and '--all' together"))
|
||||
if len(args) == 2 && rmAllSecrets {
|
||||
log.Fatal("cannot use [secret] and --all/-a together")
|
||||
}
|
||||
|
||||
if cmd.Args().Get(1) == "" && !rmAllSecrets {
|
||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("no secret(s) specified?"))
|
||||
if len(args) != 2 && !rmAllSecrets {
|
||||
log.Fatal("no secret(s) specified?")
|
||||
}
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
@ -303,8 +298,12 @@ Example:
|
||||
remoteSecretNames[cont.Spec.Annotations.Name] = true
|
||||
}
|
||||
|
||||
var secretToRm string
|
||||
if len(args) == 2 {
|
||||
secretToRm = args[1]
|
||||
}
|
||||
|
||||
match := false
|
||||
secretToRm := cmd.Args().Get(1)
|
||||
for secretName, val := range secrets {
|
||||
secretRemoteName := fmt.Sprintf("%s_%s_%s", app.StackName(), secretName, val.Version)
|
||||
if _, ok := remoteSecretNames[secretRemoteName]; ok {
|
||||
@ -314,7 +313,7 @@ Example:
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return
|
||||
}
|
||||
} else {
|
||||
match = true
|
||||
@ -333,26 +332,23 @@ Example:
|
||||
if !match {
|
||||
log.Fatal("no secrets to remove?")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var appSecretLsCommand = cli.Command{
|
||||
Name: "list",
|
||||
var AppSecretLsCommand = &cobra.Command{
|
||||
Use: "list <app>",
|
||||
Aliases: []string{"ls"},
|
||||
Flags: []cli.Flag{
|
||||
internal.OfflineFlag,
|
||||
internal.ChaosFlag,
|
||||
internal.MachineReadableFlag,
|
||||
Short: "List all secrets",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return autocomplete.AppNameComplete()
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
Usage: "List all secrets",
|
||||
UsageText: "abra app secret list [options]",
|
||||
HideHelp: true,
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app := internal.ValidateApp(args)
|
||||
|
||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@ -395,28 +391,126 @@ var appSecretLsCommand = cli.Command{
|
||||
log.Fatal("unable to render to JSON: %s", err)
|
||||
}
|
||||
fmt.Println(out)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(table)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
log.Warnf("no secrets stored for %s", app.Name)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var appSecretCommand = cli.Command{
|
||||
Name: "secret",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "Manage app secrets",
|
||||
UsageText: "abra app secret [command] [arguments] [options]",
|
||||
Commands: []*cli.Command{
|
||||
&appSecretGenerateCommand,
|
||||
&appSecretInsertCommand,
|
||||
&appSecretRmCommand,
|
||||
&appSecretLsCommand,
|
||||
},
|
||||
var AppSecretCommand = &cobra.Command{
|
||||
Use: "secret [cmd] [args] [flags]",
|
||||
Aliases: []string{"s"},
|
||||
Short: "Manage app secrets",
|
||||
}
|
||||
|
||||
var (
|
||||
storeInPass bool
|
||||
insertFromFile bool
|
||||
trimInput bool
|
||||
rmAllSecrets bool
|
||||
generateAllSecrets bool
|
||||
removeFromPass bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
AppSecretGenerateCommand.Flags().BoolVarP(
|
||||
&internal.MachineReadable,
|
||||
"machine",
|
||||
"m",
|
||||
false,
|
||||
"print machine-readable output",
|
||||
)
|
||||
|
||||
AppSecretGenerateCommand.Flags().BoolVarP(
|
||||
&storeInPass,
|
||||
"pass",
|
||||
"p",
|
||||
false,
|
||||
"store generated secrets in a local pass store",
|
||||
)
|
||||
|
||||
AppSecretGenerateCommand.Flags().BoolVarP(
|
||||
&internal.Chaos,
|
||||
"chaos",
|
||||
"C",
|
||||
false,
|
||||
"ignore uncommitted recipes changes",
|
||||
)
|
||||
|
||||
AppSecretInsertCommand.Flags().BoolVarP(
|
||||
&storeInPass,
|
||||
"pass",
|
||||
"p",
|
||||
false,
|
||||
"store generated secrets in a local pass store",
|
||||
)
|
||||
|
||||
AppSecretInsertCommand.Flags().BoolVarP(
|
||||
&insertFromFile,
|
||||
"file",
|
||||
"f",
|
||||
false,
|
||||
"treat input as a file",
|
||||
)
|
||||
|
||||
AppSecretInsertCommand.Flags().BoolVarP(
|
||||
&trimInput,
|
||||
"trim",
|
||||
"t",
|
||||
false,
|
||||
"trim input",
|
||||
)
|
||||
|
||||
AppSecretInsertCommand.Flags().BoolVarP(
|
||||
&internal.Chaos,
|
||||
"chaos",
|
||||
"C",
|
||||
false,
|
||||
"ignore uncommitted recipes changes",
|
||||
)
|
||||
|
||||
AppSecretRmCommand.Flags().BoolVarP(
|
||||
&rmAllSecrets,
|
||||
"all",
|
||||
"a",
|
||||
false,
|
||||
"remove all secrets",
|
||||
)
|
||||
|
||||
AppSecretRmCommand.Flags().BoolVarP(
|
||||
&removeFromPass,
|
||||
"pass",
|
||||
"p",
|
||||
false,
|
||||
"remove generated secrets from a local pass store",
|
||||
)
|
||||
|
||||
AppSecretRmCommand.Flags().BoolVarP(
|
||||
&internal.Chaos,
|
||||
"chaos",
|
||||
"C",
|
||||
false,
|
||||
"ignore uncommitted recipes changes",
|
||||
)
|
||||
|
||||
AppSecretLsCommand.Flags().BoolVarP(
|
||||
&internal.Chaos,
|
||||
"chaos",
|
||||
"C",
|
||||
false,
|
||||
"ignore uncommitted recipes changes",
|
||||
)
|
||||
|
||||
AppSecretLsCommand.Flags().BoolVarP(
|
||||
&internal.MachineReadable,
|
||||
"machine",
|
||||
"m",
|
||||
false,
|
||||
"print machine-readable output",
|
||||
)
|
||||
}
|
||||
|
@ -13,19 +13,23 @@ import (
|
||||
"coopcloud.tech/abra/pkg/service"
|
||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||
containerTypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var appServicesCommand = cli.Command{
|
||||
Name: "services",
|
||||
Aliases: []string{"sr"},
|
||||
Usage: "Display all services of an app",
|
||||
UsageText: "abra app services <domain> [options]",
|
||||
Before: internal.SubCommandBefore,
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
var AppServicesCommand = &cobra.Command{
|
||||
Use: "services <app> [flags]",
|
||||
Aliases: []string{"sr"},
|
||||
Short: "Display all services of an app",
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return autocomplete.AppNameComplete()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app := internal.ValidateApp(args)
|
||||
|
||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@ -87,7 +91,5 @@ var appServicesCommand = cli.Command{
|
||||
if len(rows) > 0 {
|
||||
fmt.Println(table)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@ -14,75 +14,28 @@ import (
|
||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var prune bool
|
||||
|
||||
var pruneFlag = &cli.BoolFlag{
|
||||
Name: "prune",
|
||||
Aliases: []string{"p"},
|
||||
Destination: &prune,
|
||||
Usage: "Prunes unused containers, networks, and dangling images for an app",
|
||||
}
|
||||
|
||||
// pruneApp runs the equivalent of a "docker system prune" but only filtering
|
||||
// against resources connected with the app deployment. It is not a system wide
|
||||
// prune. Volumes are not pruned to avoid unwated data loss.
|
||||
func pruneApp(cl *dockerClient.Client, app appPkg.App) error {
|
||||
stackName := app.StackName()
|
||||
ctx := context.Background()
|
||||
|
||||
pruneFilters := filters.NewArgs()
|
||||
stackSearch := fmt.Sprintf("%s*", stackName)
|
||||
pruneFilters.Add("label", stackSearch)
|
||||
cr, err := cl.ContainersPrune(ctx, pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cntSpaceReclaimed := formatter.ByteCountSI(cr.SpaceReclaimed)
|
||||
log.Infof("containers pruned: %d; space reclaimed: %s", len(cr.ContainersDeleted), cntSpaceReclaimed)
|
||||
|
||||
nr, err := cl.NetworksPrune(ctx, pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("networks pruned: %d", len(nr.NetworksDeleted))
|
||||
|
||||
ir, err := cl.ImagesPrune(ctx, pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imgSpaceReclaimed := formatter.ByteCountSI(ir.SpaceReclaimed)
|
||||
log.Infof("images pruned: %d; space reclaimed: %s", len(ir.ImagesDeleted), imgSpaceReclaimed)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var appUndeployCommand = cli.Command{
|
||||
Name: "undeploy",
|
||||
Aliases: []string{"un"},
|
||||
UsageText: "abra app undeploy <domain> [options]",
|
||||
Flags: []cli.Flag{
|
||||
internal.NoInputFlag,
|
||||
internal.OfflineFlag,
|
||||
pruneFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
Usage: "Undeploy an app",
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Description: `This does not destroy any of the application data.
|
||||
var AppUndeployCommand = &cobra.Command{
|
||||
Use: "undeploy <app> [flags]",
|
||||
Aliases: []string{"un"},
|
||||
Short: "Undeploy an app",
|
||||
Long: `This does not destroy any of the application data.
|
||||
|
||||
However, you should remain vigilant, as your swarm installation will consider
|
||||
any previously attached volumes as eligible for pruning once undeployed.
|
||||
|
||||
Passing "-p/--prune" does not remove those volumes.`,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
Passing "--prune/-p" does not remove those volumes.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return autocomplete.AppNameComplete()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app := internal.ValidateApp(args)
|
||||
stackName := app.StackName()
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
@ -123,7 +76,55 @@ Passing "-p/--prune" does not remove those volumes.`,
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// pruneApp runs the equivalent of a "docker system prune" but only filtering
|
||||
// against resources connected with the app deployment. It is not a system wide
|
||||
// prune. Volumes are not pruned to avoid unwated data loss.
|
||||
func pruneApp(cl *dockerClient.Client, app appPkg.App) error {
|
||||
stackName := app.StackName()
|
||||
ctx := context.Background()
|
||||
|
||||
pruneFilters := filters.NewArgs()
|
||||
stackSearch := fmt.Sprintf("%s*", stackName)
|
||||
pruneFilters.Add("label", stackSearch)
|
||||
cr, err := cl.ContainersPrune(ctx, pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cntSpaceReclaimed := formatter.ByteCountSI(cr.SpaceReclaimed)
|
||||
log.Infof("containers pruned: %d; space reclaimed: %s", len(cr.ContainersDeleted), cntSpaceReclaimed)
|
||||
|
||||
nr, err := cl.NetworksPrune(ctx, pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("networks pruned: %d", len(nr.NetworksDeleted))
|
||||
|
||||
ir, err := cl.ImagesPrune(ctx, pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imgSpaceReclaimed := formatter.ByteCountSI(ir.SpaceReclaimed)
|
||||
log.Infof("images pruned: %d; space reclaimed: %s", len(ir.ImagesDeleted), imgSpaceReclaimed)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
prune bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
AppUndeployCommand.Flags().BoolVarP(
|
||||
&prune,
|
||||
"prune",
|
||||
"p",
|
||||
false,
|
||||
"prune unused containers, networks, and dangling images",
|
||||
)
|
||||
}
|
||||
|
@ -15,37 +15,50 @@ import (
|
||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"coopcloud.tech/tagcmp"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var appUpgradeCommand = cli.Command{
|
||||
Name: "upgrade",
|
||||
Aliases: []string{"up"},
|
||||
Usage: "Upgrade an app",
|
||||
UsageText: "abra app upgrade <domain> [<version>] [options]",
|
||||
Flags: []cli.Flag{
|
||||
internal.ForceFlag,
|
||||
internal.NoDomainChecksFlag,
|
||||
internal.DontWaitConvergeFlag,
|
||||
internal.ReleaseNotesFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
Description: `Upgrade an app.
|
||||
var AppUpgradeCommand = &cobra.Command{
|
||||
Use: "upgrade <app> [version] [flags]",
|
||||
Aliases: []string{"up"},
|
||||
Short: "Upgrade an app",
|
||||
Long: `Upgrade an app.
|
||||
|
||||
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
|
||||
beforehand.`,
|
||||
HideHelp: true,
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
switch l := len(args); l {
|
||||
case 0:
|
||||
return autocomplete.AppNameComplete()
|
||||
case 1:
|
||||
app, err := appPkg.Get(args[0])
|
||||
if err != nil {
|
||||
log.Debugf("autocomplete failed: %s", err)
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
return autocomplete.RecipeVersionComplete(app.Recipe.Name)
|
||||
default:
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var warnMessages []string
|
||||
|
||||
app := internal.ValidateApp(cmd)
|
||||
app := internal.ValidateApp(args)
|
||||
stackName := app.StackName()
|
||||
|
||||
specificVersion := cmd.Args().Get(1)
|
||||
var specificVersion string
|
||||
if len(args) == 2 {
|
||||
specificVersion = args[1]
|
||||
}
|
||||
|
||||
if specificVersion != "" {
|
||||
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
|
||||
app.Recipe.Version = specificVersion
|
||||
@ -129,7 +142,7 @@ beforehand.`,
|
||||
|
||||
if len(availableUpgrades) == 0 && !internal.Force {
|
||||
log.Info("no available upgrades")
|
||||
return nil
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,7 +163,7 @@ beforehand.`,
|
||||
}
|
||||
|
||||
if err := survey.AskOne(prompt, &chosenUpgrade); err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -177,7 +190,7 @@ beforehand.`,
|
||||
if parsedVersion.IsGreaterThan(parsedDeployedVersion) && parsedVersion.IsLessThan(parsedChosenUpgrade) {
|
||||
note, err := app.Recipe.GetReleaseNotes(version)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Fatal(err)
|
||||
}
|
||||
if note != "" {
|
||||
releaseNotes += fmt.Sprintf("%s\n", note)
|
||||
@ -236,10 +249,10 @@ beforehand.`,
|
||||
}
|
||||
}
|
||||
|
||||
if internal.ReleaseNotes {
|
||||
if showReleaseNotes {
|
||||
fmt.Println()
|
||||
fmt.Print(releaseNotes)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
chaosVersion := config.CHAOS_DEFAULT
|
||||
@ -281,7 +294,42 @@ beforehand.`,
|
||||
if err := app.WriteRecipeVersion(app.Recipe.Version, false); err != nil {
|
||||
log.Fatalf("writing new recipe version in env file: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
showReleaseNotes bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
AppUpgradeCommand.Flags().BoolVarP(
|
||||
&internal.Force,
|
||||
"force",
|
||||
"f",
|
||||
false,
|
||||
"perform action without further prompt",
|
||||
)
|
||||
|
||||
AppUpgradeCommand.Flags().BoolVarP(
|
||||
&internal.NoDomainChecks,
|
||||
"no-domain-checks",
|
||||
"D",
|
||||
false,
|
||||
"disable public DNS checks",
|
||||
)
|
||||
|
||||
AppUpgradeCommand.Flags().BoolVarP(
|
||||
&internal.DontWaitConverge, "no-converge-checks",
|
||||
"c",
|
||||
false,
|
||||
"do not wait for converge logic checks",
|
||||
)
|
||||
|
||||
AppUpgradeCommand.Flags().BoolVarP(
|
||||
&showReleaseNotes,
|
||||
"releasenotes",
|
||||
"r",
|
||||
false,
|
||||
"only show release notes",
|
||||
)
|
||||
}
|
||||
|
@ -11,19 +11,22 @@ import (
|
||||
"coopcloud.tech/abra/pkg/log"
|
||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var appVolumeListCommand = cli.Command{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
UsageText: "abra app volume list <domain> [options]",
|
||||
Before: internal.SubCommandBefore,
|
||||
Usage: "List volumes associated with an app",
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
HideHelp: true,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
var AppVolumeListCommand = &cobra.Command{
|
||||
Use: "list <app> [flags]",
|
||||
Aliases: []string{"ls"},
|
||||
Short: "List volumes associated with an app",
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return autocomplete.AppNameComplete()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app := internal.ValidateApp(args)
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
@ -59,38 +62,36 @@ var appVolumeListCommand = cli.Command{
|
||||
|
||||
if len(rows) > 0 {
|
||||
fmt.Println(table)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
log.Warnf("no volumes created for %s", app.Name)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var appVolumeRemoveCommand = cli.Command{
|
||||
Name: "remove",
|
||||
Usage: "Remove volume(s) associated with an app",
|
||||
Description: `Remove volumes associated with an app.
|
||||
var AppVolumeRemoveCommand = &cobra.Command{
|
||||
Use: "remove <app> [flags]",
|
||||
Short: "Remove volume(s) associated with an app",
|
||||
Long: `Remove volumes associated with an app.
|
||||
|
||||
The app in question must be undeployed before you try to remove volumes. See
|
||||
"abra app undeploy <domain>" for more.
|
||||
"abra app undeploy <app>" for more.
|
||||
|
||||
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
|
||||
interface.
|
||||
|
||||
Passing "--force/-f" will select all volumes for removal. Be careful.`,
|
||||
UsageText: "abra app volume remove [options] <domain>",
|
||||
Aliases: []string{"rm"},
|
||||
Flags: []cli.Flag{
|
||||
internal.NoInputFlag,
|
||||
internal.ForceFlag,
|
||||
Aliases: []string{"rm"},
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return autocomplete.AppNameComplete()
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
ShellComplete: autocomplete.AppNameComplete,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
app := internal.ValidateApp(cmd)
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
app := internal.ValidateApp(args)
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
@ -145,18 +146,21 @@ Passing "--force/-f" will select all volumes for removal. Be careful.`,
|
||||
} else {
|
||||
log.Info("no volumes removed")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var appVolumeCommand = cli.Command{
|
||||
Name: "volume",
|
||||
Aliases: []string{"vl"},
|
||||
Usage: "Manage app volumes",
|
||||
UsageText: "abra app volume [command] [options] [arguments]",
|
||||
Commands: []*cli.Command{
|
||||
&appVolumeListCommand,
|
||||
&appVolumeRemoveCommand,
|
||||
},
|
||||
var AppVolumeCommand = &cobra.Command{
|
||||
Use: "volume [cmd] [args] [flags]",
|
||||
Aliases: []string{"vl"},
|
||||
Short: "Manage app volumes",
|
||||
}
|
||||
|
||||
func init() {
|
||||
AppVolumeRemoveCommand.Flags().BoolVarP(
|
||||
&internal.Force,
|
||||
"force",
|
||||
"f",
|
||||
false,
|
||||
"perform action without further prompt",
|
||||
)
|
||||
}
|
||||
|
Reference in New Issue
Block a user