forked from toolshed/abra
Compare commits
37 Commits
urfave-mig
...
cobra-migr
Author | SHA1 | Date | |
---|---|---|---|
1bdf1fcfae
|
|||
827edcb0da
|
|||
05489a129c
|
|||
c02e11eb0a
|
|||
8b8e158664
|
|||
e5a6dea10c
|
|||
1132b09b5b
|
|||
b2436174b0
|
|||
ea10019068
|
|||
9b0b3c2e4c
|
|||
8084bff104
|
|||
d22e2c38ce
|
|||
e945087f79
|
|||
7734dd555d
|
|||
aedf5e5ff7
|
|||
95c598d030
|
|||
56068362e8
|
|||
cf14731b46
|
|||
486cfa68d8
|
|||
1718903834
|
|||
eb9894e5bb
|
|||
a2116774e8
|
|||
d2efdf8bf5
|
|||
b15c05929c
|
|||
f167a91868
|
|||
8cded8752a
|
|||
d1876e2fae
|
|||
e42a1bca29
|
|||
b5493ba059
|
|||
a41a36b8fd
|
|||
de006782b6
|
|||
f28cffe6d8
|
|||
d3ede0f0f6
|
|||
ae4653f5e3
|
|||
7f0a74d3c3 | |||
e99114e695 | |||
b1208f9db5 |
@ -71,7 +71,6 @@ steps:
|
|||||||
port: 22
|
port: 22
|
||||||
command_timeout: 60m
|
command_timeout: 60m
|
||||||
script_stop: true
|
script_stop: true
|
||||||
envs: [ DRONE_SOURCE_BRANCH ]
|
|
||||||
request_pty: true
|
request_pty: true
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
go env -w GOPRIVATE=coopcloud.tech
|
# integration test suite
|
||||||
|
|
||||||
# export PASSWORD_STORE_DIR=$(pwd)/../../autonomic/passwords/passwords/
|
|
||||||
|
|
||||||
# export ABRA_DIR="$HOME/.abra_test"
|
# export ABRA_DIR="$HOME/.abra_test"
|
||||||
# export ABRA_TEST_DOMAIN=test.example.com
|
# export ABRA_TEST_DOMAIN=test.example.com
|
||||||
# export ABRA_SKIP_TEARDOWN=1 # for faster feedback when developing tests
|
# export ABRA_CI=1
|
||||||
|
|
||||||
|
# release automation
|
||||||
|
# export GITEA_TOKEN=
|
||||||
|
2
Makefile
2
Makefile
@ -23,6 +23,8 @@ install-abra:
|
|||||||
install-kadabra:
|
install-kadabra:
|
||||||
@go install -ldflags=$(LDFLAGS) $(KADABRA)
|
@go install -ldflags=$(LDFLAGS) $(KADABRA)
|
||||||
|
|
||||||
|
install: install-abra install-kadabra
|
||||||
|
|
||||||
build-abra:
|
build-abra:
|
||||||
@go build -v -ldflags=$(DIST_LDFLAGS) $(ABRA)
|
@go build -v -ldflags=$(DIST_LDFLAGS) $(ABRA)
|
||||||
|
|
||||||
|
@ -1,35 +1,34 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var AppCommand = cli.Command{
|
var AppCommand = cli.Command{
|
||||||
Name: "app",
|
Name: "app",
|
||||||
Aliases: []string{"a"},
|
Aliases: []string{"a"},
|
||||||
Usage: "Manage apps",
|
Usage: "Manage apps",
|
||||||
UsageText: "abra app [command] [options] [arguments]",
|
ArgsUsage: "<domain>",
|
||||||
HideHelpCommand: true,
|
Subcommands: []cli.Command{
|
||||||
Commands: []*cli.Command{
|
appBackupCommand,
|
||||||
&appBackupCommand,
|
appCheckCommand,
|
||||||
&appCheckCommand,
|
appCmdCommand,
|
||||||
&appCmdCommand,
|
appConfigCommand,
|
||||||
&appConfigCommand,
|
appCpCommand,
|
||||||
&appCpCommand,
|
appDeployCommand,
|
||||||
&appDeployCommand,
|
appListCommand,
|
||||||
&appListCommand,
|
appLogsCommand,
|
||||||
&appLogsCommand,
|
appNewCommand,
|
||||||
&appNewCommand,
|
appPsCommand,
|
||||||
&appPsCommand,
|
appRemoveCommand,
|
||||||
&appRemoveCommand,
|
appRestartCommand,
|
||||||
&appRestartCommand,
|
appRestoreCommand,
|
||||||
&appRestoreCommand,
|
appRollbackCommand,
|
||||||
&appRollbackCommand,
|
appRunCommand,
|
||||||
&appRunCommand,
|
appSecretCommand,
|
||||||
&appSecretCommand,
|
appServicesCommand,
|
||||||
&appServicesCommand,
|
appUndeployCommand,
|
||||||
&appUndeployCommand,
|
appUpgradeCommand,
|
||||||
&appUpgradeCommand,
|
appVolumeCommand,
|
||||||
&appVolumeCommand,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,32 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var snapshot string
|
var snapshot string
|
||||||
var snapshotFlag = &cli.StringFlag{
|
var snapshotFlag = &cli.StringFlag{
|
||||||
Name: "snapshot",
|
Name: "snapshot, s",
|
||||||
Aliases: []string{"s"},
|
|
||||||
Usage: "Lists specific snapshot",
|
Usage: "Lists specific snapshot",
|
||||||
Destination: &snapshot,
|
Destination: &snapshot,
|
||||||
}
|
}
|
||||||
|
|
||||||
var includePath string
|
var includePath string
|
||||||
var includePathFlag = &cli.StringFlag{
|
var includePathFlag = &cli.StringFlag{
|
||||||
Name: "path",
|
Name: "path, p",
|
||||||
Aliases: []string{"p"},
|
|
||||||
Usage: "Include path",
|
Usage: "Include path",
|
||||||
Destination: &includePath,
|
Destination: &includePath,
|
||||||
}
|
}
|
||||||
|
|
||||||
var resticRepo string
|
var resticRepo string
|
||||||
var resticRepoFlag = &cli.StringFlag{
|
var resticRepoFlag = &cli.StringFlag{
|
||||||
Name: "repo",
|
Name: "repo, r",
|
||||||
Aliases: []string{"r"},
|
|
||||||
Usage: "Restic repository",
|
Usage: "Restic repository",
|
||||||
Destination: &resticRepo,
|
Destination: &resticRepo,
|
||||||
}
|
}
|
||||||
@ -39,17 +35,16 @@ var appBackupListCommand = cli.Command{
|
|||||||
Name: "list",
|
Name: "list",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
snapshotFlag,
|
snapshotFlag,
|
||||||
includePathFlag,
|
includePathFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Usage: "List all backups",
|
Usage: "List all backups",
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
HideHelpCommand: true,
|
Action: func(c *cli.Context) error {
|
||||||
UsageText: "abra app backup list [options] <domain>",
|
app := internal.ValidateApp(c)
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
|
||||||
app := internal.ValidateApp(cmd)
|
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -87,17 +82,16 @@ var appBackupDownloadCommand = cli.Command{
|
|||||||
Name: "download",
|
Name: "download",
|
||||||
Aliases: []string{"d"},
|
Aliases: []string{"d"},
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
snapshotFlag,
|
snapshotFlag,
|
||||||
includePathFlag,
|
includePathFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Usage: "Download a backup",
|
Usage: "Download a backup",
|
||||||
UsageText: "abra app backup download [options] <domain>",
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
HideHelpCommand: true,
|
Action: func(c *cli.Context) error {
|
||||||
EnableShellCompletion: true,
|
app := internal.ValidateApp(c)
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
|
||||||
app := internal.ValidateApp(cmd)
|
|
||||||
|
|
||||||
if err := app.Recipe.EnsureExists(); err != nil {
|
if err := app.Recipe.EnsureExists(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -159,16 +153,15 @@ var appBackupCreateCommand = cli.Command{
|
|||||||
Name: "create",
|
Name: "create",
|
||||||
Aliases: []string{"c"},
|
Aliases: []string{"c"},
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
resticRepoFlag,
|
resticRepoFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Usage: "Create a new backup",
|
Usage: "Create a new backup",
|
||||||
HideHelpCommand: true,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
UsageText: "abra app backup create [options] <domain>",
|
Action: func(c *cli.Context) error {
|
||||||
EnableShellCompletion: true,
|
app := internal.ValidateApp(c)
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
|
||||||
app := internal.ValidateApp(cmd)
|
|
||||||
|
|
||||||
if err := app.Recipe.EnsureExists(); err != nil {
|
if err := app.Recipe.EnsureExists(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -218,16 +211,15 @@ var appBackupSnapshotsCommand = cli.Command{
|
|||||||
Name: "snapshots",
|
Name: "snapshots",
|
||||||
Aliases: []string{"s"},
|
Aliases: []string{"s"},
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
snapshotFlag,
|
snapshotFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Usage: "List backup snapshots",
|
Usage: "List backup snapshots",
|
||||||
UsageText: "abra app backup snapshots [options] <domain>",
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
HideHelpCommand: true,
|
Action: func(c *cli.Context) error {
|
||||||
EnableShellCompletion: true,
|
app := internal.ValidateApp(c)
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
|
||||||
app := internal.ValidateApp(cmd)
|
|
||||||
|
|
||||||
if err := app.Recipe.EnsureExists(); err != nil {
|
if err := app.Recipe.EnsureExists(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -274,15 +266,14 @@ var appBackupSnapshotsCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var appBackupCommand = cli.Command{
|
var appBackupCommand = cli.Command{
|
||||||
Name: "backup",
|
Name: "backup",
|
||||||
Aliases: []string{"b"},
|
Aliases: []string{"b"},
|
||||||
Usage: "Manage app backups",
|
Usage: "Manage app backups",
|
||||||
UsageText: "abra app backup [command] [options] [arguments]",
|
ArgsUsage: "<domain>",
|
||||||
HideHelpCommand: true,
|
Subcommands: []cli.Command{
|
||||||
Commands: []*cli.Command{
|
appBackupListCommand,
|
||||||
&appBackupListCommand,
|
appBackupSnapshotsCommand,
|
||||||
&appBackupSnapshotsCommand,
|
appBackupDownloadCommand,
|
||||||
&appBackupDownloadCommand,
|
appBackupCreateCommand,
|
||||||
&appBackupCreateCommand,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,24 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appCheckCommand = cli.Command{
|
var appCheckCommand = cli.Command{
|
||||||
Name: "check",
|
Name: "check",
|
||||||
Aliases: []string{"chk"},
|
Aliases: []string{"chk"},
|
||||||
Usage: "Ensure an app is well configured",
|
Usage: "Ensure an app is well configured",
|
||||||
Description: `Compare env vars in both the app ".env" and recipe ".env.sample" file.
|
Description: `
|
||||||
|
This command compares env vars in both the app ".env" and recipe ".env.sample"
|
||||||
|
file.
|
||||||
|
|
||||||
The goal is to ensure that recipe ".env.sample" env vars are defined in your
|
The goal is to ensure that recipe ".env.sample" env vars are defined in your
|
||||||
app ".env" file. Only env var definitions in the ".env.sample" which are
|
app ".env" file. Only env var definitions in the ".env.sample" which are
|
||||||
@ -25,24 +28,36 @@ these env vars, then "check" will complain.
|
|||||||
Recipe maintainers may or may not provide defaults for env vars within their
|
Recipe maintainers may or may not provide defaults for env vars within their
|
||||||
recipes regardless of commenting or not (e.g. through the use of
|
recipes regardless of commenting or not (e.g. through the use of
|
||||||
${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
|
${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
|
||||||
UsageText: "abra app check [options] <domain>",
|
ArgsUsage: "<domain>",
|
||||||
HideHelpCommand: true,
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
internal.ChaosFlag,
|
internal.ChaosFlag,
|
||||||
internal.OfflineFlag,
|
internal.OfflineFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
Action: func(c *cli.Context) error {
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
app := internal.ValidateApp(c)
|
||||||
app := internal.ValidateApp(cmd)
|
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCol := []string{"recipe env sample", "app env"}
|
table, err := formatter.CreateTable()
|
||||||
table := formatter.CreateTable(tableCol)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
table.
|
||||||
|
Headers("RECIPE ENV SAMPLE", "APP ENV").
|
||||||
|
StyleFunc(func(row, col int) lipgloss.Style {
|
||||||
|
switch {
|
||||||
|
case col == 1:
|
||||||
|
return lipgloss.NewStyle().Padding(0, 1, 0, 1).Align(lipgloss.Center)
|
||||||
|
default:
|
||||||
|
return lipgloss.NewStyle().Padding(0, 1, 0, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
envVars, err := appPkg.CheckEnv(app)
|
envVars, err := appPkg.CheckEnv(app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -51,13 +66,15 @@ ${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
|
|||||||
|
|
||||||
for _, envVar := range envVars {
|
for _, envVar := range envVars {
|
||||||
if envVar.Present {
|
if envVar.Present {
|
||||||
table.Append([]string{envVar.Name, "✅"})
|
val := []string{envVar.Name, "✅"}
|
||||||
|
table.Row(val...)
|
||||||
} else {
|
} else {
|
||||||
table.Append([]string{envVar.Name, "❌"})
|
val := []string{envVar.Name, "❌"}
|
||||||
|
table.Row(val...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Render()
|
fmt.Println(table)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@ -15,7 +14,7 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appCmdCommand = cli.Command{
|
var appCmdCommand = cli.Command{
|
||||||
@ -27,45 +26,47 @@ var appCmdCommand = cli.Command{
|
|||||||
These commands are bash functions, defined in the abra.sh of the recipe itself.
|
These commands are bash functions, defined in the abra.sh of the recipe itself.
|
||||||
They can be run within the context of a service (e.g. app) or locally on your
|
They can be run within the context of a service (e.g. app) or locally on your
|
||||||
work station by passing "--local". Arguments can be passed into these functions
|
work station by passing "--local". Arguments can be passed into these functions
|
||||||
using the "-- <cmd-args>" syntax.
|
using the "-- <args>" syntax.
|
||||||
|
|
||||||
**WARNING**: [options] must be passed directly after the "cmd" sub-command.`,
|
**WARNING**: options must be passed directly after the sub-command "cmd".
|
||||||
UsageText: "abra app cmd [options] <domain> [<service>] <cmd> [-- <cmd-args>]",
|
|
||||||
HideHelpCommand: true,
|
EXAMPLE:
|
||||||
|
|
||||||
|
abra app cmd --local example.com app create_user -- me@example.com`,
|
||||||
|
ArgsUsage: "<domain> [<service>] <command> [-- <args>]",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
internal.LocalCmdFlag,
|
internal.LocalCmdFlag,
|
||||||
internal.RemoteUserFlag,
|
internal.RemoteUserFlag,
|
||||||
internal.TtyFlag,
|
internal.TtyFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
internal.ChaosFlag,
|
internal.ChaosFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Commands: []*cli.Command{
|
Subcommands: []cli.Command{appCmdListCommand},
|
||||||
&appCmdListCommand,
|
BashComplete: func(ctx *cli.Context) {
|
||||||
},
|
args := ctx.Args()
|
||||||
EnableShellCompletion: true,
|
switch len(args) {
|
||||||
ShellComplete: func(ctx context.Context, cmd *cli.Command) {
|
|
||||||
args := cmd.Args()
|
|
||||||
switch args.Len() {
|
|
||||||
case 0:
|
case 0:
|
||||||
autocomplete.AppNameComplete(ctx, cmd)
|
autocomplete.AppNameComplete(ctx)
|
||||||
case 1:
|
case 1:
|
||||||
autocomplete.ServiceNameComplete(args.Get(0))
|
autocomplete.ServiceNameComplete(args.Get(0))
|
||||||
case 2:
|
case 2:
|
||||||
cmdNameComplete(args.Get(0))
|
cmdNameComplete(args.Get(0))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(c *cli.Context) error {
|
||||||
app := internal.ValidateApp(cmd)
|
app := internal.ValidateApp(c)
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.LocalCmd && internal.RemoteUser != "" {
|
if internal.LocalCmd && internal.RemoteUser != "" {
|
||||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("cannot use --local & --user together"))
|
internal.ShowSubcommandHelpAndError(c, errors.New("cannot use --local & --user together"))
|
||||||
}
|
}
|
||||||
|
|
||||||
hasCmdArgs, parsedCmdArgs := parseCmdArgs(cmd.Args().Slice(), internal.LocalCmd)
|
hasCmdArgs, parsedCmdArgs := parseCmdArgs(c.Args(), internal.LocalCmd)
|
||||||
|
|
||||||
if _, err := os.Stat(app.Recipe.AbraShPath); err != nil {
|
if _, err := os.Stat(app.Recipe.AbraShPath); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
@ -75,11 +76,11 @@ using the "-- <cmd-args>" syntax.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if internal.LocalCmd {
|
if internal.LocalCmd {
|
||||||
if !(cmd.Args().Len() >= 2) {
|
if !(len(c.Args()) >= 2) {
|
||||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("missing arguments"))
|
internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments"))
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdName := cmd.Args().Get(1)
|
cmdName := c.Args().Get(1)
|
||||||
if err := internal.EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil {
|
if err := internal.EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -111,13 +112,13 @@ using the "-- <cmd-args>" syntax.
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !(cmd.Args().Len() >= 3) {
|
if !(len(c.Args()) >= 3) {
|
||||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("missing arguments"))
|
internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments"))
|
||||||
}
|
}
|
||||||
|
|
||||||
targetServiceName := cmd.Args().Get(1)
|
targetServiceName := c.Args().Get(1)
|
||||||
|
|
||||||
cmdName := cmd.Args().Get(2)
|
cmdName := c.Args().Get(2)
|
||||||
if err := internal.EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil {
|
if err := internal.EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -194,19 +195,19 @@ func cmdNameComplete(appName string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var appCmdListCommand = cli.Command{
|
var appCmdListCommand = cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "List all available commands",
|
Usage: "List all available commands",
|
||||||
UsageText: "abra app cmd ls [options] <domain>",
|
ArgsUsage: "<domain>",
|
||||||
HideHelpCommand: true,
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
internal.ChaosFlag,
|
internal.ChaosFlag,
|
||||||
},
|
},
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
Before: internal.SubCommandBefore,
|
||||||
Before: internal.SubCommandBefore,
|
Action: func(c *cli.Context) error {
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
app := internal.ValidateApp(c)
|
||||||
app := internal.ValidateApp(cmd)
|
|
||||||
|
|
||||||
if err := app.Recipe.EnsureExists(); err != nil {
|
if err := app.Recipe.EnsureExists(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -11,23 +10,24 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appConfigCommand = cli.Command{
|
var appConfigCommand = cli.Command{
|
||||||
Name: "config",
|
Name: "config",
|
||||||
Aliases: []string{"cfg"},
|
Aliases: []string{"cfg"},
|
||||||
Usage: "Edit app config",
|
Usage: "Edit app config",
|
||||||
HideHelpCommand: true,
|
ArgsUsage: "<domain>",
|
||||||
UsageText: "abra app config [options] <domain>",
|
Flags: []cli.Flag{
|
||||||
Before: internal.SubCommandBefore,
|
internal.DebugFlag,
|
||||||
EnableShellCompletion: true,
|
},
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
Before: internal.SubCommandBefore,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
appName := cmd.Args().First()
|
Action: func(c *cli.Context) error {
|
||||||
|
appName := c.Args().First()
|
||||||
|
|
||||||
if appName == "" {
|
if appName == "" {
|
||||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("no app provided"))
|
internal.ShowSubcommandHelpAndError(c, errors.New("no app provided"))
|
||||||
}
|
}
|
||||||
|
|
||||||
files, err := appPkg.LoadAppFiles("")
|
files, err := appPkg.LoadAppFiles("")
|
||||||
@ -51,11 +51,11 @@ var appConfigCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c := exec.Command(ed, appFile.Path)
|
cmd := exec.Command(ed, appFile.Path)
|
||||||
c.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
c.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
c.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
if err := c.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,17 +22,21 @@ import (
|
|||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appCpCommand = cli.Command{
|
var appCpCommand = cli.Command{
|
||||||
Name: "cp",
|
Name: "cp",
|
||||||
Aliases: []string{"c"},
|
Aliases: []string{"c"},
|
||||||
HideHelpCommand: true,
|
ArgsUsage: "<domain> <src> <dst>",
|
||||||
UsageText: "abra app cp [options] <domain> <src> <dst>",
|
Flags: []cli.Flag{
|
||||||
Before: internal.SubCommandBefore,
|
internal.DebugFlag,
|
||||||
Usage: "Copy files to/from a deployed app service",
|
internal.NoInputFlag,
|
||||||
Description: `Copy files to and from any app service file system.
|
},
|
||||||
|
Before: internal.SubCommandBefore,
|
||||||
|
Usage: "Copy files to/from a deployed app service",
|
||||||
|
Description: `
|
||||||
|
Copy files to and from any app service file system.
|
||||||
|
|
||||||
If you want to copy a myfile.txt to the root of the app service:
|
If you want to copy a myfile.txt to the root of the app service:
|
||||||
|
|
||||||
@ -40,17 +44,18 @@ If you want to copy a myfile.txt to the root of the app service:
|
|||||||
|
|
||||||
And if you want to copy that file back to your current working directory locally:
|
And if you want to copy that file back to your current working directory locally:
|
||||||
|
|
||||||
abra app cp <domain> app:/myfile.txt`,
|
abra app cp <domain> app:/myfile.txt .
|
||||||
EnableShellCompletion: true,
|
`,
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(c *cli.Context) error {
|
||||||
app := internal.ValidateApp(cmd)
|
app := internal.ValidateApp(c)
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
src := cmd.Args().Get(1)
|
src := c.Args().Get(1)
|
||||||
dst := cmd.Args().Get(2)
|
dst := c.Args().Get(2)
|
||||||
if src == "" {
|
if src == "" {
|
||||||
log.Fatal("missing <src> argument")
|
log.Fatal("missing <src> argument")
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,11 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/envfile"
|
"coopcloud.tech/abra/pkg/envfile"
|
||||||
"coopcloud.tech/abra/pkg/secret"
|
"coopcloud.tech/abra/pkg/secret"
|
||||||
|
|
||||||
@ -15,21 +17,22 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/lint"
|
"coopcloud.tech/abra/pkg/lint"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appDeployCommand = cli.Command{
|
var appDeployCommand = cli.Command{
|
||||||
Name: "deploy",
|
Name: "deploy",
|
||||||
Aliases: []string{"d"},
|
Aliases: []string{"d"},
|
||||||
Usage: "Deploy an app",
|
Usage: "Deploy an app",
|
||||||
ArgsUsage: "<domain> [<version>]",
|
ArgsUsage: "<domain> [<version>]",
|
||||||
HideHelpCommand: true,
|
|
||||||
UsageText: "abra app deploy [options] <domain> [<version>]",
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.NoInputFlag,
|
||||||
internal.ForceFlag,
|
internal.ForceFlag,
|
||||||
internal.ChaosFlag,
|
internal.ChaosFlag,
|
||||||
internal.NoDomainChecksFlag,
|
internal.NoDomainChecksFlag,
|
||||||
internal.DontWaitConvergeFlag,
|
internal.DontWaitConvergeFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Description: `Deploy an app.
|
Description: `Deploy an app.
|
||||||
@ -37,22 +40,35 @@ var appDeployCommand = cli.Command{
|
|||||||
This command supports chaos operations. Use "--chaos" to deploy your recipe
|
This command supports chaos operations. Use "--chaos" to deploy your recipe
|
||||||
checkout as-is. Recipe commit hashes are also supported values for
|
checkout as-is. Recipe commit hashes are also supported values for
|
||||||
"[<version>]". Please note, "upgrade"/"rollback" do not support chaos
|
"[<version>]". Please note, "upgrade"/"rollback" do not support chaos
|
||||||
operations.`,
|
operations.
|
||||||
EnableShellCompletion: true,
|
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
EXAMPLE:
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
|
||||||
app := internal.ValidateApp(cmd)
|
abra app deploy foo.example.com
|
||||||
|
abra app deploy foo.example.com 1.2.3+3.2.1
|
||||||
|
abra app deploy foo.example.com 1e83340e`,
|
||||||
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
var warnMessages []string
|
||||||
|
|
||||||
|
app := internal.ValidateApp(c)
|
||||||
stackName := app.StackName()
|
stackName := app.StackName()
|
||||||
|
|
||||||
specificVersion := cmd.Args().Get(1)
|
specificVersion := c.Args().Get(1)
|
||||||
if specificVersion == "" {
|
|
||||||
specificVersion = app.Recipe.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
if specificVersion != "" && internal.Chaos {
|
if specificVersion != "" && internal.Chaos {
|
||||||
log.Fatal("cannot use <version> and --chaos together")
|
log.Fatal("cannot use <version> and --chaos together")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if specificVersion != "" {
|
||||||
|
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
|
||||||
|
app.Recipe.Version = specificVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if specificVersion == "" && app.Recipe.Version != "" && !internal.Chaos {
|
||||||
|
log.Debugf("retrieved %s as version from env file", app.Recipe.Version)
|
||||||
|
specificVersion = app.Recipe.Version
|
||||||
|
}
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -110,7 +126,7 @@ operations.`,
|
|||||||
|
|
||||||
if deployMeta.IsDeployed {
|
if deployMeta.IsDeployed {
|
||||||
if internal.Force || internal.Chaos {
|
if internal.Force || internal.Chaos {
|
||||||
log.Warnf("%s is already deployed but continuing (--force/--chaos)", app.Name)
|
warnMessages = append(warnMessages, fmt.Sprintf("%s is already deployed", app.Name))
|
||||||
} else {
|
} else {
|
||||||
log.Fatalf("%s is already deployed", app.Name)
|
log.Fatalf("%s is already deployed", app.Name)
|
||||||
}
|
}
|
||||||
@ -134,13 +150,13 @@ operations.`,
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
version = formatter.SmallSHA(head.String())
|
version = formatter.SmallSHA(head.String())
|
||||||
log.Warn("no versions detected, using latest commit")
|
warnMessages = append(warnMessages, fmt.Sprintf("no versions detected, using latest commit"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chaosVersion := "false"
|
chaosVersion := config.CHAOS_DEFAULT
|
||||||
if internal.Chaos {
|
if internal.Chaos {
|
||||||
log.Warnf("chaos mode engaged")
|
warnMessages = append(warnMessages, "chaos mode engaged")
|
||||||
|
|
||||||
if isChaosCommit {
|
if isChaosCommit {
|
||||||
chaosVersion = specificVersion
|
chaosVersion = specificVersion
|
||||||
@ -196,14 +212,12 @@ operations.`,
|
|||||||
|
|
||||||
for _, envVar := range envVars {
|
for _, envVar := range envVars {
|
||||||
if !envVar.Present {
|
if !envVar.Present {
|
||||||
log.Warnf("env var %s missing from %s.env, present in recipe .env.sample", envVar.Name, app.Domain)
|
warnMessages = append(warnMessages,
|
||||||
|
fmt.Sprintf("env var %s missing from %s.env, present in recipe .env.sample", envVar.Name, app.Domain),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := internal.DeployOverview(app, version, chaosVersion, "continue with deployment?"); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !internal.NoDomainChecks {
|
if !internal.NoDomainChecks {
|
||||||
domainName, ok := app.Env["DOMAIN"]
|
domainName, ok := app.Env["DOMAIN"]
|
||||||
if ok {
|
if ok {
|
||||||
@ -211,10 +225,14 @@ operations.`,
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warn("skipping domain checks as no DOMAIN=... configured for app")
|
warnMessages = append(warnMessages, "skipping domain checks as no DOMAIN=... configured for app")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warn("skipping domain checks as requested")
|
warnMessages = append(warnMessages, "skipping domain checks as requested")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := internal.DeployOverview(app, warnMessages, version, chaosVersion); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stack.WaitTimeout, err = appPkg.GetTimeoutFromLabel(compose, stackName)
|
stack.WaitTimeout, err = appPkg.GetTimeoutFromLabel(compose, stackName)
|
||||||
@ -235,12 +253,15 @@ operations.`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if app.Recipe.Version != "" && specificVersion != "" && specificVersion != app.Recipe.Version {
|
app.Recipe.Version = version
|
||||||
err := app.WriteRecipeVersion(specificVersion)
|
if chaosVersion != config.CHAOS_DEFAULT {
|
||||||
if err != nil {
|
app.Recipe.Version = chaosVersion
|
||||||
log.Fatalf("writing new recipe version in env file: %s", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
log.Debugf("choosing %s as version to save to env file", app.Recipe.Version)
|
||||||
|
if err := app.WriteRecipeVersion(app.Recipe.Version); err != nil {
|
||||||
|
log.Fatalf("writing new recipe version in env file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
@ -13,14 +12,13 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/tagcmp"
|
"coopcloud.tech/tagcmp"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
status bool
|
status bool
|
||||||
statusFlag = &cli.BoolFlag{
|
statusFlag = &cli.BoolFlag{
|
||||||
Name: "status",
|
Name: "status, S",
|
||||||
Aliases: []string{"S"},
|
|
||||||
Usage: "Show app deployment status",
|
Usage: "Show app deployment status",
|
||||||
Destination: &status,
|
Destination: &status,
|
||||||
}
|
}
|
||||||
@ -29,8 +27,7 @@ var (
|
|||||||
var (
|
var (
|
||||||
recipeFilter string
|
recipeFilter string
|
||||||
recipeFlag = &cli.StringFlag{
|
recipeFlag = &cli.StringFlag{
|
||||||
Name: "recipe",
|
Name: "recipe, r",
|
||||||
Aliases: []string{"r"},
|
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "Show apps of a specific recipe",
|
Usage: "Show apps of a specific recipe",
|
||||||
Destination: &recipeFilter,
|
Destination: &recipeFilter,
|
||||||
@ -40,8 +37,7 @@ var (
|
|||||||
var (
|
var (
|
||||||
listAppServer string
|
listAppServer string
|
||||||
listAppServerFlag = &cli.StringFlag{
|
listAppServerFlag = &cli.StringFlag{
|
||||||
Name: "server",
|
Name: "server, s",
|
||||||
Aliases: []string{"s"},
|
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "Show apps of a specific server",
|
Usage: "Show apps of a specific server",
|
||||||
Destination: &listAppServer,
|
Destination: &listAppServer,
|
||||||
@ -71,24 +67,26 @@ type serverStatus struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var appListCommand = cli.Command{
|
var appListCommand = cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "List all managed apps",
|
Usage: "List all managed apps",
|
||||||
HideHelpCommand: true,
|
Description: `
|
||||||
UsageText: "abra app list [options]",
|
Read the local file system listing of apps and servers (e.g. ~/.abra/) to
|
||||||
Description: `Generate a report of all managed apps.
|
generate a report of all your apps.
|
||||||
|
|
||||||
By passing the "--status/-S" flag, you can query all your servers for the
|
By passing the "--status/-S" flag, you can query all your servers for the
|
||||||
actual live deployment status. Depending on how many servers you manage, this
|
actual live deployment status. Depending on how many servers you manage, this
|
||||||
can take some time.`,
|
can take some time.`,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
internal.MachineReadableFlag,
|
internal.MachineReadableFlag,
|
||||||
statusFlag,
|
statusFlag,
|
||||||
listAppServerFlag,
|
listAppServerFlag,
|
||||||
recipeFlag,
|
recipeFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(c *cli.Context) error {
|
||||||
appFiles, err := appPkg.LoadAppFiles(listAppServer)
|
appFiles, err := appPkg.LoadAppFiles(listAppServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -241,15 +239,27 @@ can take some time.`,
|
|||||||
|
|
||||||
serverStat := allStats[app.Server]
|
serverStat := allStats[app.Server]
|
||||||
|
|
||||||
tableCol := []string{"recipe", "domain"}
|
headers := []string{"RECIPE", "DOMAIN"}
|
||||||
if status {
|
if status {
|
||||||
tableCol = append(tableCol, []string{"status", "chaos", "version", "upgrade", "autoupdate"}...)
|
headers = append(headers, []string{
|
||||||
|
"STATUS",
|
||||||
|
"CHAOS",
|
||||||
|
"VERSION",
|
||||||
|
"UPGRADE",
|
||||||
|
"AUTOUPDATE"}...,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
table := formatter.CreateTable(tableCol)
|
table, err := formatter.CreateTable()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
table.Headers(headers...)
|
||||||
|
|
||||||
|
var rows [][]string
|
||||||
for _, appStat := range serverStat.Apps {
|
for _, appStat := range serverStat.Apps {
|
||||||
tableRow := []string{appStat.Recipe, appStat.Domain}
|
row := []string{appStat.Recipe, appStat.Domain}
|
||||||
if status {
|
if status {
|
||||||
chaosStatus := appStat.Chaos
|
chaosStatus := appStat.Chaos
|
||||||
if chaosStatus != "unknown" {
|
if chaosStatus != "unknown" {
|
||||||
@ -261,17 +271,27 @@ can take some time.`,
|
|||||||
chaosStatus = appStat.ChaosVersion
|
chaosStatus = appStat.ChaosVersion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tableRow = append(tableRow, []string{appStat.Status, chaosStatus, appStat.Version, appStat.Upgrade, appStat.AutoUpdate}...)
|
|
||||||
|
row = append(row, []string{
|
||||||
|
appStat.Status,
|
||||||
|
chaosStatus,
|
||||||
|
appStat.Version,
|
||||||
|
appStat.Upgrade,
|
||||||
|
appStat.AutoUpdate}...,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
table.Append(tableRow)
|
|
||||||
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
if table.NumLines() > 0 {
|
table.Rows(rows...)
|
||||||
table.Render()
|
|
||||||
|
if len(rows) > 0 {
|
||||||
|
fmt.Println(table)
|
||||||
|
|
||||||
if status {
|
if status {
|
||||||
fmt.Println(fmt.Sprintf(
|
fmt.Println(fmt.Sprintf(
|
||||||
"server: %s | total apps: %v | versioned: %v | unversioned: %v | latest: %v | upgrade: %v",
|
"SERVER: %s | TOTAL APPS: %v | VERSIONED: %v | UNVERSIONED: %v | LATEST : %v | UPGRADE: %v",
|
||||||
app.Server,
|
app.Server,
|
||||||
serverStat.AppCount,
|
serverStat.AppCount,
|
||||||
serverStat.VersionCount,
|
serverStat.VersionCount,
|
||||||
@ -280,19 +300,21 @@ can take some time.`,
|
|||||||
serverStat.UpgradeCount,
|
serverStat.UpgradeCount,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
fmt.Println(fmt.Sprintf("server: %s | total apps: %v", app.Server, serverStat.AppCount))
|
log.Infof("SERVER: %s TOTAL APPS: %v", app.Server, serverStat.AppCount)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if len(allStats) > 1 && table.NumLines() > 0 {
|
if len(allStats) > 1 && len(rows) > 0 {
|
||||||
fmt.Println() // newline separator for multiple servers
|
fmt.Println() // newline separator for multiple servers
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
alreadySeen[app.Server] = true
|
alreadySeen[app.Server] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(allStats) > 1 {
|
if len(allStats) > 1 {
|
||||||
fmt.Println(fmt.Sprintf("total servers: %v | total apps: %v ", totalServersCount, totalAppsCount))
|
totalServers := formatter.BoldStyle.Render("TOTAL SERVERS")
|
||||||
|
totalApps := formatter.BoldStyle.Render("TOTAL APPS")
|
||||||
|
log.Infof("%s: %v | %s: %v ", totalServers, totalServersCount, totalApps, totalAppsCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -19,24 +19,23 @@ import (
|
|||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appLogsCommand = cli.Command{
|
var appLogsCommand = cli.Command{
|
||||||
Name: "logs",
|
Name: "logs",
|
||||||
Aliases: []string{"l"},
|
Aliases: []string{"l"},
|
||||||
Usage: "Tail app logs",
|
ArgsUsage: "<domain> [<service>]",
|
||||||
HideHelpCommand: true,
|
Usage: "Tail app logs",
|
||||||
UsageText: "abra app logs [options] <domain> [<service>]",
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
internal.StdErrOnlyFlag,
|
internal.StdErrOnlyFlag,
|
||||||
internal.SinceLogsFlag,
|
internal.SinceLogsFlag,
|
||||||
|
internal.DebugFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
Action: func(c *cli.Context) error {
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
app := internal.ValidateApp(c)
|
||||||
app := internal.ValidateApp(cmd)
|
|
||||||
stackName := app.StackName()
|
stackName := app.StackName()
|
||||||
|
|
||||||
if err := app.Recipe.EnsureExists(); err != nil {
|
if err := app.Recipe.EnsureExists(); err != nil {
|
||||||
@ -57,7 +56,7 @@ var appLogsCommand = cli.Command{
|
|||||||
log.Fatalf("%s is not deployed?", app.Name)
|
log.Fatalf("%s is not deployed?", app.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceName := cmd.Args().Get(1)
|
serviceName := c.Args().Get(1)
|
||||||
serviceNames := []string{}
|
serviceNames := []string{}
|
||||||
if serviceName != "" {
|
if serviceName != "" {
|
||||||
serviceNames = []string{serviceName}
|
serviceNames = []string{serviceName}
|
||||||
|
@ -1,28 +1,27 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
|
"coopcloud.tech/abra/pkg/app"
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/jsontable"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||||
"coopcloud.tech/abra/pkg/secret"
|
"coopcloud.tech/abra/pkg/secret"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
"github.com/charmbracelet/lipgloss/table"
|
||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appNewDescription = `Creates a new app from a default recipe.
|
var appNewDescription = `
|
||||||
|
Creates a new app from a default recipe. This new app configuration is stored
|
||||||
This new app configuration is stored in your $ABRA_DIR directory under the
|
in your $ABRA_DIR directory under the appropriate server.
|
||||||
appropriate server.
|
|
||||||
|
|
||||||
This command does not deploy your app for you. You will need to run "abra app
|
This command does not deploy your app for you. You will need to run "abra app
|
||||||
deploy <domain>" to do so.
|
deploy <domain>" to do so.
|
||||||
@ -30,6 +29,8 @@ deploy <domain>" 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".
|
by running "abra recipe ls".
|
||||||
|
|
||||||
|
Recipe commit hashes are supported values for "[<version>]".
|
||||||
|
|
||||||
Passing the "--secrets/-S" flag will automatically generate secrets for your
|
Passing the "--secrets/-S" flag will automatically generate secrets for your
|
||||||
app and store them encrypted at rest on the chosen target server. These
|
app and store them encrypted at rest on the chosen target server. These
|
||||||
generated secrets are only visible at generation time, so please take care to
|
generated secrets are only visible at generation time, so please take care to
|
||||||
@ -45,28 +46,30 @@ var appNewCommand = cli.Command{
|
|||||||
Usage: "Create a new app",
|
Usage: "Create a new app",
|
||||||
Description: appNewDescription,
|
Description: appNewDescription,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.NoInputFlag,
|
||||||
internal.NewAppServerFlag,
|
internal.NewAppServerFlag,
|
||||||
internal.DomainFlag,
|
internal.DomainFlag,
|
||||||
internal.PassFlag,
|
internal.PassFlag,
|
||||||
internal.SecretsFlag,
|
internal.SecretsFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
internal.ChaosFlag,
|
internal.ChaosFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
HideHelpCommand: true,
|
ArgsUsage: "[<recipe>] [<version>]",
|
||||||
UsageText: "abra app new [options] [<recipe>] [<version>]",
|
BashComplete: func(ctx *cli.Context) {
|
||||||
EnableShellCompletion: true,
|
args := ctx.Args()
|
||||||
ShellComplete: func(ctx context.Context, cmd *cli.Command) {
|
switch len(args) {
|
||||||
args := cmd.Args()
|
|
||||||
switch args.Len() {
|
|
||||||
case 0:
|
case 0:
|
||||||
autocomplete.RecipeNameComplete(ctx, cmd)
|
autocomplete.RecipeNameComplete(ctx)
|
||||||
case 1:
|
case 1:
|
||||||
autocomplete.RecipeVersionComplete(cmd.Args().Get(0))
|
autocomplete.RecipeVersionComplete(ctx.Args().Get(0))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(c *cli.Context) error {
|
||||||
recipe := internal.ValidateRecipe(cmd)
|
recipe := internal.ValidateRecipe(c)
|
||||||
|
|
||||||
|
var version string
|
||||||
if !internal.Chaos {
|
if !internal.Chaos {
|
||||||
if err := recipe.EnsureIsClean(); err != nil {
|
if err := recipe.EnsureIsClean(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -76,16 +79,13 @@ var appNewCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cmd.Args().Get(1) == "" {
|
|
||||||
var version string
|
|
||||||
|
|
||||||
|
if c.Args().Get(1) == "" {
|
||||||
recipeVersions, err := recipe.GetRecipeVersions()
|
recipeVersions, err := recipe.GetRecipeVersions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE(d1): determine whether recipe versions exist or not and check
|
|
||||||
// out the latest version or current HEAD
|
|
||||||
if len(recipeVersions) > 0 {
|
if len(recipeVersions) > 0 {
|
||||||
latest := recipeVersions[len(recipeVersions)-1]
|
latest := recipeVersions[len(recipeVersions)-1]
|
||||||
for tag := range latest {
|
for tag := range latest {
|
||||||
@ -101,7 +101,8 @@ var appNewCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if _, err := recipe.EnsureVersion(cmd.Args().Get(1)); err != nil {
|
version = c.Args().Get(1)
|
||||||
|
if _, err := recipe.EnsureVersion(version); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,7 +129,7 @@ var appNewCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var secrets AppSecrets
|
var secrets AppSecrets
|
||||||
var secretTable *jsontable.JSONTable
|
var secretsTable *table.Table
|
||||||
if internal.Secrets {
|
if internal.Secrets {
|
||||||
sampleEnv, err := recipe.SampleEnv()
|
sampleEnv, err := recipe.SampleEnv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -159,10 +160,16 @@ var appNewCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secretCols := []string{"Name", "Value"}
|
secretsTable, err = formatter.CreateTable()
|
||||||
secretTable = formatter.CreateTable(secretCols)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{"NAME", "VALUE"}
|
||||||
|
secretsTable.Headers(headers...)
|
||||||
|
|
||||||
for name, val := range secrets {
|
for name, val := range secrets {
|
||||||
secretTable.Append([]string{name, val})
|
secretsTable.Row(name, val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,14 +177,20 @@ var appNewCommand = cli.Command{
|
|||||||
internal.NewAppServer = "local"
|
internal.NewAppServer = "local"
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCol := []string{"server", "recipe", "domain"}
|
table, err := formatter.CreateTable()
|
||||||
table := formatter.CreateTable(tableCol)
|
if err != nil {
|
||||||
table.Append([]string{internal.NewAppServer, recipe.Name, internal.Domain})
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{"SERVER", "DOMAIN", "RECIPE", "VERSION"}
|
||||||
|
table.Headers(headers...)
|
||||||
|
|
||||||
|
table.Row(internal.NewAppServer, internal.Domain, recipe.Name, version)
|
||||||
|
|
||||||
log.Infof("new app '%s' created 🌞", recipe.Name)
|
log.Infof("new app '%s' created 🌞", recipe.Name)
|
||||||
|
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
table.Render()
|
fmt.Println(table)
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
|
|
||||||
fmt.Println("Configure this app:")
|
fmt.Println("Configure this app:")
|
||||||
@ -191,8 +204,23 @@ var appNewCommand = cli.Command{
|
|||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
fmt.Println("Generated secrets:")
|
fmt.Println("Generated secrets:")
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
secretTable.Render()
|
fmt.Println(secretsTable)
|
||||||
log.Warn("generated secrets are not shown again, please take note of them NOW")
|
|
||||||
|
log.Warnf(
|
||||||
|
"generated secrets %s shown again, please take note of them %s",
|
||||||
|
formatter.BoldStyle.Render("NOT"),
|
||||||
|
formatter.BoldStyle.Render("NOW"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
app, err := app.Get(internal.Domain)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("choosing %s as version to save to env file", version)
|
||||||
|
if err := app.WriteRecipeVersion(version); err != nil {
|
||||||
|
log.Fatalf("writing new recipe version in env file: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
abraService "coopcloud.tech/abra/pkg/service"
|
abraService "coopcloud.tech/abra/pkg/service"
|
||||||
@ -17,24 +18,23 @@ import (
|
|||||||
containerTypes "github.com/docker/docker/api/types/container"
|
containerTypes "github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appPsCommand = cli.Command{
|
var appPsCommand = cli.Command{
|
||||||
Name: "ps",
|
Name: "ps",
|
||||||
Aliases: []string{"p"},
|
Aliases: []string{"p"},
|
||||||
Usage: "Check app status",
|
Usage: "Check app status",
|
||||||
HideHelpCommand: true,
|
ArgsUsage: "<domain>",
|
||||||
UsageText: "abra app ps [options] <domain>",
|
Description: "Show status of a deployed app.",
|
||||||
Description: "Show status of a deployed app.",
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
internal.MachineReadableFlag,
|
internal.MachineReadableFlag,
|
||||||
|
internal.DebugFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
Action: func(c *cli.Context) error {
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
app := internal.ValidateApp(c)
|
||||||
app := internal.ValidateApp(cmd)
|
|
||||||
if err := app.Recipe.Ensure(false, false); err != nil {
|
if err := app.Recipe.Ensure(false, false); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -53,7 +53,7 @@ var appPsCommand = cli.Command{
|
|||||||
log.Fatalf("%s is not deployed?", app.Name)
|
log.Fatalf("%s is not deployed?", app.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
chaosVersion := "false"
|
chaosVersion := config.CHAOS_DEFAULT
|
||||||
statuses, err := appPkg.GetAppStatuses([]appPkg.App{app}, true)
|
statuses, err := appPkg.GetAppStatuses([]appPkg.App{app}, true)
|
||||||
if statusMeta, ok := statuses[app.StackName()]; ok {
|
if statusMeta, ok := statuses[app.StackName()]; ok {
|
||||||
isChaos, exists := statusMeta["chaos"]
|
isChaos, exists := statusMeta["chaos"]
|
||||||
@ -95,7 +95,7 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var tablerows [][]string
|
var rows [][]string
|
||||||
allContainerStats := make(map[string]map[string]string)
|
allContainerStats := make(map[string]map[string]string)
|
||||||
for _, service := range compose.Services {
|
for _, service := range compose.Services {
|
||||||
filters := filters.NewArgs()
|
filters := filters.NewArgs()
|
||||||
@ -135,9 +135,7 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
|||||||
|
|
||||||
allContainerStats[containerStats["service"]] = containerStats
|
allContainerStats[containerStats["service"]] = containerStats
|
||||||
|
|
||||||
tablerow := []string{
|
row := []string{
|
||||||
deployedVersion,
|
|
||||||
chaosVersion,
|
|
||||||
containerStats["service"],
|
containerStats["service"],
|
||||||
containerStats["image"],
|
containerStats["image"],
|
||||||
containerStats["created"],
|
containerStats["created"],
|
||||||
@ -146,25 +144,37 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
|||||||
containerStats["ports"],
|
containerStats["ports"],
|
||||||
}
|
}
|
||||||
|
|
||||||
tablerows = append(tablerows, tablerow)
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
jsonstring, err := json.Marshal(allContainerStats)
|
jsonstring, err := json.Marshal(allContainerStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal("unable to convert to JSON: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(string(jsonstring))
|
fmt.Println(string(jsonstring))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCol := []string{"version", "chaos", "service", "image", "created", "status", "state", "ports"}
|
table, err := formatter.CreateTable()
|
||||||
table := formatter.CreateTable(tableCol)
|
if err != nil {
|
||||||
for _, row := range tablerows {
|
log.Fatal(err)
|
||||||
table.Append(row)
|
|
||||||
}
|
}
|
||||||
table.SetAutoMergeCellsByColumnIndex([]int{0, 1})
|
|
||||||
table.Render()
|
headers := []string{
|
||||||
|
"SERVICE",
|
||||||
|
"IMAGE",
|
||||||
|
"CREATED",
|
||||||
|
"STATUS",
|
||||||
|
"STATE",
|
||||||
|
"PORTS",
|
||||||
|
}
|
||||||
|
|
||||||
|
table.
|
||||||
|
Headers(headers...).
|
||||||
|
Rows(rows...)
|
||||||
|
|
||||||
|
fmt.Println(table)
|
||||||
|
|
||||||
|
log.Infof("VERSION: %s CHAOS: %s", deployedVersion, chaosVersion)
|
||||||
}
|
}
|
||||||
|
@ -12,16 +12,16 @@ import (
|
|||||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appRemoveCommand = cli.Command{
|
var appRemoveCommand = cli.Command{
|
||||||
Name: "remove",
|
Name: "remove",
|
||||||
Aliases: []string{"rm"},
|
Aliases: []string{"rm"},
|
||||||
HideHelpCommand: true,
|
ArgsUsage: "<domain>",
|
||||||
UsageText: "abra app remove [options] <domain>",
|
Usage: "Remove all app data, locally and remotely",
|
||||||
Usage: "Remove all app data, locally and remotely",
|
Description: `
|
||||||
Description: `Remove everything related to an app which is already undeployed.
|
This command removes everything related to an app which is already undeployed.
|
||||||
|
|
||||||
By default, it will prompt for confirmation before proceeding. All secrets,
|
By default, it will prompt for confirmation before proceeding. All secrets,
|
||||||
volumes and the local app env file will be deleted.
|
volumes and the local app env file will be deleted.
|
||||||
@ -39,20 +39,24 @@ To delete everything without prompt, use the "--force/-f" or the "--no-input/n"
|
|||||||
flag.`,
|
flag.`,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
internal.ForceFlag,
|
internal.ForceFlag,
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.NoInputFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
},
|
},
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
Before: internal.SubCommandBefore,
|
||||||
Before: internal.SubCommandBefore,
|
Action: func(c *cli.Context) error {
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
app := internal.ValidateApp(c)
|
||||||
app := internal.ValidateApp(cmd)
|
|
||||||
|
|
||||||
if !internal.Force && !internal.NoInput {
|
if !internal.Force && !internal.NoInput {
|
||||||
|
log.Warnf("ALERTA ALERTA: this will completely remove %s data and config locally and remotely", app.Name)
|
||||||
|
|
||||||
response := false
|
response := false
|
||||||
msg := "ALERTA ALERTA: this will completely remove %s data and configurations locally and remotely, are you sure?"
|
prompt := &survey.Confirm{Message: "are you sure?"}
|
||||||
prompt := &survey.Confirm{Message: fmt.Sprintf(msg, app.Name)}
|
|
||||||
if err := survey.AskOne(prompt, &response); err != nil {
|
if err := survey.AskOne(prompt, &response); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !response {
|
if !response {
|
||||||
log.Fatal("aborting as requested")
|
log.Fatal("aborting as requested")
|
||||||
}
|
}
|
||||||
|
@ -12,36 +12,41 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
upstream "coopcloud.tech/abra/pkg/upstream/service"
|
upstream "coopcloud.tech/abra/pkg/upstream/service"
|
||||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appRestartCommand = cli.Command{
|
var appRestartCommand = cli.Command{
|
||||||
Name: "restart",
|
Name: "restart",
|
||||||
Aliases: []string{"re"},
|
Aliases: []string{"re"},
|
||||||
Usage: "Restart an app",
|
Usage: "Restart an app",
|
||||||
HideHelpCommand: true,
|
ArgsUsage: "<domain> [<service>]",
|
||||||
UsageText: "abra app restart [options] <domain> [<service>]",
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
internal.AllServicesFlag,
|
internal.AllServicesFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Description: `This command restarts services within a deployed app.
|
Description: `
|
||||||
|
This command restarts services within a deployed app.
|
||||||
|
|
||||||
Run "abra app ps <domain>" to see a list of service names.
|
Run "abra app ps <domain>" to see a list of service names.
|
||||||
|
|
||||||
Pass "--all-services/-a" to restart all services.`,
|
Pass "--all-services/-a" to restart all services.
|
||||||
EnableShellCompletion: true,
|
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
EXAMPLE:
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
|
||||||
app := internal.ValidateApp(cmd)
|
abra app restart example.com app`,
|
||||||
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
app := internal.ValidateApp(c)
|
||||||
if err := app.Recipe.Ensure(false, false); err != nil {
|
if err := app.Recipe.Ensure(false, false); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceName := cmd.Args().Get(1)
|
serviceName := c.Args().Get(1)
|
||||||
if serviceName == "" && !internal.AllServices {
|
if serviceName == "" && !internal.AllServices {
|
||||||
err := errors.New("missing <service>")
|
err := errors.New("missing <service>")
|
||||||
internal.ShowSubcommandHelpAndError(cmd, err)
|
internal.ShowSubcommandHelpAndError(c, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if serviceName != "" && internal.AllServices {
|
if serviceName != "" && internal.AllServices {
|
||||||
|
@ -1,38 +1,36 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var targetPath string
|
var targetPath string
|
||||||
var targetPathFlag = &cli.StringFlag{
|
var targetPathFlag = &cli.StringFlag{
|
||||||
Name: "target",
|
Name: "target, t",
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "Target path",
|
Usage: "Target path",
|
||||||
Destination: &targetPath,
|
Destination: &targetPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
var appRestoreCommand = cli.Command{
|
var appRestoreCommand = cli.Command{
|
||||||
Name: "restore",
|
Name: "restore",
|
||||||
Aliases: []string{"rs"},
|
Aliases: []string{"rs"},
|
||||||
Usage: "Restore an app backup",
|
Usage: "Restore an app backup",
|
||||||
HideHelpCommand: true,
|
ArgsUsage: "<domain> <service>",
|
||||||
UsageText: "abra app restore [options] <domain> <service>",
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
targetPathFlag,
|
targetPathFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
Action: func(c *cli.Context) error {
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
app := internal.ValidateApp(c)
|
||||||
app := internal.ValidateApp(cmd)
|
|
||||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/envfile"
|
"coopcloud.tech/abra/pkg/envfile"
|
||||||
"coopcloud.tech/abra/pkg/lint"
|
"coopcloud.tech/abra/pkg/lint"
|
||||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
@ -15,34 +16,49 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appRollbackCommand = cli.Command{
|
var appRollbackCommand = cli.Command{
|
||||||
Name: "rollback",
|
Name: "rollback",
|
||||||
Aliases: []string{"rl"},
|
Aliases: []string{"rl"},
|
||||||
Usage: "Roll an app back to a previous version",
|
Usage: "Roll an app back to a previous version",
|
||||||
HideHelpCommand: true,
|
ArgsUsage: "<domain> [<version>]",
|
||||||
UsageText: "abra app rollback [options] <domain> [<version>]",
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.NoInputFlag,
|
||||||
internal.ForceFlag,
|
internal.ForceFlag,
|
||||||
internal.NoDomainChecksFlag,
|
internal.NoDomainChecksFlag,
|
||||||
internal.DontWaitConvergeFlag,
|
internal.DontWaitConvergeFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Description: `This command rolls an app back to a previous version.
|
Description: `
|
||||||
|
This command rolls an app back to a previous version.
|
||||||
|
|
||||||
Unlike "deploy", chaos operations are not supported here. Only recipe versions
|
Unlike "deploy", chaos operations are not supported here. Only recipe versions
|
||||||
are supported values for "[<version>]".
|
are supported values for "[<version>]".
|
||||||
|
|
||||||
A rollback can be destructive, please ensure you have a copy of your app data
|
A rollback can be destructive, please ensure you have a copy of your app data
|
||||||
beforehand.`,
|
beforehand.
|
||||||
EnableShellCompletion: true,
|
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
EXAMPLE:
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
|
||||||
app := internal.ValidateApp(cmd)
|
abra app rollback foo.example.com
|
||||||
|
abra app rollback foo.example.com 1.2.3+3.2.1`,
|
||||||
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
var warnMessages []string
|
||||||
|
|
||||||
|
app := internal.ValidateApp(c)
|
||||||
stackName := app.StackName()
|
stackName := app.StackName()
|
||||||
|
|
||||||
|
specificVersion := c.Args().Get(1)
|
||||||
|
if specificVersion != "" {
|
||||||
|
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
|
||||||
|
app.Recipe.Version = specificVersion
|
||||||
|
}
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -75,12 +91,7 @@ beforehand.`,
|
|||||||
var availableDowngrades []string
|
var availableDowngrades []string
|
||||||
if deployMeta.Version == "unknown" {
|
if deployMeta.Version == "unknown" {
|
||||||
availableDowngrades = versions
|
availableDowngrades = versions
|
||||||
log.Warnf("failed to determine deployed version of %s", app.Name)
|
warnMessages = append(warnMessages, fmt.Sprintf("failed to determine deployed version of %s", app.Name))
|
||||||
}
|
|
||||||
|
|
||||||
specificVersion := cmd.Args().Get(1)
|
|
||||||
if specificVersion == "" {
|
|
||||||
specificVersion = app.Recipe.Version
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if specificVersion != "" {
|
if specificVersion != "" {
|
||||||
@ -107,7 +118,7 @@ beforehand.`,
|
|||||||
|
|
||||||
if deployMeta.Version != "unknown" && specificVersion == "" {
|
if deployMeta.Version != "unknown" && specificVersion == "" {
|
||||||
if deployMeta.IsChaos {
|
if deployMeta.IsChaos {
|
||||||
log.Warn("attempting to rollback a chaos deployment")
|
warnMessages = append(warnMessages, fmt.Sprintf("attempting to rollback a chaos deployment"))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, version := range versions {
|
for _, version := range versions {
|
||||||
@ -191,13 +202,20 @@ beforehand.`,
|
|||||||
appPkg.SetChaosVersionLabel(compose, stackName, chosenDowngrade)
|
appPkg.SetChaosVersionLabel(compose, stackName, chosenDowngrade)
|
||||||
appPkg.SetUpdateLabel(compose, stackName, app.Env)
|
appPkg.SetUpdateLabel(compose, stackName, app.Env)
|
||||||
|
|
||||||
chaosVersion := "false"
|
chaosVersion := config.CHAOS_DEFAULT
|
||||||
if deployMeta.IsChaos {
|
if deployMeta.IsChaos {
|
||||||
chaosVersion = deployMeta.ChaosVersion
|
chaosVersion = deployMeta.ChaosVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE(d1): no release notes implemeneted for rolling back
|
// NOTE(d1): no release notes implemeneted for rolling back
|
||||||
if err := internal.NewVersionOverview(app, deployMeta.Version, chaosVersion, chosenDowngrade, ""); err != nil {
|
if err := internal.NewVersionOverview(
|
||||||
|
app,
|
||||||
|
warnMessages,
|
||||||
|
"rollback",
|
||||||
|
deployMeta.Version,
|
||||||
|
chaosVersion,
|
||||||
|
chosenDowngrade,
|
||||||
|
""); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,11 +223,10 @@ beforehand.`,
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if app.Recipe.Version != "" {
|
app.Recipe.Version = chosenDowngrade
|
||||||
err := app.WriteRecipeVersion(chosenDowngrade)
|
log.Debugf("choosing %s as version to save to env file", app.Recipe.Version)
|
||||||
if err != nil {
|
if err := app.WriteRecipeVersion(app.Recipe.Version); err != nil {
|
||||||
log.Fatalf("writing new recipe version in env file: %s", err)
|
log.Fatalf("writing new recipe version in env file: %s", err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -14,21 +14,19 @@ import (
|
|||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var user string
|
var user string
|
||||||
var userFlag = &cli.StringFlag{
|
var userFlag = &cli.StringFlag{
|
||||||
Name: "user",
|
Name: "user, u",
|
||||||
Aliases: []string{"u"},
|
|
||||||
Value: "",
|
Value: "",
|
||||||
Destination: &user,
|
Destination: &user,
|
||||||
}
|
}
|
||||||
|
|
||||||
var noTTY bool
|
var noTTY bool
|
||||||
var noTTYFlag = &cli.BoolFlag{
|
var noTTYFlag = &cli.BoolFlag{
|
||||||
Name: "no-tty",
|
Name: "no-tty, t",
|
||||||
Aliases: []string{"t"},
|
|
||||||
Destination: &noTTY,
|
Destination: &noTTY,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,24 +34,23 @@ var appRunCommand = cli.Command{
|
|||||||
Name: "run",
|
Name: "run",
|
||||||
Aliases: []string{"r"},
|
Aliases: []string{"r"},
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
noTTYFlag,
|
noTTYFlag,
|
||||||
userFlag,
|
userFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
UsageText: "abra app run [options] <domain> <service> <args>",
|
ArgsUsage: "<domain> <service> <args>...",
|
||||||
Usage: "Run a command in an app service",
|
Usage: "Run a command in a service container",
|
||||||
HideHelpCommand: true,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
EnableShellCompletion: true,
|
Action: func(c *cli.Context) error {
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
app := internal.ValidateApp(c)
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
|
||||||
app := internal.ValidateApp(cmd)
|
|
||||||
|
|
||||||
if cmd.Args().Len() < 2 {
|
if len(c.Args()) < 2 {
|
||||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("no <service> provided?"))
|
internal.ShowSubcommandHelpAndError(c, errors.New("no <service> provided?"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.Args().Len() < 3 {
|
if len(c.Args()) < 3 {
|
||||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("no <args> provided?"))
|
internal.ShowSubcommandHelpAndError(c, errors.New("no <args> provided?"))
|
||||||
}
|
}
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
cl, err := client.New(app.Server)
|
||||||
@ -61,7 +58,7 @@ var appRunCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceName := cmd.Args().Get(1)
|
serviceName := c.Args().Get(1)
|
||||||
stackAndServiceName := fmt.Sprintf("^%s_%s", app.StackName(), serviceName)
|
stackAndServiceName := fmt.Sprintf("^%s_%s", app.StackName(), serviceName)
|
||||||
filters := filters.NewArgs()
|
filters := filters.NewArgs()
|
||||||
filters.Add("name", stackAndServiceName)
|
filters.Add("name", stackAndServiceName)
|
||||||
@ -71,12 +68,12 @@ var appRunCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c := cmd.Args().Slice()[2:]
|
cmd := c.Args()[2:]
|
||||||
execCreateOpts := types.ExecConfig{
|
execCreateOpts := types.ExecConfig{
|
||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
AttachStdin: true,
|
AttachStdin: true,
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
Cmd: c,
|
Cmd: cmd,
|
||||||
Detach: false,
|
Detach: false,
|
||||||
Tty: true,
|
Tty: true,
|
||||||
}
|
}
|
||||||
|
@ -17,14 +17,13 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/secret"
|
"coopcloud.tech/abra/pkg/secret"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
allSecrets bool
|
allSecrets bool
|
||||||
allSecretsFlag = &cli.BoolFlag{
|
allSecretsFlag = &cli.BoolFlag{
|
||||||
Name: "all",
|
Name: "all, a",
|
||||||
Aliases: []string{"a"},
|
|
||||||
Destination: &allSecrets,
|
Destination: &allSecrets,
|
||||||
Usage: "Generate all secrets",
|
Usage: "Generate all secrets",
|
||||||
}
|
}
|
||||||
@ -33,42 +32,41 @@ var (
|
|||||||
var (
|
var (
|
||||||
rmAllSecrets bool
|
rmAllSecrets bool
|
||||||
rmAllSecretsFlag = &cli.BoolFlag{
|
rmAllSecretsFlag = &cli.BoolFlag{
|
||||||
Name: "all",
|
Name: "all, a",
|
||||||
Aliases: []string{"a"},
|
|
||||||
Destination: &rmAllSecrets,
|
Destination: &rmAllSecrets,
|
||||||
Usage: "Remove all secrets",
|
Usage: "Remove all secrets",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var appSecretGenerateCommand = cli.Command{
|
var appSecretGenerateCommand = cli.Command{
|
||||||
Name: "generate",
|
Name: "generate",
|
||||||
Aliases: []string{"g"},
|
Aliases: []string{"g"},
|
||||||
Usage: "Generate secrets",
|
Usage: "Generate secrets",
|
||||||
UsageText: "abra app secret generate [options] <domain> <secret> <version>",
|
ArgsUsage: "<domain> <secret> <version>",
|
||||||
HideHelpCommand: true,
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
allSecretsFlag,
|
allSecretsFlag,
|
||||||
internal.PassFlag,
|
internal.PassFlag,
|
||||||
internal.MachineReadableFlag,
|
internal.MachineReadableFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
internal.ChaosFlag,
|
internal.ChaosFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
Action: func(c *cli.Context) error {
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
app := internal.ValidateApp(c)
|
||||||
app := internal.ValidateApp(cmd)
|
|
||||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.Args().Len() == 1 && !allSecrets {
|
if len(c.Args()) == 1 && !allSecrets {
|
||||||
err := errors.New("missing arguments <secret>/<version> or '--all'")
|
err := errors.New("missing arguments <secret>/<version> or '--all'")
|
||||||
internal.ShowSubcommandHelpAndError(cmd, err)
|
internal.ShowSubcommandHelpAndError(c, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.Args().Get(1) != "" && allSecrets {
|
if c.Args().Get(1) != "" && allSecrets {
|
||||||
err := errors.New("cannot use '<secret> <version>' and '--all' together")
|
err := errors.New("cannot use '<secret> <version>' and '--all' together")
|
||||||
internal.ShowSubcommandHelpAndError(cmd, err)
|
internal.ShowSubcommandHelpAndError(c, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
||||||
@ -82,8 +80,8 @@ var appSecretGenerateCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !allSecrets {
|
if !allSecrets {
|
||||||
secretName := cmd.Args().Get(1)
|
secretName := c.Args().Get(1)
|
||||||
secretVersion := cmd.Args().Get(2)
|
secretVersion := c.Args().Get(2)
|
||||||
s, ok := secrets[secretName]
|
s, ok := secrets[secretName]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Fatalf("%s doesn't exist in the env config?", secretName)
|
log.Fatalf("%s doesn't exist in the env config?", secretName)
|
||||||
@ -117,18 +115,37 @@ var appSecretGenerateCommand = cli.Command{
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCol := []string{"name", "value"}
|
headers := []string{"NAME", "VALUE"}
|
||||||
table := formatter.CreateTable(tableCol)
|
table, err := formatter.CreateTable()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
table.Headers(headers...)
|
||||||
|
|
||||||
|
var rows [][]string
|
||||||
for name, val := range secretVals {
|
for name, val := range secretVals {
|
||||||
table.Append([]string{name, val})
|
row := []string{name, val}
|
||||||
|
rows = append(rows, row)
|
||||||
|
table.Row(row...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
table.JSONRender()
|
out, err := formatter.ToJSON(headers, rows)
|
||||||
} else {
|
if err != nil {
|
||||||
table.Render()
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
|
}
|
||||||
|
fmt.Println(out)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
log.Warn("generated secrets are not shown again, please take note of them NOW")
|
|
||||||
|
fmt.Println(table)
|
||||||
|
|
||||||
|
log.Warnf(
|
||||||
|
"generated secrets %s shown again, please take note of them %s",
|
||||||
|
formatter.BoldStyle.Render("NOT"),
|
||||||
|
formatter.BoldStyle.Render("NOW"),
|
||||||
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -143,22 +160,31 @@ var appSecretInsertCommand = cli.Command{
|
|||||||
internal.PassFlag,
|
internal.PassFlag,
|
||||||
internal.FileFlag,
|
internal.FileFlag,
|
||||||
internal.TrimFlag,
|
internal.TrimFlag,
|
||||||
|
internal.ChaosFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
UsageText: "abra app secret insert [options] <domain> <secret> <version> <data>",
|
ArgsUsage: "<domain> <secret-name> <version> <data>",
|
||||||
HideHelpCommand: true,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
EnableShellCompletion: true,
|
Description: `
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
This command inserts a secret into an app environment.
|
||||||
Description: `This command inserts a secret into an app environment.
|
|
||||||
|
|
||||||
This can be useful when you want to manually generate secrets for an app
|
This can be useful when you want to manually generate secrets for an app
|
||||||
environment. Typically, you can let Abra generate them for you on app creation
|
environment. Typically, you can let Abra generate them for you on app creation
|
||||||
(see "abra app new --secrets" for more).`,
|
(see "abra app new --secrets" for more).
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
|
||||||
app := internal.ValidateApp(cmd)
|
|
||||||
|
|
||||||
if cmd.Args().Len() != 4 {
|
Example:
|
||||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("missing arguments?"))
|
|
||||||
|
abra app secret insert myapp db_pass v1 mySecretPassword
|
||||||
|
|
||||||
|
`,
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
app := internal.ValidateApp(c)
|
||||||
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.Args()) != 4 {
|
||||||
|
internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments?"))
|
||||||
}
|
}
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
cl, err := client.New(app.Server)
|
||||||
@ -166,9 +192,9 @@ environment. Typically, you can let Abra generate them for you on app creation
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
name := cmd.Args().Get(1)
|
name := c.Args().Get(1)
|
||||||
version := cmd.Args().Get(2)
|
version := c.Args().Get(2)
|
||||||
data := cmd.Args().Get(3)
|
data := c.Args().Get(3)
|
||||||
|
|
||||||
if internal.File {
|
if internal.File {
|
||||||
raw, err := os.ReadFile(data)
|
raw, err := os.ReadFile(data)
|
||||||
@ -230,10 +256,9 @@ var appSecretRmCommand = cli.Command{
|
|||||||
internal.OfflineFlag,
|
internal.OfflineFlag,
|
||||||
internal.ChaosFlag,
|
internal.ChaosFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
ArgsUsage: "<domain> [<secret-name>]",
|
ArgsUsage: "<domain> [<secret-name>]",
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
|
||||||
Description: `
|
Description: `
|
||||||
This command removes app secrets.
|
This command removes app secrets.
|
||||||
|
|
||||||
@ -241,8 +266,8 @@ Example:
|
|||||||
|
|
||||||
abra app secret remove myapp db_pass
|
abra app secret remove myapp db_pass
|
||||||
`,
|
`,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(c *cli.Context) error {
|
||||||
app := internal.ValidateApp(cmd)
|
app := internal.ValidateApp(c)
|
||||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -257,12 +282,12 @@ Example:
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.Args().Get(1) != "" && rmAllSecrets {
|
if c.Args().Get(1) != "" && rmAllSecrets {
|
||||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("cannot use '<secret-name>' and '--all' together"))
|
internal.ShowSubcommandHelpAndError(c, errors.New("cannot use '<secret-name>' and '--all' together"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.Args().Get(1) == "" && !rmAllSecrets {
|
if c.Args().Get(1) == "" && !rmAllSecrets {
|
||||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("no secret(s) specified?"))
|
internal.ShowSubcommandHelpAndError(c, errors.New("no secret(s) specified?"))
|
||||||
}
|
}
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
cl, err := client.New(app.Server)
|
||||||
@ -286,7 +311,7 @@ Example:
|
|||||||
}
|
}
|
||||||
|
|
||||||
match := false
|
match := false
|
||||||
secretToRm := cmd.Args().Get(1)
|
secretToRm := c.Args().Get(1)
|
||||||
for secretName, val := range secrets {
|
for secretName, val := range secrets {
|
||||||
secretRemoteName := fmt.Sprintf("%s_%s_%s", app.StackName(), secretName, val.Version)
|
secretRemoteName := fmt.Sprintf("%s_%s_%s", app.StackName(), secretName, val.Version)
|
||||||
if _, ok := remoteSecretNames[secretRemoteName]; ok {
|
if _, ok := remoteSecretNames[secretRemoteName]; ok {
|
||||||
@ -329,12 +354,11 @@ var appSecretLsCommand = cli.Command{
|
|||||||
internal.ChaosFlag,
|
internal.ChaosFlag,
|
||||||
internal.MachineReadableFlag,
|
internal.MachineReadableFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Usage: "List all secrets",
|
Usage: "List all secrets",
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
Action: func(c *cli.Context) error {
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
app := internal.ValidateApp(c)
|
||||||
app := internal.ValidateApp(cmd)
|
|
||||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -344,48 +368,61 @@ var appSecretLsCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCol := []string{"Name", "Version", "Generated Name", "Created On Server"}
|
headers := []string{"NAME", "VERSION", "GENERATED NAME", "CREATED ON SERVER"}
|
||||||
table := formatter.CreateTable(tableCol)
|
table, err := formatter.CreateTable()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
table.Headers(headers...)
|
||||||
|
|
||||||
secStats, err := secret.PollSecretsStatus(cl, app)
|
secStats, err := secret.PollSecretsStatus(cl, app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rows [][]string
|
||||||
for _, secStat := range secStats {
|
for _, secStat := range secStats {
|
||||||
tableRow := []string{
|
row := []string{
|
||||||
secStat.LocalName,
|
secStat.LocalName,
|
||||||
secStat.Version,
|
secStat.Version,
|
||||||
secStat.RemoteName,
|
secStat.RemoteName,
|
||||||
strconv.FormatBool(secStat.CreatedOnRemote),
|
strconv.FormatBool(secStat.CreatedOnRemote),
|
||||||
}
|
}
|
||||||
table.Append(tableRow)
|
|
||||||
|
rows = append(rows, row)
|
||||||
|
table.Row(row...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if table.NumLines() > 0 {
|
if len(rows) > 0 {
|
||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
table.JSONRender()
|
out, err := formatter.ToJSON(headers, rows)
|
||||||
} else {
|
if err != nil {
|
||||||
table.Render()
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
|
}
|
||||||
|
fmt.Println(out)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.Warnf("no secrets stored for %s", app.Name)
|
fmt.Println(table)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Warnf("no secrets stored for %s", app.Name)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var appSecretCommand = cli.Command{
|
var appSecretCommand = cli.Command{
|
||||||
Name: "secret",
|
Name: "secret",
|
||||||
Aliases: []string{"s"},
|
Aliases: []string{"s"},
|
||||||
Usage: "Manage app secrets",
|
Usage: "Manage app secrets",
|
||||||
HideHelpCommand: true,
|
ArgsUsage: "<domain>",
|
||||||
UsageText: "abra app secret [command] [options] [arguments]",
|
Subcommands: []cli.Command{
|
||||||
Commands: []*cli.Command{
|
appSecretGenerateCommand,
|
||||||
&appSecretGenerateCommand,
|
appSecretInsertCommand,
|
||||||
&appSecretInsertCommand,
|
appSecretRmCommand,
|
||||||
&appSecretRmCommand,
|
appSecretLsCommand,
|
||||||
&appSecretLsCommand,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -13,20 +13,21 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/service"
|
"coopcloud.tech/abra/pkg/service"
|
||||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
containerTypes "github.com/docker/docker/api/types/container"
|
containerTypes "github.com/docker/docker/api/types/container"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appServicesCommand = cli.Command{
|
var appServicesCommand = cli.Command{
|
||||||
Name: "services",
|
Name: "services",
|
||||||
Aliases: []string{"sr"},
|
Aliases: []string{"sr"},
|
||||||
Usage: "Display all services of an app",
|
Usage: "Display all services of an app",
|
||||||
HideHelpCommand: true,
|
ArgsUsage: "<domain>",
|
||||||
UsageText: "abra app services [options] <domain>",
|
Flags: []cli.Flag{
|
||||||
Before: internal.SubCommandBefore,
|
internal.DebugFlag,
|
||||||
EnableShellCompletion: true,
|
},
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
Before: internal.SubCommandBefore,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
app := internal.ValidateApp(cmd)
|
Action: func(c *cli.Context) error {
|
||||||
|
app := internal.ValidateApp(c)
|
||||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -55,9 +56,15 @@ var appServicesCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCol := []string{"service name", "image"}
|
table, err := formatter.CreateTable()
|
||||||
table := formatter.CreateTable(tableCol)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{"SERVICE (SHORT)", "SERVICE (LONG)", "IMAGE"}
|
||||||
|
table.Headers(headers...)
|
||||||
|
|
||||||
|
var rows [][]string
|
||||||
for _, container := range containers {
|
for _, container := range containers {
|
||||||
var containerNames []string
|
var containerNames []string
|
||||||
for _, containerName := range container.Names {
|
for _, containerName := range container.Names {
|
||||||
@ -68,14 +75,20 @@ var appServicesCommand = cli.Command{
|
|||||||
serviceShortName := service.ContainerToServiceName(container.Names, app.StackName())
|
serviceShortName := service.ContainerToServiceName(container.Names, app.StackName())
|
||||||
serviceLongName := fmt.Sprintf("%s_%s", app.StackName(), serviceShortName)
|
serviceLongName := fmt.Sprintf("%s_%s", app.StackName(), serviceShortName)
|
||||||
|
|
||||||
tableRow := []string{
|
row := []string{
|
||||||
|
serviceShortName,
|
||||||
serviceLongName,
|
serviceLongName,
|
||||||
formatter.RemoveSha(container.Image),
|
formatter.RemoveSha(container.Image),
|
||||||
}
|
}
|
||||||
table.Append(tableRow)
|
|
||||||
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Render()
|
table.Rows(rows...)
|
||||||
|
|
||||||
|
if len(rows) > 0 {
|
||||||
|
fmt.Println(table)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -8,19 +8,19 @@ import (
|
|||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var prune bool
|
var prune bool
|
||||||
|
|
||||||
var pruneFlag = &cli.BoolFlag{
|
var pruneFlag = &cli.BoolFlag{
|
||||||
Name: "prune",
|
Name: "prune, p",
|
||||||
Aliases: []string{"p"},
|
|
||||||
Destination: &prune,
|
Destination: &prune,
|
||||||
Usage: "Prunes unused containers, networks, and dangling images for an app",
|
Usage: "Prunes unused containers, networks, and dangling images for an app",
|
||||||
}
|
}
|
||||||
@ -62,25 +62,26 @@ func pruneApp(cl *dockerClient.Client, app appPkg.App) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var appUndeployCommand = cli.Command{
|
var appUndeployCommand = cli.Command{
|
||||||
Name: "undeploy",
|
Name: "undeploy",
|
||||||
Aliases: []string{"un"},
|
Aliases: []string{"un"},
|
||||||
UsageText: "abra app undeploy [options] <domain>",
|
ArgsUsage: "<domain>",
|
||||||
HideHelpCommand: true,
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.NoInputFlag,
|
||||||
pruneFlag,
|
pruneFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Usage: "Undeploy an app",
|
Usage: "Undeploy an app",
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
Description: `
|
||||||
Description: `This does not destroy any of the application data.
|
This does not destroy any of the application data.
|
||||||
|
|
||||||
However, you should remain vigilant, as your swarm installation will consider
|
However, you should remain vigilant, as your swarm installation will consider
|
||||||
any previously attached volumes as eligible for pruning once undeployed.
|
any previously attached volumes as eligible for pruning once undeployed.
|
||||||
|
|
||||||
Passing "-p/--prune" does not remove those volumes.`,
|
Passing "-p/--prune" does not remove those volumes.`,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(c *cli.Context) error {
|
||||||
app := internal.ValidateApp(cmd)
|
app := internal.ValidateApp(c)
|
||||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -102,12 +103,12 @@ Passing "-p/--prune" does not remove those volumes.`,
|
|||||||
log.Fatalf("%s is not deployed?", app.Name)
|
log.Fatalf("%s is not deployed?", app.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
chaosVersion := "false"
|
chaosVersion := config.CHAOS_DEFAULT
|
||||||
if deployMeta.IsChaos {
|
if deployMeta.IsChaos {
|
||||||
chaosVersion = deployMeta.ChaosVersion
|
chaosVersion = deployMeta.ChaosVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := internal.DeployOverview(app, deployMeta.Version, chaosVersion, "continue with undeploy?"); err != nil {
|
if err := internal.DeployOverview(app, []string{}, deployMeta.Version, chaosVersion); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,41 +8,57 @@ import (
|
|||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/envfile"
|
"coopcloud.tech/abra/pkg/envfile"
|
||||||
"coopcloud.tech/abra/pkg/lint"
|
"coopcloud.tech/abra/pkg/lint"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
"coopcloud.tech/tagcmp"
|
"coopcloud.tech/tagcmp"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appUpgradeCommand = cli.Command{
|
var appUpgradeCommand = cli.Command{
|
||||||
Name: "upgrade",
|
Name: "upgrade",
|
||||||
Aliases: []string{"up"},
|
Aliases: []string{"up"},
|
||||||
Usage: "Upgrade an app",
|
Usage: "Upgrade an app",
|
||||||
UsageText: "abra app upgrade [options] <domain> [<version>]",
|
ArgsUsage: "<domain> [<version>]",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.NoInputFlag,
|
||||||
internal.ForceFlag,
|
internal.ForceFlag,
|
||||||
internal.NoDomainChecksFlag,
|
internal.NoDomainChecksFlag,
|
||||||
internal.DontWaitConvergeFlag,
|
internal.DontWaitConvergeFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
internal.ReleaseNotesFlag,
|
internal.ReleaseNotesFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Description: `Upgrade an app.
|
Description: `
|
||||||
|
Upgrade an app.
|
||||||
|
|
||||||
Unlike "deploy", chaos operations are not supported here. Only recipe versions
|
Unlike "deploy", chaos operations are not supported here. Only recipe versions
|
||||||
are supported values for "[<version>]".
|
are supported values for "[<version>]".
|
||||||
|
|
||||||
An upgrade can be destructive, please ensure you have a copy of your app data
|
An upgrade can be destructive, please ensure you have a copy of your app data
|
||||||
beforehand.`,
|
beforehand.
|
||||||
EnableShellCompletion: true,
|
|
||||||
HideHelpCommand: true,
|
EXAMPLE:
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
abra app upgrade foo.example.com
|
||||||
app := internal.ValidateApp(cmd)
|
abra app upgrade foo.example.com 1.2.3+3.2.1`,
|
||||||
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
var warnMessages []string
|
||||||
|
|
||||||
|
app := internal.ValidateApp(c)
|
||||||
stackName := app.StackName()
|
stackName := app.StackName()
|
||||||
|
|
||||||
|
specificVersion := c.Args().Get(1)
|
||||||
|
if specificVersion != "" {
|
||||||
|
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
|
||||||
|
app.Recipe.Version = specificVersion
|
||||||
|
}
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -75,10 +91,9 @@ beforehand.`,
|
|||||||
var availableUpgrades []string
|
var availableUpgrades []string
|
||||||
if deployMeta.Version == "unknown" {
|
if deployMeta.Version == "unknown" {
|
||||||
availableUpgrades = versions
|
availableUpgrades = versions
|
||||||
log.Warnf("failed to determine deployed version of %s", app.Name)
|
warnMessages = append(warnMessages, fmt.Sprintf("failed to determine deployed version of %s", app.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
specificVersion := cmd.Args().Get(1)
|
|
||||||
if specificVersion != "" {
|
if specificVersion != "" {
|
||||||
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -107,7 +122,7 @@ beforehand.`,
|
|||||||
|
|
||||||
if deployMeta.Version != "unknown" && specificVersion == "" {
|
if deployMeta.Version != "unknown" && specificVersion == "" {
|
||||||
if deployMeta.IsChaos {
|
if deployMeta.IsChaos {
|
||||||
log.Warn("attempting to upgrade a chaos deployment")
|
warnMessages = append(warnMessages, fmt.Sprintf("attempting to upgrade a chaos deployment"))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, version := range versions {
|
for _, version := range versions {
|
||||||
@ -149,7 +164,7 @@ beforehand.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if internal.Force && chosenUpgrade == "" {
|
if internal.Force && chosenUpgrade == "" {
|
||||||
log.Warnf("%s is already upgraded to latest but continuing (--force)", app.Name)
|
warnMessages = append(warnMessages, fmt.Sprintf("%s is already upgraded to latest", app.Name))
|
||||||
chosenUpgrade = deployMeta.Version
|
chosenUpgrade = deployMeta.Version
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,7 +238,9 @@ beforehand.`,
|
|||||||
|
|
||||||
for _, envVar := range envVars {
|
for _, envVar := range envVars {
|
||||||
if !envVar.Present {
|
if !envVar.Present {
|
||||||
log.Warnf("env var %s missing from %s.env, present in recipe .env.sample", envVar.Name, app.Domain)
|
warnMessages = append(warnMessages,
|
||||||
|
fmt.Sprintf("env var %s missing from %s.env, present in recipe .env.sample", envVar.Name, app.Domain),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,12 +250,19 @@ beforehand.`,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
chaosVersion := "false"
|
chaosVersion := config.CHAOS_DEFAULT
|
||||||
if deployMeta.IsChaos {
|
if deployMeta.IsChaos {
|
||||||
chaosVersion = deployMeta.ChaosVersion
|
chaosVersion = deployMeta.ChaosVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := internal.NewVersionOverview(app, deployMeta.Version, chaosVersion, chosenUpgrade, releaseNotes); err != nil {
|
if err := internal.NewVersionOverview(
|
||||||
|
app,
|
||||||
|
warnMessages,
|
||||||
|
"upgrade",
|
||||||
|
deployMeta.Version,
|
||||||
|
chaosVersion,
|
||||||
|
chosenUpgrade,
|
||||||
|
releaseNotes); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,11 +284,10 @@ beforehand.`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if app.Recipe.Version != "" {
|
app.Recipe.Version = chosenUpgrade
|
||||||
err := app.WriteRecipeVersion(chosenUpgrade)
|
log.Debugf("choosing %s as version to save to env file", app.Recipe.Version)
|
||||||
if err != nil {
|
if err := app.WriteRecipeVersion(app.Recipe.Version); err != nil {
|
||||||
log.Fatalf("writing new recipe version in env file: %s", err)
|
log.Fatalf("writing new recipe version in env file: %s", err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -2,6 +2,7 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
@ -10,20 +11,22 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appVolumeListCommand = cli.Command{
|
var appVolumeListCommand = cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
UsageText: "abra app volume list [options] <domain>",
|
ArgsUsage: "<domain>",
|
||||||
Before: internal.SubCommandBefore,
|
Flags: []cli.Flag{
|
||||||
Usage: "List volumes associated with an app",
|
internal.DebugFlag,
|
||||||
HideHelpCommand: true,
|
internal.NoInputFlag,
|
||||||
EnableShellCompletion: true,
|
},
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
Before: internal.SubCommandBefore,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Usage: "List volumes associated with an app",
|
||||||
app := internal.ValidateApp(cmd)
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
app := internal.ValidateApp(c)
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
cl, err := client.New(app.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -35,26 +38,35 @@ var appVolumeListCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
volumeList, err := client.GetVolumes(cl, context.Background(), app.Server, filters)
|
volumes, err := client.GetVolumes(cl, context.Background(), app.Server, filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
table := formatter.CreateTable([]string{"name", "created", "mounted"})
|
headers := []string{"name", "created", "mounted"}
|
||||||
var volTable [][]string
|
|
||||||
for _, volume := range volumeList {
|
table, err := formatter.CreateTable()
|
||||||
volRow := []string{volume.Name, volume.CreatedAt, volume.Mountpoint}
|
if err != nil {
|
||||||
volTable = append(volTable, volRow)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
table.AppendBulk(volTable)
|
table.Headers(headers...)
|
||||||
|
|
||||||
if table.NumLines() > 0 {
|
var rows [][]string
|
||||||
table.Render()
|
for _, volume := range volumes {
|
||||||
} else {
|
row := []string{volume.Name, volume.CreatedAt, volume.Mountpoint}
|
||||||
log.Warnf("no volumes created for %s", app.Name)
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.Rows(rows...)
|
||||||
|
|
||||||
|
if len(rows) > 0 {
|
||||||
|
fmt.Println(table)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Warnf("no volumes created for %s", app.Name)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -62,28 +74,27 @@ var appVolumeListCommand = cli.Command{
|
|||||||
var appVolumeRemoveCommand = cli.Command{
|
var appVolumeRemoveCommand = cli.Command{
|
||||||
Name: "remove",
|
Name: "remove",
|
||||||
Usage: "Remove volume(s) associated with an app",
|
Usage: "Remove volume(s) associated with an app",
|
||||||
Description: `Memove volumes associated with an app.
|
Description: `
|
||||||
|
This command supports removing volumes associated with an app. The app in
|
||||||
The app in question must be undeployed before you try to remove volumes. See
|
question must be undeployed before you try to remove volumes. See "abra app
|
||||||
"abra app undeploy <domain>" for more.
|
undeploy <domain>" for more.
|
||||||
|
|
||||||
The command is interactive and will show a multiple select input which allows
|
The command is interactive and will show a multiple select input which allows
|
||||||
you to make a seclection. Use the "?" key to see more help on navigating this
|
you to make a seclection. Use the "?" key to see more help on navigating this
|
||||||
interface.
|
interface.
|
||||||
|
|
||||||
Passing "--force/-f" will select all volumes for removal. Be careful.`,
|
Passing "--force/-f" will select all volumes for removal. Be careful.`,
|
||||||
HideHelpCommand: true,
|
ArgsUsage: "<domain>",
|
||||||
UsageText: "abra app volume remove [options] <domain>",
|
Aliases: []string{"rm"},
|
||||||
Aliases: []string{"rm"},
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
internal.NoInputFlag,
|
internal.NoInputFlag,
|
||||||
internal.ForceFlag,
|
internal.ForceFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.AppNameComplete,
|
||||||
ShellComplete: autocomplete.AppNameComplete,
|
Action: func(c *cli.Context) error {
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
app := internal.ValidateApp(c)
|
||||||
app := internal.ValidateApp(cmd)
|
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
cl, err := client.New(app.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -144,13 +155,12 @@ Passing "--force/-f" will select all volumes for removal. Be careful.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
var appVolumeCommand = cli.Command{
|
var appVolumeCommand = cli.Command{
|
||||||
Name: "volume",
|
Name: "volume",
|
||||||
Aliases: []string{"vl"},
|
Aliases: []string{"vl"},
|
||||||
Usage: "Manage app volumes",
|
Usage: "Manage app volumes",
|
||||||
UsageText: "abra app volume [command] [options] [arguments]",
|
ArgsUsage: "<domain>",
|
||||||
HideHelpCommand: true,
|
Subcommands: []cli.Command{
|
||||||
Commands: []*cli.Command{
|
appVolumeListCommand,
|
||||||
&appVolumeListCommand,
|
appVolumeRemoveCommand,
|
||||||
&appVolumeRemoveCommand,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package catalogue
|
package catalogue
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -16,23 +15,25 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var catalogueGenerateCommand = cli.Command{
|
var catalogueGenerateCommand = cli.Command{
|
||||||
Name: "generate",
|
Name: "generate",
|
||||||
Aliases: []string{"g"},
|
Aliases: []string{"g"},
|
||||||
Usage: "Generate the recipe catalogue",
|
Usage: "Generate the recipe catalogue",
|
||||||
HideHelpCommand: true,
|
|
||||||
UsageText: "abra catalogue generate [options] [<recipe>]",
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.NoInputFlag,
|
||||||
internal.PublishFlag,
|
internal.PublishFlag,
|
||||||
internal.DryFlag,
|
internal.DryFlag,
|
||||||
internal.SkipUpdatesFlag,
|
internal.SkipUpdatesFlag,
|
||||||
internal.ChaosFlag,
|
internal.ChaosFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Description: `Generate a new copy of the recipe catalogue.
|
Description: `
|
||||||
|
Generate a new copy of the recipe catalogue.
|
||||||
|
|
||||||
It is possible to generate new metadata for a single recipe by passing
|
It is possible to generate new metadata for a single recipe by passing
|
||||||
<recipe>. The existing local catalogue will be updated, not overwritten.
|
<recipe>. The existing local catalogue will be updated, not overwritten.
|
||||||
@ -44,14 +45,14 @@ If you have a Hub account you can have Abra log you in to avoid this. Pass
|
|||||||
Push your new release to git.coopcloud.tech with "-p/--publish". This requires
|
Push your new release to git.coopcloud.tech with "-p/--publish". This requires
|
||||||
that you have permission to git push to these repositories and have your SSH
|
that you have permission to git push to these repositories and have your SSH
|
||||||
keys configured on your account.`,
|
keys configured on your account.`,
|
||||||
EnableShellCompletion: true,
|
ArgsUsage: "[<recipe>]",
|
||||||
ShellComplete: autocomplete.RecipeNameComplete,
|
BashComplete: autocomplete.RecipeNameComplete,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(c *cli.Context) error {
|
||||||
recipeName := cmd.Args().First()
|
recipeName := c.Args().First()
|
||||||
r := recipe.Get(recipeName)
|
r := recipe.Get(recipeName)
|
||||||
|
|
||||||
if recipeName != "" {
|
if recipeName != "" {
|
||||||
internal.ValidateRecipe(cmd)
|
internal.ValidateRecipe(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !internal.Chaos {
|
if !internal.Chaos {
|
||||||
@ -204,12 +205,11 @@ keys configured on your account.`,
|
|||||||
|
|
||||||
// CatalogueCommand defines the `abra catalogue` command and sub-commands.
|
// CatalogueCommand defines the `abra catalogue` command and sub-commands.
|
||||||
var CatalogueCommand = cli.Command{
|
var CatalogueCommand = cli.Command{
|
||||||
Name: "catalogue",
|
Name: "catalogue",
|
||||||
Usage: "Manage the recipe catalogue",
|
Usage: "Manage the recipe catalogue",
|
||||||
Aliases: []string{"c"},
|
Aliases: []string{"c"},
|
||||||
HideHelpCommand: true,
|
ArgsUsage: "<recipe>",
|
||||||
UsageText: "abra catalogue [command] [options] [arguments]",
|
Subcommands: []cli.Command{
|
||||||
Commands: []*cli.Command{
|
catalogueGenerateCommand,
|
||||||
&catalogueGenerateCommand,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
117
cli/cli.go
117
cli/cli.go
@ -2,7 +2,6 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@ -19,7 +18,8 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/web"
|
"coopcloud.tech/abra/pkg/web"
|
||||||
charmLog "github.com/charmbracelet/log"
|
charmLog "github.com/charmbracelet/log"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AutoCompleteCommand helps people set up auto-complete in their shells
|
// AutoCompleteCommand helps people set up auto-complete in their shells
|
||||||
@ -27,15 +27,23 @@ var AutoCompleteCommand = cli.Command{
|
|||||||
Name: "autocomplete",
|
Name: "autocomplete",
|
||||||
Aliases: []string{"ac"},
|
Aliases: []string{"ac"},
|
||||||
Usage: "Configure shell autocompletion",
|
Usage: "Configure shell autocompletion",
|
||||||
Description: `Set up shell auto-completion.
|
Description: `
|
||||||
|
Set up shell auto-completion.
|
||||||
|
|
||||||
Supported shells are: bash, fish, fizsh & zsh.`,
|
Supported shells are: bash, fish, fizsh & zsh.
|
||||||
|
|
||||||
|
EXAMPLE:
|
||||||
|
|
||||||
|
abra autocomplete bash`,
|
||||||
ArgsUsage: "<shell>",
|
ArgsUsage: "<shell>",
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Flags: []cli.Flag{
|
||||||
shellType := cmd.Args().First()
|
internal.DebugFlag,
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
shellType := c.Args().First()
|
||||||
|
|
||||||
if shellType == "" {
|
if shellType == "" {
|
||||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("no shell provided"))
|
internal.ShowSubcommandHelpAndError(c, errors.New("no shell provided"))
|
||||||
}
|
}
|
||||||
|
|
||||||
supportedShells := map[string]bool{
|
supportedShells := map[string]bool{
|
||||||
@ -73,26 +81,29 @@ Supported shells are: bash, fish, fizsh & zsh.`,
|
|||||||
switch shellType {
|
switch shellType {
|
||||||
case "bash":
|
case "bash":
|
||||||
fmt.Println(fmt.Sprintf(`
|
fmt.Println(fmt.Sprintf(`
|
||||||
# run the following commands to install auto-completion
|
# run the following commands once to install auto-completion
|
||||||
sudo mkdir /etc/bash_completion.d/
|
sudo mkdir -p /etc/bash_completion.d/
|
||||||
sudo cp %s /etc/bash_completion.d/abra
|
sudo cp %s /etc/bash_completion.d/abra
|
||||||
echo "source /etc/bash_completion.d/abra" >> ~/.bashrc
|
echo "source /etc/bash_completion.d/abra" >> ~/.bashrc
|
||||||
|
source /etc/bash_completion.d/abra
|
||||||
# To test, run the following: "abra app <hit tab key>" - you should see command completion!
|
# To test, run the following: "abra app <hit tab key>" - you should see command completion!
|
||||||
`, autocompletionFile))
|
`, autocompletionFile))
|
||||||
case "zsh":
|
case "zsh":
|
||||||
fmt.Println(fmt.Sprintf(`
|
fmt.Println(fmt.Sprintf(`
|
||||||
# run the following commands to install auto-completion
|
# run the following commands to once install auto-completion
|
||||||
sudo mkdir /etc/zsh/completion.d/
|
sudo mkdir -p /etc/zsh/completion.d/
|
||||||
sudo cp %s /etc/zsh/completion.d/abra
|
sudo cp %s /etc/zsh/completion.d/abra
|
||||||
echo "PROG=abra\n_CLI_ZSH_AUTOCOMPLETE_HACK=1\nsource /etc/zsh/completion.d/abra" >> ~/.zshrc
|
echo "PROG=abra\n_CLI_ZSH_AUTOCOMPLETE_HACK=1\nsource /etc/zsh/completion.d/abra" >> ~/.zshrc
|
||||||
|
source /etc/zsh/completion.d/abra
|
||||||
# to test, run the following: "abra app <hit tab key>" - you should see command completion!
|
# to test, run the following: "abra app <hit tab key>" - you should see command completion!
|
||||||
`, autocompletionFile))
|
`, autocompletionFile))
|
||||||
case "fish":
|
case "fish":
|
||||||
fmt.Println(fmt.Sprintf(`
|
fmt.Println(fmt.Sprintf(`
|
||||||
# run the following commands to install auto-completion
|
# run the following commands once to install auto-completion
|
||||||
sudo mkdir -p /etc/fish/completions
|
sudo mkdir -p /etc/fish/completions
|
||||||
sudo cp %s /etc/fish/completions/abra
|
sudo cp %s /etc/fish/completions/abra
|
||||||
echo "source /etc/fish/completions/abra" >> ~/.config/fish/config.fish
|
echo "source /etc/fish/completions/abra" >> ~/.config/fish/config.fish
|
||||||
|
source /etc/fish/completions/abra
|
||||||
# to test, run the following: "abra app <hit tab key>" - you should see command completion!
|
# to test, run the following: "abra app <hit tab key>" - you should see command completion!
|
||||||
`, autocompletionFile))
|
`, autocompletionFile))
|
||||||
}
|
}
|
||||||
@ -102,61 +113,58 @@ echo "source /etc/fish/completions/abra" >> ~/.config/fish/config.fish
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpgradeCommand upgrades abra in-place.
|
// UpgradeCommand upgrades abra in-place.
|
||||||
var UpgradeCommand = cli.Command{
|
var UpgradeCommand = &cobra.Command{
|
||||||
Name: "upgrade",
|
Use: "upgrade",
|
||||||
Aliases: []string{"u"},
|
Aliases: []string{"u"},
|
||||||
Usage: "Upgrade abra",
|
Short: "Upgrade abra",
|
||||||
Description: `Upgrade abra in-place with the latest stable or release candidate.
|
Example: "abra upgrade",
|
||||||
|
Long: `Upgrade abra in-place with the latest stable or release candidate.
|
||||||
|
|
||||||
Use "-r/--rc" to install the latest release candidate. Please bear in mind that
|
Use "-r/--rc" to install the latest release candidate. Please bear in mind that
|
||||||
it may contain absolutely catastrophic deal-breaker bugs. Thank you very much
|
it may contain absolutely catastrophic deal-breaker bugs. Thank you very much
|
||||||
for the testing efforts 💗`,
|
for the testing efforts 💗`,
|
||||||
Flags: []cli.Flag{internal.RCFlag},
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
|
||||||
mainURL := "https://install.abra.coopcloud.tech"
|
mainURL := "https://install.abra.coopcloud.tech"
|
||||||
c := exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash", mainURL))
|
comm := exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash", mainURL))
|
||||||
|
|
||||||
if internal.RC {
|
if internal.RC {
|
||||||
releaseCandidateURL := "https://git.coopcloud.tech/coop-cloud/abra/raw/branch/main/scripts/installer/installer"
|
releaseCandidateURL := "https://git.coopcloud.tech/coop-cloud/abra/raw/branch/main/scripts/installer/installer"
|
||||||
c = exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash -s -- --rc", releaseCandidateURL))
|
comm = exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash -s -- --rc", releaseCandidateURL))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("attempting to run %s", c)
|
log.Debugf("attempting to run %s", cmd)
|
||||||
|
|
||||||
if err := internal.RunCmd(c); err != nil {
|
if err := internal.RunCmd(comm); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAbraApp(version, commit string) *cli.Command {
|
func newAbraApp(version, commit string) *cli.App {
|
||||||
app := &cli.Command{
|
app := &cli.App{
|
||||||
Name: "abra",
|
Name: "abra",
|
||||||
Usage: "The Co-op Cloud command-line utility belt 🎩🐇",
|
Usage: `the Co-op Cloud command-line utility belt 🎩🐇
|
||||||
UsageText: "abra [command] [options] [arguments]",
|
____ ____ _ _
|
||||||
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
|
/ ___|___ ___ _ __ / ___| | ___ _ _ __| |
|
||||||
Flags: []cli.Flag{
|
| | / _ \ _____ / _ \| '_ \ | | | |/ _ \| | | |/ _' |
|
||||||
internal.DebugFlag,
|
| |__| (_) |_____| (_) | |_) | | |___| | (_) | |_| | (_| |
|
||||||
internal.OfflineFlag,
|
\____\___/ \___/| .__/ \____|_|\___/ \__,_|\__,_|
|
||||||
internal.NoInputFlag,
|
|_|
|
||||||
|
`,
|
||||||
|
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
|
||||||
|
Commands: []cli.Command{
|
||||||
|
app.AppCommand,
|
||||||
|
server.ServerCommand,
|
||||||
|
recipe.RecipeCommand,
|
||||||
|
catalogue.CatalogueCommand,
|
||||||
|
AutoCompleteCommand,
|
||||||
},
|
},
|
||||||
Commands: []*cli.Command{
|
BashComplete: autocomplete.SubcommandComplete,
|
||||||
&app.AppCommand,
|
|
||||||
&server.ServerCommand,
|
|
||||||
&recipe.RecipeCommand,
|
|
||||||
&catalogue.CatalogueCommand,
|
|
||||||
&UpgradeCommand,
|
|
||||||
&AutoCompleteCommand,
|
|
||||||
},
|
|
||||||
EnableShellCompletion: true,
|
|
||||||
UseShortOptionHandling: true,
|
|
||||||
HideHelpCommand: true,
|
|
||||||
ShellComplete: autocomplete.SubcommandComplete,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Before = func(ctx context.Context, cmd *cli.Command) error {
|
app.EnableBashCompletion = true
|
||||||
|
|
||||||
|
app.Before = func(c *cli.Context) error {
|
||||||
paths := []string{
|
paths := []string{
|
||||||
config.ABRA_DIR,
|
config.ABRA_DIR,
|
||||||
config.SERVERS_DIR,
|
config.SERVERS_DIR,
|
||||||
@ -174,19 +182,14 @@ func newAbraApp(version, commit string) *cli.Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Logger.SetStyles(log.Styles())
|
||||||
charmLog.SetDefault(log.Logger)
|
charmLog.SetDefault(log.Logger)
|
||||||
|
|
||||||
log.Debugf("abra version %s, commit %s", version, commit)
|
log.Debugf("abra version %s, commit %s", version, commit)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.HelpFlag = &cli.BoolFlag{
|
|
||||||
Name: "help",
|
|
||||||
Aliases: []string{"h, H"},
|
|
||||||
Usage: "Show help",
|
|
||||||
Persistent: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +197,11 @@ func newAbraApp(version, commit string) *cli.Command {
|
|||||||
func RunApp(version, commit string) {
|
func RunApp(version, commit string) {
|
||||||
app := newAbraApp(version, commit)
|
app := newAbraApp(version, commit)
|
||||||
|
|
||||||
if err := app.Run(context.Background(), os.Args); err != nil {
|
if err := app.Run(os.Args); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
UpgradeCommand.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Secrets stores the variable from SecretsFlag
|
// Secrets stores the variable from SecretsFlag
|
||||||
@ -13,8 +12,7 @@ var Secrets bool
|
|||||||
|
|
||||||
// SecretsFlag turns on/off automatically generating secrets
|
// SecretsFlag turns on/off automatically generating secrets
|
||||||
var SecretsFlag = &cli.BoolFlag{
|
var SecretsFlag = &cli.BoolFlag{
|
||||||
Name: "secrets",
|
Name: "secrets, S",
|
||||||
Aliases: []string{"S"},
|
|
||||||
Usage: "Automatically generate secrets",
|
Usage: "Automatically generate secrets",
|
||||||
Destination: &Secrets,
|
Destination: &Secrets,
|
||||||
}
|
}
|
||||||
@ -24,8 +22,7 @@ var Pass bool
|
|||||||
|
|
||||||
// PassFlag turns on/off storing generated secrets in pass
|
// PassFlag turns on/off storing generated secrets in pass
|
||||||
var PassFlag = &cli.BoolFlag{
|
var PassFlag = &cli.BoolFlag{
|
||||||
Name: "pass",
|
Name: "pass, p",
|
||||||
Aliases: []string{"p"},
|
|
||||||
Usage: "Store the generated secrets in a local pass store",
|
Usage: "Store the generated secrets in a local pass store",
|
||||||
Destination: &Pass,
|
Destination: &Pass,
|
||||||
}
|
}
|
||||||
@ -35,24 +32,21 @@ var PassRemove bool
|
|||||||
|
|
||||||
// PassRemoveFlag turns on/off removing generated secrets from pass
|
// PassRemoveFlag turns on/off removing generated secrets from pass
|
||||||
var PassRemoveFlag = &cli.BoolFlag{
|
var PassRemoveFlag = &cli.BoolFlag{
|
||||||
Name: "pass",
|
Name: "pass, p",
|
||||||
Aliases: []string{"p"},
|
|
||||||
Usage: "Remove generated secrets from a local pass store",
|
Usage: "Remove generated secrets from a local pass store",
|
||||||
Destination: &PassRemove,
|
Destination: &PassRemove,
|
||||||
}
|
}
|
||||||
|
|
||||||
var File bool
|
var File bool
|
||||||
var FileFlag = &cli.BoolFlag{
|
var FileFlag = &cli.BoolFlag{
|
||||||
Name: "file",
|
Name: "file, f",
|
||||||
Aliases: []string{"f"},
|
|
||||||
Usage: "Treat input as a file",
|
Usage: "Treat input as a file",
|
||||||
Destination: &File,
|
Destination: &File,
|
||||||
}
|
}
|
||||||
|
|
||||||
var Trim bool
|
var Trim bool
|
||||||
var TrimFlag = &cli.BoolFlag{
|
var TrimFlag = &cli.BoolFlag{
|
||||||
Name: "trim",
|
Name: "trim, t",
|
||||||
Aliases: []string{"t"},
|
|
||||||
Usage: "Trim input",
|
Usage: "Trim input",
|
||||||
Destination: &Trim,
|
Destination: &Trim,
|
||||||
}
|
}
|
||||||
@ -62,8 +56,7 @@ var Force bool
|
|||||||
|
|
||||||
// ForceFlag turns on/off force functionality.
|
// ForceFlag turns on/off force functionality.
|
||||||
var ForceFlag = &cli.BoolFlag{
|
var ForceFlag = &cli.BoolFlag{
|
||||||
Name: "force",
|
Name: "force, f",
|
||||||
Aliases: []string{"f"},
|
|
||||||
Usage: "Perform action without further prompt. Use with care!",
|
Usage: "Perform action without further prompt. Use with care!",
|
||||||
Destination: &Force,
|
Destination: &Force,
|
||||||
}
|
}
|
||||||
@ -73,8 +66,7 @@ var Chaos bool
|
|||||||
|
|
||||||
// ChaosFlag turns on/off chaos functionality.
|
// ChaosFlag turns on/off chaos functionality.
|
||||||
var ChaosFlag = &cli.BoolFlag{
|
var ChaosFlag = &cli.BoolFlag{
|
||||||
Name: "chaos",
|
Name: "chaos, C",
|
||||||
Aliases: []string{"C"},
|
|
||||||
Usage: "Proceed with uncommitted recipes changes. Use with care!",
|
Usage: "Proceed with uncommitted recipes changes. Use with care!",
|
||||||
Destination: &Chaos,
|
Destination: &Chaos,
|
||||||
}
|
}
|
||||||
@ -84,19 +76,16 @@ var Tty bool
|
|||||||
|
|
||||||
// TtyFlag turns on/off tty mode.
|
// TtyFlag turns on/off tty mode.
|
||||||
var TtyFlag = &cli.BoolFlag{
|
var TtyFlag = &cli.BoolFlag{
|
||||||
Name: "tty",
|
Name: "tty, T",
|
||||||
Aliases: []string{"T"},
|
|
||||||
Usage: "Disables TTY mode to run this command from a script.",
|
Usage: "Disables TTY mode to run this command from a script.",
|
||||||
Destination: &Tty,
|
Destination: &Tty,
|
||||||
}
|
}
|
||||||
|
|
||||||
var NoInput bool
|
var NoInput bool
|
||||||
var NoInputFlag = &cli.BoolFlag{
|
var NoInputFlag = &cli.BoolFlag{
|
||||||
Name: "no-input",
|
Name: "no-input, n",
|
||||||
Aliases: []string{"n"},
|
|
||||||
Usage: "Toggle non-interactive mode",
|
Usage: "Toggle non-interactive mode",
|
||||||
Destination: &NoInput,
|
Destination: &NoInput,
|
||||||
Persistent: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug stores the variable from DebugFlag.
|
// Debug stores the variable from DebugFlag.
|
||||||
@ -104,10 +93,8 @@ var Debug bool
|
|||||||
|
|
||||||
// DebugFlag turns on/off verbose logging down to the DEBUG level.
|
// DebugFlag turns on/off verbose logging down to the DEBUG level.
|
||||||
var DebugFlag = &cli.BoolFlag{
|
var DebugFlag = &cli.BoolFlag{
|
||||||
Name: "debug",
|
Name: "debug, d",
|
||||||
Aliases: []string{"d"},
|
|
||||||
Destination: &Debug,
|
Destination: &Debug,
|
||||||
Persistent: true,
|
|
||||||
Usage: "Show DEBUG messages",
|
Usage: "Show DEBUG messages",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,11 +103,9 @@ var Offline bool
|
|||||||
|
|
||||||
// DebugFlag turns on/off offline mode.
|
// DebugFlag turns on/off offline mode.
|
||||||
var OfflineFlag = &cli.BoolFlag{
|
var OfflineFlag = &cli.BoolFlag{
|
||||||
Name: "offline",
|
Name: "offline, o",
|
||||||
Aliases: []string{"o"},
|
|
||||||
Destination: &Offline,
|
Destination: &Offline,
|
||||||
Usage: "Prefer offline & filesystem access when possible",
|
Usage: "Prefer offline & filesystem access when possible",
|
||||||
Persistent: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReleaseNotes stores the variable from ReleaseNotesFlag.
|
// ReleaseNotes stores the variable from ReleaseNotesFlag.
|
||||||
@ -128,8 +113,7 @@ var ReleaseNotes bool
|
|||||||
|
|
||||||
// ReleaseNotesFlag turns on/off printing only release notes when upgrading.
|
// ReleaseNotesFlag turns on/off printing only release notes when upgrading.
|
||||||
var ReleaseNotesFlag = &cli.BoolFlag{
|
var ReleaseNotesFlag = &cli.BoolFlag{
|
||||||
Name: "releasenotes",
|
Name: "releasenotes, r",
|
||||||
Aliases: []string{"r"},
|
|
||||||
Destination: &ReleaseNotes,
|
Destination: &ReleaseNotes,
|
||||||
Usage: "Only show release notes",
|
Usage: "Only show release notes",
|
||||||
}
|
}
|
||||||
@ -139,8 +123,7 @@ var MachineReadable bool
|
|||||||
|
|
||||||
// MachineReadableFlag turns on/off machine readable output where supported
|
// MachineReadableFlag turns on/off machine readable output where supported
|
||||||
var MachineReadableFlag = &cli.BoolFlag{
|
var MachineReadableFlag = &cli.BoolFlag{
|
||||||
Name: "machine",
|
Name: "machine, m",
|
||||||
Aliases: []string{"m"},
|
|
||||||
Destination: &MachineReadable,
|
Destination: &MachineReadable,
|
||||||
Usage: "Output in a machine-readable format (where supported)",
|
Usage: "Output in a machine-readable format (where supported)",
|
||||||
}
|
}
|
||||||
@ -150,56 +133,49 @@ var RC bool
|
|||||||
|
|
||||||
// RCFlag chooses the latest release candidate for install
|
// RCFlag chooses the latest release candidate for install
|
||||||
var RCFlag = &cli.BoolFlag{
|
var RCFlag = &cli.BoolFlag{
|
||||||
Name: "rc",
|
Name: "rc, r",
|
||||||
Aliases: []string{"r"},
|
|
||||||
Destination: &RC,
|
Destination: &RC,
|
||||||
Usage: "Install the latest release candidate",
|
Usage: "Install the latest release candidate",
|
||||||
}
|
}
|
||||||
|
|
||||||
var Major bool
|
var Major bool
|
||||||
var MajorFlag = &cli.BoolFlag{
|
var MajorFlag = &cli.BoolFlag{
|
||||||
Name: "major",
|
Name: "major, x",
|
||||||
Aliases: []string{"x"},
|
|
||||||
Usage: "Increase the major part of the version",
|
Usage: "Increase the major part of the version",
|
||||||
Destination: &Major,
|
Destination: &Major,
|
||||||
}
|
}
|
||||||
|
|
||||||
var Minor bool
|
var Minor bool
|
||||||
var MinorFlag = &cli.BoolFlag{
|
var MinorFlag = &cli.BoolFlag{
|
||||||
Name: "minor",
|
Name: "minor, y",
|
||||||
Aliases: []string{"y"},
|
|
||||||
Usage: "Increase the minor part of the version",
|
Usage: "Increase the minor part of the version",
|
||||||
Destination: &Minor,
|
Destination: &Minor,
|
||||||
}
|
}
|
||||||
|
|
||||||
var Patch bool
|
var Patch bool
|
||||||
var PatchFlag = &cli.BoolFlag{
|
var PatchFlag = &cli.BoolFlag{
|
||||||
Name: "patch",
|
Name: "patch, z",
|
||||||
Aliases: []string{"z"},
|
|
||||||
Usage: "Increase the patch part of the version",
|
Usage: "Increase the patch part of the version",
|
||||||
Destination: &Patch,
|
Destination: &Patch,
|
||||||
}
|
}
|
||||||
|
|
||||||
var Dry bool
|
var Dry bool
|
||||||
var DryFlag = &cli.BoolFlag{
|
var DryFlag = &cli.BoolFlag{
|
||||||
Name: "dry-run",
|
Name: "dry-run, r",
|
||||||
Aliases: []string{"r"},
|
|
||||||
Usage: "Only reports changes that would be made",
|
Usage: "Only reports changes that would be made",
|
||||||
Destination: &Dry,
|
Destination: &Dry,
|
||||||
}
|
}
|
||||||
|
|
||||||
var Publish bool
|
var Publish bool
|
||||||
var PublishFlag = &cli.BoolFlag{
|
var PublishFlag = &cli.BoolFlag{
|
||||||
Name: "publish",
|
Name: "publish, p",
|
||||||
Aliases: []string{"p"},
|
|
||||||
Usage: "Publish changes to git.coopcloud.tech",
|
Usage: "Publish changes to git.coopcloud.tech",
|
||||||
Destination: &Publish,
|
Destination: &Publish,
|
||||||
}
|
}
|
||||||
|
|
||||||
var Domain string
|
var Domain string
|
||||||
var DomainFlag = &cli.StringFlag{
|
var DomainFlag = &cli.StringFlag{
|
||||||
Name: "domain",
|
Name: "domain, D",
|
||||||
Aliases: []string{"D"},
|
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "Choose a domain name",
|
Usage: "Choose a domain name",
|
||||||
Destination: &Domain,
|
Destination: &Domain,
|
||||||
@ -207,8 +183,7 @@ var DomainFlag = &cli.StringFlag{
|
|||||||
|
|
||||||
var NewAppServer string
|
var NewAppServer string
|
||||||
var NewAppServerFlag = &cli.StringFlag{
|
var NewAppServerFlag = &cli.StringFlag{
|
||||||
Name: "server",
|
Name: "server, s",
|
||||||
Aliases: []string{"s"},
|
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "Show apps of a specific server",
|
Usage: "Show apps of a specific server",
|
||||||
Destination: &NewAppServer,
|
Destination: &NewAppServer,
|
||||||
@ -216,24 +191,21 @@ var NewAppServerFlag = &cli.StringFlag{
|
|||||||
|
|
||||||
var NoDomainChecks bool
|
var NoDomainChecks bool
|
||||||
var NoDomainChecksFlag = &cli.BoolFlag{
|
var NoDomainChecksFlag = &cli.BoolFlag{
|
||||||
Name: "no-domain-checks",
|
Name: "no-domain-checks, D",
|
||||||
Aliases: []string{"D"},
|
|
||||||
Usage: "Disable public DNS checks",
|
Usage: "Disable public DNS checks",
|
||||||
Destination: &NoDomainChecks,
|
Destination: &NoDomainChecks,
|
||||||
}
|
}
|
||||||
|
|
||||||
var StdErrOnly bool
|
var StdErrOnly bool
|
||||||
var StdErrOnlyFlag = &cli.BoolFlag{
|
var StdErrOnlyFlag = &cli.BoolFlag{
|
||||||
Name: "stderr",
|
Name: "stderr, s",
|
||||||
Aliases: []string{"s"},
|
|
||||||
Usage: "Only tail stderr",
|
Usage: "Only tail stderr",
|
||||||
Destination: &StdErrOnly,
|
Destination: &StdErrOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
var SinceLogs string
|
var SinceLogs string
|
||||||
var SinceLogsFlag = &cli.StringFlag{
|
var SinceLogsFlag = &cli.StringFlag{
|
||||||
Name: "since",
|
Name: "since, S",
|
||||||
Aliases: []string{"S"},
|
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "tail logs since YYYY-MM-DDTHH:MM:SSZ",
|
Usage: "tail logs since YYYY-MM-DDTHH:MM:SSZ",
|
||||||
Destination: &SinceLogs,
|
Destination: &SinceLogs,
|
||||||
@ -241,56 +213,49 @@ var SinceLogsFlag = &cli.StringFlag{
|
|||||||
|
|
||||||
var DontWaitConverge bool
|
var DontWaitConverge bool
|
||||||
var DontWaitConvergeFlag = &cli.BoolFlag{
|
var DontWaitConvergeFlag = &cli.BoolFlag{
|
||||||
Name: "no-converge-checks",
|
Name: "no-converge-checks, c",
|
||||||
Aliases: []string{"c"},
|
|
||||||
Usage: "Don't wait for converge logic checks",
|
Usage: "Don't wait for converge logic checks",
|
||||||
Destination: &DontWaitConverge,
|
Destination: &DontWaitConverge,
|
||||||
}
|
}
|
||||||
|
|
||||||
var Watch bool
|
var Watch bool
|
||||||
var WatchFlag = &cli.BoolFlag{
|
var WatchFlag = &cli.BoolFlag{
|
||||||
Name: "watch",
|
Name: "watch, w",
|
||||||
Aliases: []string{"w"},
|
|
||||||
Usage: "Watch status by polling repeatedly",
|
Usage: "Watch status by polling repeatedly",
|
||||||
Destination: &Watch,
|
Destination: &Watch,
|
||||||
}
|
}
|
||||||
|
|
||||||
var OnlyErrors bool
|
var OnlyErrors bool
|
||||||
var OnlyErrorFlag = &cli.BoolFlag{
|
var OnlyErrorFlag = &cli.BoolFlag{
|
||||||
Name: "errors",
|
Name: "errors, e",
|
||||||
Aliases: []string{"e"},
|
|
||||||
Usage: "Only show errors",
|
Usage: "Only show errors",
|
||||||
Destination: &OnlyErrors,
|
Destination: &OnlyErrors,
|
||||||
}
|
}
|
||||||
|
|
||||||
var SkipUpdates bool
|
var SkipUpdates bool
|
||||||
var SkipUpdatesFlag = &cli.BoolFlag{
|
var SkipUpdatesFlag = &cli.BoolFlag{
|
||||||
Name: "skip-updates",
|
Name: "skip-updates, s",
|
||||||
Aliases: []string{"s"},
|
|
||||||
Usage: "Skip updating recipe repositories",
|
Usage: "Skip updating recipe repositories",
|
||||||
Destination: &SkipUpdates,
|
Destination: &SkipUpdates,
|
||||||
}
|
}
|
||||||
|
|
||||||
var AllTags bool
|
var AllTags bool
|
||||||
var AllTagsFlag = &cli.BoolFlag{
|
var AllTagsFlag = &cli.BoolFlag{
|
||||||
Name: "all-tags",
|
Name: "all-tags, a",
|
||||||
Aliases: []string{"a"},
|
|
||||||
Usage: "List all tags, not just upgrades",
|
Usage: "List all tags, not just upgrades",
|
||||||
Destination: &AllTags,
|
Destination: &AllTags,
|
||||||
}
|
}
|
||||||
|
|
||||||
var LocalCmd bool
|
var LocalCmd bool
|
||||||
var LocalCmdFlag = &cli.BoolFlag{
|
var LocalCmdFlag = &cli.BoolFlag{
|
||||||
Name: "local",
|
Name: "local, l",
|
||||||
Aliases: []string{"l"},
|
|
||||||
Usage: "Run command locally",
|
Usage: "Run command locally",
|
||||||
Destination: &LocalCmd,
|
Destination: &LocalCmd,
|
||||||
}
|
}
|
||||||
|
|
||||||
var RemoteUser string
|
var RemoteUser string
|
||||||
var RemoteUserFlag = &cli.StringFlag{
|
var RemoteUserFlag = &cli.StringFlag{
|
||||||
Name: "user",
|
Name: "user, u",
|
||||||
Aliases: []string{"u"},
|
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "User to run command within a service context",
|
Usage: "User to run command within a service context",
|
||||||
Destination: &RemoteUser,
|
Destination: &RemoteUser,
|
||||||
@ -298,8 +263,7 @@ var RemoteUserFlag = &cli.StringFlag{
|
|||||||
|
|
||||||
var GitName string
|
var GitName string
|
||||||
var GitNameFlag = &cli.StringFlag{
|
var GitNameFlag = &cli.StringFlag{
|
||||||
Name: "git-name",
|
Name: "git-name, gn",
|
||||||
Aliases: []string{"gn"},
|
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "Git (user) name to do commits with",
|
Usage: "Git (user) name to do commits with",
|
||||||
Destination: &GitName,
|
Destination: &GitName,
|
||||||
@ -307,8 +271,7 @@ var GitNameFlag = &cli.StringFlag{
|
|||||||
|
|
||||||
var GitEmail string
|
var GitEmail string
|
||||||
var GitEmailFlag = &cli.StringFlag{
|
var GitEmailFlag = &cli.StringFlag{
|
||||||
Name: "git-email",
|
Name: "git-email, ge",
|
||||||
Aliases: []string{"ge"},
|
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "Git email name to do commits with",
|
Usage: "Git email name to do commits with",
|
||||||
Destination: &GitEmail,
|
Destination: &GitEmail,
|
||||||
@ -316,14 +279,13 @@ var GitEmailFlag = &cli.StringFlag{
|
|||||||
|
|
||||||
var AllServices bool
|
var AllServices bool
|
||||||
var AllServicesFlag = &cli.BoolFlag{
|
var AllServicesFlag = &cli.BoolFlag{
|
||||||
Name: "all-services",
|
Name: "all-services, a",
|
||||||
Aliases: []string{"a"},
|
|
||||||
Usage: "Restart all services",
|
Usage: "Restart all services",
|
||||||
Destination: &AllServices,
|
Destination: &AllServices,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubCommandBefore wires up pre-action machinery (e.g. --debug handling).
|
// SubCommandBefore wires up pre-action machinery (e.g. --debug handling).
|
||||||
func SubCommandBefore(ctx context.Context, cmd *cli.Command) error {
|
func SubCommandBefore(c *cli.Context) error {
|
||||||
if Debug {
|
if Debug {
|
||||||
log.SetLevel(log.DebugLevel)
|
log.SetLevel(log.DebugLevel)
|
||||||
log.SetOutput(os.Stderr)
|
log.SetOutput(os.Stderr)
|
||||||
|
@ -6,17 +6,41 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewVersionOverview shows an upgrade or downgrade overview
|
var borderStyle = lipgloss.NewStyle().
|
||||||
func NewVersionOverview(app appPkg.App, currentVersion, chaosVersion, newVersion, releaseNotes string) error {
|
BorderStyle(lipgloss.ThickBorder()).
|
||||||
tableCol := []string{"server", "recipe", "config", "domain", "version", "chaos", "to deploy"}
|
Padding(0, 1, 0, 1).
|
||||||
table := formatter.CreateTable(tableCol)
|
MaxWidth(79).
|
||||||
|
BorderForeground(lipgloss.Color("63"))
|
||||||
|
|
||||||
|
var headerStyle = lipgloss.NewStyle().
|
||||||
|
Underline(true).
|
||||||
|
Bold(true)
|
||||||
|
|
||||||
|
var leftStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true)
|
||||||
|
|
||||||
|
var rightStyle = lipgloss.NewStyle()
|
||||||
|
|
||||||
|
// horizontal is a JoinHorizontal helper function.
|
||||||
|
func horizontal(left, mid, right string) string {
|
||||||
|
return lipgloss.JoinHorizontal(lipgloss.Left, left, mid, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVersionOverview shows an upgrade or downgrade overview
|
||||||
|
func NewVersionOverview(
|
||||||
|
app appPkg.App,
|
||||||
|
warnMessages []string,
|
||||||
|
kind,
|
||||||
|
currentVersion,
|
||||||
|
chaosVersion,
|
||||||
|
newVersion,
|
||||||
|
releaseNotes string) error {
|
||||||
deployConfig := "compose.yml"
|
deployConfig := "compose.yml"
|
||||||
if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
|
if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
|
||||||
deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n")
|
deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n")
|
||||||
@ -27,22 +51,36 @@ func NewVersionOverview(app appPkg.App, currentVersion, chaosVersion, newVersion
|
|||||||
server = "local"
|
server = "local"
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Append([]string{
|
body := strings.Builder{}
|
||||||
server,
|
body.WriteString(
|
||||||
app.Recipe.Name,
|
borderStyle.Render(
|
||||||
deployConfig,
|
lipgloss.JoinVertical(
|
||||||
app.Domain,
|
lipgloss.Center,
|
||||||
currentVersion,
|
headerStyle.Render(fmt.Sprintf("%s OVERVIEW", strings.ToUpper(kind))),
|
||||||
chaosVersion,
|
lipgloss.JoinVertical(
|
||||||
newVersion,
|
lipgloss.Left,
|
||||||
})
|
horizontal(leftStyle.Render("SERVER"), " ", rightStyle.Render(server)),
|
||||||
table.Render()
|
horizontal(leftStyle.Render("DOMAIN"), " ", rightStyle.Render(app.Domain)),
|
||||||
|
horizontal(leftStyle.Render("RECIPE"), " ", rightStyle.Render(app.Recipe.Name)),
|
||||||
|
horizontal(leftStyle.Render("CONFIG"), " ", rightStyle.Render(deployConfig)),
|
||||||
|
horizontal(leftStyle.Render("VERSION"), " ", rightStyle.Render(currentVersion)),
|
||||||
|
horizontal(leftStyle.Render("CHAOS"), " ", rightStyle.Render(chaosVersion)),
|
||||||
|
horizontal(leftStyle.Render("DEPLOY"), " ", rightStyle.Padding(0).Render(newVersion)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
fmt.Println(body.String())
|
||||||
|
|
||||||
if releaseNotes != "" && newVersion != "" {
|
if releaseNotes != "" && newVersion != "" {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Print(releaseNotes)
|
fmt.Print(releaseNotes)
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("no release notes available for %s", newVersion)
|
warnMessages = append(warnMessages, fmt.Sprintf("no release notes available for %s", newVersion))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range warnMessages {
|
||||||
|
log.Warn(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
if NoInput {
|
if NoInput {
|
||||||
@ -50,16 +88,66 @@ func NewVersionOverview(app appPkg.App, currentVersion, chaosVersion, newVersion
|
|||||||
}
|
}
|
||||||
|
|
||||||
response := false
|
response := false
|
||||||
prompt := &survey.Confirm{
|
prompt := &survey.Confirm{Message: "proceed?"}
|
||||||
Message: "continue with deployment?",
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := survey.AskOne(prompt, &response); err != nil {
|
if err := survey.AskOne(prompt, &response); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !response {
|
if !response {
|
||||||
log.Fatal("exiting as requested")
|
log.Fatal("deployment cancelled")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeployOverview shows a deployment overview
|
||||||
|
func DeployOverview(app appPkg.App, warnMessages []string, version, chaosVersion string) error {
|
||||||
|
deployConfig := "compose.yml"
|
||||||
|
if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
|
||||||
|
deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
server := app.Server
|
||||||
|
if app.Server == "default" {
|
||||||
|
server = "local"
|
||||||
|
}
|
||||||
|
|
||||||
|
body := strings.Builder{}
|
||||||
|
body.WriteString(
|
||||||
|
borderStyle.Render(
|
||||||
|
lipgloss.JoinVertical(
|
||||||
|
lipgloss.Center,
|
||||||
|
headerStyle.Render("DEPLOY OVERVIEW"),
|
||||||
|
lipgloss.JoinVertical(
|
||||||
|
lipgloss.Left,
|
||||||
|
horizontal(leftStyle.Render("SERVER"), " ", rightStyle.Render(server)),
|
||||||
|
horizontal(leftStyle.Render("DOMAIN"), " ", rightStyle.Render(app.Domain)),
|
||||||
|
horizontal(leftStyle.Render("RECIPE"), " ", rightStyle.Render(app.Recipe.Name)),
|
||||||
|
horizontal(leftStyle.Render("CONFIG"), " ", rightStyle.Render(deployConfig)),
|
||||||
|
horizontal(leftStyle.Render("VERSION"), " ", rightStyle.Render(version)),
|
||||||
|
horizontal(leftStyle.Render("CHAOS"), " ", rightStyle.Padding(0).Render(chaosVersion)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
fmt.Println(body.String())
|
||||||
|
|
||||||
|
for _, msg := range warnMessages {
|
||||||
|
log.Warn(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if NoInput {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
response := false
|
||||||
|
prompt := &survey.Confirm{Message: "proceed?"}
|
||||||
|
if err := survey.AskOne(prompt, &response); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !response {
|
||||||
|
log.Fatal("deployment cancelled")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -118,48 +206,3 @@ func PostCmds(cl *dockerClient.Client, app appPkg.App, commands string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeployOverview shows a deployment overview
|
|
||||||
func DeployOverview(app appPkg.App, version, chaosVersion, message string) error {
|
|
||||||
tableCol := []string{"server", "recipe", "config", "domain", "version", "chaos"}
|
|
||||||
table := formatter.CreateTable(tableCol)
|
|
||||||
|
|
||||||
deployConfig := "compose.yml"
|
|
||||||
if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
|
|
||||||
deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
server := app.Server
|
|
||||||
if app.Server == "default" {
|
|
||||||
server = "local"
|
|
||||||
}
|
|
||||||
|
|
||||||
table.Append([]string{
|
|
||||||
server,
|
|
||||||
app.Recipe.Name,
|
|
||||||
deployConfig,
|
|
||||||
app.Domain,
|
|
||||||
version,
|
|
||||||
chaosVersion,
|
|
||||||
})
|
|
||||||
table.Render()
|
|
||||||
|
|
||||||
if NoInput {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
response := false
|
|
||||||
prompt := &survey.Confirm{
|
|
||||||
Message: message,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := survey.AskOne(prompt, &response); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !response {
|
|
||||||
log.Fatal("exiting as requested")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -4,13 +4,13 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ShowSubcommandHelpAndError exits the program on error, logs the error to the
|
// ShowSubcommandHelpAndError exits the program on error, logs the error to the
|
||||||
// terminal, and shows the help command.
|
// terminal, and shows the help command.
|
||||||
func ShowSubcommandHelpAndError(cmd *cli.Command, err interface{}) {
|
func ShowSubcommandHelpAndError(c *cli.Context, err interface{}) {
|
||||||
if err2 := cli.ShowSubcommandHelp(cmd); err2 != nil {
|
if err2 := cli.ShowSubcommandHelp(c); err2 != nil {
|
||||||
log.Error(err2)
|
log.Error(err2)
|
||||||
}
|
}
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
@ -9,12 +9,12 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateRecipe ensures the recipe arg is valid.
|
// ValidateRecipe ensures the recipe arg is valid.
|
||||||
func ValidateRecipe(cmd *cli.Command) recipe.Recipe {
|
func ValidateRecipe(c *cli.Context) recipe.Recipe {
|
||||||
recipeName := cmd.Args().First()
|
recipeName := c.Args().First()
|
||||||
|
|
||||||
if recipeName == "" && !NoInput {
|
if recipeName == "" && !NoInput {
|
||||||
var recipes []string
|
var recipes []string
|
||||||
@ -54,7 +54,7 @@ func ValidateRecipe(cmd *cli.Command) recipe.Recipe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if recipeName == "" {
|
if recipeName == "" {
|
||||||
ShowSubcommandHelpAndError(cmd, errors.New("no recipe name provided"))
|
ShowSubcommandHelpAndError(c, errors.New("no recipe name provided"))
|
||||||
}
|
}
|
||||||
|
|
||||||
chosenRecipe := recipe.Get(recipeName)
|
chosenRecipe := recipe.Get(recipeName)
|
||||||
@ -64,7 +64,7 @@ func ValidateRecipe(cmd *cli.Command) recipe.Recipe {
|
|||||||
}
|
}
|
||||||
_, err = chosenRecipe.GetComposeConfig(nil)
|
_, err = chosenRecipe.GetComposeConfig(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if cmd.Name == "generate" {
|
if c.Command.Name == "generate" {
|
||||||
if strings.Contains(err.Error(), "missing a compose") {
|
if strings.Contains(err.Error(), "missing a compose") {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -83,11 +83,11 @@ func ValidateRecipe(cmd *cli.Command) recipe.Recipe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateApp ensures the app name arg is valid.
|
// ValidateApp ensures the app name arg is valid.
|
||||||
func ValidateApp(cmd *cli.Command) app.App {
|
func ValidateApp(c *cli.Context) app.App {
|
||||||
appName := cmd.Args().First()
|
appName := c.Args().First()
|
||||||
|
|
||||||
if appName == "" {
|
if appName == "" {
|
||||||
ShowSubcommandHelpAndError(cmd, errors.New("no app provided"))
|
ShowSubcommandHelpAndError(c, errors.New("no app provided"))
|
||||||
}
|
}
|
||||||
|
|
||||||
app, err := app.Get(appName)
|
app, err := app.Get(appName)
|
||||||
@ -101,8 +101,8 @@ func ValidateApp(cmd *cli.Command) app.App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateDomain ensures the domain name arg is valid.
|
// ValidateDomain ensures the domain name arg is valid.
|
||||||
func ValidateDomain(cmd *cli.Command) string {
|
func ValidateDomain(c *cli.Context) string {
|
||||||
domainName := cmd.Args().First()
|
domainName := c.Args().First()
|
||||||
|
|
||||||
if domainName == "" && !NoInput {
|
if domainName == "" && !NoInput {
|
||||||
prompt := &survey.Input{
|
prompt := &survey.Input{
|
||||||
@ -115,7 +115,7 @@ func ValidateDomain(cmd *cli.Command) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if domainName == "" {
|
if domainName == "" {
|
||||||
ShowSubcommandHelpAndError(cmd, errors.New("no domain provided"))
|
ShowSubcommandHelpAndError(c, errors.New("no domain provided"))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("validated %s as domain argument", domainName)
|
log.Debugf("validated %s as domain argument", domainName)
|
||||||
@ -124,10 +124,10 @@ func ValidateDomain(cmd *cli.Command) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateSubCmdFlags ensures flag order conforms to correct order
|
// ValidateSubCmdFlags ensures flag order conforms to correct order
|
||||||
func ValidateSubCmdFlags(cmd *cli.Command) bool {
|
func ValidateSubCmdFlags(c *cli.Context) bool {
|
||||||
for argIdx, arg := range cmd.Args().Slice() {
|
for argIdx, arg := range c.Args() {
|
||||||
if !strings.HasPrefix(arg, "--") {
|
if !strings.HasPrefix(arg, "--") {
|
||||||
for _, flag := range cmd.Args().Slice()[argIdx:] {
|
for _, flag := range c.Args()[argIdx:] {
|
||||||
if strings.HasPrefix(flag, "--") {
|
if strings.HasPrefix(flag, "--") {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -138,8 +138,8 @@ func ValidateSubCmdFlags(cmd *cli.Command) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateServer ensures the server name arg is valid.
|
// ValidateServer ensures the server name arg is valid.
|
||||||
func ValidateServer(cmd *cli.Command) string {
|
func ValidateServer(c *cli.Context) string {
|
||||||
serverName := cmd.Args().First()
|
serverName := c.Args().First()
|
||||||
|
|
||||||
serverNames, err := config.ReadServerNames()
|
serverNames, err := config.ReadServerNames()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -164,11 +164,11 @@ func ValidateServer(cmd *cli.Command) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if serverName == "" {
|
if serverName == "" {
|
||||||
ShowSubcommandHelpAndError(cmd, errors.New("no server provided"))
|
ShowSubcommandHelpAndError(c, errors.New("no server provided"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !matched {
|
if !matched {
|
||||||
ShowSubcommandHelpAndError(cmd, errors.New("server doesn't exist?"))
|
ShowSubcommandHelpAndError(c, errors.New("server doesn't exist?"))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("validated %s as server argument", serverName)
|
log.Debugf("validated %s as server argument", serverName)
|
||||||
|
46
cli/newcli.go
Normal file
46
cli/newcli.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"coopcloud.tech/abra/pkg/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Debug bool
|
||||||
|
Offline bool
|
||||||
|
NoInput bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func RunApp2(version, commit string) {
|
||||||
|
rootCmd := &cobra.Command{
|
||||||
|
Use: "abra",
|
||||||
|
Short: "The Co-op Cloud command-line utility belt 🎩🐇",
|
||||||
|
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
|
||||||
|
PreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
log.Info("HELLO LOGGING")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(
|
||||||
|
&Debug, "debug", "d", false,
|
||||||
|
"show debug messages",
|
||||||
|
)
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(
|
||||||
|
&NoInput, "no-input", "n", false,
|
||||||
|
"toggle non-interactive mode",
|
||||||
|
)
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(
|
||||||
|
&Offline, "offline", "o", false,
|
||||||
|
"prefer offline & filesystem access",
|
||||||
|
)
|
||||||
|
|
||||||
|
rootCmd.AddCommand(UpgradeCommand)
|
||||||
|
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +1,27 @@
|
|||||||
package recipe
|
package recipe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
gitPkg "coopcloud.tech/abra/pkg/git"
|
gitPkg "coopcloud.tech/abra/pkg/git"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var recipeDiffCommand = cli.Command{
|
var recipeDiffCommand = cli.Command{
|
||||||
Name: "diff",
|
Name: "diff",
|
||||||
Usage: "Show unstaged changes in recipe config",
|
Usage: "Show unstaged changes in recipe config",
|
||||||
Description: "This command requires /usr/bin/git.",
|
Description: "This command requires /usr/bin/git.",
|
||||||
HideHelpCommand: true,
|
Aliases: []string{"d"},
|
||||||
Aliases: []string{"d"},
|
ArgsUsage: "<recipe>",
|
||||||
UsageText: "abra recipe diff [options] <recipe>",
|
Flags: []cli.Flag{
|
||||||
Before: internal.SubCommandBefore,
|
internal.DebugFlag,
|
||||||
EnableShellCompletion: true,
|
internal.NoInputFlag,
|
||||||
ShellComplete: autocomplete.RecipeNameComplete,
|
},
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Before: internal.SubCommandBefore,
|
||||||
r := internal.ValidateRecipe(cmd)
|
BashComplete: autocomplete.RecipeNameComplete,
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
r := internal.ValidateRecipe(c)
|
||||||
|
|
||||||
if err := gitPkg.DiffUnstaged(r.Dir); err != nil {
|
if err := gitPkg.DiffUnstaged(r.Dir); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -1,30 +1,32 @@
|
|||||||
package recipe
|
package recipe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var recipeFetchCommand = cli.Command{
|
var recipeFetchCommand = cli.Command{
|
||||||
Name: "fetch",
|
Name: "fetch",
|
||||||
Usage: "Fetch recipe(s)",
|
Usage: "Fetch recipe(s)",
|
||||||
Aliases: []string{"f"},
|
Aliases: []string{"f"},
|
||||||
UsageText: "abra recipe fetch [options] [<recipe>]",
|
ArgsUsage: "[<recipe>]",
|
||||||
Description: "Retrieves all recipes if no <recipe> argument is passed",
|
Description: "Retrieves all recipes if no <recipe> argument is passed",
|
||||||
Before: internal.SubCommandBefore,
|
Flags: []cli.Flag{
|
||||||
EnableShellCompletion: true,
|
internal.DebugFlag,
|
||||||
ShellComplete: autocomplete.RecipeNameComplete,
|
internal.NoInputFlag,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
internal.OfflineFlag,
|
||||||
recipeName := cmd.Args().First()
|
},
|
||||||
|
Before: internal.SubCommandBefore,
|
||||||
|
BashComplete: autocomplete.RecipeNameComplete,
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
recipeName := c.Args().First()
|
||||||
r := recipe.Get(recipeName)
|
r := recipe.Get(recipeName)
|
||||||
if recipeName != "" {
|
if recipeName != "" {
|
||||||
internal.ValidateRecipe(cmd)
|
internal.ValidateRecipe(c)
|
||||||
if err := r.Ensure(false, false); err != nil {
|
if err := r.Ensure(false, false); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package recipe
|
package recipe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
@ -9,34 +8,49 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/lint"
|
"coopcloud.tech/abra/pkg/lint"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var recipeLintCommand = cli.Command{
|
var recipeLintCommand = cli.Command{
|
||||||
Name: "lint",
|
Name: "lint",
|
||||||
Usage: "Lint a recipe",
|
Usage: "Lint a recipe",
|
||||||
Aliases: []string{"l"},
|
Aliases: []string{"l"},
|
||||||
UsageText: "abra recipe lint [options] <recipe>",
|
ArgsUsage: "<recipe>",
|
||||||
HideHelpCommand: true,
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
internal.OnlyErrorFlag,
|
internal.OnlyErrorFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
|
internal.NoInputFlag,
|
||||||
internal.ChaosFlag,
|
internal.ChaosFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.RecipeNameComplete,
|
||||||
ShellComplete: autocomplete.RecipeNameComplete,
|
Action: func(c *cli.Context) error {
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
recipe := internal.ValidateRecipe(c)
|
||||||
recipe := internal.ValidateRecipe(cmd)
|
|
||||||
|
|
||||||
if err := recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
if err := recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCol := []string{"ref", "rule", "severity", "satisfied", "skipped", "resolve"}
|
headers := []string{
|
||||||
table := formatter.CreateTable(tableCol)
|
"ref",
|
||||||
|
"rule",
|
||||||
|
"severity",
|
||||||
|
"satisfied",
|
||||||
|
"skipped",
|
||||||
|
"resolve",
|
||||||
|
}
|
||||||
|
|
||||||
|
table, err := formatter.CreateTable()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
table.Headers(headers...)
|
||||||
|
|
||||||
hasError := false
|
hasError := false
|
||||||
bar := formatter.CreateProgressbar(-1, "running recipe lint rules...")
|
var rows [][]string
|
||||||
|
var warnMessages []string
|
||||||
for level := range lint.LintRules {
|
for level := range lint.LintRules {
|
||||||
for _, rule := range lint.LintRules[level] {
|
for _, rule := range lint.LintRules[level] {
|
||||||
if internal.OnlyErrors && rule.Level != "error" {
|
if internal.OnlyErrors && rule.Level != "error" {
|
||||||
@ -58,7 +72,7 @@ var recipeLintCommand = cli.Command{
|
|||||||
if !skipped {
|
if !skipped {
|
||||||
ok, err := rule.Function(recipe)
|
ok, err := rule.Function(recipe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(err)
|
warnMessages = append(warnMessages, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok && rule.Level == "error" {
|
if !ok && rule.Level == "error" {
|
||||||
@ -78,26 +92,30 @@ var recipeLintCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Append([]string{
|
row := []string{
|
||||||
rule.Ref,
|
rule.Ref,
|
||||||
rule.Description,
|
rule.Description,
|
||||||
rule.Level,
|
rule.Level,
|
||||||
satisfiedOutput,
|
satisfiedOutput,
|
||||||
skippedOutput,
|
skippedOutput,
|
||||||
rule.HowToResolve,
|
rule.HowToResolve,
|
||||||
})
|
}
|
||||||
|
|
||||||
bar.Add(1)
|
rows = append(rows, row)
|
||||||
|
table.Row(row...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if table.NumLines() > 0 {
|
if len(rows) > 0 {
|
||||||
fmt.Println()
|
fmt.Println(table)
|
||||||
table.Render()
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasError {
|
for _, warnMsg := range warnMessages {
|
||||||
log.Warn("watch out, some critical errors are present in your recipe config")
|
log.Warn(warnMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasError {
|
||||||
|
log.Warnf("critical errors present in %s config", recipe.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package recipe
|
package recipe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -11,30 +10,29 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var pattern string
|
var pattern string
|
||||||
var patternFlag = &cli.StringFlag{
|
var patternFlag = &cli.StringFlag{
|
||||||
Name: "pattern",
|
Name: "pattern, p",
|
||||||
Aliases: []string{"p"},
|
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "Simple string to filter recipes",
|
Usage: "Simple string to filter recipes",
|
||||||
Destination: &pattern,
|
Destination: &pattern,
|
||||||
}
|
}
|
||||||
|
|
||||||
var recipeListCommand = cli.Command{
|
var recipeListCommand = cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Usage: "List recipes",
|
Usage: "List available recipes",
|
||||||
HideHelpCommand: true,
|
Aliases: []string{"ls"},
|
||||||
UsageText: "abra recipe list [options]",
|
|
||||||
Aliases: []string{"ls"},
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
internal.MachineReadableFlag,
|
internal.MachineReadableFlag,
|
||||||
patternFlag,
|
patternFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(c *cli.Context) error {
|
||||||
catl, err := recipe.ReadRecipeCatalogue(internal.Offline)
|
catl, err := recipe.ReadRecipeCatalogue(internal.Offline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
||||||
@ -43,12 +41,27 @@ var recipeListCommand = cli.Command{
|
|||||||
recipes := catl.Flatten()
|
recipes := catl.Flatten()
|
||||||
sort.Sort(recipe.ByRecipeName(recipes))
|
sort.Sort(recipe.ByRecipeName(recipes))
|
||||||
|
|
||||||
tableCol := []string{"name", "category", "status", "healthcheck", "backups", "email", "tests", "SSO"}
|
table, err := formatter.CreateTable()
|
||||||
table := formatter.CreateTable(tableCol)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
len := 0
|
headers := []string{
|
||||||
|
"name",
|
||||||
|
"category",
|
||||||
|
"status",
|
||||||
|
"healthcheck",
|
||||||
|
"backups",
|
||||||
|
"email",
|
||||||
|
"tests",
|
||||||
|
"SSO",
|
||||||
|
}
|
||||||
|
|
||||||
|
table.Headers(headers...)
|
||||||
|
|
||||||
|
var rows [][]string
|
||||||
for _, recipe := range recipes {
|
for _, recipe := range recipes {
|
||||||
tableRow := []string{
|
row := []string{
|
||||||
recipe.Name,
|
recipe.Name,
|
||||||
recipe.Category,
|
recipe.Category,
|
||||||
strconv.Itoa(recipe.Features.Status),
|
strconv.Itoa(recipe.Features.Status),
|
||||||
@ -61,23 +74,27 @@ var recipeListCommand = cli.Command{
|
|||||||
|
|
||||||
if pattern != "" {
|
if pattern != "" {
|
||||||
if strings.Contains(recipe.Name, pattern) {
|
if strings.Contains(recipe.Name, pattern) {
|
||||||
table.Append(tableRow)
|
table.Row(row...)
|
||||||
len++
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
table.Append(tableRow)
|
table.Row(row...)
|
||||||
len++
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if table.NumLines() > 0 {
|
if len(rows) > 0 {
|
||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
table.SetCaption(false, "")
|
out, err := formatter.ToJSON(headers, rows)
|
||||||
table.JSONRender()
|
if err != nil {
|
||||||
} else {
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
table.SetCaption(true, fmt.Sprintf("total recipes: %v", len))
|
}
|
||||||
table.Render()
|
fmt.Println(out)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println(table)
|
||||||
|
log.Infof("total recipes: %v", len(rows))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -2,7 +2,6 @@ package recipe
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@ -14,7 +13,7 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/git"
|
"coopcloud.tech/abra/pkg/git"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// recipeMetadata is the recipe metadata for the README.md
|
// recipeMetadata is the recipe metadata for the README.md
|
||||||
@ -35,24 +34,27 @@ var recipeNewCommand = cli.Command{
|
|||||||
Name: "new",
|
Name: "new",
|
||||||
Aliases: []string{"n"},
|
Aliases: []string{"n"},
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.NoInputFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
internal.GitNameFlag,
|
internal.GitNameFlag,
|
||||||
internal.GitEmailFlag,
|
internal.GitEmailFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Usage: "Create a new recipe",
|
Usage: "Create a new recipe",
|
||||||
UsageText: "abra recipe new [options] <recipe>",
|
ArgsUsage: "<recipe>",
|
||||||
HideHelpCommand: true,
|
Description: `
|
||||||
Description: `Create a new recipe.
|
Create a new recipe.
|
||||||
|
|
||||||
Abra uses the built-in example repository which is available here:
|
Abra uses the built-in example repository which is available here:
|
||||||
|
|
||||||
https://git.coopcloud.tech/coop-cloud/example`,
|
https://git.coopcloud.tech/coop-cloud/example`,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(c *cli.Context) error {
|
||||||
recipeName := cmd.Args().First()
|
recipeName := c.Args().First()
|
||||||
r := recipe.Get(recipeName)
|
r := recipe.Get(recipeName)
|
||||||
|
|
||||||
if recipeName == "" {
|
if recipeName == "" {
|
||||||
internal.ShowSubcommandHelpAndError(cmd, errors.New("no recipe name provided"))
|
internal.ShowSubcommandHelpAndError(c, errors.New("no recipe name provided"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(r.Dir); !os.IsNotExist(err) {
|
if _, err := os.Stat(r.Dir); !os.IsNotExist(err) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package recipe
|
package recipe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RecipeCommand defines all recipe related sub-commands.
|
// RecipeCommand defines all recipe related sub-commands.
|
||||||
@ -9,8 +9,9 @@ var RecipeCommand = cli.Command{
|
|||||||
Name: "recipe",
|
Name: "recipe",
|
||||||
Aliases: []string{"r"},
|
Aliases: []string{"r"},
|
||||||
Usage: "Manage recipes",
|
Usage: "Manage recipes",
|
||||||
UsageText: "abra recipe [command] [options] [arguments]",
|
ArgsUsage: "<recipe>",
|
||||||
Description: `A recipe is a blueprint for an app. It is a bunch of config files which
|
Description: `
|
||||||
|
A recipe is a blueprint for an app. It is a bunch of config files which
|
||||||
describe how to deploy and maintain an app. Recipes are maintained by the Co-op
|
describe how to deploy and maintain an app. Recipes are maintained by the Co-op
|
||||||
Cloud community and you can use Abra to read them, deploy them and create apps
|
Cloud community and you can use Abra to read them, deploy them and create apps
|
||||||
for you.
|
for you.
|
||||||
@ -18,17 +19,16 @@ for you.
|
|||||||
Anyone who uses a recipe can become a maintainer. Maintainers typically make
|
Anyone who uses a recipe can become a maintainer. Maintainers typically make
|
||||||
sure the recipe is in good working order and the config upgraded in a timely
|
sure the recipe is in good working order and the config upgraded in a timely
|
||||||
manner.`,
|
manner.`,
|
||||||
HideHelpCommand: true,
|
Subcommands: []cli.Command{
|
||||||
Commands: []*cli.Command{
|
recipeFetchCommand,
|
||||||
&recipeFetchCommand,
|
recipeLintCommand,
|
||||||
&recipeLintCommand,
|
recipeListCommand,
|
||||||
&recipeListCommand,
|
recipeNewCommand,
|
||||||
&recipeNewCommand,
|
recipeReleaseCommand,
|
||||||
&recipeReleaseCommand,
|
recipeSyncCommand,
|
||||||
&recipeSyncCommand,
|
recipeUpgradeCommand,
|
||||||
&recipeUpgradeCommand,
|
recipeVersionCommand,
|
||||||
&recipeVersionCommand,
|
recipeResetCommand,
|
||||||
&recipeResetCommand,
|
recipeDiffCommand,
|
||||||
&recipeDiffCommand,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package recipe
|
package recipe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@ -19,19 +18,17 @@ import (
|
|||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var recipeReleaseCommand = cli.Command{
|
var recipeReleaseCommand = cli.Command{
|
||||||
Name: "release",
|
Name: "release",
|
||||||
Aliases: []string{"rl"},
|
Aliases: []string{"rl"},
|
||||||
Usage: "Release a new recipe version",
|
Usage: "Release a new recipe version",
|
||||||
HideHelpCommand: true,
|
ArgsUsage: "<recipe> [<version>]",
|
||||||
UsageText: "abra recipe release [options] <recipe> [<version>]",
|
Description: `
|
||||||
Description: `Create a new version of a recipe.
|
Create a new version of a recipe. These versions are then published on the
|
||||||
|
Co-op Cloud recipe catalogue. These versions take the following form:
|
||||||
These versions are then published on the Co-op Cloud recipe catalogue. These
|
|
||||||
versions take the following form:
|
|
||||||
|
|
||||||
a.b.c+x.y.z
|
a.b.c+x.y.z
|
||||||
|
|
||||||
@ -49,17 +46,19 @@ Publish your new release to git.coopcloud.tech with "-p/--publish". This
|
|||||||
requires that you have permission to git push to these repositories and have
|
requires that you have permission to git push to these repositories and have
|
||||||
your SSH keys configured on your account.`,
|
your SSH keys configured on your account.`,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.NoInputFlag,
|
||||||
internal.DryFlag,
|
internal.DryFlag,
|
||||||
internal.MajorFlag,
|
internal.MajorFlag,
|
||||||
internal.MinorFlag,
|
internal.MinorFlag,
|
||||||
internal.PatchFlag,
|
internal.PatchFlag,
|
||||||
internal.PublishFlag,
|
internal.PublishFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.RecipeNameComplete,
|
||||||
ShellComplete: autocomplete.RecipeNameComplete,
|
Action: func(c *cli.Context) error {
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
recipe := internal.ValidateRecipe(c)
|
||||||
recipe := internal.ValidateRecipe(cmd)
|
|
||||||
|
|
||||||
imagesTmp, err := getImageVersions(recipe)
|
imagesTmp, err := getImageVersions(recipe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -76,7 +75,7 @@ your SSH keys configured on your account.`,
|
|||||||
log.Fatalf("main app service version for %s is empty?", recipe.Name)
|
log.Fatalf("main app service version for %s is empty?", recipe.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
tagString := cmd.Args().Get(1)
|
tagString := c.Args().Get(1)
|
||||||
if tagString != "" {
|
if tagString != "" {
|
||||||
if _, err := tagcmp.Parse(tagString); err != nil {
|
if _, err := tagcmp.Parse(tagString); err != nil {
|
||||||
log.Fatalf("cannot parse %s, invalid tag specified?", tagString)
|
log.Fatalf("cannot parse %s, invalid tag specified?", tagString)
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
package recipe
|
package recipe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var recipeResetCommand = cli.Command{
|
var recipeResetCommand = cli.Command{
|
||||||
Name: "reset",
|
Name: "reset",
|
||||||
Usage: "Remove all unstaged changes from recipe config",
|
Usage: "Remove all unstaged changes from recipe config",
|
||||||
Description: "WARNING: this will delete your changes. Be Careful.",
|
Description: "WARNING: this will delete your changes. Be Careful.",
|
||||||
HideHelpCommand: true,
|
Aliases: []string{"rs"},
|
||||||
Aliases: []string{"rs"},
|
ArgsUsage: "<recipe>",
|
||||||
UsageText: "abra recipe reset [options] <recipe>",
|
Flags: []cli.Flag{
|
||||||
Before: internal.SubCommandBefore,
|
internal.DebugFlag,
|
||||||
EnableShellCompletion: true,
|
internal.NoInputFlag,
|
||||||
ShellComplete: autocomplete.RecipeNameComplete,
|
},
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Before: internal.SubCommandBefore,
|
||||||
recipeName := cmd.Args().First()
|
BashComplete: autocomplete.RecipeNameComplete,
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
recipeName := c.Args().First()
|
||||||
r := recipe.Get(recipeName)
|
r := recipe.Get(recipeName)
|
||||||
|
|
||||||
if recipeName != "" {
|
if recipeName != "" {
|
||||||
internal.ValidateRecipe(cmd)
|
internal.ValidateRecipe(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
repo, err := git.PlainOpen(r.Dir)
|
repo, err := git.PlainOpen(r.Dir)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package recipe
|
package recipe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@ -13,35 +12,35 @@ import (
|
|||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var recipeSyncCommand = cli.Command{
|
var recipeSyncCommand = cli.Command{
|
||||||
Name: "sync",
|
Name: "sync",
|
||||||
Aliases: []string{"s"},
|
Aliases: []string{"s"},
|
||||||
Usage: "Sync recipe version label",
|
Usage: "Sync recipe version label",
|
||||||
HideHelpCommand: true,
|
ArgsUsage: "<recipe> [<version>]",
|
||||||
UsageText: "abra recipe lint [options] <recipe> [<version>]",
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.NoInputFlag,
|
||||||
internal.DryFlag,
|
internal.DryFlag,
|
||||||
internal.MajorFlag,
|
internal.MajorFlag,
|
||||||
internal.MinorFlag,
|
internal.MinorFlag,
|
||||||
internal.PatchFlag,
|
internal.PatchFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Description: `Generate labels for the main recipe service.
|
Description: `
|
||||||
|
Generate labels for the main recipe service (i.e. by convention, the service
|
||||||
By convention, the service named "app" using the following format:
|
named "app") which corresponds to the following format:
|
||||||
|
|
||||||
coop-cloud.${STACK_NAME}.version=<version>
|
coop-cloud.${STACK_NAME}.version=<version>
|
||||||
|
|
||||||
Where <version> can be specifed on the command-line or Abra can attempt to
|
Where <version> can be specifed on the command-line or Abra can attempt to
|
||||||
auto-generate it for you. The <recipe> configuration will be updated on the
|
auto-generate it for you. The <recipe> configuration will be updated on the
|
||||||
local file system.`,
|
local file system.`,
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.RecipeNameComplete,
|
||||||
ShellComplete: autocomplete.RecipeNameComplete,
|
Action: func(c *cli.Context) error {
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
recipe := internal.ValidateRecipe(c)
|
||||||
recipe := internal.ValidateRecipe(cmd)
|
|
||||||
|
|
||||||
mainApp, err := internal.GetMainAppImage(recipe)
|
mainApp, err := internal.GetMainAppImage(recipe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -60,7 +59,7 @@ local file system.`,
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTag := cmd.Args().Get(1)
|
nextTag := c.Args().Get(1)
|
||||||
if len(tags) == 0 && nextTag == "" {
|
if len(tags) == 0 && nextTag == "" {
|
||||||
log.Warnf("no git tags found for %s", recipe.Name)
|
log.Warnf("no git tags found for %s", recipe.Name)
|
||||||
if internal.NoInput {
|
if internal.NoInput {
|
||||||
|
@ -2,7 +2,6 @@ package recipe
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@ -20,7 +19,7 @@ import (
|
|||||||
"coopcloud.tech/tagcmp"
|
"coopcloud.tech/tagcmp"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
type imgPin struct {
|
type imgPin struct {
|
||||||
@ -28,8 +27,8 @@ type imgPin struct {
|
|||||||
version tagcmp.Tag
|
version tagcmp.Tag
|
||||||
}
|
}
|
||||||
|
|
||||||
// anUpgrade represents a single service upgrade (as within a recipe), and the
|
// anUpgrade represents a single service upgrade (as within a recipe), and the list of tags that it can be upgraded to,
|
||||||
// list of tags that it can be upgraded to, for serialization purposes.
|
// for serialization purposes.
|
||||||
type anUpgrade struct {
|
type anUpgrade struct {
|
||||||
Service string `json:"service"`
|
Service string `json:"service"`
|
||||||
Image string `json:"image"`
|
Image string `json:"image"`
|
||||||
@ -38,13 +37,13 @@ type anUpgrade struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var recipeUpgradeCommand = cli.Command{
|
var recipeUpgradeCommand = cli.Command{
|
||||||
Name: "upgrade",
|
Name: "upgrade",
|
||||||
Aliases: []string{"u"},
|
Aliases: []string{"u"},
|
||||||
Usage: "Upgrade recipe image tags",
|
Usage: "Upgrade recipe image tags",
|
||||||
HideHelpCommand: true,
|
Description: `
|
||||||
Description: `Upgrade a given <recipe> configuration.
|
Parse all image tags within the given <recipe> configuration and prompt with
|
||||||
|
more recent tags to upgrade to. It will update the relevant compose file tags
|
||||||
It will update the relevant compose file tags on the local file system.
|
on the local file system.
|
||||||
|
|
||||||
Some image tags cannot be parsed because they do not follow some sort of
|
Some image tags cannot be parsed because they do not follow some sort of
|
||||||
semver-like convention. In this case, all possible tags will be listed and it
|
semver-like convention. In this case, all possible tags will be listed and it
|
||||||
@ -54,20 +53,25 @@ The command is interactive and will show a select input which allows you to
|
|||||||
make a seclection. Use the "?" key to see more help on navigating this
|
make a seclection. Use the "?" key to see more help on navigating this
|
||||||
interface.
|
interface.
|
||||||
|
|
||||||
You may invoke this command in "wizard" mode and be prompted for input.`,
|
You may invoke this command in "wizard" mode and be prompted for input.
|
||||||
UsageText: "abra recipe upgrade [options] [<recipe>]",
|
|
||||||
|
EXAMPLE:
|
||||||
|
|
||||||
|
abra recipe upgrade`,
|
||||||
|
ArgsUsage: "<recipe>",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.NoInputFlag,
|
||||||
internal.PatchFlag,
|
internal.PatchFlag,
|
||||||
internal.MinorFlag,
|
internal.MinorFlag,
|
||||||
internal.MajorFlag,
|
internal.MajorFlag,
|
||||||
internal.MachineReadableFlag,
|
internal.MachineReadableFlag,
|
||||||
internal.AllTagsFlag,
|
internal.AllTagsFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.RecipeNameComplete,
|
||||||
ShellComplete: autocomplete.RecipeNameComplete,
|
Action: func(c *cli.Context) error {
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
recipe := internal.ValidateRecipe(c)
|
||||||
recipe := internal.ValidateRecipe(cmd)
|
|
||||||
|
|
||||||
if err := recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
if err := recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package recipe
|
package recipe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
@ -10,8 +9,7 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/olekukonko/tablewriter"
|
"github.com/urfave/cli"
|
||||||
"github.com/urfave/cli/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func sortServiceByName(versions [][]string) func(i, j int) bool {
|
func sortServiceByName(versions [][]string) func(i, j int) bool {
|
||||||
@ -25,19 +23,22 @@ func sortServiceByName(versions [][]string) func(i, j int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var recipeVersionCommand = cli.Command{
|
var recipeVersionCommand = cli.Command{
|
||||||
Name: "versions",
|
Name: "versions",
|
||||||
Aliases: []string{"v"},
|
Aliases: []string{"v"},
|
||||||
Usage: "List recipe versions",
|
Usage: "List recipe versions",
|
||||||
UsageText: "abra recipe version [options] <recipe>",
|
ArgsUsage: "<recipe>",
|
||||||
HideHelpCommand: true,
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
|
internal.NoInputFlag,
|
||||||
internal.MachineReadableFlag,
|
internal.MachineReadableFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.RecipeNameComplete,
|
||||||
ShellComplete: autocomplete.RecipeNameComplete,
|
Action: func(c *cli.Context) error {
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
var warnMessages []string
|
||||||
recipe := internal.ValidateRecipe(cmd)
|
|
||||||
|
recipe := internal.ValidateRecipe(c)
|
||||||
|
|
||||||
catl, err := recipePkg.ReadRecipeCatalogue(internal.Offline)
|
catl, err := recipePkg.ReadRecipeCatalogue(internal.Offline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -46,47 +47,65 @@ var recipeVersionCommand = cli.Command{
|
|||||||
|
|
||||||
recipeMeta, ok := catl[recipe.Name]
|
recipeMeta, ok := catl[recipe.Name]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Warn("no published versions in catalogue, trying local recipe repository")
|
warnMessages = append(warnMessages, "retrieved versions from local recipe repository")
|
||||||
|
|
||||||
recipeVersions, err := recipe.GetRecipeVersions()
|
recipeVersions, err := recipe.GetRecipeVersions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(err)
|
warnMessages = append(warnMessages, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
recipeMeta = recipePkg.RecipeMeta{Versions: recipeVersions}
|
recipeMeta = recipePkg.RecipeMeta{Versions: recipeVersions}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(recipeMeta.Versions) == 0 {
|
if len(recipeMeta.Versions) == 0 {
|
||||||
log.Fatalf("%s has no catalogue published versions?", recipe.Name)
|
log.Fatalf("%s has no published versions?", recipe.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
tableCols := []string{"version", "service", "image", "tag"}
|
|
||||||
aggregated_table := formatter.CreateTable(tableCols)
|
|
||||||
for i := len(recipeMeta.Versions) - 1; i >= 0; i-- {
|
for i := len(recipeMeta.Versions) - 1; i >= 0; i-- {
|
||||||
table := formatter.CreateTable(tableCols)
|
table, err := formatter.CreateTable()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
table.Headers("SERVICE", "NAME", "TAG")
|
||||||
|
|
||||||
for version, meta := range recipeMeta.Versions[i] {
|
for version, meta := range recipeMeta.Versions[i] {
|
||||||
var versions [][]string
|
var allRows [][]string
|
||||||
|
var rows [][]string
|
||||||
|
|
||||||
for service, serviceMeta := range meta {
|
for service, serviceMeta := range meta {
|
||||||
versions = append(versions, []string{version, service, serviceMeta.Image, serviceMeta.Tag})
|
rows = append(rows, []string{service, serviceMeta.Image, serviceMeta.Tag})
|
||||||
|
allRows = append(allRows, []string{version, service, serviceMeta.Image, serviceMeta.Tag})
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(versions, sortServiceByName(versions))
|
sort.Slice(rows, sortServiceByName(rows))
|
||||||
|
|
||||||
for _, version := range versions {
|
table.Rows(rows...)
|
||||||
table.Append(version)
|
|
||||||
aggregated_table.Append(version)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !internal.MachineReadable {
|
if !internal.MachineReadable {
|
||||||
table.SetAutoMergeCellsByColumnIndex([]int{0})
|
fmt.Println(table)
|
||||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
log.Infof("VERSION: %s", version)
|
||||||
table.Render()
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if internal.MachineReadable {
|
||||||
|
sort.Slice(allRows, sortServiceByName(allRows))
|
||||||
|
headers := []string{"VERSION", "SERVICE", "NAME", "TAG"}
|
||||||
|
out, err := formatter.ToJSON(headers, allRows)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
|
}
|
||||||
|
fmt.Println(out)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if internal.MachineReadable {
|
|
||||||
aggregated_table.JSONRender()
|
if !internal.MachineReadable {
|
||||||
|
for _, warnMsg := range warnMessages {
|
||||||
|
log.Warn(warnMsg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -14,13 +13,12 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/server"
|
"coopcloud.tech/abra/pkg/server"
|
||||||
sshPkg "coopcloud.tech/abra/pkg/ssh"
|
sshPkg "coopcloud.tech/abra/pkg/ssh"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var local bool
|
var local bool
|
||||||
var localFlag = &cli.BoolFlag{
|
var localFlag = &cli.BoolFlag{
|
||||||
Name: "local",
|
Name: "local, l",
|
||||||
Aliases: []string{"l"},
|
|
||||||
Usage: "Use local server",
|
Usage: "Use local server",
|
||||||
Destination: &local,
|
Destination: &local,
|
||||||
}
|
}
|
||||||
@ -94,16 +92,15 @@ func createServerDir(name string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var serverAddCommand = cli.Command{
|
var serverAddCommand = cli.Command{
|
||||||
Name: "add",
|
Name: "add",
|
||||||
Aliases: []string{"a"},
|
Aliases: []string{"a"},
|
||||||
Usage: "Add a new server",
|
Usage: "Add a new server to your configuration",
|
||||||
UsageText: "abra server add [options] <domain>",
|
Description: `
|
||||||
HideHelpCommand: true,
|
Add a new server to your configuration so that it can be managed by Abra.
|
||||||
Description: `Add a new server to your configuration so that it can be managed by Abra.
|
|
||||||
|
|
||||||
Abra relies on the standard SSH command-line and ~/.ssh/config for client
|
Abra relies on the standard SSH command-line and ~/.ssh/config for client
|
||||||
connection details. You must configure an entry per-host in your ~/.ssh/config
|
connection details. You must configure an entry per-host in your ~/.ssh/config
|
||||||
for each server:
|
for each server. For example:
|
||||||
|
|
||||||
Host example.com example
|
Host example.com example
|
||||||
Hostname example.com
|
Hostname example.com
|
||||||
@ -111,6 +108,10 @@ for each server:
|
|||||||
Port 12345
|
Port 12345
|
||||||
IdentityFile ~/.ssh/example@somewhere
|
IdentityFile ~/.ssh/example@somewhere
|
||||||
|
|
||||||
|
You can then add a server like so:
|
||||||
|
|
||||||
|
abra server add example.com
|
||||||
|
|
||||||
If "--local" is passed, then Abra assumes that the current local server is
|
If "--local" is passed, then Abra assumes that the current local server is
|
||||||
intended as the target server. This is useful when you want to have your entire
|
intended as the target server. This is useful when you want to have your entire
|
||||||
Co-op Cloud config located on the server itself, and not on your local
|
Co-op Cloud config located on the server itself, and not on your local
|
||||||
@ -118,24 +119,28 @@ developer machine. The domain is then set to "default".
|
|||||||
|
|
||||||
You can also pass "--no-domain-checks/-D" flag to use any arbitrary name
|
You can also pass "--no-domain-checks/-D" flag to use any arbitrary name
|
||||||
instead of a real domain. The host will be resolved with the "Hostname" entry
|
instead of a real domain. The host will be resolved with the "Hostname" entry
|
||||||
of your ~/.ssh/config. Checks for a valid online domain will be skipped.`,
|
of your ~/.ssh/config. Checks for a valid online domain will be skipped:
|
||||||
|
|
||||||
|
abra server add -D example`,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.NoInputFlag,
|
||||||
internal.NoDomainChecksFlag,
|
internal.NoDomainChecksFlag,
|
||||||
localFlag,
|
localFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
ArgsUsage: "<name>",
|
ArgsUsage: "<name>",
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(c *cli.Context) error {
|
||||||
if cmd.Args().Len() > 0 && local || !internal.ValidateSubCmdFlags(cmd) {
|
if len(c.Args()) > 0 && local || !internal.ValidateSubCmdFlags(c) {
|
||||||
err := errors.New("cannot use <name> and --local together")
|
err := errors.New("cannot use <name> and --local together")
|
||||||
internal.ShowSubcommandHelpAndError(cmd, err)
|
internal.ShowSubcommandHelpAndError(c, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var name string
|
var name string
|
||||||
if local {
|
if local {
|
||||||
name = "default"
|
name = "default"
|
||||||
} else {
|
} else {
|
||||||
name = internal.ValidateDomain(cmd)
|
name = internal.ValidateDomain(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE(d1): reasonable 5 second timeout for connections which can't
|
// NOTE(d1): reasonable 5 second timeout for connections which can't
|
||||||
|
@ -1,54 +1,59 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
contextPkg "coopcloud.tech/abra/pkg/context"
|
"coopcloud.tech/abra/pkg/context"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/docker/cli/cli/connhelper/ssh"
|
"github.com/docker/cli/cli/connhelper/ssh"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var serverListCommand = cli.Command{
|
var serverListCommand = cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "List managed servers",
|
Usage: "List managed servers",
|
||||||
UsageText: "abra server list [options]",
|
|
||||||
HideHelpCommand: true,
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
internal.DebugFlag,
|
internal.DebugFlag,
|
||||||
internal.MachineReadableFlag,
|
internal.MachineReadableFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(c *cli.Context) error {
|
||||||
dockerContextStore := contextPkg.NewDefaultDockerContextStore()
|
dockerContextStore := context.NewDefaultDockerContextStore()
|
||||||
contexts, err := dockerContextStore.Store.List()
|
contexts, err := dockerContextStore.Store.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tableColumns := []string{"name", "host"}
|
table, err := formatter.CreateTable()
|
||||||
table := formatter.CreateTable(tableColumns)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{"NAME", "HOST"}
|
||||||
|
table.Headers(headers...)
|
||||||
|
|
||||||
serverNames, err := config.ReadServerNames()
|
serverNames, err := config.ReadServerNames()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rows [][]string
|
||||||
for _, serverName := range serverNames {
|
for _, serverName := range serverNames {
|
||||||
var row []string
|
var row []string
|
||||||
for _, dockerCtx := range contexts {
|
for _, ctx := range contexts {
|
||||||
endpoint, err := contextPkg.GetContextEndpoint(dockerCtx)
|
endpoint, err := context.GetContextEndpoint(ctx)
|
||||||
if err != nil && strings.Contains(err.Error(), "does not exist") {
|
if err != nil && strings.Contains(err.Error(), "does not exist") {
|
||||||
// No local context found, we can continue safely
|
// No local context found, we can continue safely
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if dockerCtx.Name == serverName {
|
if ctx.Name == serverName {
|
||||||
sp, err := ssh.ParseURL(endpoint)
|
sp, err := ssh.ParseURL(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -59,6 +64,7 @@ var serverListCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
row = []string{serverName, sp.Host}
|
row = []string{serverName, sp.Host}
|
||||||
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,17 +74,22 @@ var serverListCommand = cli.Command{
|
|||||||
} else {
|
} else {
|
||||||
row = []string{serverName, "unknown"}
|
row = []string{serverName, "unknown"}
|
||||||
}
|
}
|
||||||
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Append(row)
|
table.Row(row...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
table.JSONRender()
|
out, err := formatter.ToJSON(headers, rows)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
|
}
|
||||||
|
fmt.Println(out)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Render()
|
fmt.Println(table)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -9,14 +9,13 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var allFilter bool
|
var allFilter bool
|
||||||
|
|
||||||
var allFilterFlag = &cli.BoolFlag{
|
var allFilterFlag = &cli.BoolFlag{
|
||||||
Name: "all",
|
Name: "all, a",
|
||||||
Aliases: []string{"a"},
|
|
||||||
Usage: "Remove all unused images not just dangling ones",
|
Usage: "Remove all unused images not just dangling ones",
|
||||||
Destination: &allFilter,
|
Destination: &allFilter,
|
||||||
}
|
}
|
||||||
@ -24,19 +23,17 @@ var allFilterFlag = &cli.BoolFlag{
|
|||||||
var volumesFilter bool
|
var volumesFilter bool
|
||||||
|
|
||||||
var volumesFilterFlag = &cli.BoolFlag{
|
var volumesFilterFlag = &cli.BoolFlag{
|
||||||
Name: "volumes",
|
Name: "volumes, v",
|
||||||
Aliases: []string{"v"},
|
|
||||||
Usage: "Prune volumes. This will remove app data, Be Careful!",
|
Usage: "Prune volumes. This will remove app data, Be Careful!",
|
||||||
Destination: &volumesFilter,
|
Destination: &volumesFilter,
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverPruneCommand = cli.Command{
|
var serverPruneCommand = cli.Command{
|
||||||
Name: "prune",
|
Name: "prune",
|
||||||
Aliases: []string{"p"},
|
Aliases: []string{"p"},
|
||||||
Usage: "Prune resources on a server",
|
Usage: "Prune resources on a server",
|
||||||
UsageText: "abra server prune [options] <server>",
|
Description: `
|
||||||
HideHelpCommand: true,
|
Prunes unused containers, networks, and dangling images.
|
||||||
Description: `Prunes unused containers, networks, and dangling images.
|
|
||||||
|
|
||||||
Use "-v/--volumes" to remove volumes that are not associated with a deployed
|
Use "-v/--volumes" to remove volumes that are not associated with a deployed
|
||||||
app. This can result in unwanted data loss if not used carefully.`,
|
app. This can result in unwanted data loss if not used carefully.`,
|
||||||
@ -44,12 +41,14 @@ app. This can result in unwanted data loss if not used carefully.`,
|
|||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
allFilterFlag,
|
allFilterFlag,
|
||||||
volumesFilterFlag,
|
volumesFilterFlag,
|
||||||
|
internal.DebugFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
|
internal.NoInputFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
EnableShellCompletion: true,
|
BashComplete: autocomplete.ServerNameComplete,
|
||||||
ShellComplete: autocomplete.ServerNameComplete,
|
Action: func(c *cli.Context) error {
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
serverName := internal.ValidateServer(c)
|
||||||
serverName := internal.ValidateServer(cmd)
|
|
||||||
|
|
||||||
cl, err := client.New(serverName)
|
cl, err := client.New(serverName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -58,6 +57,7 @@ app. This can result in unwanted data loss if not used carefully.`,
|
|||||||
|
|
||||||
var args filters.Args
|
var args filters.Args
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
cr, err := cl.ContainersPrune(ctx, args)
|
cr, err := cl.ContainersPrune(ctx, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@ -10,25 +9,29 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var serverRemoveCommand = cli.Command{
|
var serverRemoveCommand = cli.Command{
|
||||||
Name: "remove",
|
Name: "remove",
|
||||||
Aliases: []string{"rm"},
|
Aliases: []string{"rm"},
|
||||||
UsageText: "abra server remove [options] <domain>",
|
ArgsUsage: "<server>",
|
||||||
Usage: "Remove a managed server",
|
Usage: "Remove a managed server",
|
||||||
HideHelpCommand: true,
|
Description: `
|
||||||
Description: `Remove a managed server.
|
Remove a managed server.
|
||||||
|
|
||||||
Abra will remove the internal bookkeeping ($ABRA_DIR/servers/...) and
|
Abra will remove the internal bookkeeping (~/.abra/servers/...) and underlying
|
||||||
underlying client connection context. This server will then be lost in time,
|
client connection context. This server will then be lost in time, like tears in
|
||||||
like tears in rain.`,
|
rain.`,
|
||||||
Before: internal.SubCommandBefore,
|
Flags: []cli.Flag{
|
||||||
EnableShellCompletion: true,
|
internal.DebugFlag,
|
||||||
ShellComplete: autocomplete.ServerNameComplete,
|
internal.NoInputFlag,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
internal.OfflineFlag,
|
||||||
serverName := internal.ValidateServer(cmd)
|
},
|
||||||
|
Before: internal.SubCommandBefore,
|
||||||
|
BashComplete: autocomplete.ServerNameComplete,
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
serverName := internal.ValidateServer(c)
|
||||||
|
|
||||||
if err := client.DeleteContext(serverName); err != nil {
|
if err := client.DeleteContext(serverName); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServerCommand defines the `abra server` command and its subcommands
|
// ServerCommand defines the `abra server` command and its subcommands
|
||||||
var ServerCommand = cli.Command{
|
var ServerCommand = cli.Command{
|
||||||
Name: "server",
|
Name: "server",
|
||||||
Aliases: []string{"s"},
|
Aliases: []string{"s"},
|
||||||
Usage: "Manage servers",
|
Usage: "Manage servers",
|
||||||
UsageText: "abra server [command] [options] [arguments]",
|
Subcommands: []cli.Command{
|
||||||
HideHelpCommand: true,
|
serverAddCommand,
|
||||||
Commands: []*cli.Command{
|
serverListCommand,
|
||||||
&serverAddCommand,
|
serverRemoveCommand,
|
||||||
&serverListCommand,
|
serverPruneCommand,
|
||||||
&serverRemoveCommand,
|
|
||||||
&serverPruneCommand,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -23,44 +23,44 @@ import (
|
|||||||
dockerclient "github.com/docker/docker/client"
|
dockerclient "github.com/docker/docker/client"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
const SERVER = "localhost"
|
const SERVER = "localhost"
|
||||||
|
|
||||||
var majorUpdate bool
|
var majorUpdate bool
|
||||||
var majorFlag = &cli.BoolFlag{
|
var majorFlag = &cli.BoolFlag{
|
||||||
Name: "major",
|
Name: "major, m",
|
||||||
Aliases: []string{"m"},
|
|
||||||
Usage: "Also check for major updates",
|
Usage: "Also check for major updates",
|
||||||
Destination: &majorUpdate,
|
Destination: &majorUpdate,
|
||||||
}
|
}
|
||||||
|
|
||||||
var updateAll bool
|
var updateAll bool
|
||||||
var allFlag = &cli.BoolFlag{
|
var allFlag = &cli.BoolFlag{
|
||||||
Name: "all",
|
Name: "all, a",
|
||||||
Aliases: []string{"a"},
|
|
||||||
Usage: "Update all deployed apps",
|
Usage: "Update all deployed apps",
|
||||||
Destination: &updateAll,
|
Destination: &updateAll,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify checks for available upgrades
|
// Notify checks for available upgrades
|
||||||
var Notify = cli.Command{
|
var Notify = cli.Command{
|
||||||
Name: "notify",
|
Name: "notify",
|
||||||
Aliases: []string{"n"},
|
Aliases: []string{"n"},
|
||||||
Usage: "Check for available upgrades",
|
Usage: "Check for available upgrades",
|
||||||
UsageText: "kadabra notify [options]",
|
|
||||||
HideHelpCommand: true,
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
majorFlag,
|
majorFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Description: `Notify on new versions for deployed apps.
|
Description: `
|
||||||
|
Read the deployed app versions and look for new versions in the recipe
|
||||||
|
catalogue.
|
||||||
|
|
||||||
If a new patch/minor version is available, a notification is printed.
|
If a new patch/minor version is available, a notification is printed.
|
||||||
|
|
||||||
Use "--major" to include new major versions.`,
|
Use "--major" to include new major versions.`,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(c *cli.Context) error {
|
||||||
cl, err := client.New("default")
|
cl, err := client.New("default")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -92,18 +92,20 @@ Use "--major" to include new major versions.`,
|
|||||||
|
|
||||||
// UpgradeApp upgrades apps.
|
// UpgradeApp upgrades apps.
|
||||||
var UpgradeApp = cli.Command{
|
var UpgradeApp = cli.Command{
|
||||||
Name: "upgrade",
|
Name: "upgrade",
|
||||||
Aliases: []string{"u"},
|
Aliases: []string{"u"},
|
||||||
Usage: "Upgrade apps",
|
Usage: "Upgrade apps",
|
||||||
UsageText: "kadabra notify [options] <stack> <recipe>",
|
ArgsUsage: "<stack-name> <recipe>",
|
||||||
HideHelpCommand: true,
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
internal.DebugFlag,
|
||||||
internal.ChaosFlag,
|
internal.ChaosFlag,
|
||||||
majorFlag,
|
majorFlag,
|
||||||
allFlag,
|
allFlag,
|
||||||
|
internal.OfflineFlag,
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Before: internal.SubCommandBefore,
|
||||||
Description: `Upgrade an app by specifying stack name and recipe.
|
Description: `
|
||||||
|
Upgrade an app by specifying stack name and recipe.
|
||||||
|
|
||||||
Use "--all" to upgrade every deployed app.
|
Use "--all" to upgrade every deployed app.
|
||||||
|
|
||||||
@ -114,15 +116,15 @@ available, the app is upgraded.
|
|||||||
To include major versions use the "--major" flag. You probably don't want that
|
To include major versions use the "--major" flag. You probably don't want that
|
||||||
as it will break things. Only apps that are not deployed with "--chaos" are
|
as it will break things. Only apps that are not deployed with "--chaos" are
|
||||||
upgraded, to update chaos deployments use the "--chaos" flag. Use it with care.`,
|
upgraded, to update chaos deployments use the "--chaos" flag. Use it with care.`,
|
||||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
Action: func(c *cli.Context) error {
|
||||||
cl, err := client.New("default")
|
cl, err := client.New("default")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !updateAll {
|
if !updateAll {
|
||||||
stackName := cmd.Args().Get(0)
|
stackName := c.Args().Get(0)
|
||||||
recipeName := cmd.Args().Get(1)
|
recipeName := c.Args().Get(1)
|
||||||
err = tryUpgrade(cl, stackName, recipeName)
|
err = tryUpgrade(cl, stackName, recipeName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -466,26 +468,30 @@ func upgrade(cl *dockerclient.Client, stackName, recipeName, upgradeVersion stri
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAbraApp(version, commit string) *cli.Command {
|
func newAbraApp(version, commit string) *cli.App {
|
||||||
app := &cli.Command{
|
app := &cli.App{
|
||||||
Name: "kadabra",
|
Name: "kadabra",
|
||||||
Usage: "The Co-op Cloud auto-updater 🤖🚀",
|
Usage: `The Co-op Cloud auto-updater
|
||||||
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
|
____ ____ _ _
|
||||||
UsageText: "kadabra [command] [options] [arguments]",
|
/ ___|___ ___ _ __ / ___| | ___ _ _ __| |
|
||||||
HideHelpCommand: true,
|
| | / _ \ _____ / _ \| '_ \ | | | |/ _ \| | | |/ _' |
|
||||||
Flags: []cli.Flag{
|
| |__| (_) |_____| (_) | |_) | | |___| | (_) | |_| | (_| |
|
||||||
internal.OfflineFlag,
|
\____\___/ \___/| .__/ \____|_|\___/ \__,_|\__,_|
|
||||||
internal.DebugFlag,
|
|_|
|
||||||
},
|
`,
|
||||||
Commands: []*cli.Command{
|
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
|
||||||
&Notify,
|
Commands: []cli.Command{
|
||||||
&UpgradeApp,
|
Notify,
|
||||||
|
UpgradeApp,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Before = func(ctx context.Context, cmd *cli.Command) error {
|
app.Before = func(c *cli.Context) error {
|
||||||
|
log.Logger.SetStyles(log.Styles())
|
||||||
charmLog.SetDefault(log.Logger)
|
charmLog.SetDefault(log.Logger)
|
||||||
|
|
||||||
log.Debugf("kadabra version %s, commit %s", version, commit)
|
log.Debugf("kadabra version %s, commit %s", version, commit)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -496,7 +502,7 @@ func newAbraApp(version, commit string) *cli.Command {
|
|||||||
func RunApp(version, commit string) {
|
func RunApp(version, commit string) {
|
||||||
app := newAbraApp(version, commit)
|
app := newAbraApp(version, commit)
|
||||||
|
|
||||||
if err := app.Run(context.Background(), os.Args); err != nil {
|
if err := app.Run(os.Args); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,5 +19,5 @@ func main() {
|
|||||||
Commit = " "
|
Commit = " "
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.RunApp(Version, Commit)
|
cli.RunApp2(Version, Commit)
|
||||||
}
|
}
|
||||||
|
15
go.mod
15
go.mod
@ -2,12 +2,11 @@ module coopcloud.tech/abra
|
|||||||
|
|
||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
replace github.com/urfave/cli/v3 => github.com/fiatjaf/cli/v3 v3.0.0-20240704165307-ad0e1925dd42
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb
|
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb
|
||||||
git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355
|
git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
|
github.com/charmbracelet/lipgloss v0.11.1
|
||||||
github.com/charmbracelet/log v0.4.0
|
github.com/charmbracelet/log v0.4.0
|
||||||
github.com/distribution/reference v0.6.0
|
github.com/distribution/reference v0.6.0
|
||||||
github.com/docker/cli v27.0.3+incompatible
|
github.com/docker/cli v27.0.3+incompatible
|
||||||
@ -17,10 +16,9 @@ require (
|
|||||||
github.com/google/go-cmp v0.6.0
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/moby/sys/signal v0.7.0
|
github.com/moby/sys/signal v0.7.0
|
||||||
github.com/moby/term v0.5.0
|
github.com/moby/term v0.5.0
|
||||||
github.com/olekukonko/tablewriter v0.0.5
|
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/schollz/progressbar/v3 v3.14.4
|
github.com/schollz/progressbar/v3 v3.14.4
|
||||||
github.com/urfave/cli/v3 v3.0.0-alpha9
|
golang.org/x/term v0.22.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gotest.tools/v3 v3.5.1
|
gotest.tools/v3 v3.5.1
|
||||||
)
|
)
|
||||||
@ -35,10 +33,10 @@ require (
|
|||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/charmbracelet/lipgloss v0.11.0 // indirect
|
github.com/charmbracelet/x/ansi v0.1.3 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.1.2 // indirect
|
|
||||||
github.com/cloudflare/circl v1.3.9 // indirect
|
github.com/cloudflare/circl v1.3.9 // indirect
|
||||||
github.com/containerd/log v0.1.0 // indirect
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
|
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||||
@ -86,6 +84,7 @@ require (
|
|||||||
github.com/prometheus/common v0.55.0 // indirect
|
github.com/prometheus/common v0.55.0 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/skeema/knownhosts v1.2.2 // indirect
|
github.com/skeema/knownhosts v1.2.2 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
@ -106,7 +105,6 @@ require (
|
|||||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
||||||
golang.org/x/net v0.27.0 // indirect
|
golang.org/x/net v0.27.0 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
golang.org/x/term v0.22.0 // indirect
|
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||||
@ -132,9 +130,10 @@ require (
|
|||||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||||
github.com/spf13/cobra v1.8.1 // indirect
|
github.com/spf13/cobra v1.8.1
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||||
|
github.com/urfave/cli v1.22.15
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
golang.org/x/sys v0.22.0
|
golang.org/x/sys v0.22.0
|
||||||
)
|
)
|
||||||
|
24
go.sum
24
go.sum
@ -49,6 +49,7 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
|
|||||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
@ -134,12 +135,12 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
|
|||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g=
|
github.com/charmbracelet/lipgloss v0.11.1 h1:a8KgVPHa7kOoP95vm2tQQrjD2AKhbWmfr4uJ2RW6kNk=
|
||||||
github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8=
|
github.com/charmbracelet/lipgloss v0.11.1/go.mod h1:beLlcmkF7MWA+5UrKKIRo/VJ21xGXr7YJ9miWfdMRIU=
|
||||||
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
|
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
|
||||||
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
|
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
|
||||||
github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY=
|
github.com/charmbracelet/x/ansi v0.1.3 h1:RBh/eleNWML5R524mjUF0yVRePTwqN9tPtV+DPgO5Lw=
|
||||||
github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
github.com/charmbracelet/x/ansi v0.1.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||||
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
|
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
|
||||||
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
|
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
|
||||||
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
||||||
@ -273,6 +274,7 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc
|
|||||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
@ -355,8 +357,6 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
|||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/fiatjaf/cli/v3 v3.0.0-20240704165307-ad0e1925dd42 h1:yId1d3b2PHJ9vnYCxojs9NslXYVQ1iv7svK/CuDRoU0=
|
|
||||||
github.com/fiatjaf/cli/v3 v3.0.0-20240704165307-ad0e1925dd42/go.mod h1:Z1ItyMma7t6I7zHG9OpbExhHQOSkFf/96n+mAZ9MtVI=
|
|
||||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
@ -621,7 +621,6 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
|||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
|
||||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||||
@ -687,8 +686,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
|
|||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
|
||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
|
||||||
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
@ -803,6 +800,7 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
|||||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
@ -860,6 +858,9 @@ github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
@ -867,6 +868,9 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
|||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
@ -886,6 +890,8 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb
|
|||||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
|
github.com/urfave/cli v1.22.15 h1:nuqt+pdC/KqswQKhETJjo7pvn/k4xMUxgW6liI7XpnM=
|
||||||
|
github.com/urfave/cli v1.22.15/go.mod h1:wSan1hmo5zeyLGBjRJbzRTNk8gwoYa2B9n4q9dmRIc0=
|
||||||
github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
|
github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
|
||||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||||
|
@ -576,6 +576,7 @@ func (a App) WriteRecipeVersion(version string) error {
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
|
skipped := false
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
lines := []string{}
|
lines := []string{}
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
@ -584,6 +585,18 @@ func (a App) WriteRecipeVersion(version string) error {
|
|||||||
lines = append(lines, line)
|
lines = append(lines, line)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(line, "#") {
|
||||||
|
lines = append(lines, line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(line, version) {
|
||||||
|
skipped = true
|
||||||
|
lines = append(lines, line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
splitted := strings.Split(line, ":")
|
splitted := strings.Split(line, ":")
|
||||||
line = fmt.Sprintf("%s:%s", splitted[0], version)
|
line = fmt.Sprintf("%s:%s", splitted[0], version)
|
||||||
lines = append(lines, line)
|
lines = append(lines, line)
|
||||||
@ -593,5 +606,15 @@ func (a App) WriteRecipeVersion(version string) error {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.WriteFile(a.Path, []byte(strings.Join(lines, "\n")), os.ModePerm)
|
if err := os.WriteFile(a.Path, []byte(strings.Join(lines, "\n")), os.ModePerm); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !skipped {
|
||||||
|
log.Infof("version %s saved to %s.env", version, a.Domain)
|
||||||
|
} else {
|
||||||
|
log.Debugf("skipping version %s write as already exists in %s.env", version, a.Domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,22 @@
|
|||||||
package autocomplete
|
package autocomplete
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/app"
|
"coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AppNameComplete copletes app names.
|
// AppNameComplete copletes app names.
|
||||||
func AppNameComplete(ctx context.Context, cmd *cli.Command) {
|
func AppNameComplete(c *cli.Context) {
|
||||||
appNames, err := app.GetAppNames()
|
appNames, err := app.GetAppNames()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(err)
|
log.Warn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.NArg() > 0 {
|
if c.NArg() > 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,13 +36,13 @@ func ServiceNameComplete(appName string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecipeNameComplete completes recipe names.
|
// RecipeNameComplete completes recipe names.
|
||||||
func RecipeNameComplete(ctx context.Context, cmd *cli.Command) {
|
func RecipeNameComplete(c *cli.Context) {
|
||||||
catl, err := recipe.ReadRecipeCatalogue(false)
|
catl, err := recipe.ReadRecipeCatalogue(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(err)
|
log.Warn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.NArg() > 0 {
|
if c.NArg() > 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,13 +66,13 @@ func RecipeVersionComplete(recipeName string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ServerNameComplete completes server names.
|
// ServerNameComplete completes server names.
|
||||||
func ServerNameComplete(ctx context.Context, cmd *cli.Command) {
|
func ServerNameComplete(c *cli.Context) {
|
||||||
files, err := app.LoadAppFiles("")
|
files, err := app.LoadAppFiles("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.NArg() > 0 {
|
if c.NArg() > 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,8 +82,8 @@ func ServerNameComplete(ctx context.Context, cmd *cli.Command) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SubcommandComplete completes sub-commands.
|
// SubcommandComplete completes sub-commands.
|
||||||
func SubcommandComplete(ctx context.Context, cmd *cli.Command) {
|
func SubcommandComplete(c *cli.Context) {
|
||||||
if cmd.NArg() > 0 {
|
if c.NArg() > 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,4 +107,5 @@ var (
|
|||||||
REPOS_BASE_URL = "https://git.coopcloud.tech/coop-cloud"
|
REPOS_BASE_URL = "https://git.coopcloud.tech/coop-cloud"
|
||||||
CATALOGUE_JSON_REPO_NAME = "recipes-catalogue-json"
|
CATALOGUE_JSON_REPO_NAME = "recipes-catalogue-json"
|
||||||
SSH_URL_TEMPLATE = "ssh://git@git.coopcloud.tech:2222/coop-cloud/%s.git"
|
SSH_URL_TEMPLATE = "ssh://git@git.coopcloud.tech:2222/coop-cloud/%s.git"
|
||||||
|
CHAOS_DEFAULT = "false"
|
||||||
)
|
)
|
||||||
|
@ -1,18 +1,26 @@
|
|||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"github.com/charmbracelet/lipgloss/table"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
// "github.com/olekukonko/tablewriter"
|
"golang.org/x/term"
|
||||||
"coopcloud.tech/abra/pkg/jsontable"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/schollz/progressbar/v3"
|
"github.com/schollz/progressbar/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var BoldStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Underline(true)
|
||||||
|
|
||||||
func ShortenID(str string) string {
|
func ShortenID(str string) string {
|
||||||
return str[:12]
|
return str[:12]
|
||||||
}
|
}
|
||||||
@ -34,11 +42,67 @@ func HumanDuration(timestamp int64) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateTable prepares a table layout for output.
|
// CreateTable prepares a table layout for output.
|
||||||
func CreateTable(columns []string) *jsontable.JSONTable {
|
func CreateTable() (*table.Table, error) {
|
||||||
table := jsontable.NewJSONTable(os.Stdout)
|
table := table.New().
|
||||||
table.SetAutoWrapText(false)
|
Border(lipgloss.ThickBorder()).
|
||||||
table.SetHeader(columns)
|
BorderStyle(
|
||||||
return table
|
lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color("63")),
|
||||||
|
)
|
||||||
|
|
||||||
|
if isAbraCI, ok := os.LookupEnv("ABRA_CI"); ok && isAbraCI == "1" {
|
||||||
|
// NOTE(d1): no width limits for CI testing since we test against outputs
|
||||||
|
log.Debug("detected ABRA_CI=1")
|
||||||
|
return table, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
width, _, err := term.GetSize(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if width-10 < 79 {
|
||||||
|
// NOTE(d1): maintain standard minimum width
|
||||||
|
table.Width(79)
|
||||||
|
} else {
|
||||||
|
// NOTE(d1): tests show that this produces stable border drawing
|
||||||
|
table.Width(width - 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
return table, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToJSON converts a lipgloss.Table to JSON representation. It's not a robust
|
||||||
|
// implementation and mainly caters for our current use case which is basically
|
||||||
|
// a bunch of strings. See https://github.com/charmbracelet/lipgloss/issues/335
|
||||||
|
// for the real thing (hopefully).
|
||||||
|
func ToJSON(headers []string, rows [][]string) (string, error) {
|
||||||
|
var buff bytes.Buffer
|
||||||
|
|
||||||
|
buff.Write([]byte("["))
|
||||||
|
|
||||||
|
for idx, row := range rows {
|
||||||
|
payload := make(map[string]string)
|
||||||
|
|
||||||
|
for idx, header := range headers {
|
||||||
|
payload[strings.ToLower(header)] = row[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
serialized, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
buff.Write(serialized)
|
||||||
|
|
||||||
|
if idx < (len(rows) - 1) {
|
||||||
|
buff.Write([]byte(","))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buff.Write([]byte("]"))
|
||||||
|
|
||||||
|
return buff.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateProgressbar generates a progress bar
|
// CreateProgressbar generates a progress bar
|
||||||
|
@ -1,211 +0,0 @@
|
|||||||
package jsontable
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/olekukonko/tablewriter"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A quick-and-dirty proxy/emulator of tablewriter to enable more easy machine readable output
|
|
||||||
// - Does not strictly support types, just quoted or unquoted values
|
|
||||||
// - Does not support nested values.
|
|
||||||
// If a datalabel is set with SetDataLabel(true, "..."), that will be used as the key for teh data of the table,
|
|
||||||
// otherwise if the caption is set with SetCaption(true, "..."), the data label will be set to the default of
|
|
||||||
// "rows", otherwise the table will output as a JSON list.
|
|
||||||
//
|
|
||||||
// Proxys all actions through to the tablewriter except addrow and addbatch, which it does at render time
|
|
||||||
//
|
|
||||||
|
|
||||||
type JSONTable struct {
|
|
||||||
out io.Writer
|
|
||||||
colsize int
|
|
||||||
rows [][]string
|
|
||||||
keys []string
|
|
||||||
quoted []bool // hack to do output typing, quoted vs. unquoted
|
|
||||||
hasDataLabel bool
|
|
||||||
dataLabel string
|
|
||||||
hasCaption bool
|
|
||||||
caption string // the actual caption
|
|
||||||
hasCaptionLabel bool
|
|
||||||
captionLabel string // the key in the dictionary for the caption
|
|
||||||
tbl *tablewriter.Table
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeChar(w io.Writer, c byte) {
|
|
||||||
w.Write([]byte{c})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewJSONTable(writer io.Writer) *JSONTable {
|
|
||||||
t := &JSONTable{
|
|
||||||
out: writer,
|
|
||||||
colsize: 0,
|
|
||||||
rows: [][]string{},
|
|
||||||
keys: []string{},
|
|
||||||
quoted: []bool{},
|
|
||||||
hasDataLabel: false,
|
|
||||||
dataLabel: "rows",
|
|
||||||
hasCaption: false,
|
|
||||||
caption: "",
|
|
||||||
hasCaptionLabel: false,
|
|
||||||
captionLabel: "caption",
|
|
||||||
tbl: tablewriter.NewWriter(writer),
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) NumLines() int {
|
|
||||||
// JSON only but reflects a shared state.
|
|
||||||
return len(t.rows)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) SetHeader(keys []string) {
|
|
||||||
// Set the keys value which will assign each column to the keys.
|
|
||||||
// Note that we'll ignore values that are beyond the length of the keys list
|
|
||||||
t.colsize = len(keys)
|
|
||||||
t.keys = []string{}
|
|
||||||
for _, k := range keys {
|
|
||||||
t.keys = append(t.keys, k)
|
|
||||||
t.quoted = append(t.quoted, true)
|
|
||||||
}
|
|
||||||
t.tbl.SetHeader(keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) SetColumnQuoting(quoting []bool) {
|
|
||||||
// Specify which columns are quoted or unquoted in output
|
|
||||||
// JSON only
|
|
||||||
for i := 0; i < t.colsize; i++ {
|
|
||||||
t.quoted[i] = quoting[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) Append(row []string) {
|
|
||||||
// We'll just append whatever to the rows list. If they fix the keys after appending rows, it'll work as
|
|
||||||
// expected.
|
|
||||||
// We should detect if the row is narrower than the key list tho.
|
|
||||||
// JSON only (but we use the rows later when rendering a regular table)
|
|
||||||
t.rows = append(t.rows, row)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) Render() {
|
|
||||||
// Load the table with rows and render.
|
|
||||||
// Proxy only
|
|
||||||
for _, row := range t.rows {
|
|
||||||
t.tbl.Append(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.tbl.Render()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) _JSONRenderInner() {
|
|
||||||
// JSON only
|
|
||||||
// Render the list of dictionaries to the writer.
|
|
||||||
//// inner render loop
|
|
||||||
writeChar(t.out, '[')
|
|
||||||
for rowidx, row := range t.rows {
|
|
||||||
if rowidx != 0 {
|
|
||||||
writeChar(t.out, ',')
|
|
||||||
}
|
|
||||||
writeChar(t.out, '{')
|
|
||||||
for keyidx, key := range t.keys {
|
|
||||||
key := strings.ToLower(key)
|
|
||||||
key = strings.ReplaceAll(key, " ", "-")
|
|
||||||
|
|
||||||
value := "nil"
|
|
||||||
if keyidx < len(row) {
|
|
||||||
value = row[keyidx]
|
|
||||||
}
|
|
||||||
if keyidx != 0 {
|
|
||||||
writeChar(t.out, ',')
|
|
||||||
}
|
|
||||||
if t.quoted[keyidx] {
|
|
||||||
fmt.Fprintf(t.out, "\"%s\":\"%s\"", key, value)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(t.out, "\"%s\":%s", key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeChar(t.out, '}')
|
|
||||||
}
|
|
||||||
writeChar(t.out, ']')
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) JSONRender() {
|
|
||||||
// write JSON table to output
|
|
||||||
// JSON only
|
|
||||||
|
|
||||||
if t.hasDataLabel || t.hasCaption {
|
|
||||||
// dict mode
|
|
||||||
writeChar(t.out, '{')
|
|
||||||
|
|
||||||
if t.hasCaption {
|
|
||||||
fmt.Fprintf(t.out, "\"%s\":\"%s\",", t.captionLabel, t.caption)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(t.out, "\"%s\":", t.dataLabel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// write list
|
|
||||||
t._JSONRenderInner()
|
|
||||||
|
|
||||||
if t.hasDataLabel || t.hasCaption {
|
|
||||||
// dict mode
|
|
||||||
writeChar(t.out, '}')
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) SetCaption(caption bool, captionText ...string) {
|
|
||||||
t.hasCaption = caption
|
|
||||||
if len(captionText) == 1 {
|
|
||||||
t.caption = captionText[0]
|
|
||||||
}
|
|
||||||
t.tbl.SetCaption(caption, captionText...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) SetCaptionLabel(captionLabel bool, captionLabelText ...string) {
|
|
||||||
// JSON only
|
|
||||||
t.hasCaptionLabel = captionLabel
|
|
||||||
if len(captionLabelText) == 1 {
|
|
||||||
t.captionLabel = captionLabelText[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) SetDataLabel(dataLabel bool, dataLabelText ...string) {
|
|
||||||
// JSON only
|
|
||||||
t.hasDataLabel = dataLabel
|
|
||||||
if len(dataLabelText) == 1 {
|
|
||||||
t.dataLabel = dataLabelText[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) AppendBulk(rows [][]string) {
|
|
||||||
// JSON only but reflects shared state
|
|
||||||
for _, row := range rows {
|
|
||||||
t.Append(row)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stuff we should implement but we just proxy for now.
|
|
||||||
func (t *JSONTable) SetAutoMergeCellsByColumnIndex(cols []int) {
|
|
||||||
// FIXME
|
|
||||||
t.tbl.SetAutoMergeCellsByColumnIndex(cols)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stuff we should implement but we just proxy for now.
|
|
||||||
func (t *JSONTable) SetAlignment(align int) {
|
|
||||||
// FIXME
|
|
||||||
t.tbl.SetAlignment(align)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *JSONTable) SetAutoMergeCells(auto bool) {
|
|
||||||
// FIXME
|
|
||||||
t.tbl.SetAutoMergeCells(auto)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stub functions
|
|
||||||
func (t *JSONTable) SetAutoWrapText(auto bool) {
|
|
||||||
t.tbl.SetAutoWrapText(auto)
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
package jsontable
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/olekukonko/tablewriter"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TestLine = []string{"1", "2"}
|
|
||||||
var TestGroup = [][]string{{"1", "2", "3"}, {"a", "teohunteohu", "c", "d"}, {"☺", "☹"}}
|
|
||||||
var TestKeys = []string{"key0", "key1", "key2"}
|
|
||||||
|
|
||||||
// test creation
|
|
||||||
func TestNewTable(t *testing.T) {
|
|
||||||
var b bytes.Buffer
|
|
||||||
tbl := NewJSONTable(&b)
|
|
||||||
if tbl.NumLines() != 0 {
|
|
||||||
t.Fatalf("Something went weird when making table (should have 0 lines)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// test adding things
|
|
||||||
func TestTableAdd(t *testing.T) {
|
|
||||||
var b bytes.Buffer
|
|
||||||
tbl := NewJSONTable(&b)
|
|
||||||
|
|
||||||
tbl.Append(TestLine)
|
|
||||||
if tbl.NumLines() != 1 {
|
|
||||||
t.Fatalf("Appending a line does not result in a length of 1.")
|
|
||||||
}
|
|
||||||
|
|
||||||
tbl.AppendBulk(TestGroup)
|
|
||||||
numlines := tbl.NumLines()
|
|
||||||
if numlines != (len(TestGroup) + 1) {
|
|
||||||
t.Fatalf("Appending two lines does not result in a length of 4 (length is %d).", numlines)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// test JSON output is parsable
|
|
||||||
func TestJsonParsable(t *testing.T) {
|
|
||||||
var b bytes.Buffer
|
|
||||||
tbl := NewJSONTable(&b)
|
|
||||||
|
|
||||||
tbl.AppendBulk(TestGroup)
|
|
||||||
tbl.SetHeader(TestKeys)
|
|
||||||
|
|
||||||
tbl.JSONRender()
|
|
||||||
|
|
||||||
var son []map[string]interface{}
|
|
||||||
|
|
||||||
err := json.Unmarshal(b.Bytes(), &son)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Did not produce parsable JSON: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// test identical commands to a tablewriter and jsontable produce the same rendered output
|
|
||||||
func TestTableWriter(t *testing.T) {
|
|
||||||
var bjson bytes.Buffer
|
|
||||||
var btable bytes.Buffer
|
|
||||||
|
|
||||||
tbl := NewJSONTable(&bjson)
|
|
||||||
|
|
||||||
tbl.AppendBulk(TestGroup)
|
|
||||||
tbl.SetHeader(TestKeys)
|
|
||||||
tbl.Render()
|
|
||||||
|
|
||||||
wtbl := tablewriter.NewWriter(&btable)
|
|
||||||
|
|
||||||
wtbl.AppendBulk(TestGroup)
|
|
||||||
wtbl.SetHeader(TestKeys)
|
|
||||||
wtbl.Render()
|
|
||||||
|
|
||||||
if bytes.Compare(bjson.Bytes(), btable.Bytes()) != 0 {
|
|
||||||
t.Fatalf("JSON table and TableWriter produce non-identical outputs.\n%s\n%s", bjson.Bytes(), btable.Bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// FIXME test different output formats when captions etc. are added
|
|
@ -3,7 +3,9 @@ package log
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
charmLog "github.com/charmbracelet/log"
|
charmLog "github.com/charmbracelet/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,3 +34,42 @@ var SetLevel = Logger.SetLevel
|
|||||||
var DebugLevel = charmLog.DebugLevel
|
var DebugLevel = charmLog.DebugLevel
|
||||||
var SetOutput = charmLog.SetOutput
|
var SetOutput = charmLog.SetOutput
|
||||||
var SetReportCaller = charmLog.SetReportCaller
|
var SetReportCaller = charmLog.SetReportCaller
|
||||||
|
|
||||||
|
func Styles() *charmLog.Styles {
|
||||||
|
styles := charmLog.DefaultStyles()
|
||||||
|
|
||||||
|
styles.Levels = map[charmLog.Level]lipgloss.Style{
|
||||||
|
charmLog.DebugLevel: lipgloss.NewStyle().
|
||||||
|
SetString(strings.ToUpper(DebugLevel.String())).
|
||||||
|
Bold(true).
|
||||||
|
Padding(0, 1, 0, 1).
|
||||||
|
Background(lipgloss.Color("63")).
|
||||||
|
Foreground(lipgloss.Color("15")),
|
||||||
|
charmLog.InfoLevel: lipgloss.NewStyle().
|
||||||
|
SetString(strings.ToUpper(charmLog.InfoLevel.String())).
|
||||||
|
Bold(true).
|
||||||
|
Padding(0, 1, 0, 1).
|
||||||
|
Background(lipgloss.Color("86")).
|
||||||
|
Foreground(lipgloss.Color("16")),
|
||||||
|
charmLog.WarnLevel: lipgloss.NewStyle().
|
||||||
|
SetString(strings.ToUpper(charmLog.WarnLevel.String())).
|
||||||
|
Bold(true).
|
||||||
|
Padding(0, 1, 0, 1).
|
||||||
|
Background(lipgloss.Color("192")).
|
||||||
|
Foreground(lipgloss.Color("16")),
|
||||||
|
charmLog.ErrorLevel: lipgloss.NewStyle().
|
||||||
|
SetString(strings.ToUpper(charmLog.ErrorLevel.String())).
|
||||||
|
Bold(true).
|
||||||
|
Padding(0, 1, 0, 1).
|
||||||
|
Background(lipgloss.Color("204")).
|
||||||
|
Foreground(lipgloss.Color("15")),
|
||||||
|
charmLog.FatalLevel: lipgloss.NewStyle().
|
||||||
|
SetString(strings.ToUpper(charmLog.FatalLevel.String())).
|
||||||
|
Bold(true).
|
||||||
|
Padding(0, 1, 0, 1).
|
||||||
|
Background(lipgloss.Color("134")).
|
||||||
|
Foreground(lipgloss.Color("15")),
|
||||||
|
}
|
||||||
|
|
||||||
|
return styles
|
||||||
|
}
|
||||||
|
@ -26,20 +26,26 @@ func (r Recipe) Ensure(chaos bool, offline bool) error {
|
|||||||
if err := r.EnsureIsClean(); err != nil {
|
if err := r.EnsureIsClean(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !offline {
|
if !offline {
|
||||||
if err := r.EnsureUpToDate(); err != nil {
|
if err := r.EnsureUpToDate(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Version != "" {
|
if r.Version != "" {
|
||||||
|
log.Debugf("ensuring version %s", r.Version)
|
||||||
if _, err := r.EnsureVersion(r.Version); err != nil {
|
if _, err := r.EnsureVersion(r.Version); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if err := r.EnsureLatest(); err != nil {
|
return nil
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := r.EnsureLatest(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,6 +127,9 @@ func Get(name string) Recipe {
|
|||||||
version := ""
|
version := ""
|
||||||
if strings.Contains(name, ":") {
|
if strings.Contains(name, ":") {
|
||||||
split := strings.Split(name, ":")
|
split := strings.Split(name, ":")
|
||||||
|
if len(split) > 2 {
|
||||||
|
log.Fatalf("version seems invalid: %s", name)
|
||||||
|
}
|
||||||
name = split[0]
|
name = split[0]
|
||||||
version = split[1]
|
version = split[1]
|
||||||
}
|
}
|
||||||
|
@ -181,7 +181,7 @@ func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server
|
|||||||
|
|
||||||
if err := client.StoreSecret(cl, secret.RemoteName, passwords[0], server); err != nil {
|
if err := client.StoreSecret(cl, secret.RemoteName, passwords[0], server); err != nil {
|
||||||
if strings.Contains(err.Error(), "AlreadyExists") {
|
if strings.Contains(err.Error(), "AlreadyExists") {
|
||||||
log.Warnf("%s already exists, moving on...", secret.RemoteName)
|
log.Warnf("%s already exists", secret.RemoteName)
|
||||||
ch <- nil
|
ch <- nil
|
||||||
} else {
|
} else {
|
||||||
ch <- err
|
ch <- err
|
||||||
@ -201,7 +201,7 @@ func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server
|
|||||||
|
|
||||||
if err := client.StoreSecret(cl, secret.RemoteName, passphrases[0], server); err != nil {
|
if err := client.StoreSecret(cl, secret.RemoteName, passphrases[0], server); err != nil {
|
||||||
if strings.Contains(err.Error(), "AlreadyExists") {
|
if strings.Contains(err.Error(), "AlreadyExists") {
|
||||||
log.Warnf("%s already exists, moving on...", secret.RemoteName)
|
log.Warnf("%s already exists", secret.RemoteName)
|
||||||
ch <- nil
|
ch <- nil
|
||||||
} else {
|
} else {
|
||||||
ch <- err
|
ch <- err
|
||||||
|
@ -175,6 +175,7 @@ func pruneServices(ctx context.Context, cl *dockerClient.Client, namespace conve
|
|||||||
pruneServices = append(pruneServices, service)
|
pruneServices = append(pruneServices, service)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeServices(ctx, cl, pruneServices)
|
removeServices(ctx, cl, pruneServices)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,10 +256,12 @@ func deployCompose(ctx context.Context, cl *dockerClient.Client, opts Deploy, co
|
|||||||
|
|
||||||
log.Infof("waiting for %s to deploy... please hold 🤚", appName)
|
log.Infof("waiting for %s to deploy... please hold 🤚", appName)
|
||||||
|
|
||||||
if err := waitOnServices(ctx, cl, serviceIDs, appName); err == nil {
|
if err := waitOnServices(ctx, cl, serviceIDs, appName); err != nil {
|
||||||
log.Infof("successfully deployed %s", appName)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infof("successfully deployed %s", appName)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,7 +398,7 @@ func deployServices(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if service, exists := existingServiceMap[name]; exists {
|
if service, exists := existingServiceMap[name]; exists {
|
||||||
log.Infof("updating service %s (id: %s)", name, service.ID)
|
log.Infof("updating %s", name)
|
||||||
|
|
||||||
updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}
|
updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}
|
||||||
|
|
||||||
@ -430,7 +433,7 @@ func deployServices(
|
|||||||
|
|
||||||
response, err := cl.ServiceUpdate(ctx, service.ID, service.Version, serviceSpec, updateOpts)
|
response, err := cl.ServiceUpdate(ctx, service.ID, service.Version, serviceSpec, updateOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "failed to update service %s", name)
|
return nil, errors.Wrapf(err, "failed to update %s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, warning := range response.Warnings {
|
for _, warning := range response.Warnings {
|
||||||
@ -439,7 +442,7 @@ func deployServices(
|
|||||||
|
|
||||||
serviceIDs = append(serviceIDs, service.ID)
|
serviceIDs = append(serviceIDs, service.ID)
|
||||||
} else {
|
} else {
|
||||||
log.Infof("creating service %s", name)
|
log.Infof("creating %s", name)
|
||||||
|
|
||||||
createOpts := types.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth}
|
createOpts := types.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth}
|
||||||
|
|
||||||
@ -450,7 +453,7 @@ func deployServices(
|
|||||||
|
|
||||||
serviceCreateResponse, err := cl.ServiceCreate(ctx, serviceSpec, createOpts)
|
serviceCreateResponse, err := cl.ServiceCreate(ctx, serviceSpec, createOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "failed to create service %s", name)
|
return nil, errors.Wrapf(err, "failed to create %s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceIDs = append(serviceIDs, serviceCreateResponse.ID)
|
serviceIDs = append(serviceIDs, serviceCreateResponse.ID)
|
||||||
@ -510,13 +513,14 @@ func WaitOnService(ctx context.Context, cl *dockerClient.Client, serviceID, appN
|
|||||||
case err := <-errChan:
|
case err := <-errChan:
|
||||||
return err
|
return err
|
||||||
case <-sigintChannel:
|
case <-sigintChannel:
|
||||||
return fmt.Errorf(fmt.Sprintf(`
|
return fmt.Errorf(`
|
||||||
Not waiting for %s to deploy. The deployment is ongoing...
|
Not waiting for %s to deploy. The deployment is ongoing...
|
||||||
|
|
||||||
If you want to stop the deployment, try:
|
If you want to stop the deployment, try:
|
||||||
abra app undeploy %s`, appName, appName))
|
|
||||||
|
abra app undeploy %s`, appName, appName)
|
||||||
case <-time.After(timeout):
|
case <-time.After(timeout):
|
||||||
return fmt.Errorf(fmt.Sprintf(`
|
return fmt.Errorf(`
|
||||||
%s has not converged (%s second timeout reached).
|
%s has not converged (%s second timeout reached).
|
||||||
|
|
||||||
This does not necessarily mean your deployment has failed, it may just be that
|
This does not necessarily mean your deployment has failed, it may just be that
|
||||||
@ -530,7 +534,7 @@ You can track latest deployment status with:
|
|||||||
And inspect the logs with:
|
And inspect the logs with:
|
||||||
|
|
||||||
abra app logs %s
|
abra app logs %s
|
||||||
`, appName, timeout, appName, appName))
|
`, appName, timeout, appName, appName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -548,7 +552,7 @@ func GetStacks(cl *dockerClient.Client) ([]*formatter.Stack, error) {
|
|||||||
labels := service.Spec.Labels
|
labels := service.Spec.Labels
|
||||||
name, ok := labels[convert.LabelNamespace]
|
name, ok := labels[convert.LabelNamespace]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.Errorf("cannot get label %s for service %s",
|
return nil, errors.Errorf("cannot get label %s for %s",
|
||||||
convert.LabelNamespace, service.ID)
|
convert.LabelNamespace, service.ID)
|
||||||
}
|
}
|
||||||
ztack, ok := m[name]
|
ztack, ok := m[name]
|
||||||
|
@ -8,9 +8,9 @@ _cli_bash_autocomplete() {
|
|||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
if [[ "$cur" == "-"* ]]; then
|
if [[ "$cur" == "-"* ]]; then
|
||||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-shell-completion )
|
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
|
||||||
else
|
else
|
||||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-shell-completion )
|
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
||||||
fi
|
fi
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||||
return 0
|
return 0
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
function complete_abra_args
|
function complete_abra_args
|
||||||
set -l cmd (commandline -poc) --generate-shell-completion
|
set -l cmd (commandline -poc) --generate-bash-completion
|
||||||
$cmd
|
$cmd
|
||||||
end
|
end
|
||||||
complete -c abra -f -n "not __fish_seen_subcommand_from -h --help -v --version complete_abra_args" -a "(complete_abra_args)"
|
complete -c abra -f -n "not __fish_seen_subcommand_from -h --help -v --version complete_abra_args" -a "(complete_abra_args)"
|
||||||
|
@ -2,7 +2,7 @@ $fn = $($MyInvocation.MyCommand.Name)
|
|||||||
$name = $fn -replace "(.*)\.ps1$", '$1'
|
$name = $fn -replace "(.*)\.ps1$", '$1'
|
||||||
Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock {
|
Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock {
|
||||||
param($commandName, $wordToComplete, $cursorPosition)
|
param($commandName, $wordToComplete, $cursorPosition)
|
||||||
$other = "$wordToComplete --generate-shell-completion"
|
$other = "$wordToComplete --generate-bash-completion"
|
||||||
Invoke-Expression $other | ForEach-Object {
|
Invoke-Expression $other | ForEach-Object {
|
||||||
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@ _cli_zsh_autocomplete() {
|
|||||||
local cur
|
local cur
|
||||||
cur=${words[-1]}
|
cur=${words[-1]}
|
||||||
if [[ "$cur" == "-"* ]]; then
|
if [[ "$cur" == "-"* ]]; then
|
||||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-shell-completion)}")
|
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
|
||||||
else
|
else
|
||||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-shell-completion)}")
|
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${opts[1]}" != "" ]]; then
|
if [[ "${opts[1]}" != "" ]]; then
|
||||||
|
@ -87,7 +87,7 @@ function install_abra_release {
|
|||||||
|
|
||||||
x=$(echo $PATH | grep $HOME/.local/bin)
|
x=$(echo $PATH | grep $HOME/.local/bin)
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "$(tput setaf 3)WARNING: $HOME/.local/bin/ is not in \$PATH! If you want to run abra by just typing "abra" you should add it to your \$PATH! To do that run:$(tput sgr0)"
|
echo "$(tput setaf 3)WARNING: $HOME/.local/bin/ is not in \$PATH! If you want to run abra by just typing "abra" you should add it to your \$PATH! To do that run this once and restart your terminal:$(tput sgr0)"
|
||||||
p=$HOME/.local/bin
|
p=$HOME/.local/bin
|
||||||
com="echo PATH=\$PATH:$p"
|
com="echo PATH=\$PATH:$p"
|
||||||
if [[ $SHELL =~ "bash" ]]; then
|
if [[ $SHELL =~ "bash" ]]; then
|
||||||
|
@ -7,10 +7,9 @@
|
|||||||
# destroys resources on the swarm server you run it against. This is for
|
# destroys resources on the swarm server you run it against. This is for
|
||||||
# setup/teardown for the integration test suite.
|
# setup/teardown for the integration test suite.
|
||||||
#
|
#
|
||||||
# export DRONE_SOURCE_BRANCH=<your-branch-name>
|
|
||||||
# ./run-ci-int
|
# ./run-ci-int
|
||||||
|
|
||||||
set +e
|
set -eu
|
||||||
|
|
||||||
echo "========================================================================"
|
echo "========================================================================"
|
||||||
echo "WIPING DOCKER RESOURCES FOR A CLEAN SLATE"
|
echo "WIPING DOCKER RESOURCES FOR A CLEAN SLATE"
|
||||||
@ -45,17 +44,7 @@ echo "========================================================================"
|
|||||||
rm -rf abra
|
rm -rf abra
|
||||||
git clone ssh://git@git.coopcloud.tech:2222/coop-cloud/abra.git
|
git clone ssh://git@git.coopcloud.tech:2222/coop-cloud/abra.git
|
||||||
cd abra
|
cd abra
|
||||||
echo "========================================================================"
|
git checkout main
|
||||||
|
|
||||||
echo "========================================================================"
|
|
||||||
echo "FETCHING ABRA BRANCH FOR TESTING"
|
|
||||||
echo "========================================================================"
|
|
||||||
if [ -z "$DRONE_SOURCE_BRANCH" ]; then
|
|
||||||
DRONE_SOURCE_BRANCH="main"
|
|
||||||
fi
|
|
||||||
|
|
||||||
git fetch --all
|
|
||||||
git checkout $DRONE_SOURCE_BRANCH
|
|
||||||
echo "========================================================================"
|
echo "========================================================================"
|
||||||
|
|
||||||
echo "========================================================================"
|
echo "========================================================================"
|
||||||
@ -83,6 +72,7 @@ echo "========================================================================"
|
|||||||
export ABRA_DIR="$HOME/.abra_test"
|
export ABRA_DIR="$HOME/.abra_test"
|
||||||
export TERM=xterm
|
export TERM=xterm
|
||||||
export TEST_SERVER=default
|
export TEST_SERVER=default
|
||||||
|
export ABRA_CI=1
|
||||||
|
|
||||||
rm -rf "$ABRA_DIR"
|
rm -rf "$ABRA_DIR"
|
||||||
bats -Tp tests/integration --filter-tags \!dns --print-output-on-failure
|
bats -Tp tests/integration --filter-tags \!dns --print-output-on-failure
|
||||||
|
@ -20,6 +20,7 @@ setup(){
|
|||||||
|
|
||||||
teardown(){
|
teardown(){
|
||||||
_reset_recipe
|
_reset_recipe
|
||||||
|
_reset_tags
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "validate app argument" {
|
@test "validate app argument" {
|
||||||
@ -82,19 +83,17 @@ teardown(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
@test "ensure recipe not up to date if --offline" {
|
@test "ensure recipe not up to date if --offline" {
|
||||||
wantHash=$(_get_n_hash 1)
|
_ensure_env_version "0.1.0+1.20.0"
|
||||||
|
latestRelease=$(_latest_release)
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~1
|
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -d "$latestRelease"
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
assert_equal $(_get_current_hash) "$wantHash"
|
# NOTE(d1): don't assert success because it might flake
|
||||||
|
|
||||||
# NOTE(d1): we can't quite tell if this will fail or not in the future, so,
|
|
||||||
# since it isn't an important part of what we're testing here, we don't check
|
|
||||||
# it
|
|
||||||
run $ABRA app check "$TEST_APP_DOMAIN" --offline
|
run $ABRA app check "$TEST_APP_DOMAIN" --offline
|
||||||
|
|
||||||
assert_equal $(_get_current_hash) "$wantHash"
|
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -l
|
||||||
|
refute_output --partial "$latestRelease"
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "error if missing .env.sample" {
|
@test "error if missing .env.sample" {
|
||||||
@ -118,3 +117,20 @@ teardown(){
|
|||||||
assert_success
|
assert_success
|
||||||
assert_output --partial '❌'
|
assert_output --partial '❌'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "respects env version" {
|
||||||
|
tagHash=$(_get_tag_hash "0.1.0+1.20.0")
|
||||||
|
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input --no-converge-checks
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app check "$TEST_APP_DOMAIN"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
assert_equal $(_get_current_hash) "$tagHash"
|
||||||
|
}
|
||||||
|
@ -19,8 +19,9 @@ setup(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
teardown(){
|
teardown(){
|
||||||
_undeploy_app
|
|
||||||
_reset_recipe
|
_reset_recipe
|
||||||
|
_reset_tags
|
||||||
|
_undeploy_app
|
||||||
}
|
}
|
||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@ -105,20 +106,18 @@ test_cmd_export"
|
|||||||
}
|
}
|
||||||
|
|
||||||
@test "ensure recipe not up to date if --offline" {
|
@test "ensure recipe not up to date if --offline" {
|
||||||
wantHash=$(_get_n_hash 3)
|
_ensure_env_version "0.1.0+1.20.0"
|
||||||
|
latestRelease=$(_latest_release)
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3
|
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -d "$latestRelease"
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
assert_equal $(_get_current_hash) "$wantHash"
|
|
||||||
|
|
||||||
run $ABRA app cmd --local --offline "$TEST_APP_DOMAIN" test_cmd
|
run $ABRA app cmd --local --offline "$TEST_APP_DOMAIN" test_cmd
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial 'baz'
|
assert_output --partial 'baz'
|
||||||
|
|
||||||
assert_equal $(_get_current_hash) $wantHash
|
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -l
|
||||||
|
refute_output --partial "$latestRelease"
|
||||||
_reset_recipe "$TEST_RECIPE"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "error if missing arguments without passing --local" {
|
@test "error if missing arguments without passing --local" {
|
||||||
@ -187,6 +186,24 @@ test_cmd_export"
|
|||||||
assert_output --partial 'baz'
|
assert_output --partial 'baz'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "respects env version" {
|
||||||
|
tagHash=$(_get_tag_hash "0.1.0+1.20.0")
|
||||||
|
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app cmd "$TEST_APP_DOMAIN" app test_cmd
|
||||||
|
assert_success
|
||||||
|
assert_output --partial 'baz'
|
||||||
|
|
||||||
|
assert_equal $(_get_current_hash) "$tagHash"
|
||||||
|
}
|
||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@test "error if missing service" {
|
@test "error if missing service" {
|
||||||
_deploy_app
|
_deploy_app
|
||||||
|
@ -16,12 +16,13 @@ teardown_file(){
|
|||||||
setup(){
|
setup(){
|
||||||
load "$PWD/tests/integration/helpers/common"
|
load "$PWD/tests/integration/helpers/common"
|
||||||
_common_setup
|
_common_setup
|
||||||
|
_ensure_catalogue
|
||||||
}
|
}
|
||||||
|
|
||||||
teardown(){
|
teardown(){
|
||||||
_undeploy_app
|
|
||||||
_reset_recipe
|
_reset_recipe
|
||||||
_reset_app
|
_reset_app
|
||||||
|
_undeploy_app
|
||||||
_reset_tags
|
_reset_tags
|
||||||
|
|
||||||
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
@ -86,19 +87,18 @@ teardown(){
|
|||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@test "ensure recipe not up to date if --offline" {
|
@test "ensure recipe not up to date if --offline" {
|
||||||
wantHash=$(_get_n_hash 3)
|
_ensure_env_version "0.1.0+1.20.0"
|
||||||
|
latestRelease=$(_latest_release)
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3
|
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -d "$latestRelease"
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
assert_equal $(_get_current_hash) "$wantHash"
|
|
||||||
|
|
||||||
# NOTE(d1): need to use --chaos to force same commit
|
|
||||||
run $ABRA app deploy "$TEST_APP_DOMAIN" \
|
run $ABRA app deploy "$TEST_APP_DOMAIN" \
|
||||||
--no-input --no-converge-checks --chaos --offline
|
--no-input --no-converge-checks --offline
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
assert_equal $(_get_current_hash) "$wantHash"
|
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -l
|
||||||
|
refute_output --partial "$latestRelease"
|
||||||
}
|
}
|
||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@ -106,6 +106,7 @@ teardown(){
|
|||||||
latestCommit="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)"
|
latestCommit="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)"
|
||||||
|
|
||||||
_remove_tags
|
_remove_tags
|
||||||
|
_wipe_env_version
|
||||||
|
|
||||||
# NOTE(d1): need to pass --offline to stop tags being pulled again
|
# NOTE(d1): need to pass --offline to stop tags being pulled again
|
||||||
run $ABRA app deploy "$TEST_APP_DOMAIN" \
|
run $ABRA app deploy "$TEST_APP_DOMAIN" \
|
||||||
@ -125,6 +126,8 @@ teardown(){
|
|||||||
|
|
||||||
assert_equal $(_get_current_hash) "$wantHash"
|
assert_equal $(_get_current_hash) "$wantHash"
|
||||||
|
|
||||||
|
_wipe_env_version
|
||||||
|
|
||||||
run $ABRA app deploy "$TEST_APP_DOMAIN" \
|
run $ABRA app deploy "$TEST_APP_DOMAIN" \
|
||||||
--no-input --no-converge-checks --chaos
|
--no-input --no-converge-checks --chaos
|
||||||
assert_success
|
assert_success
|
||||||
@ -171,14 +174,12 @@ teardown(){
|
|||||||
run $ABRA app deploy "$TEST_APP_DOMAIN" \
|
run $ABRA app deploy "$TEST_APP_DOMAIN" \
|
||||||
--no-input --no-converge-checks --force
|
--no-input --no-converge-checks --force
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial 'already deployed but continuing'
|
assert_output --partial 'already deployed'
|
||||||
assert_output --partial '--force'
|
|
||||||
|
|
||||||
run $ABRA app deploy "$TEST_APP_DOMAIN" \
|
run $ABRA app deploy "$TEST_APP_DOMAIN" \
|
||||||
--no-input --no-converge-checks --chaos
|
--no-input --no-converge-checks --chaos
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial 'already deployed but continuing'
|
assert_output --partial 'already deployed'
|
||||||
assert_output --partial '--chaos'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@ -351,39 +352,6 @@ teardown(){
|
|||||||
|
|
||||||
_undeploy_app
|
_undeploy_app
|
||||||
|
|
||||||
# TODO(d1): use of `--chaos` is a hack while the following is not fixed
|
run $ABRA app secret rm "$TEST_APP_DOMAIN" --all
|
||||||
# https://git.coopcloud.tech/coop-cloud/organising/issues/620
|
|
||||||
run $ABRA app secret rm "$TEST_APP_DOMAIN" --all --chaos
|
|
||||||
assert_success
|
assert_success
|
||||||
}
|
}
|
||||||
|
|
||||||
# bats test_tags=slow
|
|
||||||
@test "deploy chaos commit" {
|
|
||||||
tagHash=$(_get_tag_hash "0.1.0+1.20.0")
|
|
||||||
|
|
||||||
run $ABRA app deploy "$TEST_APP_DOMAIN" "$tagHash" --no-input --no-converge-checks
|
|
||||||
assert_success
|
|
||||||
assert_output --partial 'chaos mode'
|
|
||||||
}
|
|
||||||
|
|
||||||
# bats test_tags=slow
|
|
||||||
@test "deploy remote recipe" {
|
|
||||||
run sed -i 's/TYPE=abra-test-recipe/RECIPE=git.coopcloud.tech\/coop-cloud\/abra-test-recipe/g' \
|
|
||||||
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
|
||||||
assert_success
|
|
||||||
|
|
||||||
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
|
||||||
assert_success
|
|
||||||
assert_output --partial "git.coopcloud.tech/coop-cloud/abra-test-recipe"
|
|
||||||
}
|
|
||||||
|
|
||||||
# bats test_tags=slow
|
|
||||||
@test "deploy remote recipe with version" {
|
|
||||||
run sed -i 's/TYPE=abra-test-recipe/RECIPE=git.coopcloud.tech\/coop-cloud\/abra-test-recipe:0.2.0+1.21.0/g' \
|
|
||||||
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
|
||||||
assert_success
|
|
||||||
|
|
||||||
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
|
||||||
assert_success
|
|
||||||
assert_output --partial '0.2.0+1.21.0'
|
|
||||||
}
|
|
||||||
|
99
tests/integration/app_deploy_env_version.bats
Normal file
99
tests/integration/app_deploy_env_version.bats
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
setup_file(){
|
||||||
|
load "$PWD/tests/integration/helpers/common"
|
||||||
|
_common_setup
|
||||||
|
_add_server
|
||||||
|
_new_app
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown_file(){
|
||||||
|
_rm_app
|
||||||
|
_rm_server
|
||||||
|
_reset_recipe
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(){
|
||||||
|
load "$PWD/tests/integration/helpers/common"
|
||||||
|
_common_setup
|
||||||
|
_ensure_catalogue
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown(){
|
||||||
|
_reset_recipe
|
||||||
|
_undeploy_app
|
||||||
|
_reset_app
|
||||||
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "deploy version written to env" {
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input --no-converge-checks
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "redeploy overwrites env version" {
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input --no-converge-checks
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.2.0+1.21.0" \
|
||||||
|
--no-input --no-converge-checks --force
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run grep -q "TYPE=$TEST_RECIPE:0.2.0+1.21.0" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "chaos commit written to env" {
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" "1e83340e" --no-input --no-converge-checks
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run grep -q "TYPE=$TEST_RECIPE:1e83340e" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "redeploy reads from env version" {
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input --no-converge-checks
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
_undeploy_app
|
||||||
|
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
||||||
|
assert_success
|
||||||
|
assert_output --partial '0.1.0+1.20.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "specific version overrides env version" {
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input --no-converge-checks
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.2.0+1.21.0" \
|
||||||
|
--no-input --no-converge-checks --force --debug
|
||||||
|
assert_success
|
||||||
|
assert_output --partial "overriding env file version"
|
||||||
|
|
||||||
|
run grep -q "TYPE=$TEST_RECIPE:0.2.0+1.21.0" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
}
|
73
tests/integration/app_deploy_remote_recipes.bats
Normal file
73
tests/integration/app_deploy_remote_recipes.bats
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
setup_file(){
|
||||||
|
load "$PWD/tests/integration/helpers/common"
|
||||||
|
_common_setup
|
||||||
|
_add_server
|
||||||
|
_new_app
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown_file(){
|
||||||
|
_rm_app
|
||||||
|
_rm_server
|
||||||
|
_reset_recipe
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(){
|
||||||
|
load "$PWD/tests/integration/helpers/common"
|
||||||
|
_common_setup
|
||||||
|
_ensure_catalogue
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown(){
|
||||||
|
_reset_recipe
|
||||||
|
_undeploy_app
|
||||||
|
_reset_app
|
||||||
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "deploy remote recipe" {
|
||||||
|
run sed -i 's/TYPE=abra-test-recipe:.*/TYPE=git.coopcloud.tech\/coop-cloud\/abra-test-recipe/g' \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
||||||
|
assert_success
|
||||||
|
assert_output --partial "git.coopcloud.tech/coop-cloud/abra-test-recipe"
|
||||||
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "deploy remote recipe with version" {
|
||||||
|
run sed -i 's/TYPE=abra-test-recipe:.*/TYPE=git.coopcloud.tech\/coop-cloud\/abra-test-recipe:0.2.0+1.21.0/g' \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
||||||
|
assert_success
|
||||||
|
assert_output --partial '0.2.0+1.21.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "deploy remote recipe with chaos commit" {
|
||||||
|
run sed -i 's/TYPE=abra-test-recipe:.*/TYPE=git.coopcloud.tech\/coop-cloud\/abra-test-recipe:1e83340e/g' \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
||||||
|
assert_success
|
||||||
|
assert_output --partial '1e83340e'
|
||||||
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "remote recipe version written to env" {
|
||||||
|
run sed -i 's/TYPE=abra-test-recipe:.*/TYPE=git.coopcloud.tech\/coop-cloud\/abra-test-recipe/g' \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run grep -q "TYPE=git.coopcloud.tech\/coop-cloud\/abra-test-recipe:$(_latest_release)" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
}
|
44
tests/integration/app_env_version.bats
Normal file
44
tests/integration/app_env_version.bats
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
setup_file(){
|
||||||
|
load "$PWD/tests/integration/helpers/common"
|
||||||
|
_common_setup
|
||||||
|
_add_server
|
||||||
|
_new_app
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown_file(){
|
||||||
|
_rm_app
|
||||||
|
_rm_server
|
||||||
|
_reset_recipe
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(){
|
||||||
|
load "$PWD/tests/integration/helpers/common"
|
||||||
|
_common_setup
|
||||||
|
_ensure_catalogue
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown(){
|
||||||
|
_reset_app
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "badly formatted env version bails out" {
|
||||||
|
run sed -i 's/TYPE=abra-test-recipe/TYPE=abra-test-recipe:0.2.0+1.21.0/g' \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
||||||
|
assert_failure
|
||||||
|
assert_output --partial 'seems invalid'
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "invalid env version bails out" {
|
||||||
|
run sed -i 's/TYPE=abra-test-recipe:.*/TYPE=abra-test-recipe:DOESNTEXIST/g' \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
||||||
|
assert_failure
|
||||||
|
assert_output --partial 'not found'
|
||||||
|
}
|
@ -62,8 +62,8 @@ teardown(){
|
|||||||
|
|
||||||
run $ABRA app ls --server foo.com
|
run $ABRA app ls --server foo.com
|
||||||
assert_success
|
assert_success
|
||||||
refute_output --partial "server: $TEST_SERVER |"
|
refute_output --partial "SERVER: $TEST_SERVER"
|
||||||
assert_output --partial "server: foo.com |"
|
assert_output --partial "SERVER: foo.com"
|
||||||
|
|
||||||
run rm -rf "$ABRA_DIR/servers/foo.com"
|
run rm -rf "$ABRA_DIR/servers/foo.com"
|
||||||
assert_success
|
assert_success
|
||||||
@ -97,8 +97,8 @@ teardown(){
|
|||||||
@test "server stats are correct" {
|
@test "server stats are correct" {
|
||||||
run $ABRA app ls
|
run $ABRA app ls
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial "server: $TEST_SERVER"
|
assert_output --partial "SERVER: $TEST_SERVER"
|
||||||
assert_output --partial "total apps: 1"
|
assert_output --partial "TOTAL APPS: 1"
|
||||||
|
|
||||||
run mkdir -p "$ABRA_DIR/servers/foo.com"
|
run mkdir -p "$ABRA_DIR/servers/foo.com"
|
||||||
assert_success
|
assert_success
|
||||||
@ -113,8 +113,8 @@ teardown(){
|
|||||||
assert_success
|
assert_success
|
||||||
assert_output --partial "$TEST_SERVER"
|
assert_output --partial "$TEST_SERVER"
|
||||||
assert_output --partial "foo.com"
|
assert_output --partial "foo.com"
|
||||||
assert_output --partial "total servers: 2"
|
assert_output --partial "TOTAL SERVERS: 2"
|
||||||
assert_output --partial "total apps: 2"
|
assert_output --partial "TOTAL APPS: 2"
|
||||||
|
|
||||||
run rm -rf "$ABRA_DIR/servers/foo.com"
|
run rm -rf "$ABRA_DIR/servers/foo.com"
|
||||||
assert_success
|
assert_success
|
||||||
|
@ -39,17 +39,41 @@ teardown(){
|
|||||||
_get_head_hash
|
_get_head_hash
|
||||||
_get_current_hash
|
_get_current_hash
|
||||||
assert_equal "$headHash" "$currentHash"
|
assert_equal "$headHash" "$currentHash"
|
||||||
|
|
||||||
|
run grep -q "TYPE=$TEST_RECIPE:$(_latest_release)" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "create new app with version" {
|
@test "create new app with version" {
|
||||||
run $ABRA app new "$TEST_RECIPE" 0.1.1+1.20.2 \
|
run $ABRA app new "$TEST_RECIPE" 0.3.0+1.21.0 \
|
||||||
--no-input \
|
--no-input \
|
||||||
--server "$TEST_SERVER" \
|
--server "$TEST_SERVER" \
|
||||||
--domain "$TEST_APP_DOMAIN"
|
--domain "$TEST_APP_DOMAIN"
|
||||||
assert_success
|
assert_success
|
||||||
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
|
||||||
assert_equal $(_get_tag_hash 0.1.1+1.20.2) $(_get_current_hash)
|
assert_equal $(_get_tag_hash 0.3.0+1.21.0) $(_get_current_hash)
|
||||||
|
|
||||||
|
run grep -q "TYPE=$TEST_RECIPE:0.3.0+1.21.0" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "create new app with chaos commit" {
|
||||||
|
run $ABRA app new "$TEST_RECIPE" 1e83340e \
|
||||||
|
--no-input \
|
||||||
|
--server "$TEST_SERVER" \
|
||||||
|
--domain "$TEST_APP_DOMAIN"
|
||||||
|
assert_success
|
||||||
|
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
|
||||||
|
currentHash=$(_get_current_hash)
|
||||||
|
assert_equal 1e83340e ${currentHash:0:8}
|
||||||
|
|
||||||
|
run grep -q "TYPE=$TEST_RECIPE:1e83340e" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "does not overwrite existing env files" {
|
@test "does not overwrite existing env files" {
|
||||||
|
@ -41,13 +41,11 @@ teardown(){
|
|||||||
@test "show ps report" {
|
@test "show ps report" {
|
||||||
_deploy_app
|
_deploy_app
|
||||||
|
|
||||||
latestRelease=$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -l | tail -n 1)
|
|
||||||
|
|
||||||
run $ABRA app ps "$TEST_APP_DOMAIN"
|
run $ABRA app ps "$TEST_APP_DOMAIN"
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial 'app'
|
assert_output --partial 'app'
|
||||||
assert_output --partial 'healthy'
|
assert_output --partial 'healthy'
|
||||||
assert_output --partial "$latestRelease"
|
assert_output --partial $(_latest_release)
|
||||||
assert_output --partial 'false' # not a chaos deploy
|
assert_output --partial 'false' # not a chaos deploy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,18 +58,18 @@ teardown(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
@test "ensure recipe not up to date if --offline" {
|
@test "ensure recipe not up to date if --offline" {
|
||||||
wantHash=$(_get_n_hash 3)
|
_ensure_env_version "0.1.0+1.20.0"
|
||||||
|
latestRelease=$(_latest_release)
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3
|
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -d "$latestRelease"
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
assert_equal $(_get_current_hash) "$wantHash"
|
|
||||||
|
|
||||||
run $ABRA app rollback "$TEST_APP_DOMAIN" \
|
run $ABRA app rollback "$TEST_APP_DOMAIN" \
|
||||||
--no-input --no-converge-checks --offline
|
--no-input --no-converge-checks --offline
|
||||||
assert_failure
|
assert_failure
|
||||||
|
|
||||||
assert_equal $(_get_current_hash) $wantHash
|
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -l
|
||||||
|
refute_output --partial "$latestRelease"
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "error if not already deployed" {
|
@test "error if not already deployed" {
|
||||||
|
44
tests/integration/app_rollback_env_version.bats
Normal file
44
tests/integration/app_rollback_env_version.bats
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
setup_file(){
|
||||||
|
load "$PWD/tests/integration/helpers/common"
|
||||||
|
_common_setup
|
||||||
|
_add_server
|
||||||
|
_new_app
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown_file(){
|
||||||
|
_rm_app
|
||||||
|
_rm_server
|
||||||
|
_reset_recipe
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(){
|
||||||
|
load "$PWD/tests/integration/helpers/common"
|
||||||
|
_common_setup
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown(){
|
||||||
|
_undeploy_app
|
||||||
|
_reset_recipe
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "rollback writes version to env file" {
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.2.0+1.21.0" --no-input --no-converge-checks
|
||||||
|
assert_success
|
||||||
|
assert_output --partial "0.2.0+1.21.0"
|
||||||
|
|
||||||
|
run grep -q "TYPE=abra-test-recipe:0.2.0+1.21.0" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.0+1.20.0" \
|
||||||
|
--no-input --no-converge-checks --debug
|
||||||
|
assert_success
|
||||||
|
assert_output --partial "0.1.0+1.20.0"
|
||||||
|
assert_output --partial "overriding env file version"
|
||||||
|
|
||||||
|
run grep -q "TYPE=abra-test-recipe:0.1.0+1.20.0" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
}
|
@ -217,7 +217,8 @@ teardown(){
|
|||||||
run bash -c "echo bar >> $ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
run bash -c "echo bar >> $ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
|
|
||||||
run $ABRA app secret insert \
|
run $ABRA app secret insert \
|
||||||
--file "$TEST_APP_DOMAIN" test_pass_one v1 "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
--chaos \
|
||||||
|
--file "$TEST_APP_DOMAIN" test_pass_one v1 "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial 'successfully stored on server'
|
assert_output --partial 'successfully stored on server'
|
||||||
|
|
||||||
@ -317,9 +318,10 @@ teardown(){
|
|||||||
run $ABRA app secret generate "$TEST_APP_DOMAIN" --all
|
run $ABRA app secret generate "$TEST_APP_DOMAIN" --all
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
run $ABRA app secret ls "$TEST_APP_DOMAIN" --machine
|
run bash -c '$ABRA app secret ls "$TEST_APP_DOMAIN" --machine \
|
||||||
|
| jq -r ".[] | select(.name==\"test_pass_two\") | .version"'
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial '"created-on-server":"true"'
|
assert_output --partial 'v1'
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "ls: bail if unstaged changes and no --chaos" {
|
@test "ls: bail if unstaged changes and no --chaos" {
|
||||||
|
93
tests/integration/app_secret_env_version.bats
Normal file
93
tests/integration/app_secret_env_version.bats
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
setup_file(){
|
||||||
|
load "$PWD/tests/integration/helpers/common"
|
||||||
|
_common_setup
|
||||||
|
_add_server
|
||||||
|
|
||||||
|
# NOTE(d1): create new app without secrets
|
||||||
|
run $ABRA app new "$TEST_RECIPE" \
|
||||||
|
--no-input \
|
||||||
|
--server "$TEST_SERVER" \
|
||||||
|
--domain "$TEST_APP_DOMAIN"
|
||||||
|
assert_success
|
||||||
|
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown_file(){
|
||||||
|
_rm_app
|
||||||
|
_rm_server
|
||||||
|
_reset_recipe
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(){
|
||||||
|
load "$PWD/tests/integration/helpers/common"
|
||||||
|
_common_setup
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown(){
|
||||||
|
_reset_recipe
|
||||||
|
_reset_app
|
||||||
|
|
||||||
|
run $ABRA app secret rm "$TEST_APP_DOMAIN" --all --no-input
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "generate: respect env version" {
|
||||||
|
tagHash=$(_get_tag_hash "0.2.0+1.21.0")
|
||||||
|
|
||||||
|
run sed -i 's/TYPE=abra-test-recipe:.*/TYPE=abra-test-recipe:0.2.0+1.21.0/g' \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app secret generate "$TEST_APP_DOMAIN" --all
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
assert_equal $(_get_current_hash) "$tagHash"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "insert: respect env version" {
|
||||||
|
tagHash=$(_get_tag_hash "0.2.0+1.21.0")
|
||||||
|
|
||||||
|
run sed -i 's/TYPE=abra-test-recipe:.*/TYPE=abra-test-recipe:0.2.0+1.21.0/g' \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app secret insert "$TEST_APP_DOMAIN" test_pass_one v1 foo
|
||||||
|
assert_success
|
||||||
|
assert_output --partial 'successfully stored on server'
|
||||||
|
|
||||||
|
assert_equal $(_get_current_hash) "$tagHash"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "rm: respect env version" {
|
||||||
|
tagHash=$(_get_tag_hash "0.2.0+1.21.0")
|
||||||
|
|
||||||
|
run sed -i 's/TYPE=abra-test-recipe:.*/TYPE=abra-test-recipe:0.2.0+1.21.0/g' \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app secret generate "$TEST_APP_DOMAIN" --all
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app secret rm "$TEST_APP_DOMAIN" --all
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
assert_equal $(_get_current_hash) "$tagHash"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "ls: respect env version" {
|
||||||
|
tagHash=$(_get_tag_hash "0.2.0+1.21.0")
|
||||||
|
|
||||||
|
run sed -i 's/TYPE=abra-test-recipe:.*/TYPE=abra-test-recipe:0.2.0+1.21.0/g' \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app secret generate "$TEST_APP_DOMAIN" --all
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app secret ls "$TEST_APP_DOMAIN"
|
||||||
|
assert_success
|
||||||
|
assert_output --partial 'true'
|
||||||
|
|
||||||
|
assert_equal $(_get_current_hash) "$tagHash"
|
||||||
|
}
|
@ -18,8 +18,9 @@ setup(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
teardown(){
|
teardown(){
|
||||||
_undeploy_app
|
|
||||||
_reset_recipe
|
_reset_recipe
|
||||||
|
_reset_app
|
||||||
|
_undeploy_app
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "validate app argument" {
|
@test "validate app argument" {
|
||||||
@ -123,11 +124,9 @@ teardown(){
|
|||||||
assert_success
|
assert_success
|
||||||
assert_output --partial '0.1.0+1.20.0'
|
assert_output --partial '0.1.0+1.20.0'
|
||||||
|
|
||||||
latestRelease=$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -l | tail -n 1)
|
|
||||||
|
|
||||||
run $ABRA app upgrade "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
run $ABRA app upgrade "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial "$latestRelease"
|
assert_output --partial "$(_latest_release)"
|
||||||
}
|
}
|
||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@ -136,11 +135,9 @@ teardown(){
|
|||||||
assert_success
|
assert_success
|
||||||
assert_output --partial '0.1.1+1.20.2'
|
assert_output --partial '0.1.1+1.20.2'
|
||||||
|
|
||||||
latestRelease=$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -l | tail -n 1)
|
|
||||||
|
|
||||||
run $ABRA app upgrade "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
run $ABRA app upgrade "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial "$latestRelease"
|
assert_output --partial "$(_latest_release)"
|
||||||
assert_output --partial 'release notes baz' # 0.2.0+1.21.0
|
assert_output --partial 'release notes baz' # 0.2.0+1.21.0
|
||||||
refute_output --partial 'release notes bar' # 0.1.1+1.20.2
|
refute_output --partial 'release notes bar' # 0.1.1+1.20.2
|
||||||
}
|
}
|
||||||
@ -164,11 +161,9 @@ teardown(){
|
|||||||
assert_success
|
assert_success
|
||||||
assert_output --partial '0.1.0+1.20.0'
|
assert_output --partial '0.1.0+1.20.0'
|
||||||
|
|
||||||
latestRelease=$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -l | tail -n 1)
|
|
||||||
|
|
||||||
run $ABRA app upgrade "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
run $ABRA app upgrade "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial "$latestRelease"
|
assert_output --partial "$(_latest_release)"
|
||||||
assert_output --partial 'release notes bar' # 0.1.1+1.20.2
|
assert_output --partial 'release notes bar' # 0.1.1+1.20.2
|
||||||
assert_output --partial 'release notes baz' # 0.2.0+1.21.0
|
assert_output --partial 'release notes baz' # 0.2.0+1.21.0
|
||||||
}
|
}
|
||||||
|
43
tests/integration/app_upgrade_env_version.bats
Normal file
43
tests/integration/app_upgrade_env_version.bats
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
setup_file(){
|
||||||
|
load "$PWD/tests/integration/helpers/common"
|
||||||
|
_common_setup
|
||||||
|
_add_server
|
||||||
|
_new_app
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown_file(){
|
||||||
|
_rm_app
|
||||||
|
_rm_server
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(){
|
||||||
|
load "$PWD/tests/integration/helpers/common"
|
||||||
|
_common_setup
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown(){
|
||||||
|
_undeploy_app
|
||||||
|
_reset_recipe
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "upgrade writes version to env file" {
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input --no-converge-checks
|
||||||
|
assert_success
|
||||||
|
assert_output --partial '0.1.0+1.20.0'
|
||||||
|
|
||||||
|
run grep -q "TYPE=abra-test-recipe:0.1.0+1.20.0" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.2.0+1.21.0" \
|
||||||
|
--no-input --no-converge-checks --debug
|
||||||
|
assert_success
|
||||||
|
assert_output --partial "0.2.0+1.21.0"
|
||||||
|
assert_output --partial "overriding env file version"
|
||||||
|
|
||||||
|
run grep -q "TYPE=abra-test-recipe:0.2.0+1.21.0" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
}
|
@ -1,5 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
_latest_release(){
|
||||||
|
echo $(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -l | tail -n 1)
|
||||||
|
}
|
||||||
|
|
||||||
_fetch_recipe() {
|
_fetch_recipe() {
|
||||||
if [[ ! -d "$ABRA_DIR/recipes/$TEST_RECIPE" ]]; then
|
if [[ ! -d "$ABRA_DIR/recipes/$TEST_RECIPE" ]]; then
|
||||||
run mkdir -p "$ABRA_DIR/recipes"
|
run mkdir -p "$ABRA_DIR/recipes"
|
||||||
@ -19,10 +23,29 @@ _reset_recipe(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ensure_latest_version(){
|
_ensure_latest_version(){
|
||||||
latestRelease=$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -l | tail -n 1)
|
latestRelease=$(_latest_release)
|
||||||
|
|
||||||
if [ ! $latestRelease = "$1" ]; then
|
if [ ! $latestRelease = "$1" ]; then
|
||||||
echo "expected latest recipe version of '$1', saw: $latestRelease"
|
echo "expected latest recipe version of '$1', saw: $latestRelease"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ensure_catalogue(){
|
||||||
|
if [[ ! -d "$ABRA_DIR/catalogue" ]]; then
|
||||||
|
run git clone https://git.coopcloud.tech/coop-cloud/recipes-catalogue-json.git $ABRA_DIR/catalogue
|
||||||
|
assert_success
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_ensure_env_version(){
|
||||||
|
run sed -i 's/TYPE=abra-test-recipe:.*/TYPE=abra-test-recipe:$1/g' \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
}
|
||||||
|
|
||||||
|
_wipe_env_version(){
|
||||||
|
run sed -i 's/TYPE=abra-test-recipe:.*/TYPE=abra-test-recipe/g' \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
}
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
setup_file(){
|
||||||
|
load "$PWD/tests/integration/helpers/common"
|
||||||
|
_common_setup
|
||||||
|
_ensure_catalogue
|
||||||
|
}
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
load "$PWD/tests/integration/helpers/common"
|
load "$PWD/tests/integration/helpers/common"
|
||||||
_common_setup
|
_common_setup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@test "recipe versions" {
|
@test "recipe versions" {
|
||||||
run $ABRA recipe versions gitea
|
run $ABRA recipe versions gitea
|
||||||
assert_success
|
assert_success
|
||||||
@ -12,11 +19,9 @@ setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@test "local tags used if no catalogue entry" {
|
@test "local tags used if no catalogue entry" {
|
||||||
latestRelease=$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -l | tail -n 1)
|
|
||||||
|
|
||||||
run $ABRA recipe versions "$TEST_RECIPE"
|
run $ABRA recipe versions "$TEST_RECIPE"
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial "$latestRelease"
|
assert_output --partial "$(_latest_release)"
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "versions listed in correct order" {
|
@test "versions listed in correct order" {
|
||||||
|
@ -13,6 +13,7 @@ teardown_file(){
|
|||||||
setup(){
|
setup(){
|
||||||
load "$PWD/tests/integration/helpers/common"
|
load "$PWD/tests/integration/helpers/common"
|
||||||
_common_setup
|
_common_setup
|
||||||
|
_add_server
|
||||||
}
|
}
|
||||||
|
|
||||||
teardown(){
|
teardown(){
|
||||||
@ -61,12 +62,13 @@ teardown(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
@test "machine readable output" {
|
@test "machine readable output" {
|
||||||
run "$ABRA" server ls --machine
|
if [ ! "$TEST_SERVER" = "default" ]; then
|
||||||
|
skip "can only diff output against 'default' server (local)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
output=$("$ABRA" server ls --machine)
|
||||||
|
run diff \
|
||||||
|
<(jq -S "." <(echo "$output")) \
|
||||||
|
<(jq -S "." <(echo '[{"host":"local","name":"default"}]'))
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
expectedOutput='[{"name":"'
|
|
||||||
expectedOutput+="$TEST_SERVER"
|
|
||||||
expectedOutput+='"'
|
|
||||||
|
|
||||||
assert_output --partial "$expectedOutput"
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user