forked from toolshed/abra
Compare commits
43 Commits
small-impr
...
deploy-rel
Author | SHA1 | Date | |
---|---|---|---|
5a4dac7e76 | |||
628a9a4b3f | |||
74108b0dd9 | |||
3727c7fa78
|
|||
9a4414fd13
|
|||
9f189680f3
|
|||
356e527f1f
|
|||
7ec61c6d03
|
|||
fab93a559a
|
|||
8ac31330be
|
|||
03000c25e0
|
|||
3f32dbb1a3
|
|||
27f68b1dc5
|
|||
a0da5299fe | |||
866c5c4536
|
|||
dc4c6784cb
|
|||
97959ef5da | |||
b6573720ec | |||
4e8995cc0e
|
|||
efb3fd8759
|
|||
008582c3d9
|
|||
8fa20e2c7f
|
|||
aa1dc795ef
|
|||
18df498295
|
|||
671e1ca276
|
|||
0df2b15c33
|
|||
3f29084664
|
|||
0bb25a00ec
|
|||
28c7676413 | |||
730fef09a3 | |||
8d076a308a | |||
9510c04aeb | |||
d9e60afd71
|
|||
31fa9b1a7a
|
|||
f664599836 | |||
bba1640913 | |||
7b54c2b5b9 | |||
8ee1947fe9 | |||
b313b0a145
|
|||
1f9b863be0
|
|||
3b3ce85ef9
|
|||
1f8662cd95
|
|||
375e17a4a0
|
@ -3,12 +3,12 @@ kind: pipeline
|
|||||||
name: coopcloud.tech/abra
|
name: coopcloud.tech/abra
|
||||||
steps:
|
steps:
|
||||||
- name: make check
|
- name: make check
|
||||||
image: golang:1.21
|
image: golang:1.22
|
||||||
commands:
|
commands:
|
||||||
- make check
|
- make check
|
||||||
|
|
||||||
- name: make test
|
- name: make test
|
||||||
image: golang:1.21
|
image: golang:1.22
|
||||||
environment:
|
environment:
|
||||||
CATL_URL: https://git.coopcloud.tech/coop-cloud/recipes-catalogue-json.git
|
CATL_URL: https://git.coopcloud.tech/coop-cloud/recipes-catalogue-json.git
|
||||||
commands:
|
commands:
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,7 +2,7 @@
|
|||||||
.e2e.env
|
.e2e.env
|
||||||
.envrc
|
.envrc
|
||||||
.vscode/
|
.vscode/
|
||||||
|
/abra
|
||||||
/kadabra
|
/kadabra
|
||||||
abra
|
|
||||||
dist/
|
dist/
|
||||||
tests/integration/.bats
|
tests/integration/.bats
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Build image
|
# Build image
|
||||||
FROM golang:1.21-alpine AS build
|
FROM golang:1.22-alpine AS build
|
||||||
|
|
||||||
ENV GOPRIVATE coopcloud.tech
|
ENV GOPRIVATE coopcloud.tech
|
||||||
|
|
||||||
|
6
Makefile
6
Makefile
@ -2,7 +2,7 @@ ABRA := ./cmd/abra
|
|||||||
KADABRA := ./cmd/kadabra
|
KADABRA := ./cmd/kadabra
|
||||||
COMMIT := $(shell git rev-list -1 HEAD)
|
COMMIT := $(shell git rev-list -1 HEAD)
|
||||||
GOPATH := $(shell go env GOPATH)
|
GOPATH := $(shell go env GOPATH)
|
||||||
GOVERSION := 1.21
|
GOVERSION := 1.22
|
||||||
LDFLAGS := "-X 'main.Commit=$(COMMIT)'"
|
LDFLAGS := "-X 'main.Commit=$(COMMIT)'"
|
||||||
DIST_LDFLAGS := $(LDFLAGS)" -s -w"
|
DIST_LDFLAGS := $(LDFLAGS)" -s -w"
|
||||||
GCFLAGS := "all=-l -B"
|
GCFLAGS := "all=-l -B"
|
||||||
@ -45,10 +45,10 @@ clean:
|
|||||||
@rm '$(GOPATH)/bin/kadabra'
|
@rm '$(GOPATH)/bin/kadabra'
|
||||||
|
|
||||||
format:
|
format:
|
||||||
@gofmt -s -w .
|
@gofmt -s -w $$(find . -type f -name '*.go' | grep -v "/vendor/")
|
||||||
|
|
||||||
check:
|
check:
|
||||||
@test -z $$(gofmt -l .) || \
|
@test -z $$(gofmt -l $$(find . -type f -name '*.go' | grep -v "/vendor/")) || \
|
||||||
(echo "gofmt: formatting issue - run 'make format' to resolve" && exit 1)
|
(echo "gofmt: formatting issue - run 'make format' to resolve" && exit 1)
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
@ -1,34 +1,11 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var AppCommand = cli.Command{
|
var AppCommand = &cobra.Command{
|
||||||
Name: "app",
|
Use: "app [cmd] [args] [flags]",
|
||||||
Aliases: []string{"a"},
|
Aliases: []string{"a"},
|
||||||
Usage: "Manage apps",
|
Short: "Manage apps",
|
||||||
ArgsUsage: "<domain>",
|
|
||||||
Subcommands: []cli.Command{
|
|
||||||
appBackupCommand,
|
|
||||||
appCheckCommand,
|
|
||||||
appCmdCommand,
|
|
||||||
appConfigCommand,
|
|
||||||
appCpCommand,
|
|
||||||
appDeployCommand,
|
|
||||||
appListCommand,
|
|
||||||
appLogsCommand,
|
|
||||||
appNewCommand,
|
|
||||||
appPsCommand,
|
|
||||||
appRemoveCommand,
|
|
||||||
appRestartCommand,
|
|
||||||
appRestoreCommand,
|
|
||||||
appRollbackCommand,
|
|
||||||
appRunCommand,
|
|
||||||
appSecretCommand,
|
|
||||||
appServicesCommand,
|
|
||||||
appUndeployCommand,
|
|
||||||
appUpgradeCommand,
|
|
||||||
appVolumeCommand,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
@ -7,44 +7,76 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var snapshot string
|
var AppBackupListCommand = &cobra.Command{
|
||||||
var snapshotFlag = &cli.StringFlag{
|
Use: "list <app> [flags]",
|
||||||
Name: "snapshot, s",
|
|
||||||
Usage: "Lists specific snapshot",
|
|
||||||
Destination: &snapshot,
|
|
||||||
}
|
|
||||||
|
|
||||||
var includePath string
|
|
||||||
var includePathFlag = &cli.StringFlag{
|
|
||||||
Name: "path, p",
|
|
||||||
Usage: "Include path",
|
|
||||||
Destination: &includePath,
|
|
||||||
}
|
|
||||||
|
|
||||||
var resticRepo string
|
|
||||||
var resticRepoFlag = &cli.StringFlag{
|
|
||||||
Name: "repo, r",
|
|
||||||
Usage: "Restic repository",
|
|
||||||
Destination: &resticRepo,
|
|
||||||
}
|
|
||||||
|
|
||||||
var appBackupListCommand = cli.Command{
|
|
||||||
Name: "list",
|
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Flags: []cli.Flag{
|
Short: "List the contents of a snapshot",
|
||||||
internal.DebugFlag,
|
Args: cobra.ExactArgs(1),
|
||||||
internal.OfflineFlag,
|
ValidArgsFunction: func(
|
||||||
snapshotFlag,
|
cmd *cobra.Command,
|
||||||
includePathFlag,
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
Usage: "List all backups",
|
app := internal.ValidateApp(args)
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
|
||||||
Action: func(c *cli.Context) error {
|
cl, err := client.New(app.Server)
|
||||||
app := internal.ValidateApp(c)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
targetContainer, err := internal.RetrieveBackupBotContainer(cl)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
execEnv := []string{
|
||||||
|
fmt.Sprintf("SERVICE=%s", app.Domain),
|
||||||
|
"MACHINE_LOGS=true",
|
||||||
|
}
|
||||||
|
|
||||||
|
if snapshot != "" {
|
||||||
|
log.Debugf("including SNAPSHOT=%s in backupbot exec invocation", snapshot)
|
||||||
|
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
|
||||||
|
}
|
||||||
|
|
||||||
|
if showAllPaths {
|
||||||
|
log.Debugf("including SHOW_ALL=%v in backupbot exec invocation", showAllPaths)
|
||||||
|
execEnv = append(execEnv, fmt.Sprintf("SHOW_ALL=%v", showAllPaths))
|
||||||
|
}
|
||||||
|
|
||||||
|
if timestamps {
|
||||||
|
log.Debugf("including TIMESTAMPS=%v in backupbot exec invocation", timestamps)
|
||||||
|
execEnv = append(execEnv, fmt.Sprintf("TIMESTAMPS=%v", timestamps))
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = internal.RunBackupCmdRemote(cl, "ls", targetContainer.ID, execEnv); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var AppBackupDownloadCommand = &cobra.Command{
|
||||||
|
Use: "download <app> [flags]",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Short: "Download a snapshot",
|
||||||
|
Long: `Downloads a backup.tar.gz to the current working directory.
|
||||||
|
|
||||||
|
"--volumes/-v" includes data contained in volumes alongide paths specified in
|
||||||
|
"backupbot.backup.path" labels.`,
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
ValidArgsFunction: func(
|
||||||
|
cmd *cobra.Command,
|
||||||
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
|
},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -60,80 +92,32 @@ var appBackupListCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
execEnv := []string{fmt.Sprintf("SERVICE=%s", app.Domain)}
|
execEnv := []string{
|
||||||
|
fmt.Sprintf("SERVICE=%s", app.Domain),
|
||||||
|
"MACHINE_LOGS=true",
|
||||||
|
}
|
||||||
|
|
||||||
if snapshot != "" {
|
if snapshot != "" {
|
||||||
log.Debugf("including SNAPSHOT=%s in backupbot exec invocation", snapshot)
|
log.Debugf("including SNAPSHOT=%s in backupbot exec invocation", snapshot)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
|
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
|
||||||
}
|
}
|
||||||
|
|
||||||
if includePath != "" {
|
if includePath != "" {
|
||||||
log.Debugf("including INCLUDE_PATH=%s in backupbot exec invocation", includePath)
|
log.Debugf("including INCLUDE_PATH=%s in backupbot exec invocation", includePath)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("INCLUDE_PATH=%s", includePath))
|
execEnv = append(execEnv, fmt.Sprintf("INCLUDE_PATH=%s", includePath))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := internal.RunBackupCmdRemote(cl, "ls", targetContainer.ID, execEnv); err != nil {
|
if includeSecrets {
|
||||||
log.Fatal(err)
|
log.Debugf("including SECRETS=%v in backupbot exec invocation", includeSecrets)
|
||||||
|
execEnv = append(execEnv, fmt.Sprintf("SECRETS=%v", includeSecrets))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if includeVolumes {
|
||||||
},
|
log.Debugf("including VOLUMES=%v in backupbot exec invocation", includeVolumes)
|
||||||
}
|
execEnv = append(execEnv, fmt.Sprintf("VOLUMES=%v", includeVolumes))
|
||||||
|
|
||||||
var appBackupDownloadCommand = cli.Command{
|
|
||||||
Name: "download",
|
|
||||||
Aliases: []string{"d"},
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
internal.DebugFlag,
|
|
||||||
internal.OfflineFlag,
|
|
||||||
snapshotFlag,
|
|
||||||
includePathFlag,
|
|
||||||
},
|
|
||||||
Before: internal.SubCommandBefore,
|
|
||||||
Usage: "Download a backup",
|
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
app := internal.ValidateApp(c)
|
|
||||||
|
|
||||||
if err := app.Recipe.EnsureExists(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !internal.Chaos {
|
if _, err := internal.RunBackupCmdRemote(cl, "download", targetContainer.ID, execEnv); err != nil {
|
||||||
if err := app.Recipe.EnsureIsClean(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !internal.Offline {
|
|
||||||
if err := app.Recipe.EnsureUpToDate(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := app.Recipe.EnsureLatest(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
targetContainer, err := internal.RetrieveBackupBotContainer(cl)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
execEnv := []string{fmt.Sprintf("SERVICE=%s", app.Domain)}
|
|
||||||
if snapshot != "" {
|
|
||||||
log.Debugf("including SNAPSHOT=%s in backupbot exec invocation", snapshot)
|
|
||||||
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
|
|
||||||
}
|
|
||||||
if includePath != "" {
|
|
||||||
log.Debugf("including INCLUDE_PATH=%s in backupbot exec invocation", includePath)
|
|
||||||
execEnv = append(execEnv, fmt.Sprintf("INCLUDE_PATH=%s", includePath))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := internal.RunBackupCmdRemote(cl, "download", targetContainer.ID, execEnv); err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,47 +126,27 @@ var appBackupDownloadCommand = cli.Command{
|
|||||||
if err = CopyFromContainer(cl, targetContainer.ID, remoteBackupDir, currentWorkingDir); err != nil {
|
if err = CopyFromContainer(cl, targetContainer.ID, remoteBackupDir, currentWorkingDir); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("backup successfully downloaded to current working directory")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var appBackupCreateCommand = cli.Command{
|
var AppBackupCreateCommand = &cobra.Command{
|
||||||
Name: "create",
|
Use: "create <app> [flags]",
|
||||||
Aliases: []string{"c"},
|
Aliases: []string{"c"},
|
||||||
Flags: []cli.Flag{
|
Short: "Create a new snapshot",
|
||||||
internal.DebugFlag,
|
Args: cobra.ExactArgs(1),
|
||||||
internal.OfflineFlag,
|
ValidArgsFunction: func(
|
||||||
resticRepoFlag,
|
cmd *cobra.Command,
|
||||||
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
Usage: "Create a new backup",
|
app := internal.ValidateApp(args)
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
app := internal.ValidateApp(c)
|
|
||||||
|
|
||||||
if err := app.Recipe.EnsureExists(); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !internal.Chaos {
|
|
||||||
if err := app.Recipe.EnsureIsClean(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !internal.Offline {
|
|
||||||
if err := app.Recipe.EnsureUpToDate(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := app.Recipe.EnsureLatest(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
cl, err := client.New(app.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -193,53 +157,35 @@ var appBackupCreateCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
execEnv := []string{fmt.Sprintf("SERVICE=%s", app.Domain)}
|
execEnv := []string{
|
||||||
if resticRepo != "" {
|
fmt.Sprintf("SERVICE=%s", app.Domain),
|
||||||
log.Debugf("including RESTIC_REPO=%s in backupbot exec invocation", resticRepo)
|
"MACHINE_LOGS=true",
|
||||||
execEnv = append(execEnv, fmt.Sprintf("RESTIC_REPO=%s", resticRepo))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := internal.RunBackupCmdRemote(cl, "create", targetContainer.ID, execEnv); err != nil {
|
if retries != "" {
|
||||||
|
log.Debugf("including RETRIES=%s in backupbot exec invocation", retries)
|
||||||
|
execEnv = append(execEnv, fmt.Sprintf("RETRIES=%s", retries))
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := internal.RunBackupCmdRemote(cl, "create", targetContainer.ID, execEnv); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var appBackupSnapshotsCommand = cli.Command{
|
var AppBackupSnapshotsCommand = &cobra.Command{
|
||||||
Name: "snapshots",
|
Use: "snapshots <app> [flags]",
|
||||||
Aliases: []string{"s"},
|
Aliases: []string{"s"},
|
||||||
Flags: []cli.Flag{
|
Short: "List all snapshots",
|
||||||
internal.DebugFlag,
|
Args: cobra.ExactArgs(1),
|
||||||
internal.OfflineFlag,
|
ValidArgsFunction: func(
|
||||||
snapshotFlag,
|
cmd *cobra.Command,
|
||||||
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
Usage: "List backup snapshots",
|
app := internal.ValidateApp(args)
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
app := internal.ValidateApp(c)
|
|
||||||
|
|
||||||
if err := app.Recipe.EnsureExists(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !internal.Chaos {
|
|
||||||
if err := app.Recipe.EnsureIsClean(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !internal.Offline {
|
|
||||||
if err := app.Recipe.EnsureUpToDate(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := app.Recipe.EnsureLatest(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
cl, err := client.New(app.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -251,29 +197,111 @@ var appBackupSnapshotsCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
execEnv := []string{fmt.Sprintf("SERVICE=%s", app.Domain)}
|
execEnv := []string{
|
||||||
if snapshot != "" {
|
fmt.Sprintf("SERVICE=%s", app.Domain),
|
||||||
log.Debugf("including SNAPSHOT=%s in backupbot exec invocation", snapshot)
|
"MACHINE_LOGS=true",
|
||||||
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := internal.RunBackupCmdRemote(cl, "snapshots", targetContainer.ID, execEnv); err != nil {
|
if _, err = internal.RunBackupCmdRemote(cl, "snapshots", targetContainer.ID, execEnv); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var appBackupCommand = cli.Command{
|
var AppBackupCommand = &cobra.Command{
|
||||||
Name: "backup",
|
Use: "backup [cmd] [args] [flags]",
|
||||||
Aliases: []string{"b"},
|
Aliases: []string{"b"},
|
||||||
Usage: "Manage app backups",
|
Short: "Manage app backups",
|
||||||
ArgsUsage: "<domain>",
|
}
|
||||||
Subcommands: []cli.Command{
|
|
||||||
appBackupListCommand,
|
var (
|
||||||
appBackupSnapshotsCommand,
|
snapshot string
|
||||||
appBackupDownloadCommand,
|
retries string
|
||||||
appBackupCreateCommand,
|
includePath string
|
||||||
},
|
showAllPaths bool
|
||||||
|
timestamps bool
|
||||||
|
includeSecrets bool
|
||||||
|
includeVolumes bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AppBackupListCommand.Flags().StringVarP(
|
||||||
|
&snapshot,
|
||||||
|
"snapshot",
|
||||||
|
"s",
|
||||||
|
"",
|
||||||
|
"list specific snapshot",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppBackupListCommand.Flags().BoolVarP(
|
||||||
|
&showAllPaths,
|
||||||
|
"all",
|
||||||
|
"a",
|
||||||
|
false,
|
||||||
|
"show all paths",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppBackupListCommand.Flags().BoolVarP(
|
||||||
|
×tamps,
|
||||||
|
"timestamps",
|
||||||
|
"t",
|
||||||
|
false,
|
||||||
|
"include timestamps",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppBackupDownloadCommand.Flags().StringVarP(
|
||||||
|
&snapshot,
|
||||||
|
"snapshot",
|
||||||
|
"s",
|
||||||
|
"",
|
||||||
|
"list specific snapshot",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppBackupDownloadCommand.Flags().StringVarP(
|
||||||
|
&includePath,
|
||||||
|
"path",
|
||||||
|
"p",
|
||||||
|
"",
|
||||||
|
"volumes path",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppBackupDownloadCommand.Flags().BoolVarP(
|
||||||
|
&includeSecrets,
|
||||||
|
"secrets",
|
||||||
|
"S",
|
||||||
|
false,
|
||||||
|
"include secrets",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppBackupDownloadCommand.Flags().BoolVarP(
|
||||||
|
&includeVolumes,
|
||||||
|
"volumes",
|
||||||
|
"v",
|
||||||
|
false,
|
||||||
|
"include volumes",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppBackupDownloadCommand.Flags().BoolVarP(
|
||||||
|
&internal.Chaos,
|
||||||
|
"chaos",
|
||||||
|
"C",
|
||||||
|
false,
|
||||||
|
"ignore uncommitted recipes changes",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppBackupCreateCommand.Flags().StringVarP(
|
||||||
|
&retries,
|
||||||
|
"retries",
|
||||||
|
"r",
|
||||||
|
"1",
|
||||||
|
"number of retry attempts",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppBackupCreateCommand.Flags().BoolVarP(
|
||||||
|
&internal.Chaos,
|
||||||
|
"chaos",
|
||||||
|
"C",
|
||||||
|
false,
|
||||||
|
"ignore uncommitted recipes changes",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -9,16 +9,14 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appCheckCommand = cli.Command{
|
var AppCheckCommand = &cobra.Command{
|
||||||
Name: "check",
|
Use: "check <app> [flags]",
|
||||||
Aliases: []string{"chk"},
|
Aliases: []string{"chk"},
|
||||||
Usage: "Ensure an app is well configured",
|
Short: "Ensure an app is well configured",
|
||||||
Description: `
|
Long: `Compare env vars in both the app ".env" and recipe ".env.sample" file.
|
||||||
This command compares env vars in both the app ".env" and recipe ".env.sample"
|
|
||||||
file.
|
|
||||||
|
|
||||||
The goal is to ensure that recipe ".env.sample" env vars are defined in your
|
The goal is to ensure that recipe ".env.sample" env vars are defined in your
|
||||||
app ".env" file. Only env var definitions in the ".env.sample" which are
|
app ".env" file. Only env var definitions in the ".env.sample" which are
|
||||||
@ -28,16 +26,15 @@ these env vars, then "check" will complain.
|
|||||||
Recipe maintainers may or may not provide defaults for env vars within their
|
Recipe maintainers may or may not provide defaults for env vars within their
|
||||||
recipes regardless of commenting or not (e.g. through the use of
|
recipes regardless of commenting or not (e.g. through the use of
|
||||||
${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
|
${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
|
||||||
ArgsUsage: "<domain>",
|
Args: cobra.ExactArgs(1),
|
||||||
Flags: []cli.Flag{
|
ValidArgsFunction: func(
|
||||||
internal.DebugFlag,
|
cmd *cobra.Command,
|
||||||
internal.ChaosFlag,
|
args []string,
|
||||||
internal.OfflineFlag,
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
app := internal.ValidateApp(args)
|
||||||
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)
|
||||||
@ -49,7 +46,10 @@ ${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
table.
|
table.
|
||||||
Headers("RECIPE ENV SAMPLE", "APP ENV").
|
Headers(
|
||||||
|
fmt.Sprintf("%s .env.sample", app.Recipe.Name),
|
||||||
|
fmt.Sprintf("%s.env", app.Name),
|
||||||
|
).
|
||||||
StyleFunc(func(row, col int) lipgloss.Style {
|
StyleFunc(func(row, col int) lipgloss.Style {
|
||||||
switch {
|
switch {
|
||||||
case col == 1:
|
case col == 1:
|
||||||
@ -74,8 +74,18 @@ ${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(table)
|
if err := formatter.PrintTable(table); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
return nil
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AppCheckCommand.Flags().BoolVarP(
|
||||||
|
&internal.Chaos,
|
||||||
|
"chaos",
|
||||||
|
"C",
|
||||||
|
false,
|
||||||
|
"ignore uncommitted recipes changes",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
319
cli/app/cmd.go
319
cli/app/cmd.go
@ -5,68 +5,102 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"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/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appCmdCommand = cli.Command{
|
var AppCmdCommand = &cobra.Command{
|
||||||
Name: "command",
|
Use: "command <app> [service | --local] <cmd> [[args] [flags] | [flags] -- [args]]",
|
||||||
Aliases: []string{"cmd"},
|
Aliases: []string{"cmd"},
|
||||||
Usage: "Run app commands",
|
Short: "Run app commands",
|
||||||
Description: `Run an app specific command.
|
Long: `Run an app specific 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/-l".
|
||||||
using the "-- <args>" syntax.
|
|
||||||
|
|
||||||
**WARNING**: options must be passed directly after the sub-command "cmd".
|
N.B. If using the "--" style to pass arguments, flags (e.g. "--local/-l") must
|
||||||
|
be passed *before* the "--". It is possible to pass arguments without the "--"
|
||||||
|
as long as no dashes are present (i.e. "foo" works without "--", "-foo"
|
||||||
|
does not).`,
|
||||||
|
Example: ` # pass <cmd> args/flags without "--"
|
||||||
|
abra app cmd 1312.net app my_cmd_arg foo --user bar
|
||||||
|
|
||||||
EXAMPLE:
|
# pass <cmd> args/flags with "--"
|
||||||
|
abra app cmd 1312.net app my_cmd_args --user bar -- foo -vvv
|
||||||
|
|
||||||
abra app cmd --local example.com app create_user -- me@example.com`,
|
# drop the [service] arg if using "--local/-l"
|
||||||
ArgsUsage: "<domain> [<service>] <command> [-- <args>]",
|
abra app cmd 1312.net my_cmd --local`,
|
||||||
Flags: []cli.Flag{
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
internal.DebugFlag,
|
if local {
|
||||||
internal.LocalCmdFlag,
|
if !(len(args) >= 2) {
|
||||||
internal.RemoteUserFlag,
|
return errors.New("requires at least 2 arguments with --local/-l")
|
||||||
internal.TtyFlag,
|
}
|
||||||
internal.OfflineFlag,
|
|
||||||
internal.ChaosFlag,
|
if slices.Contains(os.Args, "--") {
|
||||||
|
if cmd.ArgsLenAtDash() > 2 {
|
||||||
|
return errors.New("accepts at most 2 args with --local/-l")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(d1): it is unclear how to correctly validate this case
|
||||||
|
//
|
||||||
|
// abra app cmd 1312.net app test_cmd_args foo --local
|
||||||
|
// FATAL <recipe> doesn't have a app function
|
||||||
|
//
|
||||||
|
// "app" should not be there, but there is no reliable way to detect arg
|
||||||
|
// count when the user can pass an arbitrary amount of recipe command
|
||||||
|
// arguments
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(len(args) >= 3) {
|
||||||
|
return errors.New("requires at least 3 arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
ValidArgsFunction: func(
|
||||||
Subcommands: []cli.Command{appCmdListCommand},
|
cmd *cobra.Command,
|
||||||
BashComplete: func(ctx *cli.Context) {
|
args []string,
|
||||||
args := ctx.Args()
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
switch len(args) {
|
switch l := len(args); l {
|
||||||
case 0:
|
case 0:
|
||||||
autocomplete.AppNameComplete(ctx)
|
return autocomplete.AppNameComplete()
|
||||||
case 1:
|
case 1:
|
||||||
autocomplete.ServiceNameComplete(args.Get(0))
|
if !local {
|
||||||
|
return autocomplete.ServiceNameComplete(args[0])
|
||||||
|
}
|
||||||
|
return autocomplete.CommandNameComplete(args[0])
|
||||||
case 2:
|
case 2:
|
||||||
cmdNameComplete(args.Get(0))
|
if !local {
|
||||||
|
return autocomplete.CommandNameComplete(args[0])
|
||||||
|
}
|
||||||
|
return nil, cobra.ShellCompDirectiveDefault
|
||||||
|
default:
|
||||||
|
return nil, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
app := internal.ValidateApp(c)
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
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 local && remoteUser != "" {
|
||||||
internal.ShowSubcommandHelpAndError(c, errors.New("cannot use --local & --user together"))
|
log.Fatal("cannot use --local & --user together")
|
||||||
}
|
}
|
||||||
|
|
||||||
hasCmdArgs, parsedCmdArgs := parseCmdArgs(c.Args(), internal.LocalCmd)
|
hasCmdArgs, parsedCmdArgs := parseCmdArgs(args, local)
|
||||||
|
|
||||||
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,12 +109,8 @@ EXAMPLE:
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.LocalCmd {
|
if local {
|
||||||
if !(len(c.Args()) >= 2) {
|
cmdName := args[1]
|
||||||
internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments"))
|
|
||||||
}
|
|
||||||
|
|
||||||
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,53 +141,78 @@ EXAMPLE:
|
|||||||
if err := internal.RunCmd(cmd); err != nil {
|
if err := internal.RunCmd(cmd); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if !(len(c.Args()) >= 3) {
|
|
||||||
internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments"))
|
|
||||||
}
|
|
||||||
|
|
||||||
targetServiceName := c.Args().Get(1)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
cmdName := c.Args().Get(2)
|
cmdName := args[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)
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
matchingServiceName := false
|
matchingServiceName := false
|
||||||
for _, serviceName := range serviceNames {
|
targetServiceName := args[1]
|
||||||
if serviceName == targetServiceName {
|
for _, serviceName := range serviceNames {
|
||||||
matchingServiceName = true
|
if serviceName == targetServiceName {
|
||||||
}
|
matchingServiceName = true
|
||||||
}
|
|
||||||
|
|
||||||
if !matchingServiceName {
|
|
||||||
log.Fatalf("no service %s for %s?", targetServiceName, app.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("running command %s within the context of %s_%s", cmdName, app.StackName(), targetServiceName)
|
|
||||||
|
|
||||||
if hasCmdArgs {
|
|
||||||
log.Debugf("parsed following command arguments: %s", parsedCmdArgs)
|
|
||||||
} else {
|
|
||||||
log.Debug("did not detect any command arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := internal.RunCmdRemote(cl, app, app.Recipe.AbraShPath, targetServiceName, cmdName, parsedCmdArgs); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if !matchingServiceName {
|
||||||
|
log.Fatalf("no service %s for %s?", targetServiceName, app.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("running command %s within the context of %s_%s", cmdName, app.StackName(), targetServiceName)
|
||||||
|
|
||||||
|
if hasCmdArgs {
|
||||||
|
log.Debugf("parsed following command arguments: %s", parsedCmdArgs)
|
||||||
|
} else {
|
||||||
|
log.Debug("did not detect any command arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
cl, err := client.New(app.Server)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := internal.RunCmdRemote(
|
||||||
|
cl,
|
||||||
|
app,
|
||||||
|
requestTTY,
|
||||||
|
app.Recipe.AbraShPath,
|
||||||
|
targetServiceName, cmdName, parsedCmdArgs, remoteUser); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var AppCmdListCommand = &cobra.Command{
|
||||||
|
Use: "list <app> [flags]",
|
||||||
|
Aliases: []string{"ls"},
|
||||||
|
Short: "List all available commands",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdNames, err := appPkg.ReadAbraShCmdNames(app.Recipe.AbraShPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(cmdNames)
|
||||||
|
|
||||||
|
for _, cmdName := range cmdNames {
|
||||||
|
fmt.Println(cmdName)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,74 +235,42 @@ func parseCmdArgs(args []string, isLocal bool) (bool, string) {
|
|||||||
return hasCmdArgs, parsedCmdArgs
|
return hasCmdArgs, parsedCmdArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdNameComplete(appName string) {
|
var (
|
||||||
app, err := app.Get(appName)
|
local bool
|
||||||
if err != nil {
|
remoteUser string
|
||||||
return
|
requestTTY bool
|
||||||
}
|
)
|
||||||
cmdNames, _ := getShCmdNames(app)
|
|
||||||
if err != nil {
|
func init() {
|
||||||
return
|
AppCmdCommand.Flags().BoolVarP(
|
||||||
}
|
&local,
|
||||||
for _, n := range cmdNames {
|
"local",
|
||||||
fmt.Println(n)
|
"l",
|
||||||
}
|
false,
|
||||||
}
|
"run command locally",
|
||||||
|
)
|
||||||
var appCmdListCommand = cli.Command{
|
|
||||||
Name: "list",
|
AppCmdCommand.Flags().StringVarP(
|
||||||
Aliases: []string{"ls"},
|
&remoteUser,
|
||||||
Usage: "List all available commands",
|
"user",
|
||||||
ArgsUsage: "<domain>",
|
"u",
|
||||||
Flags: []cli.Flag{
|
"",
|
||||||
internal.DebugFlag,
|
"request remote user",
|
||||||
internal.OfflineFlag,
|
)
|
||||||
internal.ChaosFlag,
|
|
||||||
},
|
AppCmdCommand.Flags().BoolVarP(
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
&requestTTY,
|
||||||
Before: internal.SubCommandBefore,
|
"tty",
|
||||||
Action: func(c *cli.Context) error {
|
"t",
|
||||||
app := internal.ValidateApp(c)
|
false,
|
||||||
|
"request remote TTY",
|
||||||
if err := app.Recipe.EnsureExists(); err != nil {
|
)
|
||||||
log.Fatal(err)
|
|
||||||
}
|
AppCmdCommand.Flags().BoolVarP(
|
||||||
|
&internal.Chaos,
|
||||||
if !internal.Chaos {
|
"chaos",
|
||||||
if err := app.Recipe.EnsureIsClean(); err != nil {
|
"C",
|
||||||
log.Fatal(err)
|
false,
|
||||||
}
|
"ignore uncommitted recipes changes",
|
||||||
|
)
|
||||||
if !internal.Offline {
|
|
||||||
if err := app.Recipe.EnsureUpToDate(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := app.Recipe.EnsureLatest(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cmdNames, err := getShCmdNames(app)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, cmdName := range cmdNames {
|
|
||||||
fmt.Println(cmdName)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func getShCmdNames(app appPkg.App) ([]string, error) {
|
|
||||||
cmdNames, err := appPkg.ReadAbraShCmdNames(app.Recipe.AbraShPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(cmdNames)
|
|
||||||
return cmdNames, nil
|
|
||||||
}
|
}
|
||||||
|
@ -1,40 +1,35 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"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/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appConfigCommand = cli.Command{
|
var AppConfigCommand = &cobra.Command{
|
||||||
Name: "config",
|
Use: "config <app> [flags]",
|
||||||
Aliases: []string{"cfg"},
|
Aliases: []string{"cfg"},
|
||||||
Usage: "Edit app config",
|
Short: "Edit app config",
|
||||||
ArgsUsage: "<domain>",
|
Example: " abra config 1312.net",
|
||||||
Flags: []cli.Flag{
|
Args: cobra.ExactArgs(1),
|
||||||
internal.DebugFlag,
|
ValidArgsFunction: func(
|
||||||
|
cmd *cobra.Command,
|
||||||
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
appName := c.Args().First()
|
|
||||||
|
|
||||||
if appName == "" {
|
|
||||||
internal.ShowSubcommandHelpAndError(c, errors.New("no app provided"))
|
|
||||||
}
|
|
||||||
|
|
||||||
files, err := appPkg.LoadAppFiles("")
|
files, err := appPkg.LoadAppFiles("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appName := args[0]
|
||||||
appFile, exists := files[appName]
|
appFile, exists := files[appName]
|
||||||
if !exists {
|
if !exists {
|
||||||
log.Fatalf("cannot find app with name %s", appName)
|
log.Fatalf("cannot find app with name %s", appName)
|
||||||
@ -51,14 +46,12 @@ var appConfigCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(ed, appFile.Path)
|
c := exec.Command(ed, appFile.Path)
|
||||||
cmd.Stdin = os.Stdin
|
c.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
c.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
c.Stderr = os.Stderr
|
||||||
if err := cmd.Run(); err != nil {
|
if err := c.Run(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -22,47 +22,39 @@ import (
|
|||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appCpCommand = cli.Command{
|
var AppCpCommand = &cobra.Command{
|
||||||
Name: "cp",
|
Use: "cp <app> <src> <dst> [flags]",
|
||||||
Aliases: []string{"c"},
|
Aliases: []string{"c"},
|
||||||
ArgsUsage: "<domain> <src> <dst>",
|
Short: "Copy files to/from a deployed app service",
|
||||||
Flags: []cli.Flag{
|
Example: ` # copy myfile.txt to the root of the app service
|
||||||
internal.DebugFlag,
|
abra app cp 1312.net myfile.txt app:/
|
||||||
internal.NoInputFlag,
|
|
||||||
|
# copy that file back to your current working directory locally
|
||||||
|
abra app cp 1312.net app:/myfile.txt`,
|
||||||
|
Args: cobra.ExactArgs(3),
|
||||||
|
ValidArgsFunction: func(
|
||||||
|
cmd *cobra.Command,
|
||||||
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
switch l := len(args); l {
|
||||||
|
case 0:
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
|
default:
|
||||||
|
return nil, cobra.ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
Usage: "Copy files to/from a deployed app service",
|
app := internal.ValidateApp(args)
|
||||||
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:
|
|
||||||
|
|
||||||
abra app cp <domain> myfile.txt app:/
|
|
||||||
|
|
||||||
And if you want to copy that file back to your current working directory locally:
|
|
||||||
|
|
||||||
abra app cp <domain> app:/myfile.txt .
|
|
||||||
`,
|
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
src := c.Args().Get(1)
|
src := args[1]
|
||||||
dst := c.Args().Get(2)
|
dst := args[2]
|
||||||
if src == "" {
|
|
||||||
log.Fatal("missing <src> argument")
|
|
||||||
}
|
|
||||||
if dst == "" {
|
|
||||||
log.Fatal("missing <dest> argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
srcPath, dstPath, service, toContainer, err := parseSrcAndDst(src, dst)
|
srcPath, dstPath, service, toContainer, err := parseSrcAndDst(src, dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -87,8 +79,6 @@ And if you want to copy that file back to your current working directory locally
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -381,3 +371,13 @@ func moveFile(sourcePath, destPath string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AppCpCommand.Flags().BoolVarP(
|
||||||
|
&internal.Chaos,
|
||||||
|
"chaos",
|
||||||
|
"C",
|
||||||
|
false,
|
||||||
|
"ignore uncommitted recipes changes",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
@ -9,6 +10,7 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/config"
|
"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"
|
||||||
|
"coopcloud.tech/tagcmp"
|
||||||
|
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
@ -17,48 +19,62 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/lint"
|
"coopcloud.tech/abra/pkg/lint"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appDeployCommand = cli.Command{
|
var AppDeployCommand = &cobra.Command{
|
||||||
Name: "deploy",
|
Use: "deploy <app> [version] [flags]",
|
||||||
Aliases: []string{"d"},
|
Aliases: []string{"d"},
|
||||||
Usage: "Deploy an app",
|
Short: "Deploy an app",
|
||||||
ArgsUsage: "<domain> [<version>]",
|
Long: `Deploy an app.
|
||||||
Flags: []cli.Flag{
|
|
||||||
internal.DebugFlag,
|
This command supports chaos operations. Use "--chaos/-c" to deploy your recipe
|
||||||
internal.NoInputFlag,
|
checkout as-is. Recipe commit hashes are also supported values for "[version]".
|
||||||
internal.ForceFlag,
|
Please note, "upgrade"/"rollback" do not support chaos operations.`,
|
||||||
internal.ChaosFlag,
|
Example: ` # standard deployment
|
||||||
internal.NoDomainChecksFlag,
|
abra app deploy 1312.net
|
||||||
internal.DontWaitConvergeFlag,
|
|
||||||
internal.OfflineFlag,
|
# chaos deployment
|
||||||
|
abra app deploy 1312.net --chaos
|
||||||
|
|
||||||
|
# deploy specific version
|
||||||
|
abra app deploy 1312.net 2.0.0+1.2.3
|
||||||
|
|
||||||
|
# deploy a specific git hash
|
||||||
|
abra app deploy 1312.net 886db76d`,
|
||||||
|
Args: cobra.RangeArgs(1, 2),
|
||||||
|
ValidArgsFunction: func(
|
||||||
|
cmd *cobra.Command,
|
||||||
|
args []string,
|
||||||
|
toComplete string,
|
||||||
|
) ([]string, cobra.ShellCompDirective) {
|
||||||
|
switch l := len(args); l {
|
||||||
|
case 0:
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
|
case 1:
|
||||||
|
app, err := appPkg.Get(args[0])
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
|
return []string{errMsg}, cobra.ShellCompDirectiveError
|
||||||
|
}
|
||||||
|
return autocomplete.RecipeVersionComplete(app.Recipe.Name)
|
||||||
|
default:
|
||||||
|
return nil, cobra.ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
Description: `Deploy an app.
|
|
||||||
|
|
||||||
This command supports chaos operations. Use "--chaos" to deploy your recipe
|
|
||||||
checkout as-is. Recipe commit hashes are also supported values for
|
|
||||||
"[<version>]". Please note, "upgrade"/"rollback" do not support chaos
|
|
||||||
operations.
|
|
||||||
|
|
||||||
EXAMPLE:
|
|
||||||
|
|
||||||
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
|
var warnMessages []string
|
||||||
|
|
||||||
app := internal.ValidateApp(c)
|
app := internal.ValidateApp(args)
|
||||||
stackName := app.StackName()
|
stackName := app.StackName()
|
||||||
|
|
||||||
specificVersion := c.Args().Get(1)
|
ok, err := validateChaosXORVersion(args)
|
||||||
if specificVersion != "" && internal.Chaos {
|
if !ok {
|
||||||
log.Fatal("cannot use <version> and --chaos together")
|
log.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
specificVersion := getSpecifiedVersion(args)
|
||||||
|
|
||||||
if specificVersion != "" {
|
if specificVersion != "" {
|
||||||
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
|
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
|
||||||
app.Recipe.Version = specificVersion
|
app.Recipe.Version = specificVersion
|
||||||
@ -96,19 +112,19 @@ EXAMPLE:
|
|||||||
// is because we need to deal with GetComposeFiles under the hood and these
|
// is because we need to deal with GetComposeFiles under the hood and these
|
||||||
// files change from version to version which therefore affects which
|
// files change from version to version which therefore affects which
|
||||||
// secrets might be generated
|
// secrets might be generated
|
||||||
version := deployMeta.Version
|
toDeployVersion := deployMeta.Version
|
||||||
if specificVersion != "" {
|
if specificVersion != "" {
|
||||||
version = specificVersion
|
toDeployVersion = specificVersion
|
||||||
log.Debugf("choosing %s as version to deploy", version)
|
log.Debugf("choosing %s as version to deploy", toDeployVersion)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
isChaosCommit, err = app.Recipe.EnsureVersion(version)
|
isChaosCommit, err = app.Recipe.EnsureVersion(toDeployVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isChaosCommit {
|
if isChaosCommit {
|
||||||
log.Debugf("assuming '%s' is a chaos commit", version)
|
log.Debugf("assuming '%s' is a chaos commit", toDeployVersion)
|
||||||
internal.Chaos = true
|
internal.Chaos = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,14 +140,6 @@ EXAMPLE:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if deployMeta.IsDeployed {
|
|
||||||
if internal.Force || internal.Chaos {
|
|
||||||
warnMessages = append(warnMessages, fmt.Sprintf("%s is already deployed", app.Name))
|
|
||||||
} else {
|
|
||||||
log.Fatalf("%s is already deployed", app.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !internal.Chaos && specificVersion == "" {
|
if !internal.Chaos && specificVersion == "" {
|
||||||
versions, err := app.Recipe.Tags()
|
versions, err := app.Recipe.Tags()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -139,9 +147,9 @@ EXAMPLE:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(versions) > 0 && !internal.Chaos {
|
if len(versions) > 0 && !internal.Chaos {
|
||||||
version = versions[len(versions)-1]
|
toDeployVersion = versions[len(versions)-1]
|
||||||
log.Debugf("choosing %s as version to deploy", version)
|
log.Debugf("choosing %s as version to deploy", toDeployVersion)
|
||||||
if _, err := app.Recipe.EnsureVersion(version); err != nil {
|
if _, err := app.Recipe.EnsureVersion(toDeployVersion); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -149,31 +157,55 @@ EXAMPLE:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
version = formatter.SmallSHA(head.String())
|
toDeployVersion = formatter.SmallSHA(head.String())
|
||||||
warnMessages = append(warnMessages, fmt.Sprintf("no versions detected, using latest commit"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chaosVersion := config.CHAOS_DEFAULT
|
toDeployChaosVersion := config.CHAOS_DEFAULT
|
||||||
if internal.Chaos {
|
if internal.Chaos {
|
||||||
warnMessages = append(warnMessages, "chaos mode engaged")
|
|
||||||
|
|
||||||
if isChaosCommit {
|
if isChaosCommit {
|
||||||
chaosVersion = specificVersion
|
toDeployChaosVersion = specificVersion
|
||||||
versionLabelLocal, err := app.Recipe.GetVersionLabelLocal()
|
versionLabelLocal, err := app.Recipe.GetVersionLabelLocal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
version = versionLabelLocal
|
toDeployVersion = versionLabelLocal
|
||||||
} else {
|
} else {
|
||||||
var err error
|
var err error
|
||||||
chaosVersion, err = app.Recipe.ChaosVersion()
|
toDeployChaosVersion, err = app.Recipe.ChaosVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if deployMeta.IsDeployed {
|
||||||
|
if !internal.Force {
|
||||||
|
appStatus, err := app.Status()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if appStatus.Chaos && !internal.Chaos {
|
||||||
|
log.Fatalf("%s is deployed from a chaos version. Are you sure the local changes are in the current version (%s)?", app.Name, toDeployVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if toDeployVersion != "" && appStatus.Version != "" {
|
||||||
|
localVersion, err := tagcmp.Parse(toDeployVersion)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
remoteVersion, err := tagcmp.Parse(appStatus.Version)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if localVersion.IsLessThan(remoteVersion) {
|
||||||
|
log.Fatalf("%s is deployed at %s. Are you sure you want to downgrade to %s?", app.Name, appStatus.Version, toDeployVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abraShEnv, err := envfile.ReadAbraShEnvVars(app.Recipe.AbraShPath)
|
abraShEnv, err := envfile.ReadAbraShEnvVars(app.Recipe.AbraShPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -202,7 +234,7 @@ EXAMPLE:
|
|||||||
appPkg.ExposeAllEnv(stackName, compose, app.Env)
|
appPkg.ExposeAllEnv(stackName, compose, app.Env)
|
||||||
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
|
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
|
||||||
appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
|
appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
|
||||||
appPkg.SetChaosVersionLabel(compose, stackName, chaosVersion)
|
appPkg.SetChaosVersionLabel(compose, stackName, toDeployChaosVersion)
|
||||||
appPkg.SetUpdateLabel(compose, stackName, app.Env)
|
appPkg.SetUpdateLabel(compose, stackName, app.Env)
|
||||||
|
|
||||||
envVars, err := appPkg.CheckEnv(app)
|
envVars, err := appPkg.CheckEnv(app)
|
||||||
@ -225,13 +257,24 @@ EXAMPLE:
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
warnMessages = append(warnMessages, "skipping domain checks as no DOMAIN=... configured for app")
|
log.Debug("skipping domain checks as no DOMAIN=... configured for app")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
warnMessages = append(warnMessages, "skipping domain checks as requested")
|
log.Debug("skipping domain checks as requested")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := internal.DeployOverview(app, warnMessages, version, chaosVersion); err != nil {
|
deployedVersion := config.NO_VERSION_DEFAULT
|
||||||
|
if deployMeta.IsDeployed {
|
||||||
|
deployedVersion = deployMeta.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := internal.DeployOverview(
|
||||||
|
app,
|
||||||
|
warnMessages,
|
||||||
|
deployedVersion,
|
||||||
|
deployMeta.ChaosVersion,
|
||||||
|
toDeployVersion,
|
||||||
|
toDeployChaosVersion); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,15 +296,63 @@ EXAMPLE:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Recipe.Version = version
|
app.Recipe.Version = toDeployVersion
|
||||||
if chaosVersion != config.CHAOS_DEFAULT {
|
if toDeployChaosVersion != config.CHAOS_DEFAULT {
|
||||||
app.Recipe.Version = chaosVersion
|
app.Recipe.Version = toDeployChaosVersion
|
||||||
}
|
}
|
||||||
log.Debugf("choosing %s as version to save to env file", app.Recipe.Version)
|
log.Debugf("choosing %s as version to save to env file", app.Recipe.Version)
|
||||||
if err := app.WriteRecipeVersion(app.Recipe.Version, false); err != nil {
|
if err := app.WriteRecipeVersion(app.Recipe.Version, false); 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
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateChaosXORVersion xor checks version/chaos mode
|
||||||
|
func validateChaosXORVersion(args []string) (bool, error) {
|
||||||
|
if getSpecifiedVersion(args) != "" && internal.Chaos {
|
||||||
|
return false, errors.New("cannot use <version> and --chaos together")
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSpecifiedVersion retrieves the specific version if available
|
||||||
|
func getSpecifiedVersion(args []string) string {
|
||||||
|
if len(args) >= 2 {
|
||||||
|
return args[1]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AppDeployCommand.Flags().BoolVarP(
|
||||||
|
&internal.Chaos,
|
||||||
|
"chaos",
|
||||||
|
"C",
|
||||||
|
false,
|
||||||
|
"ignore uncommitted recipes changes",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppDeployCommand.Flags().BoolVarP(
|
||||||
|
&internal.Force,
|
||||||
|
"force",
|
||||||
|
"f",
|
||||||
|
false,
|
||||||
|
"perform action without further prompt",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppDeployCommand.Flags().BoolVarP(
|
||||||
|
&internal.NoDomainChecks,
|
||||||
|
"no-domain-checks",
|
||||||
|
"D",
|
||||||
|
false,
|
||||||
|
"disable public DNS checks",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppDeployCommand.Flags().BoolVarP(
|
||||||
|
&internal.DontWaitConverge,
|
||||||
|
"no-converge-checks",
|
||||||
|
"c",
|
||||||
|
false,
|
||||||
|
"do not wait for converge logic checks",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
57
cli/app/deploy_test.go
Normal file
57
cli/app/deploy_test.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"coopcloud.tech/abra/cli/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetSpecificVersion(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input []string
|
||||||
|
expectedOutput string
|
||||||
|
}{
|
||||||
|
// No specified version when command has one or less args
|
||||||
|
{[]string{}, ""},
|
||||||
|
{[]string{"arg0"}, ""},
|
||||||
|
// Second in arg (index-1) is the specified result when command has more than 1 args
|
||||||
|
{[]string{"arg0", "arg1"}, "arg1"},
|
||||||
|
{[]string{"arg0", "arg1", "arg2"}, "arg1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
if test.expectedOutput != getSpecifiedVersion(test.input) {
|
||||||
|
t.Fatalf("result for %s should be %s", test.input, test.expectedOutput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateChaosXORVersion(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input []string
|
||||||
|
isChaos bool
|
||||||
|
expectedResult bool
|
||||||
|
}{
|
||||||
|
// Chaos = true, Specified Version absent
|
||||||
|
{[]string{}, true, true},
|
||||||
|
// Chaos = false, Specified Version absent
|
||||||
|
{[]string{}, false, true},
|
||||||
|
// Chaos = true, Specified Version present
|
||||||
|
{[]string{"arg0", "arg1"}, true, false},
|
||||||
|
// Chaos = false, Specified Version present
|
||||||
|
{[]string{"arg0", "arg1", "arg2"}, false, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
internal.Chaos = test.isChaos
|
||||||
|
res, _ := validateChaosXORVersion(test.input)
|
||||||
|
if res != test.expectedResult {
|
||||||
|
t.Fatalf(
|
||||||
|
"When args are %s and Chaos mode is %t result needs to be %t",
|
||||||
|
test.input,
|
||||||
|
test.isChaos,
|
||||||
|
test.expectedResult,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
153
cli/app/list.go
153
cli/app/list.go
@ -9,39 +9,11 @@ import (
|
|||||||
|
|
||||||
"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/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/tagcmp"
|
"coopcloud.tech/tagcmp"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
status bool
|
|
||||||
statusFlag = &cli.BoolFlag{
|
|
||||||
Name: "status, S",
|
|
||||||
Usage: "Show app deployment status",
|
|
||||||
Destination: &status,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
recipeFilter string
|
|
||||||
recipeFlag = &cli.StringFlag{
|
|
||||||
Name: "recipe, r",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Show apps of a specific recipe",
|
|
||||||
Destination: &recipeFilter,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
listAppServer string
|
|
||||||
listAppServerFlag = &cli.StringFlag{
|
|
||||||
Name: "server, s",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Show apps of a specific server",
|
|
||||||
Destination: &listAppServer,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type appStatus struct {
|
type appStatus struct {
|
||||||
@ -66,27 +38,23 @@ type serverStatus struct {
|
|||||||
UpgradeCount int `json:"upgradeCount"`
|
UpgradeCount int `json:"upgradeCount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var appListCommand = cli.Command{
|
var AppListCommand = &cobra.Command{
|
||||||
Name: "list",
|
Use: "list [flags]",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "List all managed apps",
|
Short: "List all managed apps",
|
||||||
Description: `
|
Long: `Generate a report of all managed apps.
|
||||||
Read the local file system listing of apps and servers (e.g. ~/.abra/) to
|
|
||||||
generate a report of all your apps.
|
|
||||||
|
|
||||||
By passing the "--status/-S" flag, you can query all your servers for the
|
Use "--status/-S" flag to query all servers for the live deployment status.`,
|
||||||
actual live deployment status. Depending on how many servers you manage, this
|
Example: ` # list apps of all servers without live status
|
||||||
can take some time.`,
|
abra app ls
|
||||||
Flags: []cli.Flag{
|
|
||||||
internal.DebugFlag,
|
# list apps of a specific server with live status
|
||||||
internal.MachineReadableFlag,
|
abra app ls -s 1312.net -S
|
||||||
statusFlag,
|
|
||||||
listAppServerFlag,
|
# list apps of all servers which match a specific recipe
|
||||||
recipeFlag,
|
abra app ls -r gitea`,
|
||||||
internal.OfflineFlag,
|
Args: cobra.NoArgs,
|
||||||
},
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
Before: internal.SubCommandBefore,
|
|
||||||
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)
|
||||||
@ -205,7 +173,7 @@ can take some time.`,
|
|||||||
stats.LatestCount++
|
stats.LatestCount++
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newUpdates = internal.ReverseStringList(newUpdates)
|
newUpdates = internal.SortVersionsDesc(newUpdates)
|
||||||
appStats.Upgrade = strings.Join(newUpdates, "\n")
|
appStats.Upgrade = strings.Join(newUpdates, "\n")
|
||||||
stats.UpgradeCount++
|
stats.UpgradeCount++
|
||||||
}
|
}
|
||||||
@ -228,7 +196,8 @@ can take some time.`,
|
|||||||
} else {
|
} else {
|
||||||
fmt.Println(string(jsonstring))
|
fmt.Println(string(jsonstring))
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
alreadySeen := make(map[string]bool)
|
alreadySeen := make(map[string]bool)
|
||||||
@ -239,7 +208,7 @@ can take some time.`,
|
|||||||
|
|
||||||
serverStat := allStats[app.Server]
|
serverStat := allStats[app.Server]
|
||||||
|
|
||||||
headers := []string{"RECIPE", "DOMAIN"}
|
headers := []string{"RECIPE", "DOMAIN", "SERVER"}
|
||||||
if status {
|
if status {
|
||||||
headers = append(headers, []string{
|
headers = append(headers, []string{
|
||||||
"STATUS",
|
"STATUS",
|
||||||
@ -259,7 +228,7 @@ can take some time.`,
|
|||||||
|
|
||||||
var rows [][]string
|
var rows [][]string
|
||||||
for _, appStat := range serverStat.Apps {
|
for _, appStat := range serverStat.Apps {
|
||||||
row := []string{appStat.Recipe, appStat.Domain}
|
row := []string{appStat.Recipe, appStat.Domain, appStat.Server}
|
||||||
if status {
|
if status {
|
||||||
chaosStatus := appStat.Chaos
|
chaosStatus := appStat.Chaos
|
||||||
if chaosStatus != "unknown" {
|
if chaosStatus != "unknown" {
|
||||||
@ -287,20 +256,8 @@ can take some time.`,
|
|||||||
table.Rows(rows...)
|
table.Rows(rows...)
|
||||||
|
|
||||||
if len(rows) > 0 {
|
if len(rows) > 0 {
|
||||||
fmt.Println(table)
|
if err := formatter.PrintTable(table); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
if status {
|
|
||||||
fmt.Println(fmt.Sprintf(
|
|
||||||
"SERVER: %s | TOTAL APPS: %v | VERSIONED: %v | UNVERSIONED: %v | LATEST : %v | UPGRADE: %v",
|
|
||||||
app.Server,
|
|
||||||
serverStat.AppCount,
|
|
||||||
serverStat.VersionCount,
|
|
||||||
serverStat.UnversionedCount,
|
|
||||||
serverStat.LatestCount,
|
|
||||||
serverStat.UpgradeCount,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
log.Infof("SERVER: %s TOTAL APPS: %v", app.Server, serverStat.AppCount)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(allStats) > 1 && len(rows) > 0 {
|
if len(allStats) > 1 && len(rows) > 0 {
|
||||||
@ -310,13 +267,59 @@ can take some time.`,
|
|||||||
|
|
||||||
alreadySeen[app.Server] = true
|
alreadySeen[app.Server] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(allStats) > 1 {
|
|
||||||
totalServers := formatter.BoldStyle.Render("TOTAL SERVERS")
|
|
||||||
totalApps := formatter.BoldStyle.Render("TOTAL APPS")
|
|
||||||
log.Infof("%s: %v | %s: %v ", totalServers, totalServersCount, totalApps, totalAppsCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
status bool
|
||||||
|
recipeFilter string
|
||||||
|
listAppServer string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AppListCommand.Flags().BoolVarP(
|
||||||
|
&status,
|
||||||
|
"status",
|
||||||
|
"S",
|
||||||
|
false,
|
||||||
|
"show app deployment status",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppListCommand.Flags().StringVarP(
|
||||||
|
&recipeFilter,
|
||||||
|
"recipe",
|
||||||
|
"r",
|
||||||
|
"",
|
||||||
|
"show apps of a specific recipe",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppListCommand.RegisterFlagCompletionFunc(
|
||||||
|
"recipe",
|
||||||
|
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.RecipeNameComplete()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
AppListCommand.Flags().BoolVarP(
|
||||||
|
&internal.MachineReadable,
|
||||||
|
"machine",
|
||||||
|
"m",
|
||||||
|
false,
|
||||||
|
"print machine-readable output",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppListCommand.Flags().StringVarP(
|
||||||
|
&listAppServer,
|
||||||
|
"server",
|
||||||
|
"s",
|
||||||
|
"",
|
||||||
|
"show apps of a specific server",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppListCommand.RegisterFlagCompletionFunc(
|
||||||
|
"server",
|
||||||
|
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.ServerNameComplete()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
@ -19,23 +20,34 @@ import (
|
|||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appLogsCommand = cli.Command{
|
var AppLogsCommand = &cobra.Command{
|
||||||
Name: "logs",
|
Use: "logs <app> [service] [flags]",
|
||||||
Aliases: []string{"l"},
|
Aliases: []string{"l"},
|
||||||
ArgsUsage: "<domain> [<service>]",
|
Short: "Tail app logs",
|
||||||
Usage: "Tail app logs",
|
Args: cobra.RangeArgs(1, 2),
|
||||||
Flags: []cli.Flag{
|
ValidArgsFunction: func(
|
||||||
internal.StdErrOnlyFlag,
|
cmd *cobra.Command,
|
||||||
internal.SinceLogsFlag,
|
args []string,
|
||||||
internal.DebugFlag,
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
switch l := len(args); l {
|
||||||
|
case 0:
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
|
case 1:
|
||||||
|
app, err := appPkg.Get(args[0])
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
|
return []string{errMsg}, cobra.ShellCompDirectiveError
|
||||||
|
}
|
||||||
|
return autocomplete.ServiceNameComplete(app.Name)
|
||||||
|
default:
|
||||||
|
return nil, cobra.ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
app := internal.ValidateApp(args)
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
app := internal.ValidateApp(c)
|
|
||||||
stackName := app.StackName()
|
stackName := app.StackName()
|
||||||
|
|
||||||
if err := app.Recipe.EnsureExists(); err != nil {
|
if err := app.Recipe.EnsureExists(); err != nil {
|
||||||
@ -56,17 +68,14 @@ var appLogsCommand = cli.Command{
|
|||||||
log.Fatalf("%s is not deployed?", app.Name)
|
log.Fatalf("%s is not deployed?", app.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceName := c.Args().Get(1)
|
var serviceNames []string
|
||||||
serviceNames := []string{}
|
if len(args) == 2 {
|
||||||
if serviceName != "" {
|
serviceNames = []string{args[1]}
|
||||||
serviceNames = []string{serviceName}
|
|
||||||
}
|
|
||||||
err = tailLogs(cl, app, serviceNames)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if err = tailLogs(cl, app, serviceNames); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,8 +121,8 @@ func tailLogs(cl *dockerClient.Client, app appPkg.App, serviceNames []string) er
|
|||||||
go func(serviceID string) {
|
go func(serviceID string) {
|
||||||
logs, err := cl.ServiceLogs(context.Background(), serviceID, containerTypes.LogsOptions{
|
logs, err := cl.ServiceLogs(context.Background(), serviceID, containerTypes.LogsOptions{
|
||||||
ShowStderr: true,
|
ShowStderr: true,
|
||||||
ShowStdout: !internal.StdErrOnly,
|
ShowStdout: !stdErr,
|
||||||
Since: internal.SinceLogs,
|
Since: sinceLogs,
|
||||||
Until: "",
|
Until: "",
|
||||||
Timestamps: true,
|
Timestamps: true,
|
||||||
Follow: true,
|
Follow: true,
|
||||||
@ -137,3 +146,26 @@ func tailLogs(cl *dockerClient.Client, app appPkg.App, serviceNames []string) er
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
stdErr bool
|
||||||
|
sinceLogs string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AppLogsCommand.Flags().BoolVarP(
|
||||||
|
&stdErr,
|
||||||
|
"stderr",
|
||||||
|
"s",
|
||||||
|
false,
|
||||||
|
"only tail stderr",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppLogsCommand.Flags().StringVarP(
|
||||||
|
&sinceLogs,
|
||||||
|
"since",
|
||||||
|
"S",
|
||||||
|
"",
|
||||||
|
"tail logs since YYYY-MM-DDTHH:MM:SSZ",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
282
cli/app/new.go
282
cli/app/new.go
@ -16,20 +16,21 @@ import (
|
|||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/charmbracelet/lipgloss/table"
|
"github.com/charmbracelet/lipgloss/table"
|
||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appNewDescription = `
|
var appNewDescription = `Creates a new app from a default recipe.
|
||||||
Creates a new app from a default recipe. This new app configuration is stored
|
|
||||||
in your $ABRA_DIR directory under the appropriate server.
|
This new app configuration is stored in your $ABRA_DIR directory under the
|
||||||
|
appropriate server.
|
||||||
|
|
||||||
This command does not deploy your app for you. You will need to run "abra app
|
This command does not deploy your app for you. You will need to run "abra app
|
||||||
deploy <domain>" to do so.
|
deploy <app>" to do so.
|
||||||
|
|
||||||
You can see what recipes are available (i.e. values for the <recipe> argument)
|
You can see what recipes are available (i.e. values for the [recipe] argument)
|
||||||
by running "abra recipe ls".
|
by running "abra recipe ls".
|
||||||
|
|
||||||
Recipe commit hashes are supported values for "[<version>]".
|
Recipe commit hashes are supported values for "[version]".
|
||||||
|
|
||||||
Passing the "--secrets/-S" flag will automatically generate secrets for your
|
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
|
||||||
@ -40,71 +41,76 @@ You can use the "--pass/-P" to store these generated passwords locally in a
|
|||||||
pass store (see passwordstore.org for more). The pass command must be available
|
pass store (see passwordstore.org for more). The pass command must be available
|
||||||
on your $PATH.`
|
on your $PATH.`
|
||||||
|
|
||||||
var appNewCommand = cli.Command{
|
var AppNewCommand = &cobra.Command{
|
||||||
Name: "new",
|
Use: "new [recipe] [version] [flags]",
|
||||||
Aliases: []string{"n"},
|
Aliases: []string{"n"},
|
||||||
Usage: "Create a new app",
|
Short: "Create a new app",
|
||||||
Description: appNewDescription,
|
Long: appNewDescription,
|
||||||
Flags: []cli.Flag{
|
Args: cobra.RangeArgs(0, 2),
|
||||||
internal.DebugFlag,
|
ValidArgsFunction: func(
|
||||||
internal.NoInputFlag,
|
cmd *cobra.Command,
|
||||||
internal.NewAppServerFlag,
|
args []string,
|
||||||
internal.DomainFlag,
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
internal.PassFlag,
|
switch l := len(args); l {
|
||||||
internal.SecretsFlag,
|
|
||||||
internal.OfflineFlag,
|
|
||||||
internal.ChaosFlag,
|
|
||||||
},
|
|
||||||
Before: internal.SubCommandBefore,
|
|
||||||
ArgsUsage: "[<recipe>] [<version>]",
|
|
||||||
BashComplete: func(ctx *cli.Context) {
|
|
||||||
args := ctx.Args()
|
|
||||||
switch len(args) {
|
|
||||||
case 0:
|
case 0:
|
||||||
autocomplete.RecipeNameComplete(ctx)
|
return autocomplete.RecipeNameComplete()
|
||||||
case 1:
|
case 1:
|
||||||
autocomplete.RecipeVersionComplete(ctx.Args().Get(0))
|
recipe := internal.ValidateRecipe(args, cmd.Name())
|
||||||
|
return autocomplete.RecipeVersionComplete(recipe.Name)
|
||||||
|
default:
|
||||||
|
return nil, cobra.ShellCompDirectiveDefault
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Action: func(c *cli.Context) error {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
recipe := internal.ValidateRecipe(c)
|
recipe := internal.ValidateRecipe(args, cmd.Name())
|
||||||
|
|
||||||
|
if len(args) == 2 && internal.Chaos {
|
||||||
|
log.Fatal("cannot use [version] and --chaos together")
|
||||||
|
}
|
||||||
|
|
||||||
|
var recipeVersion string
|
||||||
|
if len(args) == 2 {
|
||||||
|
recipeVersion = args[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
chaosVersion := config.CHAOS_DEFAULT
|
||||||
|
if internal.Chaos {
|
||||||
|
recipeVersion = chaosVersion
|
||||||
|
|
||||||
var version string
|
|
||||||
if !internal.Chaos {
|
|
||||||
if err := recipe.EnsureIsClean(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if !internal.Offline {
|
if !internal.Offline {
|
||||||
if err := recipe.EnsureUpToDate(); err != nil {
|
if err := recipe.EnsureUpToDate(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if c.Args().Get(1) == "" {
|
if !internal.Chaos {
|
||||||
recipeVersions, err := recipe.GetRecipeVersions()
|
if err := recipe.EnsureIsClean(); err != nil {
|
||||||
if err != nil {
|
log.Fatal(err)
|
||||||
log.Fatal(err)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(recipeVersions) > 0 {
|
var recipeVersions recipePkg.RecipeVersions
|
||||||
latest := recipeVersions[len(recipeVersions)-1]
|
if recipeVersion == "" {
|
||||||
for tag := range latest {
|
var err error
|
||||||
version = tag
|
recipeVersions, err = recipe.GetRecipeVersions()
|
||||||
}
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := recipe.EnsureVersion(version); err != nil {
|
if len(recipeVersions) > 0 {
|
||||||
log.Fatal(err)
|
latest := recipeVersions[len(recipeVersions)-1]
|
||||||
}
|
for tag := range latest {
|
||||||
} else {
|
recipeVersion = tag
|
||||||
if err := recipe.EnsureLatest(); err != nil {
|
}
|
||||||
log.Fatal(err)
|
|
||||||
}
|
if _, err := recipe.EnsureVersion(recipeVersion); err != nil {
|
||||||
}
|
log.Fatal(err)
|
||||||
} else {
|
}
|
||||||
version = c.Args().Get(1)
|
} else {
|
||||||
if _, err := recipe.EnsureVersion(version); err != nil {
|
if err := recipe.EnsureLatest(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,25 +118,25 @@ var appNewCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ensureDomainFlag(recipe, internal.NewAppServer); err != nil {
|
if err := ensureDomainFlag(recipe, newAppServer); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sanitisedAppName := appPkg.SanitiseAppName(internal.Domain)
|
sanitisedAppName := appPkg.SanitiseAppName(appDomain)
|
||||||
log.Debugf("%s sanitised as %s for new app", internal.Domain, sanitisedAppName)
|
log.Debugf("%s sanitised as %s for new app", appDomain, sanitisedAppName)
|
||||||
|
|
||||||
if err := appPkg.TemplateAppEnvSample(
|
if err := appPkg.TemplateAppEnvSample(
|
||||||
recipe,
|
recipe,
|
||||||
internal.Domain,
|
appDomain,
|
||||||
internal.NewAppServer,
|
newAppServer,
|
||||||
internal.Domain,
|
appDomain,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var secrets AppSecrets
|
var appSecrets AppSecrets
|
||||||
var secretsTable *table.Table
|
var secretsTable *table.Table
|
||||||
if internal.Secrets {
|
if generateSecrets {
|
||||||
sampleEnv, err := recipe.SampleEnv()
|
sampleEnv, err := recipe.SampleEnv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -141,21 +147,25 @@ var appNewCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsConfig, err := secret.ReadSecretsConfig(recipe.SampleEnvPath, composeFiles, appPkg.StackName(internal.Domain))
|
secretsConfig, err := secret.ReadSecretsConfig(
|
||||||
|
recipe.SampleEnvPath,
|
||||||
|
composeFiles,
|
||||||
|
appPkg.StackName(appDomain),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := promptForSecrets(recipe.Name, secretsConfig); err != nil {
|
if err := promptForSecrets(recipe.Name, secretsConfig); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cl, err := client.New(internal.NewAppServer)
|
cl, err := client.New(newAppServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secrets, err = createSecrets(cl, secretsConfig, sanitisedAppName)
|
appSecrets, err = createSecrets(cl, secretsConfig, sanitisedAppName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -168,62 +178,43 @@ var appNewCommand = cli.Command{
|
|||||||
headers := []string{"NAME", "VALUE"}
|
headers := []string{"NAME", "VALUE"}
|
||||||
secretsTable.Headers(headers...)
|
secretsTable.Headers(headers...)
|
||||||
|
|
||||||
for name, val := range secrets {
|
for name, val := range appSecrets {
|
||||||
secretsTable.Row(name, val)
|
secretsTable.Row(name, val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.NewAppServer == "default" {
|
if newAppServer == "default" {
|
||||||
internal.NewAppServer = "local"
|
newAppServer = "local"
|
||||||
}
|
}
|
||||||
|
|
||||||
table, err := formatter.CreateTable()
|
log.Infof("%s created successfully (version: %s, chaos: %s)", appDomain, recipeVersion, chaosVersion)
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
headers := []string{"SERVER", "DOMAIN", "RECIPE", "VERSION"}
|
if len(appSecrets) > 0 {
|
||||||
table.Headers(headers...)
|
rows := [][]string{}
|
||||||
|
for k, v := range appSecrets {
|
||||||
|
rows = append(rows, []string{k, v})
|
||||||
|
}
|
||||||
|
|
||||||
table.Row(internal.NewAppServer, internal.Domain, recipe.Name, version)
|
overview := formatter.CreateOverview("SECRETS OVERVIEW", rows)
|
||||||
|
|
||||||
log.Infof("new app '%s' created 🌞", recipe.Name)
|
fmt.Println(overview)
|
||||||
|
|
||||||
fmt.Println("")
|
|
||||||
fmt.Println(table)
|
|
||||||
fmt.Println("")
|
|
||||||
|
|
||||||
fmt.Println("Configure this app:")
|
|
||||||
fmt.Println(fmt.Sprintf("\n abra app config %s", internal.Domain))
|
|
||||||
|
|
||||||
fmt.Println("")
|
|
||||||
fmt.Println("Deploy this app:")
|
|
||||||
fmt.Println(fmt.Sprintf("\n abra app deploy %s", internal.Domain))
|
|
||||||
|
|
||||||
if len(secrets) > 0 {
|
|
||||||
fmt.Println("")
|
|
||||||
fmt.Println("Generated secrets:")
|
|
||||||
fmt.Println("")
|
|
||||||
fmt.Println(secretsTable)
|
|
||||||
|
|
||||||
log.Warnf(
|
log.Warnf(
|
||||||
"generated secrets %s shown again, please take note of them %s",
|
"secrets are %s shown again, please save them %s",
|
||||||
formatter.BoldStyle.Render("NOT"),
|
formatter.BoldStyle.Render("NOT"),
|
||||||
formatter.BoldStyle.Render("NOW"),
|
formatter.BoldStyle.Render("NOW"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
app, err := app.Get(internal.Domain)
|
app, err := app.Get(appDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("choosing %s as version to save to env file", version)
|
log.Debugf("choosing %s as version to save to env file", recipeVersion)
|
||||||
if err := app.WriteRecipeVersion(version, false); err != nil {
|
if err := app.WriteRecipeVersion(recipeVersion, false); 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
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,19 +229,19 @@ func createSecrets(cl *dockerClient.Client, secretsConfig map[string]secret.Secr
|
|||||||
sanitisedAppName = sanitisedAppName[:config.MAX_SANITISED_APP_NAME_LENGTH]
|
sanitisedAppName = sanitisedAppName[:config.MAX_SANITISED_APP_NAME_LENGTH]
|
||||||
}
|
}
|
||||||
|
|
||||||
secrets, err := secret.GenerateSecrets(cl, secretsConfig, internal.NewAppServer)
|
secrets, err := secret.GenerateSecrets(cl, secretsConfig, newAppServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.Pass {
|
if saveInPass {
|
||||||
for secretName := range secrets {
|
for secretName := range secrets {
|
||||||
secretValue := secrets[secretName]
|
secretValue := secrets[secretName]
|
||||||
if err := secret.PassInsertSecret(
|
if err := secret.PassInsertSecret(
|
||||||
secretValue,
|
secretValue,
|
||||||
secretName,
|
secretName,
|
||||||
internal.Domain,
|
appDomain,
|
||||||
internal.NewAppServer,
|
newAppServer,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -262,17 +253,17 @@ func createSecrets(cl *dockerClient.Client, secretsConfig map[string]secret.Secr
|
|||||||
|
|
||||||
// ensureDomainFlag checks if the domain flag was used. if not, asks the user for it/
|
// ensureDomainFlag checks if the domain flag was used. if not, asks the user for it/
|
||||||
func ensureDomainFlag(recipe recipePkg.Recipe, server string) error {
|
func ensureDomainFlag(recipe recipePkg.Recipe, server string) error {
|
||||||
if internal.Domain == "" && !internal.NoInput {
|
if appDomain == "" && !internal.NoInput {
|
||||||
prompt := &survey.Input{
|
prompt := &survey.Input{
|
||||||
Message: "Specify app domain",
|
Message: "Specify app domain",
|
||||||
Default: fmt.Sprintf("%s.%s", recipe.Name, server),
|
Default: fmt.Sprintf("%s.%s", recipe.Name, server),
|
||||||
}
|
}
|
||||||
if err := survey.AskOne(prompt, &internal.Domain); err != nil {
|
if err := survey.AskOne(prompt, &appDomain); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.Domain == "" {
|
if appDomain == "" {
|
||||||
return fmt.Errorf("no domain provided")
|
return fmt.Errorf("no domain provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,11 +277,11 @@ func promptForSecrets(recipeName string, secretsConfig map[string]secret.Secret)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !internal.Secrets && !internal.NoInput {
|
if !generateSecrets && !internal.NoInput {
|
||||||
prompt := &survey.Confirm{
|
prompt := &survey.Confirm{
|
||||||
Message: "Generate app secrets?",
|
Message: "Generate app secrets?",
|
||||||
}
|
}
|
||||||
if err := survey.AskOne(prompt, &internal.Secrets); err != nil {
|
if err := survey.AskOne(prompt, &generateSecrets); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -305,19 +296,76 @@ func ensureServerFlag() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.NewAppServer == "" && !internal.NoInput {
|
if newAppServer == "" && !internal.NoInput {
|
||||||
prompt := &survey.Select{
|
prompt := &survey.Select{
|
||||||
Message: "Select app server:",
|
Message: "Select app server:",
|
||||||
Options: servers,
|
Options: servers,
|
||||||
}
|
}
|
||||||
if err := survey.AskOne(prompt, &internal.NewAppServer); err != nil {
|
if err := survey.AskOne(prompt, &newAppServer); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.NewAppServer == "" {
|
if newAppServer == "" {
|
||||||
return fmt.Errorf("no server provided")
|
return fmt.Errorf("no server provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
newAppServer string
|
||||||
|
appDomain string
|
||||||
|
saveInPass bool
|
||||||
|
generateSecrets bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AppNewCommand.Flags().StringVarP(
|
||||||
|
&newAppServer,
|
||||||
|
"server",
|
||||||
|
"s",
|
||||||
|
"",
|
||||||
|
"specify server for new app",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppNewCommand.RegisterFlagCompletionFunc(
|
||||||
|
"server",
|
||||||
|
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.ServerNameComplete()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
AppNewCommand.Flags().StringVarP(
|
||||||
|
&appDomain,
|
||||||
|
"domain",
|
||||||
|
"D",
|
||||||
|
"",
|
||||||
|
"domain name for app",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppNewCommand.Flags().BoolVarP(
|
||||||
|
&saveInPass,
|
||||||
|
"pass",
|
||||||
|
"p",
|
||||||
|
false,
|
||||||
|
"store secrets in a local pass store",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppNewCommand.Flags().BoolVarP(
|
||||||
|
&generateSecrets,
|
||||||
|
"secrets",
|
||||||
|
"S",
|
||||||
|
false,
|
||||||
|
"automatically generate secrets",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppNewCommand.Flags().BoolVarP(
|
||||||
|
&internal.Chaos,
|
||||||
|
"chaos",
|
||||||
|
"C",
|
||||||
|
false,
|
||||||
|
"ignore uncommitted recipes changes",
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -18,25 +18,23 @@ import (
|
|||||||
containerTypes "github.com/docker/docker/api/types/container"
|
containerTypes "github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appPsCommand = cli.Command{
|
var AppPsCommand = &cobra.Command{
|
||||||
Name: "ps",
|
Use: "ps <app> [flags]",
|
||||||
Aliases: []string{"p"},
|
Aliases: []string{"p"},
|
||||||
Usage: "Check app status",
|
Short: "Check app status",
|
||||||
ArgsUsage: "<domain>",
|
Args: cobra.ExactArgs(1),
|
||||||
Description: "Show status of a deployed app.",
|
ValidArgsFunction: func(
|
||||||
Flags: []cli.Flag{
|
cmd *cobra.Command,
|
||||||
internal.MachineReadableFlag,
|
args []string,
|
||||||
internal.DebugFlag,
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
internal.ChaosFlag,
|
return autocomplete.AppNameComplete()
|
||||||
internal.OfflineFlag,
|
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
app := internal.ValidateApp(args)
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -67,8 +65,6 @@ var appPsCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
showPSOutput(app, cl, deployMeta.Version, chaosVersion)
|
showPSOutput(app, cl, deployMeta.Version, chaosVersion)
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,24 +128,35 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
|||||||
|
|
||||||
allContainerStats[containerStats["service"]] = containerStats
|
allContainerStats[containerStats["service"]] = containerStats
|
||||||
|
|
||||||
|
// NOTE(d1): don't clobber these variables for --machine output
|
||||||
|
dVersion := deployedVersion
|
||||||
|
cVersion := chaosVersion
|
||||||
|
|
||||||
|
if containerStats["service"] != "app" {
|
||||||
|
// NOTE(d1): don't repeat info which only relevant for the "app" service
|
||||||
|
dVersion = ""
|
||||||
|
cVersion = ""
|
||||||
|
}
|
||||||
|
|
||||||
row := []string{
|
row := []string{
|
||||||
containerStats["service"],
|
containerStats["service"],
|
||||||
containerStats["image"],
|
containerStats["image"],
|
||||||
containerStats["created"],
|
dVersion,
|
||||||
|
cVersion,
|
||||||
containerStats["status"],
|
containerStats["status"],
|
||||||
containerStats["state"],
|
|
||||||
containerStats["ports"],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rows = append(rows, row)
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
jsonstring, err := json.Marshal(allContainerStats)
|
rendered, err := json.Marshal(allContainerStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("unable to convert to JSON: %s", err)
|
log.Fatal("unable to convert to JSON: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Println(string(jsonstring))
|
|
||||||
|
fmt.Println(string(rendered))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,17 +168,34 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
|||||||
headers := []string{
|
headers := []string{
|
||||||
"SERVICE",
|
"SERVICE",
|
||||||
"IMAGE",
|
"IMAGE",
|
||||||
"CREATED",
|
"VERSION",
|
||||||
|
"CHAOS",
|
||||||
"STATUS",
|
"STATUS",
|
||||||
"STATE",
|
|
||||||
"PORTS",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
table.
|
table.
|
||||||
Headers(headers...).
|
Headers(headers...).
|
||||||
Rows(rows...)
|
Rows(rows...)
|
||||||
|
|
||||||
fmt.Println(table)
|
if err := formatter.PrintTable(table); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
log.Infof("VERSION: %s CHAOS: %s", deployedVersion, chaosVersion)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AppPsCommand.Flags().BoolVarP(
|
||||||
|
&internal.MachineReadable,
|
||||||
|
"machine",
|
||||||
|
"m",
|
||||||
|
false,
|
||||||
|
"print machine-readable output",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppPsCommand.Flags().BoolVarP(
|
||||||
|
&internal.Chaos,
|
||||||
|
"chaos",
|
||||||
|
"C",
|
||||||
|
false,
|
||||||
|
"ignore uncommitted recipes changes",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -12,16 +12,14 @@ import (
|
|||||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appRemoveCommand = cli.Command{
|
var AppRemoveCommand = &cobra.Command{
|
||||||
Name: "remove",
|
Use: "remove <app> [flags]",
|
||||||
Aliases: []string{"rm"},
|
Aliases: []string{"rm"},
|
||||||
ArgsUsage: "<domain>",
|
Short: "Remove all app data, locally and remotely",
|
||||||
Usage: "Remove all app data, locally and remotely",
|
Long: `Remove everything related to an app which is already undeployed.
|
||||||
Description: `
|
|
||||||
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.
|
||||||
@ -37,19 +35,19 @@ secrets first, Abra will *not* be able to help you remove them afterwards.
|
|||||||
|
|
||||||
To delete everything without prompt, use the "--force/-f" or the "--no-input/n"
|
To delete everything without prompt, use the "--force/-f" or the "--no-input/n"
|
||||||
flag.`,
|
flag.`,
|
||||||
Flags: []cli.Flag{
|
Example: " abra app remove 1312.net",
|
||||||
internal.ForceFlag,
|
Args: cobra.ExactArgs(1),
|
||||||
internal.DebugFlag,
|
ValidArgsFunction: func(
|
||||||
internal.NoInputFlag,
|
cmd *cobra.Command,
|
||||||
internal.OfflineFlag,
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
},
|
},
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
Before: internal.SubCommandBefore,
|
app := internal.ValidateApp(args)
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
app := internal.ValidateApp(c)
|
|
||||||
|
|
||||||
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)
|
log.Warnf("ALERTA ALERTA: deleting %s data and config (local/remote)", app.Name)
|
||||||
|
|
||||||
response := false
|
response := false
|
||||||
prompt := &survey.Confirm{Message: "are you sure?"}
|
prompt := &survey.Confirm{Message: "are you sure?"}
|
||||||
@ -132,7 +130,15 @@ flag.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Info(fmt.Sprintf("file: %s removed", app.Path))
|
log.Info(fmt.Sprintf("file: %s removed", app.Path))
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AppRemoveCommand.Flags().BoolVarP(
|
||||||
|
&internal.Force,
|
||||||
|
"force",
|
||||||
|
"f",
|
||||||
|
false,
|
||||||
|
"perform action without further prompt",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -2,7 +2,6 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
@ -12,49 +11,62 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
upstream "coopcloud.tech/abra/pkg/upstream/service"
|
upstream "coopcloud.tech/abra/pkg/upstream/service"
|
||||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appRestartCommand = cli.Command{
|
var AppRestartCommand = &cobra.Command{
|
||||||
Name: "restart",
|
Use: "restart <app> [[service] | --all-services] [flags]",
|
||||||
Aliases: []string{"re"},
|
Aliases: []string{"re"},
|
||||||
Usage: "Restart an app",
|
Short: "Restart an app",
|
||||||
ArgsUsage: "<domain> [<service>]",
|
Long: `This command restarts services within a deployed app.
|
||||||
Flags: []cli.Flag{
|
|
||||||
internal.DebugFlag,
|
Run "abra app ps <app>" to see a list of service names.
|
||||||
internal.OfflineFlag,
|
|
||||||
internal.AllServicesFlag,
|
Pass "--all-services/-a" to restart all services.`,
|
||||||
|
Example: ` # restart a single app service
|
||||||
|
abra app restart 1312.net app
|
||||||
|
|
||||||
|
# restart all app services
|
||||||
|
abra app restart 1312.net -a`,
|
||||||
|
Args: cobra.RangeArgs(1, 2),
|
||||||
|
ValidArgsFunction: func(
|
||||||
|
cmd *cobra.Command,
|
||||||
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
switch l := len(args); l {
|
||||||
|
case 0:
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
|
case 1:
|
||||||
|
if !allServices {
|
||||||
|
return autocomplete.ServiceNameComplete(args[0])
|
||||||
|
}
|
||||||
|
return nil, cobra.ShellCompDirectiveDefault
|
||||||
|
default:
|
||||||
|
return nil, cobra.ShellCompDirectiveError
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
Description: `
|
app := internal.ValidateApp(args)
|
||||||
This command restarts services within a deployed app.
|
|
||||||
|
|
||||||
Run "abra app ps <domain>" to see a list of service names.
|
|
||||||
|
|
||||||
Pass "--all-services/-a" to restart all services.
|
|
||||||
|
|
||||||
EXAMPLE:
|
|
||||||
|
|
||||||
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 := c.Args().Get(1)
|
var serviceName string
|
||||||
if serviceName == "" && !internal.AllServices {
|
if len(args) == 2 {
|
||||||
err := errors.New("missing <service>")
|
serviceName = args[1]
|
||||||
internal.ShowSubcommandHelpAndError(c, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if serviceName != "" && internal.AllServices {
|
if serviceName == "" && !allServices {
|
||||||
log.Fatal("cannot use <service> and --all-services together")
|
log.Fatal("missing [service]")
|
||||||
|
}
|
||||||
|
|
||||||
|
if serviceName != "" && allServices {
|
||||||
|
log.Fatal("cannot use [service] and --all-services/-a together")
|
||||||
}
|
}
|
||||||
|
|
||||||
var serviceNames []string
|
var serviceNames []string
|
||||||
if internal.AllServices {
|
if allServices {
|
||||||
var err error
|
var err error
|
||||||
serviceNames, err = appPkg.GetAppServiceNames(app.Name)
|
serviceNames, err = appPkg.GetAppServiceNames(app.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -105,7 +117,17 @@ EXAMPLE:
|
|||||||
log.Debugf("%s has been scaled to 1", stackServiceName)
|
log.Debugf("%s has been scaled to 1", stackServiceName)
|
||||||
log.Infof("%s service successfully restarted", serviceName)
|
log.Infof("%s service successfully restarted", serviceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var allServices bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AppRestartCommand.Flags().BoolVarP(
|
||||||
|
&allServices,
|
||||||
|
"all-services",
|
||||||
|
"a",
|
||||||
|
false,
|
||||||
|
"restart all services",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -2,35 +2,32 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var targetPath string
|
var AppRestoreCommand = &cobra.Command{
|
||||||
var targetPathFlag = &cli.StringFlag{
|
Use: "restore <app> [flags]",
|
||||||
Name: "target, t",
|
Aliases: []string{"rs"},
|
||||||
Usage: "Target path",
|
Short: "Restore a snapshot",
|
||||||
Destination: &targetPath,
|
Long: `Snapshots are restored while apps are deployed.
|
||||||
}
|
|
||||||
|
|
||||||
var appRestoreCommand = cli.Command{
|
Some restore scenarios may require service / app restarts.`,
|
||||||
Name: "restore",
|
Args: cobra.ExactArgs(1),
|
||||||
Aliases: []string{"rs"},
|
ValidArgsFunction: func(
|
||||||
Usage: "Restore an app backup",
|
cmd *cobra.Command,
|
||||||
ArgsUsage: "<domain> <service>",
|
args []string,
|
||||||
Flags: []cli.Flag{
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
internal.DebugFlag,
|
return autocomplete.AppNameComplete()
|
||||||
internal.OfflineFlag,
|
|
||||||
targetPathFlag,
|
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
app := internal.ValidateApp(args)
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -45,20 +42,94 @@ var appRestoreCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
execEnv := []string{fmt.Sprintf("SERVICE=%s", app.Domain)}
|
execEnv := []string{
|
||||||
|
fmt.Sprintf("SERVICE=%s", app.Domain),
|
||||||
|
"MACHINE_LOGS=true",
|
||||||
|
}
|
||||||
|
|
||||||
if snapshot != "" {
|
if snapshot != "" {
|
||||||
log.Debugf("including SNAPSHOT=%s in backupbot exec invocation", snapshot)
|
log.Debugf("including SNAPSHOT=%s in backupbot exec invocation", snapshot)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
|
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
|
||||||
}
|
}
|
||||||
|
|
||||||
if targetPath != "" {
|
if targetPath != "" {
|
||||||
log.Debugf("including TARGET=%s in backupbot exec invocation", targetPath)
|
log.Debugf("including TARGET=%s in backupbot exec invocation", targetPath)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("TARGET=%s", targetPath))
|
execEnv = append(execEnv, fmt.Sprintf("TARGET=%s", targetPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := internal.RunBackupCmdRemote(cl, "restore", targetContainer.ID, execEnv); err != nil {
|
if internal.NoInput {
|
||||||
log.Fatal(err)
|
log.Debugf("including NONINTERACTIVE=%v in backupbot exec invocation", internal.NoInput)
|
||||||
|
execEnv = append(execEnv, fmt.Sprintf("NONINTERACTIVE=%v", internal.NoInput))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if len(volumes) > 0 {
|
||||||
|
allVolumes := strings.Join(volumes, ",")
|
||||||
|
log.Debugf("including VOLUMES=%s in backupbot exec invocation", allVolumes)
|
||||||
|
execEnv = append(execEnv, fmt.Sprintf("VOLUMES=%s", allVolumes))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(services) > 0 {
|
||||||
|
allServices := strings.Join(services, ",")
|
||||||
|
log.Debugf("including CONTAINER=%s in backupbot exec invocation", allServices)
|
||||||
|
execEnv = append(execEnv, fmt.Sprintf("CONTAINER=%s", allServices))
|
||||||
|
}
|
||||||
|
|
||||||
|
if hooks {
|
||||||
|
log.Debugf("including NO_COMMANDS=%v in backupbot exec invocation", false)
|
||||||
|
execEnv = append(execEnv, fmt.Sprintf("NO_COMMANDS=%v", false))
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := internal.RunBackupCmdRemote(cl, "restore", targetContainer.ID, execEnv); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
targetPath string
|
||||||
|
hooks bool
|
||||||
|
services []string
|
||||||
|
volumes []string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AppRestoreCommand.Flags().StringVarP(
|
||||||
|
&targetPath,
|
||||||
|
"target",
|
||||||
|
"t",
|
||||||
|
"/",
|
||||||
|
"target path",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppRestoreCommand.Flags().StringArrayVarP(
|
||||||
|
&services,
|
||||||
|
"services",
|
||||||
|
"s",
|
||||||
|
[]string{},
|
||||||
|
"restore specific services",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppRestoreCommand.Flags().StringArrayVarP(
|
||||||
|
&volumes,
|
||||||
|
"volumes",
|
||||||
|
"v",
|
||||||
|
[]string{},
|
||||||
|
"restore specific volumes",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppRestoreCommand.Flags().BoolVarP(
|
||||||
|
&hooks,
|
||||||
|
"hooks",
|
||||||
|
"H",
|
||||||
|
false,
|
||||||
|
"enable pre/post-hook command execution",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppRestoreCommand.Flags().BoolVarP(
|
||||||
|
&internal.Chaos,
|
||||||
|
"chaos",
|
||||||
|
"C",
|
||||||
|
false,
|
||||||
|
"ignore uncommitted recipes changes",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -16,44 +16,55 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appRollbackCommand = cli.Command{
|
var AppRollbackCommand = &cobra.Command{
|
||||||
Name: "rollback",
|
Use: "rollback <app> [version] [flags]",
|
||||||
Aliases: []string{"rl"},
|
Aliases: []string{"rl"},
|
||||||
Usage: "Roll an app back to a previous version",
|
Short: "Roll an app back to a previous version",
|
||||||
ArgsUsage: "<domain> [<version>]",
|
Long: `This command rolls an app back to a previous version.
|
||||||
Flags: []cli.Flag{
|
|
||||||
internal.DebugFlag,
|
|
||||||
internal.NoInputFlag,
|
|
||||||
internal.ForceFlag,
|
|
||||||
internal.NoDomainChecksFlag,
|
|
||||||
internal.DontWaitConvergeFlag,
|
|
||||||
internal.OfflineFlag,
|
|
||||||
},
|
|
||||||
Before: internal.SubCommandBefore,
|
|
||||||
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.`,
|
||||||
|
Example: ` # standard rollback
|
||||||
|
abra app rollback 1312.net
|
||||||
|
|
||||||
EXAMPLE:
|
# rollback to specific version
|
||||||
|
abra app rollback 1312.net 2.0.0+1.2.3`,
|
||||||
abra app rollback foo.example.com
|
Args: cobra.RangeArgs(1, 2),
|
||||||
abra app rollback foo.example.com 1.2.3+3.2.1`,
|
ValidArgsFunction: func(
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
cmd *cobra.Command,
|
||||||
Action: func(c *cli.Context) error {
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
switch l := len(args); l {
|
||||||
|
case 0:
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
|
case 1:
|
||||||
|
app, err := appPkg.Get(args[0])
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
|
return []string{errMsg}, cobra.ShellCompDirectiveError
|
||||||
|
}
|
||||||
|
return autocomplete.RecipeVersionComplete(app.Recipe.Name)
|
||||||
|
default:
|
||||||
|
return nil, cobra.ShellCompDirectiveError
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
var warnMessages []string
|
var warnMessages []string
|
||||||
|
|
||||||
app := internal.ValidateApp(c)
|
app := internal.ValidateApp(args)
|
||||||
stackName := app.StackName()
|
stackName := app.StackName()
|
||||||
|
|
||||||
specificVersion := c.Args().Get(1)
|
var specificVersion string
|
||||||
|
if len(args) == 2 {
|
||||||
|
specificVersion = args[1]
|
||||||
|
}
|
||||||
|
|
||||||
if specificVersion != "" {
|
if specificVersion != "" {
|
||||||
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
|
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
|
||||||
app.Recipe.Version = specificVersion
|
app.Recipe.Version = specificVersion
|
||||||
@ -139,7 +150,7 @@ EXAMPLE:
|
|||||||
|
|
||||||
if len(availableDowngrades) == 0 && !internal.Force {
|
if len(availableDowngrades) == 0 && !internal.Force {
|
||||||
log.Info("no available downgrades")
|
log.Info("no available downgrades")
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,11 +167,11 @@ EXAMPLE:
|
|||||||
|
|
||||||
prompt := &survey.Select{
|
prompt := &survey.Select{
|
||||||
Message: msg,
|
Message: msg,
|
||||||
Options: internal.ReverseStringList(availableDowngrades),
|
Options: internal.SortVersionsDesc(availableDowngrades),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := survey.AskOne(prompt, &chosenDowngrade); err != nil {
|
if err := survey.AskOne(prompt, &chosenDowngrade); err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -228,7 +239,30 @@ EXAMPLE:
|
|||||||
if err := app.WriteRecipeVersion(app.Recipe.Version, false); err != nil {
|
if err := app.WriteRecipeVersion(app.Recipe.Version, false); 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
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AppRollbackCommand.Flags().BoolVarP(
|
||||||
|
&internal.Force,
|
||||||
|
"force",
|
||||||
|
"f",
|
||||||
|
false,
|
||||||
|
"perform action without further prompt",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppRollbackCommand.Flags().BoolVarP(
|
||||||
|
&internal.NoDomainChecks,
|
||||||
|
"no-domain-checks",
|
||||||
|
"D",
|
||||||
|
false,
|
||||||
|
"disable public DNS checks",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppRollbackCommand.Flags().BoolVarP(
|
||||||
|
&internal.DontWaitConverge, "no-converge-checks",
|
||||||
|
"c",
|
||||||
|
false,
|
||||||
|
"do not wait for converge logic checks",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
100
cli/app/run.go
100
cli/app/run.go
@ -2,7 +2,6 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
@ -14,52 +13,48 @@ import (
|
|||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var user string
|
var AppRunCommand = &cobra.Command{
|
||||||
var userFlag = &cli.StringFlag{
|
Use: "run <app> <service> <cmd> [[args] [flags] | [flags] -- [args]]",
|
||||||
Name: "user, u",
|
|
||||||
Value: "",
|
|
||||||
Destination: &user,
|
|
||||||
}
|
|
||||||
|
|
||||||
var noTTY bool
|
|
||||||
var noTTYFlag = &cli.BoolFlag{
|
|
||||||
Name: "no-tty, t",
|
|
||||||
Destination: &noTTY,
|
|
||||||
}
|
|
||||||
|
|
||||||
var appRunCommand = cli.Command{
|
|
||||||
Name: "run",
|
|
||||||
Aliases: []string{"r"},
|
Aliases: []string{"r"},
|
||||||
Flags: []cli.Flag{
|
Short: "Run a command inside a service container",
|
||||||
internal.DebugFlag,
|
Example: ` # run <cmd> with args/flags
|
||||||
noTTYFlag,
|
abra app run 1312.net app -- ls -lha
|
||||||
userFlag,
|
|
||||||
|
# run <cmd> without args/flags
|
||||||
|
abra app run 1312.net app bash --user nobody
|
||||||
|
|
||||||
|
# run <cmd> with both kinds of args/flags
|
||||||
|
abra app run 1312.net app --user nobody -- ls -lha`,
|
||||||
|
Args: cobra.MinimumNArgs(3),
|
||||||
|
ValidArgsFunction: func(
|
||||||
|
cmd *cobra.Command,
|
||||||
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
switch l := len(args); l {
|
||||||
|
case 0:
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
|
case 1:
|
||||||
|
return autocomplete.ServiceNameComplete(args[0])
|
||||||
|
case 2:
|
||||||
|
return autocomplete.CommandNameComplete(args[0])
|
||||||
|
default:
|
||||||
|
return nil, cobra.ShellCompDirectiveError
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
ArgsUsage: "<domain> <service> <args>...",
|
app := internal.ValidateApp(args)
|
||||||
Usage: "Run a command in a service container",
|
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
app := internal.ValidateApp(c)
|
|
||||||
|
|
||||||
if len(c.Args()) < 2 {
|
|
||||||
internal.ShowSubcommandHelpAndError(c, errors.New("no <service> provided?"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.Args()) < 3 {
|
|
||||||
internal.ShowSubcommandHelpAndError(c, errors.New("no <args> provided?"))
|
|
||||||
}
|
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
cl, err := client.New(app.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceName := c.Args().Get(1)
|
serviceName := args[1]
|
||||||
stackAndServiceName := fmt.Sprintf("^%s_%s", app.StackName(), serviceName)
|
stackAndServiceName := fmt.Sprintf("^%s_%s", app.StackName(), serviceName)
|
||||||
|
|
||||||
filters := filters.NewArgs()
|
filters := filters.NewArgs()
|
||||||
filters.Add("name", stackAndServiceName)
|
filters.Add("name", stackAndServiceName)
|
||||||
|
|
||||||
@ -68,24 +63,23 @@ var appRunCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := c.Args()[2:]
|
userCmd := args[2:]
|
||||||
execCreateOpts := types.ExecConfig{
|
execCreateOpts := types.ExecConfig{
|
||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
AttachStdin: true,
|
AttachStdin: true,
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
Cmd: cmd,
|
Cmd: userCmd,
|
||||||
Detach: false,
|
Detach: false,
|
||||||
Tty: true,
|
Tty: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if user != "" {
|
if runAsUser != "" {
|
||||||
execCreateOpts.User = user
|
execCreateOpts.User = runAsUser
|
||||||
}
|
}
|
||||||
if noTTY {
|
if noTTY {
|
||||||
execCreateOpts.Tty = false
|
execCreateOpts.Tty = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: avoid instantiating a new CLI
|
|
||||||
dcli, err := command.NewDockerCli()
|
dcli, err := command.NewDockerCli()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -94,7 +88,27 @@ var appRunCommand = cli.Command{
|
|||||||
if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil {
|
if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
noTTY bool
|
||||||
|
runAsUser string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AppRunCommand.Flags().BoolVarP(&noTTY,
|
||||||
|
"no-tty",
|
||||||
|
"t",
|
||||||
|
false,
|
||||||
|
"do not request a TTY",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppRunCommand.Flags().StringVarP(
|
||||||
|
&runAsUser,
|
||||||
|
"user",
|
||||||
|
"u",
|
||||||
|
"",
|
||||||
|
"run command as user",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -2,7 +2,6 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -17,56 +16,45 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/secret"
|
"coopcloud.tech/abra/pkg/secret"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var AppSecretGenerateCommand = &cobra.Command{
|
||||||
allSecrets bool
|
Use: "generate <app> [[secret] [version] | --all] [flags]",
|
||||||
allSecretsFlag = &cli.BoolFlag{
|
Aliases: []string{"g"},
|
||||||
Name: "all, a",
|
Short: "Generate secrets",
|
||||||
Destination: &allSecrets,
|
Args: cobra.RangeArgs(1, 3),
|
||||||
Usage: "Generate all secrets",
|
ValidArgsFunction: func(
|
||||||
}
|
cmd *cobra.Command,
|
||||||
)
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
var (
|
switch l := len(args); l {
|
||||||
rmAllSecrets bool
|
case 0:
|
||||||
rmAllSecretsFlag = &cli.BoolFlag{
|
return autocomplete.AppNameComplete()
|
||||||
Name: "all, a",
|
case 1:
|
||||||
Destination: &rmAllSecrets,
|
app, err := appPkg.Get(args[0])
|
||||||
Usage: "Remove all secrets",
|
if err != nil {
|
||||||
}
|
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
)
|
return []string{errMsg}, cobra.ShellCompDirectiveError
|
||||||
|
}
|
||||||
var appSecretGenerateCommand = cli.Command{
|
return autocomplete.SecretComplete(app.Recipe.Name)
|
||||||
Name: "generate",
|
default:
|
||||||
Aliases: []string{"g"},
|
return nil, cobra.ShellCompDirectiveDefault
|
||||||
Usage: "Generate secrets",
|
}
|
||||||
ArgsUsage: "<domain> <secret> <version>",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
internal.DebugFlag,
|
|
||||||
allSecretsFlag,
|
|
||||||
internal.PassFlag,
|
|
||||||
internal.MachineReadableFlag,
|
|
||||||
internal.OfflineFlag,
|
|
||||||
internal.ChaosFlag,
|
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
app := internal.ValidateApp(args)
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.Args()) == 1 && !allSecrets {
|
if len(args) == 1 && !generateAllSecrets {
|
||||||
err := errors.New("missing arguments <secret>/<version> or '--all'")
|
log.Fatal("missing arguments [secret]/[version] or '--all'")
|
||||||
internal.ShowSubcommandHelpAndError(c, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Args().Get(1) != "" && allSecrets {
|
if len(args) > 1 && generateAllSecrets {
|
||||||
err := errors.New("cannot use '<secret> <version>' and '--all' together")
|
log.Fatal("cannot use '[secret] [version]' and '--all' together")
|
||||||
internal.ShowSubcommandHelpAndError(c, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
||||||
@ -79,9 +67,9 @@ var appSecretGenerateCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !allSecrets {
|
if !generateAllSecrets {
|
||||||
secretName := c.Args().Get(1)
|
secretName := args[1]
|
||||||
secretVersion := c.Args().Get(2)
|
secretVersion := args[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)
|
||||||
@ -102,7 +90,7 @@ var appSecretGenerateCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.Pass {
|
if storeInPass {
|
||||||
for name, data := range secretVals {
|
for name, data := range secretVals {
|
||||||
if err := secret.PassInsertSecret(data, name, app.Name, app.Server); err != nil {
|
if err := secret.PassInsertSecret(data, name, app.Name, app.Server); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -136,67 +124,66 @@ var appSecretGenerateCommand = cli.Command{
|
|||||||
log.Fatal("unable to render to JSON: %s", err)
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Println(out)
|
fmt.Println(out)
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(table)
|
if err := formatter.PrintTable(table); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
log.Warnf(
|
log.Warnf(
|
||||||
"generated secrets %s shown again, please take note of them %s",
|
"generated secrets %s shown again, please take note of them %s",
|
||||||
formatter.BoldStyle.Render("NOT"),
|
formatter.BoldStyle.Render("NOT"),
|
||||||
formatter.BoldStyle.Render("NOW"),
|
formatter.BoldStyle.Render("NOW"),
|
||||||
)
|
)
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var appSecretInsertCommand = cli.Command{
|
var AppSecretInsertCommand = &cobra.Command{
|
||||||
Name: "insert",
|
Use: "insert <app> <secret> <version> <data> [flags]",
|
||||||
Aliases: []string{"i"},
|
Aliases: []string{"i"},
|
||||||
Usage: "Insert secret",
|
Short: "Insert secret",
|
||||||
Flags: []cli.Flag{
|
Long: `This command inserts a secret into an app environment.
|
||||||
internal.DebugFlag,
|
|
||||||
internal.PassFlag,
|
|
||||||
internal.FileFlag,
|
|
||||||
internal.TrimFlag,
|
|
||||||
internal.ChaosFlag,
|
|
||||||
},
|
|
||||||
Before: internal.SubCommandBefore,
|
|
||||||
ArgsUsage: "<domain> <secret-name> <version> <data>",
|
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
|
||||||
Description: `
|
|
||||||
This command inserts a secret into an app environment.
|
|
||||||
|
|
||||||
This can be useful when you want to manually generate secrets for an app
|
This can be useful when you want to manually generate secrets for an app
|
||||||
environment. Typically, you can let Abra generate them for you on app creation
|
environment. Typically, you can let Abra generate them for you on app creation
|
||||||
(see "abra app new --secrets" for more).
|
(see "abra app new --secrets/-S" for more).`,
|
||||||
|
Args: cobra.MinimumNArgs(4),
|
||||||
|
ValidArgsFunction: func(
|
||||||
|
cmd *cobra.Command,
|
||||||
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
switch l := len(args); l {
|
||||||
|
case 0:
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
|
case 1:
|
||||||
|
app, err := appPkg.Get(args[0])
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
|
return []string{errMsg}, cobra.ShellCompDirectiveError
|
||||||
|
}
|
||||||
|
return autocomplete.SecretComplete(app.Recipe.Name)
|
||||||
|
default:
|
||||||
|
return nil, cobra.ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
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 {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
name := c.Args().Get(1)
|
name := args[1]
|
||||||
version := c.Args().Get(2)
|
version := args[2]
|
||||||
data := c.Args().Get(3)
|
data := args[3]
|
||||||
|
|
||||||
if internal.File {
|
if insertFromFile {
|
||||||
raw, err := os.ReadFile(data)
|
raw, err := os.ReadFile(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("reading secret from file: %s", err)
|
log.Fatalf("reading secret from file: %s", err)
|
||||||
@ -204,7 +191,7 @@ Example:
|
|||||||
data = string(raw)
|
data = string(raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.Trim {
|
if trimInput {
|
||||||
data = strings.TrimSpace(data)
|
data = strings.TrimSpace(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,13 +202,11 @@ Example:
|
|||||||
|
|
||||||
log.Infof("%s successfully stored on server", secretName)
|
log.Infof("%s successfully stored on server", secretName)
|
||||||
|
|
||||||
if internal.Pass {
|
if storeInPass {
|
||||||
if err := secret.PassInsertSecret(data, name, app.Name, app.Server); err != nil {
|
if err := secret.PassInsertSecret(data, name, app.Name, app.Server); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,7 +218,7 @@ func secretRm(cl *dockerClient.Client, app appPkg.App, secretName, parsed string
|
|||||||
|
|
||||||
log.Infof("deleted %s successfully from server", secretName)
|
log.Infof("deleted %s successfully from server", secretName)
|
||||||
|
|
||||||
if internal.PassRemove {
|
if removeFromPass {
|
||||||
if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil {
|
if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -244,30 +229,35 @@ func secretRm(cl *dockerClient.Client, app appPkg.App, secretName, parsed string
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var appSecretRmCommand = cli.Command{
|
var AppSecretRmCommand = &cobra.Command{
|
||||||
Name: "remove",
|
Use: "remove <app> [[secret] | --all] [flags]",
|
||||||
Aliases: []string{"rm"},
|
Aliases: []string{"rm"},
|
||||||
Usage: "Remove a secret",
|
Short: "Remove a secret",
|
||||||
Flags: []cli.Flag{
|
Args: cobra.RangeArgs(1, 2),
|
||||||
internal.DebugFlag,
|
ValidArgsFunction: func(
|
||||||
internal.NoInputFlag,
|
cmd *cobra.Command,
|
||||||
rmAllSecretsFlag,
|
args []string,
|
||||||
internal.PassRemoveFlag,
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
internal.OfflineFlag,
|
switch l := len(args); l {
|
||||||
internal.ChaosFlag,
|
case 0:
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
|
case 1:
|
||||||
|
if !rmAllSecrets {
|
||||||
|
app, err := appPkg.Get(args[0])
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
|
return []string{errMsg}, cobra.ShellCompDirectiveError
|
||||||
|
}
|
||||||
|
return autocomplete.SecretComplete(app.Recipe.Name)
|
||||||
|
}
|
||||||
|
return nil, cobra.ShellCompDirectiveDefault
|
||||||
|
default:
|
||||||
|
return nil, cobra.ShellCompDirectiveError
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
ArgsUsage: "<domain> [<secret-name>]",
|
app := internal.ValidateApp(args)
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
|
||||||
Description: `
|
|
||||||
This command removes app secrets.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
abra app secret remove myapp db_pass
|
|
||||||
`,
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -282,12 +272,12 @@ Example:
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Args().Get(1) != "" && rmAllSecrets {
|
if len(args) == 2 && rmAllSecrets {
|
||||||
internal.ShowSubcommandHelpAndError(c, errors.New("cannot use '<secret-name>' and '--all' together"))
|
log.Fatal("cannot use [secret] and --all/-a together")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Args().Get(1) == "" && !rmAllSecrets {
|
if len(args) != 2 && !rmAllSecrets {
|
||||||
internal.ShowSubcommandHelpAndError(c, errors.New("no secret(s) specified?"))
|
log.Fatal("no secret(s) specified?")
|
||||||
}
|
}
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
cl, err := client.New(app.Server)
|
||||||
@ -310,8 +300,12 @@ Example:
|
|||||||
remoteSecretNames[cont.Spec.Annotations.Name] = true
|
remoteSecretNames[cont.Spec.Annotations.Name] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var secretToRm string
|
||||||
|
if len(args) == 2 {
|
||||||
|
secretToRm = args[1]
|
||||||
|
}
|
||||||
|
|
||||||
match := false
|
match := false
|
||||||
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 {
|
||||||
@ -321,7 +315,7 @@ Example:
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match = true
|
match = true
|
||||||
@ -340,25 +334,23 @@ Example:
|
|||||||
if !match {
|
if !match {
|
||||||
log.Fatal("no secrets to remove?")
|
log.Fatal("no secrets to remove?")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var appSecretLsCommand = cli.Command{
|
var AppSecretLsCommand = &cobra.Command{
|
||||||
Name: "list",
|
Use: "list <app>",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Flags: []cli.Flag{
|
Short: "List all secrets",
|
||||||
internal.DebugFlag,
|
Args: cobra.MinimumNArgs(1),
|
||||||
internal.OfflineFlag,
|
ValidArgsFunction: func(
|
||||||
internal.ChaosFlag,
|
cmd *cobra.Command,
|
||||||
internal.MachineReadableFlag,
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
Usage: "List all secrets",
|
app := internal.ValidateApp(args)
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -401,28 +393,137 @@ var appSecretLsCommand = cli.Command{
|
|||||||
log.Fatal("unable to render to JSON: %s", err)
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Println(out)
|
fmt.Println(out)
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(table)
|
if err := formatter.PrintTable(table); err != nil {
|
||||||
return nil
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Warnf("no secrets stored for %s", app.Name)
|
log.Warnf("no secrets stored for %s", app.Name)
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var appSecretCommand = cli.Command{
|
var AppSecretCommand = &cobra.Command{
|
||||||
Name: "secret",
|
Use: "secret [cmd] [args] [flags]",
|
||||||
Aliases: []string{"s"},
|
Aliases: []string{"s"},
|
||||||
Usage: "Manage app secrets",
|
Short: "Manage app secrets",
|
||||||
ArgsUsage: "<domain>",
|
}
|
||||||
Subcommands: []cli.Command{
|
|
||||||
appSecretGenerateCommand,
|
var (
|
||||||
appSecretInsertCommand,
|
storeInPass bool
|
||||||
appSecretRmCommand,
|
insertFromFile bool
|
||||||
appSecretLsCommand,
|
trimInput bool
|
||||||
},
|
rmAllSecrets bool
|
||||||
|
generateAllSecrets bool
|
||||||
|
removeFromPass bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AppSecretGenerateCommand.Flags().BoolVarP(
|
||||||
|
&internal.MachineReadable,
|
||||||
|
"machine",
|
||||||
|
"m",
|
||||||
|
false,
|
||||||
|
"print machine-readable output",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppSecretGenerateCommand.Flags().BoolVarP(
|
||||||
|
&storeInPass,
|
||||||
|
"pass",
|
||||||
|
"p",
|
||||||
|
false,
|
||||||
|
"store generated secrets in a local pass store",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppSecretGenerateCommand.Flags().BoolVarP(
|
||||||
|
&internal.Chaos,
|
||||||
|
"chaos",
|
||||||
|
"C",
|
||||||
|
false,
|
||||||
|
"ignore uncommitted recipes changes",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppSecretGenerateCommand.Flags().BoolVarP(
|
||||||
|
&generateAllSecrets,
|
||||||
|
"all",
|
||||||
|
"a",
|
||||||
|
false,
|
||||||
|
"generate all secrets",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppSecretInsertCommand.Flags().BoolVarP(
|
||||||
|
&storeInPass,
|
||||||
|
"pass",
|
||||||
|
"p",
|
||||||
|
false,
|
||||||
|
"store generated secrets in a local pass store",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppSecretInsertCommand.Flags().BoolVarP(
|
||||||
|
&insertFromFile,
|
||||||
|
"file",
|
||||||
|
"f",
|
||||||
|
false,
|
||||||
|
"treat input as a file",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppSecretInsertCommand.Flags().BoolVarP(
|
||||||
|
&trimInput,
|
||||||
|
"trim",
|
||||||
|
"t",
|
||||||
|
false,
|
||||||
|
"trim input",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppSecretInsertCommand.Flags().BoolVarP(
|
||||||
|
&internal.Chaos,
|
||||||
|
"chaos",
|
||||||
|
"C",
|
||||||
|
false,
|
||||||
|
"ignore uncommitted recipes changes",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppSecretRmCommand.Flags().BoolVarP(
|
||||||
|
&rmAllSecrets,
|
||||||
|
"all",
|
||||||
|
"a",
|
||||||
|
false,
|
||||||
|
"remove all secrets",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppSecretRmCommand.Flags().BoolVarP(
|
||||||
|
&removeFromPass,
|
||||||
|
"pass",
|
||||||
|
"p",
|
||||||
|
false,
|
||||||
|
"remove generated secrets from a local pass store",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppSecretRmCommand.Flags().BoolVarP(
|
||||||
|
&internal.Chaos,
|
||||||
|
"chaos",
|
||||||
|
"C",
|
||||||
|
false,
|
||||||
|
"ignore uncommitted recipes changes",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppSecretLsCommand.Flags().BoolVarP(
|
||||||
|
&internal.Chaos,
|
||||||
|
"chaos",
|
||||||
|
"C",
|
||||||
|
false,
|
||||||
|
"ignore uncommitted recipes changes",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppSecretLsCommand.Flags().BoolVarP(
|
||||||
|
&internal.MachineReadable,
|
||||||
|
"machine",
|
||||||
|
"m",
|
||||||
|
false,
|
||||||
|
"print machine-readable output",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -13,21 +13,23 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/service"
|
"coopcloud.tech/abra/pkg/service"
|
||||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
containerTypes "github.com/docker/docker/api/types/container"
|
containerTypes "github.com/docker/docker/api/types/container"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appServicesCommand = cli.Command{
|
var AppServicesCommand = &cobra.Command{
|
||||||
Name: "services",
|
Use: "services <app> [flags]",
|
||||||
Aliases: []string{"sr"},
|
Aliases: []string{"sr"},
|
||||||
Usage: "Display all services of an app",
|
Short: "Display all services of an app",
|
||||||
ArgsUsage: "<domain>",
|
Args: cobra.ExactArgs(1),
|
||||||
Flags: []cli.Flag{
|
ValidArgsFunction: func(
|
||||||
internal.DebugFlag,
|
cmd *cobra.Command,
|
||||||
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
app := internal.ValidateApp(args)
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -61,7 +63,7 @@ var appServicesCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
headers := []string{"SERVICE (SHORT)", "SERVICE (LONG)", "IMAGE"}
|
headers := []string{"SERVICE (SHORT)", "SERVICE (LONG)"}
|
||||||
table.Headers(headers...)
|
table.Headers(headers...)
|
||||||
|
|
||||||
var rows [][]string
|
var rows [][]string
|
||||||
@ -78,7 +80,6 @@ var appServicesCommand = cli.Command{
|
|||||||
row := []string{
|
row := []string{
|
||||||
serviceShortName,
|
serviceShortName,
|
||||||
serviceLongName,
|
serviceLongName,
|
||||||
formatter.RemoveSha(container.Image),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rows = append(rows, row)
|
rows = append(rows, row)
|
||||||
@ -87,9 +88,9 @@ var appServicesCommand = cli.Command{
|
|||||||
table.Rows(rows...)
|
table.Rows(rows...)
|
||||||
|
|
||||||
if len(rows) > 0 {
|
if len(rows) > 0 {
|
||||||
fmt.Println(table)
|
if err := formatter.PrintTable(table); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -14,15 +14,77 @@ import (
|
|||||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var prune bool
|
var AppUndeployCommand = &cobra.Command{
|
||||||
|
Use: "undeploy <app> [flags]",
|
||||||
|
Aliases: []string{"un"},
|
||||||
|
Short: "Undeploy an app",
|
||||||
|
Long: `This does not destroy any application data.
|
||||||
|
|
||||||
var pruneFlag = &cli.BoolFlag{
|
However, you should remain vigilant, as your swarm installation will consider
|
||||||
Name: "prune, p",
|
any previously attached volumes as eligible for pruning once undeployed.
|
||||||
Destination: &prune,
|
|
||||||
Usage: "Prunes unused containers, networks, and dangling images for an app",
|
Passing "--prune/-p" does not remove those volumes.`,
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
ValidArgsFunction: func(
|
||||||
|
cmd *cobra.Command,
|
||||||
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
|
},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
app := internal.ValidateApp(args)
|
||||||
|
stackName := app.StackName()
|
||||||
|
|
||||||
|
cl, err := client.New(app.Server)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("checking whether %s is already deployed", stackName)
|
||||||
|
|
||||||
|
deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !deployMeta.IsDeployed {
|
||||||
|
log.Fatalf("%s is not deployed?", app.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
chaosVersion := config.CHAOS_DEFAULT
|
||||||
|
if deployMeta.IsChaos {
|
||||||
|
chaosVersion = deployMeta.ChaosVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := internal.UndeployOverview(
|
||||||
|
app,
|
||||||
|
deployMeta.Version,
|
||||||
|
chaosVersion); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rmOpts := stack.Remove{
|
||||||
|
Namespaces: []string{app.StackName()},
|
||||||
|
Detach: false,
|
||||||
|
}
|
||||||
|
if err := stack.RunRemove(context.Background(), cl, rmOpts); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if prune {
|
||||||
|
if err := pruneApp(cl, app); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("choosing %s as version to save to env file", deployMeta.Version)
|
||||||
|
if err := app.WriteRecipeVersion(deployMeta.Version, false); err != nil {
|
||||||
|
log.Fatalf("writing undeployed recipe version in env file: %s", err)
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// pruneApp runs the equivalent of a "docker system prune" but only filtering
|
// pruneApp runs the equivalent of a "docker system prune" but only filtering
|
||||||
@ -61,69 +123,16 @@ func pruneApp(cl *dockerClient.Client, app appPkg.App) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var appUndeployCommand = cli.Command{
|
var (
|
||||||
Name: "undeploy",
|
prune bool
|
||||||
Aliases: []string{"un"},
|
)
|
||||||
ArgsUsage: "<domain>",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
internal.DebugFlag,
|
|
||||||
internal.NoInputFlag,
|
|
||||||
internal.OfflineFlag,
|
|
||||||
pruneFlag,
|
|
||||||
},
|
|
||||||
Before: internal.SubCommandBefore,
|
|
||||||
Usage: "Undeploy an app",
|
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
|
||||||
Description: `
|
|
||||||
This does not destroy any of the application data.
|
|
||||||
|
|
||||||
However, you should remain vigilant, as your swarm installation will consider
|
func init() {
|
||||||
any previously attached volumes as eligible for pruning once undeployed.
|
AppUndeployCommand.Flags().BoolVarP(
|
||||||
|
&prune,
|
||||||
Passing "-p/--prune" does not remove those volumes.`,
|
"prune",
|
||||||
Action: func(c *cli.Context) error {
|
"p",
|
||||||
app := internal.ValidateApp(c)
|
false,
|
||||||
stackName := app.StackName()
|
"prune unused containers, networks, and dangling images",
|
||||||
|
)
|
||||||
cl, err := client.New(app.Server)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("checking whether %s is already deployed", stackName)
|
|
||||||
|
|
||||||
deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !deployMeta.IsDeployed {
|
|
||||||
log.Fatalf("%s is not deployed?", app.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
chaosVersion := config.CHAOS_DEFAULT
|
|
||||||
if deployMeta.IsChaos {
|
|
||||||
chaosVersion = deployMeta.ChaosVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := internal.DeployOverview(app, []string{}, deployMeta.Version, chaosVersion); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rmOpts := stack.Remove{
|
|
||||||
Namespaces: []string{app.StackName()},
|
|
||||||
Detach: false,
|
|
||||||
}
|
|
||||||
if err := stack.RunRemove(context.Background(), cl, rmOpts); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if prune {
|
|
||||||
if err := pruneApp(cl, app); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
@ -15,45 +15,50 @@ import (
|
|||||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
"coopcloud.tech/tagcmp"
|
"coopcloud.tech/tagcmp"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appUpgradeCommand = cli.Command{
|
var AppUpgradeCommand = &cobra.Command{
|
||||||
Name: "upgrade",
|
Use: "upgrade <app> [version] [flags]",
|
||||||
Aliases: []string{"up"},
|
Aliases: []string{"up"},
|
||||||
Usage: "Upgrade an app",
|
Short: "Upgrade an app",
|
||||||
ArgsUsage: "<domain> [<version>]",
|
Long: `Upgrade an app.
|
||||||
Flags: []cli.Flag{
|
|
||||||
internal.DebugFlag,
|
|
||||||
internal.NoInputFlag,
|
|
||||||
internal.ForceFlag,
|
|
||||||
internal.NoDomainChecksFlag,
|
|
||||||
internal.DontWaitConvergeFlag,
|
|
||||||
internal.OfflineFlag,
|
|
||||||
internal.ReleaseNotesFlag,
|
|
||||||
},
|
|
||||||
Before: internal.SubCommandBefore,
|
|
||||||
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.`,
|
||||||
|
Args: cobra.RangeArgs(1, 2),
|
||||||
EXAMPLE:
|
ValidArgsFunction: func(
|
||||||
|
cmd *cobra.Command,
|
||||||
abra app upgrade foo.example.com
|
args []string,
|
||||||
abra app upgrade foo.example.com 1.2.3+3.2.1`,
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
switch l := len(args); l {
|
||||||
Action: func(c *cli.Context) error {
|
case 0:
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
|
case 1:
|
||||||
|
app, err := appPkg.Get(args[0])
|
||||||
|
if err != nil {
|
||||||
|
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
|
return []string{errMsg}, cobra.ShellCompDirectiveError
|
||||||
|
}
|
||||||
|
return autocomplete.RecipeVersionComplete(app.Recipe.Name)
|
||||||
|
default:
|
||||||
|
return nil, cobra.ShellCompDirectiveError
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
var warnMessages []string
|
var warnMessages []string
|
||||||
|
|
||||||
app := internal.ValidateApp(c)
|
app := internal.ValidateApp(args)
|
||||||
stackName := app.StackName()
|
stackName := app.StackName()
|
||||||
|
|
||||||
specificVersion := c.Args().Get(1)
|
var specificVersion string
|
||||||
|
if len(args) == 2 {
|
||||||
|
specificVersion = args[1]
|
||||||
|
}
|
||||||
|
|
||||||
if specificVersion != "" {
|
if specificVersion != "" {
|
||||||
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
|
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
|
||||||
app.Recipe.Version = specificVersion
|
app.Recipe.Version = specificVersion
|
||||||
@ -137,7 +142,7 @@ EXAMPLE:
|
|||||||
|
|
||||||
if len(availableUpgrades) == 0 && !internal.Force {
|
if len(availableUpgrades) == 0 && !internal.Force {
|
||||||
log.Info("no available upgrades")
|
log.Info("no available upgrades")
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,11 +159,11 @@ EXAMPLE:
|
|||||||
|
|
||||||
prompt := &survey.Select{
|
prompt := &survey.Select{
|
||||||
Message: msg,
|
Message: msg,
|
||||||
Options: internal.ReverseStringList(availableUpgrades),
|
Options: internal.SortVersionsDesc(availableUpgrades),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := survey.AskOne(prompt, &chosenUpgrade); err != nil {
|
if err := survey.AskOne(prompt, &chosenUpgrade); err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -185,7 +190,7 @@ EXAMPLE:
|
|||||||
if parsedVersion.IsGreaterThan(parsedDeployedVersion) && parsedVersion.IsLessThan(parsedChosenUpgrade) {
|
if parsedVersion.IsGreaterThan(parsedDeployedVersion) && parsedVersion.IsLessThan(parsedChosenUpgrade) {
|
||||||
note, err := app.Recipe.GetReleaseNotes(version)
|
note, err := app.Recipe.GetReleaseNotes(version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if note != "" {
|
if note != "" {
|
||||||
releaseNotes += fmt.Sprintf("%s\n", note)
|
releaseNotes += fmt.Sprintf("%s\n", note)
|
||||||
@ -244,10 +249,9 @@ EXAMPLE:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.ReleaseNotes {
|
if showReleaseNotes {
|
||||||
fmt.Println()
|
|
||||||
fmt.Print(releaseNotes)
|
fmt.Print(releaseNotes)
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
chaosVersion := config.CHAOS_DEFAULT
|
chaosVersion := config.CHAOS_DEFAULT
|
||||||
@ -289,7 +293,42 @@ EXAMPLE:
|
|||||||
if err := app.WriteRecipeVersion(app.Recipe.Version, false); err != nil {
|
if err := app.WriteRecipeVersion(app.Recipe.Version, false); 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
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
showReleaseNotes bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
AppUpgradeCommand.Flags().BoolVarP(
|
||||||
|
&internal.Force,
|
||||||
|
"force",
|
||||||
|
"f",
|
||||||
|
false,
|
||||||
|
"perform action without further prompt",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppUpgradeCommand.Flags().BoolVarP(
|
||||||
|
&internal.NoDomainChecks,
|
||||||
|
"no-domain-checks",
|
||||||
|
"D",
|
||||||
|
false,
|
||||||
|
"disable public DNS checks",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppUpgradeCommand.Flags().BoolVarP(
|
||||||
|
&internal.DontWaitConverge, "no-converge-checks",
|
||||||
|
"c",
|
||||||
|
false,
|
||||||
|
"do not wait for converge logic checks",
|
||||||
|
)
|
||||||
|
|
||||||
|
AppUpgradeCommand.Flags().BoolVarP(
|
||||||
|
&showReleaseNotes,
|
||||||
|
"releasenotes",
|
||||||
|
"r",
|
||||||
|
false,
|
||||||
|
"only show release notes",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -2,7 +2,6 @@ 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"
|
||||||
@ -11,22 +10,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"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appVolumeListCommand = cli.Command{
|
var AppVolumeListCommand = &cobra.Command{
|
||||||
Name: "list",
|
Use: "list <app> [flags]",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
ArgsUsage: "<domain>",
|
Short: "List volumes associated with an app",
|
||||||
Flags: []cli.Flag{
|
Args: cobra.ExactArgs(1),
|
||||||
internal.DebugFlag,
|
ValidArgsFunction: func(
|
||||||
internal.NoInputFlag,
|
cmd *cobra.Command,
|
||||||
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
Usage: "List volumes associated with an app",
|
app := internal.ValidateApp(args)
|
||||||
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 {
|
||||||
@ -43,7 +42,7 @@ var appVolumeListCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
headers := []string{"name", "created", "mounted"}
|
headers := []string{"NAME", "ON SERVER"}
|
||||||
|
|
||||||
table, err := formatter.CreateTable()
|
table, err := formatter.CreateTable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -54,47 +53,46 @@ var appVolumeListCommand = cli.Command{
|
|||||||
|
|
||||||
var rows [][]string
|
var rows [][]string
|
||||||
for _, volume := range volumes {
|
for _, volume := range volumes {
|
||||||
row := []string{volume.Name, volume.CreatedAt, volume.Mountpoint}
|
row := []string{volume.Name, volume.Mountpoint}
|
||||||
rows = append(rows, row)
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Rows(rows...)
|
table.Rows(rows...)
|
||||||
|
|
||||||
if len(rows) > 0 {
|
if len(rows) > 0 {
|
||||||
fmt.Println(table)
|
if err := formatter.PrintTable(table); err != nil {
|
||||||
return nil
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Warnf("no volumes created for %s", app.Name)
|
log.Warnf("no volumes created for %s", app.Name)
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var appVolumeRemoveCommand = cli.Command{
|
var AppVolumeRemoveCommand = &cobra.Command{
|
||||||
Name: "remove",
|
Use: "remove <app> [flags]",
|
||||||
Usage: "Remove volume(s) associated with an app",
|
Short: "Remove volume(s) associated with an app",
|
||||||
Description: `
|
Long: `Remove volumes associated with an app.
|
||||||
This command supports removing volumes associated with an app. The app in
|
|
||||||
question must be undeployed before you try to remove volumes. See "abra app
|
The app in question must be undeployed before you try to remove volumes. See
|
||||||
undeploy <domain>" for more.
|
"abra app undeploy <app>" for more.
|
||||||
|
|
||||||
The command is interactive and will show a multiple select input which allows
|
The command is interactive and will show a multiple select input which allows
|
||||||
you to make a seclection. Use the "?" key to see more help on navigating this
|
you to make a seclection. Use the "?" key to see more help on navigating this
|
||||||
interface.
|
interface.
|
||||||
|
|
||||||
Passing "--force/-f" will select all volumes for removal. Be careful.`,
|
Passing "--force/-f" will select all volumes for removal. Be careful.`,
|
||||||
ArgsUsage: "<domain>",
|
Aliases: []string{"rm"},
|
||||||
Aliases: []string{"rm"},
|
Args: cobra.MinimumNArgs(1),
|
||||||
Flags: []cli.Flag{
|
ValidArgsFunction: func(
|
||||||
internal.DebugFlag,
|
cmd *cobra.Command,
|
||||||
internal.NoInputFlag,
|
args []string,
|
||||||
internal.ForceFlag,
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.AppNameComplete()
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
BashComplete: autocomplete.AppNameComplete,
|
app := internal.ValidateApp(args)
|
||||||
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 {
|
||||||
@ -149,18 +147,21 @@ Passing "--force/-f" will select all volumes for removal. Be careful.`,
|
|||||||
} else {
|
} else {
|
||||||
log.Info("no volumes removed")
|
log.Info("no volumes removed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var appVolumeCommand = cli.Command{
|
var AppVolumeCommand = &cobra.Command{
|
||||||
Name: "volume",
|
Use: "volume [cmd] [args] [flags]",
|
||||||
Aliases: []string{"vl"},
|
Aliases: []string{"vl"},
|
||||||
Usage: "Manage app volumes",
|
Short: "Manage app volumes",
|
||||||
ArgsUsage: "<domain>",
|
}
|
||||||
Subcommands: []cli.Command{
|
|
||||||
appVolumeListCommand,
|
func init() {
|
||||||
appVolumeRemoveCommand,
|
AppVolumeRemoveCommand.Flags().BoolVarP(
|
||||||
},
|
&internal.Force,
|
||||||
|
"force",
|
||||||
|
"f",
|
||||||
|
false,
|
||||||
|
"perform action without further prompt",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -15,44 +15,42 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var catalogueGenerateCommand = cli.Command{
|
var CatalogueGenerateCommand = &cobra.Command{
|
||||||
Name: "generate",
|
Use: "generate [recipe] [flags]",
|
||||||
Aliases: []string{"g"},
|
Aliases: []string{"g"},
|
||||||
Usage: "Generate the recipe catalogue",
|
Short: "Generate the recipe catalogue",
|
||||||
Flags: []cli.Flag{
|
Long: `Generate a new copy of the recipe catalogue.
|
||||||
internal.DebugFlag,
|
|
||||||
internal.NoInputFlag,
|
|
||||||
internal.PublishFlag,
|
|
||||||
internal.DryFlag,
|
|
||||||
internal.SkipUpdatesFlag,
|
|
||||||
internal.ChaosFlag,
|
|
||||||
internal.OfflineFlag,
|
|
||||||
},
|
|
||||||
Before: internal.SubCommandBefore,
|
|
||||||
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.
|
||||||
|
|
||||||
It is quite easy to get rate limited by Docker Hub when running this command.
|
It is quite easy to get rate limited by Docker Hub when running this command.
|
||||||
If you have a Hub account you can have Abra log you in to avoid this. Pass
|
If you have a Hub account you can have Abra log you in to avoid this. Pass
|
||||||
"--user" and "--pass".
|
"--user" and "--pass".
|
||||||
|
|
||||||
Push your new release to git.coopcloud.tech with "-p/--publish". This requires
|
Push your new release to git.coopcloud.tech with "--publish/-p". This requires
|
||||||
that you have permission to git push to these repositories and have your SSH
|
that you have permission to git push to these repositories and have your SSH
|
||||||
keys configured on your account.`,
|
keys configured on your account.`,
|
||||||
ArgsUsage: "[<recipe>]",
|
Args: cobra.RangeArgs(0, 1),
|
||||||
BashComplete: autocomplete.RecipeNameComplete,
|
ValidArgsFunction: func(
|
||||||
Action: func(c *cli.Context) error {
|
cmd *cobra.Command,
|
||||||
recipeName := c.Args().First()
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.RecipeNameComplete()
|
||||||
|
},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
var recipeName string
|
||||||
|
if len(args) > 0 {
|
||||||
|
recipeName = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
r := recipe.Get(recipeName)
|
r := recipe.Get(recipeName)
|
||||||
|
|
||||||
if recipeName != "" {
|
if recipeName != "" {
|
||||||
internal.ValidateRecipe(c)
|
internal.ValidateRecipe(args, cmd.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !internal.Chaos {
|
if !internal.Chaos {
|
||||||
@ -76,7 +74,7 @@ keys configured on your account.`,
|
|||||||
logMsg = fmt.Sprintf("ensuring %v recipes are cloned & up-to-date, this could take some time...", barLength)
|
logMsg = fmt.Sprintf("ensuring %v recipes are cloned & up-to-date, this could take some time...", barLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !internal.SkipUpdates {
|
if !skipUpdates {
|
||||||
log.Warn(logMsg)
|
log.Warn(logMsg)
|
||||||
if err := recipe.UpdateRepositories(repos, recipeName); err != nil {
|
if err := recipe.UpdateRepositories(repos, recipeName); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -147,7 +145,7 @@ keys configured on your account.`,
|
|||||||
log.Infof("generated new recipe catalogue in %s", config.RECIPES_JSON)
|
log.Infof("generated new recipe catalogue in %s", config.RECIPES_JSON)
|
||||||
|
|
||||||
cataloguePath := path.Join(config.ABRA_DIR, "catalogue")
|
cataloguePath := path.Join(config.ABRA_DIR, "catalogue")
|
||||||
if internal.Publish {
|
if publishChanges {
|
||||||
|
|
||||||
isClean, err := gitPkg.IsClean(cataloguePath)
|
isClean, err := gitPkg.IsClean(cataloguePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -190,7 +188,7 @@ keys configured on your account.`,
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !internal.Dry && internal.Publish {
|
if !internal.Dry && publishChanges {
|
||||||
url := fmt.Sprintf("%s/%s/commit/%s", config.REPOS_BASE_URL, config.CATALOGUE_JSON_REPO_NAME, head.Hash())
|
url := fmt.Sprintf("%s/%s/commit/%s", config.REPOS_BASE_URL, config.CATALOGUE_JSON_REPO_NAME, head.Hash())
|
||||||
log.Infof("new changes published: %s", url)
|
log.Infof("new changes published: %s", url)
|
||||||
}
|
}
|
||||||
@ -198,18 +196,51 @@ keys configured on your account.`,
|
|||||||
if internal.Dry {
|
if internal.Dry {
|
||||||
log.Info("dry run: no changes published")
|
log.Info("dry run: no changes published")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// CatalogueCommand defines the `abra catalogue` command and sub-commands.
|
// CatalogueCommand defines the `abra catalogue` command and sub-commands.
|
||||||
var CatalogueCommand = cli.Command{
|
var CatalogueCommand = &cobra.Command{
|
||||||
Name: "catalogue",
|
Use: "catalogue [cmd] [args] [flags]",
|
||||||
Usage: "Manage the recipe catalogue",
|
Short: "Manage the recipe catalogue",
|
||||||
Aliases: []string{"c"},
|
Aliases: []string{"c"},
|
||||||
ArgsUsage: "<recipe>",
|
}
|
||||||
Subcommands: []cli.Command{
|
|
||||||
catalogueGenerateCommand,
|
var (
|
||||||
},
|
publishChanges bool
|
||||||
|
skipUpdates bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
CatalogueGenerateCommand.Flags().BoolVarP(
|
||||||
|
&publishChanges,
|
||||||
|
"publish",
|
||||||
|
"p",
|
||||||
|
false,
|
||||||
|
"publish changes to git.coopcloud.tech",
|
||||||
|
)
|
||||||
|
|
||||||
|
CatalogueGenerateCommand.Flags().BoolVarP(
|
||||||
|
&internal.Dry,
|
||||||
|
"dry-run",
|
||||||
|
"r",
|
||||||
|
false,
|
||||||
|
"report changes that would be made",
|
||||||
|
)
|
||||||
|
|
||||||
|
CatalogueGenerateCommand.Flags().BoolVarP(
|
||||||
|
&skipUpdates,
|
||||||
|
"skip-updates",
|
||||||
|
"s",
|
||||||
|
false,
|
||||||
|
"skip updating recipe repositories",
|
||||||
|
)
|
||||||
|
|
||||||
|
CatalogueGenerateCommand.Flags().BoolVarP(
|
||||||
|
&internal.Chaos,
|
||||||
|
"chaos",
|
||||||
|
"C",
|
||||||
|
false,
|
||||||
|
"ignore uncommitted recipes changes",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
211
cli/cli.go
211
cli/cli.go
@ -1,211 +0,0 @@
|
|||||||
// Package cli provides the interface for the command-line.
|
|
||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/app"
|
|
||||||
"coopcloud.tech/abra/cli/catalogue"
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
|
||||||
"coopcloud.tech/abra/cli/recipe"
|
|
||||||
"coopcloud.tech/abra/cli/server"
|
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
|
||||||
"coopcloud.tech/abra/pkg/config"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
|
||||||
"coopcloud.tech/abra/pkg/web"
|
|
||||||
charmLog "github.com/charmbracelet/log"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AutoCompleteCommand helps people set up auto-complete in their shells
|
|
||||||
var AutoCompleteCommand = cli.Command{
|
|
||||||
Name: "autocomplete",
|
|
||||||
Aliases: []string{"ac"},
|
|
||||||
Usage: "Configure shell autocompletion",
|
|
||||||
Description: `
|
|
||||||
Set up shell auto-completion.
|
|
||||||
|
|
||||||
Supported shells are: bash, fish, fizsh & zsh.
|
|
||||||
|
|
||||||
EXAMPLE:
|
|
||||||
|
|
||||||
abra autocomplete bash`,
|
|
||||||
ArgsUsage: "<shell>",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
internal.DebugFlag,
|
|
||||||
},
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
shellType := c.Args().First()
|
|
||||||
|
|
||||||
if shellType == "" {
|
|
||||||
internal.ShowSubcommandHelpAndError(c, errors.New("no shell provided"))
|
|
||||||
}
|
|
||||||
|
|
||||||
supportedShells := map[string]bool{
|
|
||||||
"bash": true,
|
|
||||||
"zsh": true,
|
|
||||||
"fizsh": true,
|
|
||||||
"fish": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := supportedShells[shellType]; !ok {
|
|
||||||
log.Fatalf("%s is not a supported shell right now, sorry", shellType)
|
|
||||||
}
|
|
||||||
|
|
||||||
if shellType == "fizsh" {
|
|
||||||
shellType = "zsh" // handled the same on the autocompletion side
|
|
||||||
}
|
|
||||||
|
|
||||||
autocompletionDir := path.Join(config.ABRA_DIR, "autocompletion")
|
|
||||||
if err := os.Mkdir(autocompletionDir, 0764); err != nil {
|
|
||||||
if !os.IsExist(err) {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
log.Debugf("%s already created", autocompletionDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
autocompletionFile := path.Join(config.ABRA_DIR, "autocompletion", shellType)
|
|
||||||
if _, err := os.Stat(autocompletionFile); err != nil && os.IsNotExist(err) {
|
|
||||||
url := fmt.Sprintf("https://git.coopcloud.tech/coop-cloud/abra/raw/branch/main/scripts/autocomplete/%s", shellType)
|
|
||||||
log.Infof("fetching %s", url)
|
|
||||||
if err := web.GetFile(autocompletionFile, url); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch shellType {
|
|
||||||
case "bash":
|
|
||||||
fmt.Println(fmt.Sprintf(`
|
|
||||||
# run the following commands once to install auto-completion
|
|
||||||
sudo mkdir -p /etc/bash_completion.d/
|
|
||||||
sudo cp %s /etc/bash_completion.d/abra
|
|
||||||
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!
|
|
||||||
`, autocompletionFile))
|
|
||||||
case "zsh":
|
|
||||||
fmt.Println(fmt.Sprintf(`
|
|
||||||
# run the following commands to once install auto-completion
|
|
||||||
sudo mkdir -p /etc/zsh/completion.d/
|
|
||||||
sudo cp %s /etc/zsh/completion.d/abra
|
|
||||||
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!
|
|
||||||
`, autocompletionFile))
|
|
||||||
case "fish":
|
|
||||||
fmt.Println(fmt.Sprintf(`
|
|
||||||
# run the following commands once to install auto-completion
|
|
||||||
sudo mkdir -p /etc/fish/completions
|
|
||||||
sudo cp %s /etc/fish/completions/abra
|
|
||||||
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!
|
|
||||||
`, autocompletionFile))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpgradeCommand upgrades abra in-place.
|
|
||||||
var UpgradeCommand = cli.Command{
|
|
||||||
Name: "upgrade",
|
|
||||||
Aliases: []string{"u"},
|
|
||||||
Usage: "Upgrade abra",
|
|
||||||
Description: `
|
|
||||||
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
|
|
||||||
it may contain absolutely catastrophic deal-breaker bugs. Thank you very much
|
|
||||||
for the testing efforts 💗
|
|
||||||
|
|
||||||
EXAMPLE:
|
|
||||||
|
|
||||||
abra upgrade
|
|
||||||
abra upgrade --rc`,
|
|
||||||
Flags: []cli.Flag{internal.RCFlag},
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
mainURL := "https://install.abra.coopcloud.tech"
|
|
||||||
cmd := exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash", mainURL))
|
|
||||||
|
|
||||||
if internal.RC {
|
|
||||||
releaseCandidateURL := "https://git.coopcloud.tech/coop-cloud/abra/raw/branch/main/scripts/installer/installer"
|
|
||||||
cmd = exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash -s -- --rc", releaseCandidateURL))
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("attempting to run %s", cmd)
|
|
||||||
|
|
||||||
if err := internal.RunCmd(cmd); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAbraApp(version, commit string) *cli.App {
|
|
||||||
app := &cli.App{
|
|
||||||
Name: "abra",
|
|
||||||
Usage: `the Co-op Cloud command-line utility belt 🎩🐇
|
|
||||||
____ ____ _ _
|
|
||||||
/ ___|___ ___ _ __ / ___| | ___ _ _ __| |
|
|
||||||
| | / _ \ _____ / _ \| '_ \ | | | |/ _ \| | | |/ _' |
|
|
||||||
| |__| (_) |_____| (_) | |_) | | |___| | (_) | |_| | (_| |
|
|
||||||
\____\___/ \___/| .__/ \____|_|\___/ \__,_|\__,_|
|
|
||||||
|_|
|
|
||||||
`,
|
|
||||||
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
|
|
||||||
Commands: []cli.Command{
|
|
||||||
app.AppCommand,
|
|
||||||
server.ServerCommand,
|
|
||||||
recipe.RecipeCommand,
|
|
||||||
catalogue.CatalogueCommand,
|
|
||||||
UpgradeCommand,
|
|
||||||
AutoCompleteCommand,
|
|
||||||
},
|
|
||||||
BashComplete: autocomplete.SubcommandComplete,
|
|
||||||
}
|
|
||||||
|
|
||||||
app.EnableBashCompletion = true
|
|
||||||
|
|
||||||
app.Before = func(c *cli.Context) error {
|
|
||||||
paths := []string{
|
|
||||||
config.ABRA_DIR,
|
|
||||||
config.SERVERS_DIR,
|
|
||||||
config.RECIPES_DIR,
|
|
||||||
config.VENDOR_DIR,
|
|
||||||
config.BACKUP_DIR,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, path := range paths {
|
|
||||||
if err := os.Mkdir(path, 0764); err != nil {
|
|
||||||
if !os.IsExist(err) {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Logger.SetStyles(log.Styles())
|
|
||||||
charmLog.SetDefault(log.Logger)
|
|
||||||
|
|
||||||
log.Debugf("abra version %s, commit %s", version, commit)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunApp runs CLI abra app.
|
|
||||||
func RunApp(version, commit string) {
|
|
||||||
app := newAbraApp(version, commit)
|
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
65
cli/complete.go
Normal file
65
cli/complete.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var AutocompleteCommand = &cobra.Command{
|
||||||
|
Use: "autocomplete [bash|zsh|fish|powershell]",
|
||||||
|
Short: "Generate autocompletion script",
|
||||||
|
Long: `To load completions:
|
||||||
|
|
||||||
|
Bash:
|
||||||
|
|
||||||
|
$ source <(abra autocomplete bash)
|
||||||
|
|
||||||
|
# To load autocompletion for each session, execute once:
|
||||||
|
# Linux:
|
||||||
|
$ abra autocomplete bash > /etc/bash_completion.d/abra
|
||||||
|
# macOS:
|
||||||
|
$ abra autocomplete bash > $(brew --prefix)/etc/bash_completion.d/abra
|
||||||
|
|
||||||
|
Zsh:
|
||||||
|
|
||||||
|
# If shell autocompletion is not already enabled in your environment,
|
||||||
|
# you will need to enable it. You can execute the following once:
|
||||||
|
|
||||||
|
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
|
||||||
|
|
||||||
|
# To load autocompletions for each session, execute once:
|
||||||
|
$ abra autocomplete zsh > "${fpath[1]}/_abra"
|
||||||
|
|
||||||
|
# You will need to start a new shell for this setup to take effect.
|
||||||
|
|
||||||
|
fish:
|
||||||
|
|
||||||
|
$ abra autocomplete fish | source
|
||||||
|
|
||||||
|
# To load autocompletions for each session, execute once:
|
||||||
|
$ abra autocomplete fish > ~/.config/fish/completions/abra.fish
|
||||||
|
|
||||||
|
PowerShell:
|
||||||
|
|
||||||
|
PS> abra autocomplete powershell | Out-String | Invoke-Expression
|
||||||
|
|
||||||
|
# To load autocompletions for every new session, run:
|
||||||
|
PS> abra autocomplete powershell > abra.ps1
|
||||||
|
# and source this file from your PowerShell profile.`,
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
|
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||||
|
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
switch args[0] {
|
||||||
|
case "bash":
|
||||||
|
cmd.Root().GenBashCompletion(os.Stdout)
|
||||||
|
case "zsh":
|
||||||
|
cmd.Root().GenZshCompletion(os.Stdout)
|
||||||
|
case "fish":
|
||||||
|
cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||||
|
case "powershell":
|
||||||
|
cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
@ -2,6 +2,8 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
containerPkg "coopcloud.tech/abra/pkg/container"
|
containerPkg "coopcloud.tech/abra/pkg/container"
|
||||||
@ -19,7 +21,7 @@ func RetrieveBackupBotContainer(cl *dockerClient.Client) (types.Container, error
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
chosenService, err := service.GetServiceByLabel(ctx, cl, config.BackupbotLabel, NoInput)
|
chosenService, err := service.GetServiceByLabel(ctx, cl, config.BackupbotLabel, NoInput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.Container{}, err
|
return types.Container{}, fmt.Errorf("no backupbot discovered, is it deployed?")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("retrieved %s as backup enabled service", chosenService.Spec.Name)
|
log.Debugf("retrieved %s as backup enabled service", chosenService.Spec.Name)
|
||||||
@ -40,7 +42,11 @@ func RetrieveBackupBotContainer(cl *dockerClient.Client) (types.Container, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunBackupCmdRemote runs a backup related command on a remote backupbot container.
|
// RunBackupCmdRemote runs a backup related command on a remote backupbot container.
|
||||||
func RunBackupCmdRemote(cl *dockerClient.Client, backupCmd string, containerID string, execEnv []string) error {
|
func RunBackupCmdRemote(
|
||||||
|
cl *dockerClient.Client,
|
||||||
|
backupCmd string,
|
||||||
|
containerID string,
|
||||||
|
execEnv []string) (io.Writer, error) {
|
||||||
execBackupListOpts := types.ExecConfig{
|
execBackupListOpts := types.ExecConfig{
|
||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
AttachStdin: true,
|
AttachStdin: true,
|
||||||
@ -56,12 +62,13 @@ func RunBackupCmdRemote(cl *dockerClient.Client, backupCmd string, containerID s
|
|||||||
// FIXME: avoid instantiating a new CLI
|
// FIXME: avoid instantiating a new CLI
|
||||||
dcli, err := command.NewDockerCli()
|
dcli, err := command.NewDockerCli()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := container.RunExec(dcli, cl, containerID, &execBackupListOpts); err != nil {
|
out, err := container.RunExec(dcli, cl, containerID, &execBackupListOpts)
|
||||||
return err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
@ -1,296 +1,19 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
var (
|
||||||
"os"
|
// NOTE(d1): global
|
||||||
|
Debug bool
|
||||||
|
NoInput bool
|
||||||
|
Offline bool
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
// NOTE(d1): sub-command specific
|
||||||
"github.com/urfave/cli"
|
Chaos bool
|
||||||
|
DontWaitConverge bool
|
||||||
|
Dry bool
|
||||||
|
Force bool
|
||||||
|
MachineReadable bool
|
||||||
|
Major bool
|
||||||
|
Minor bool
|
||||||
|
NoDomainChecks bool
|
||||||
|
Patch bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// Secrets stores the variable from SecretsFlag
|
|
||||||
var Secrets bool
|
|
||||||
|
|
||||||
// SecretsFlag turns on/off automatically generating secrets
|
|
||||||
var SecretsFlag = &cli.BoolFlag{
|
|
||||||
Name: "secrets, S",
|
|
||||||
Usage: "Automatically generate secrets",
|
|
||||||
Destination: &Secrets,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass stores the variable from PassFlag
|
|
||||||
var Pass bool
|
|
||||||
|
|
||||||
// PassFlag turns on/off storing generated secrets in pass
|
|
||||||
var PassFlag = &cli.BoolFlag{
|
|
||||||
Name: "pass, p",
|
|
||||||
Usage: "Store the generated secrets in a local pass store",
|
|
||||||
Destination: &Pass,
|
|
||||||
}
|
|
||||||
|
|
||||||
// PassRemove stores the variable for PassRemoveFlag
|
|
||||||
var PassRemove bool
|
|
||||||
|
|
||||||
// PassRemoveFlag turns on/off removing generated secrets from pass
|
|
||||||
var PassRemoveFlag = &cli.BoolFlag{
|
|
||||||
Name: "pass, p",
|
|
||||||
Usage: "Remove generated secrets from a local pass store",
|
|
||||||
Destination: &PassRemove,
|
|
||||||
}
|
|
||||||
|
|
||||||
var File bool
|
|
||||||
var FileFlag = &cli.BoolFlag{
|
|
||||||
Name: "file, f",
|
|
||||||
Usage: "Treat input as a file",
|
|
||||||
Destination: &File,
|
|
||||||
}
|
|
||||||
|
|
||||||
var Trim bool
|
|
||||||
var TrimFlag = &cli.BoolFlag{
|
|
||||||
Name: "trim, t",
|
|
||||||
Usage: "Trim input",
|
|
||||||
Destination: &Trim,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force force functionality without asking.
|
|
||||||
var Force bool
|
|
||||||
|
|
||||||
// ForceFlag turns on/off force functionality.
|
|
||||||
var ForceFlag = &cli.BoolFlag{
|
|
||||||
Name: "force, f",
|
|
||||||
Usage: "Perform action without further prompt. Use with care!",
|
|
||||||
Destination: &Force,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chaos engages chaos mode.
|
|
||||||
var Chaos bool
|
|
||||||
|
|
||||||
// ChaosFlag turns on/off chaos functionality.
|
|
||||||
var ChaosFlag = &cli.BoolFlag{
|
|
||||||
Name: "chaos, C",
|
|
||||||
Usage: "Proceed with uncommitted recipes changes. Use with care!",
|
|
||||||
Destination: &Chaos,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable tty to run commands from script
|
|
||||||
var Tty bool
|
|
||||||
|
|
||||||
// TtyFlag turns on/off tty mode.
|
|
||||||
var TtyFlag = &cli.BoolFlag{
|
|
||||||
Name: "tty, T",
|
|
||||||
Usage: "Disables TTY mode to run this command from a script.",
|
|
||||||
Destination: &Tty,
|
|
||||||
}
|
|
||||||
|
|
||||||
var NoInput bool
|
|
||||||
var NoInputFlag = &cli.BoolFlag{
|
|
||||||
Name: "no-input, n",
|
|
||||||
Usage: "Toggle non-interactive mode",
|
|
||||||
Destination: &NoInput,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug stores the variable from DebugFlag.
|
|
||||||
var Debug bool
|
|
||||||
|
|
||||||
// DebugFlag turns on/off verbose logging down to the DEBUG level.
|
|
||||||
var DebugFlag = &cli.BoolFlag{
|
|
||||||
Name: "debug, d",
|
|
||||||
Destination: &Debug,
|
|
||||||
Usage: "Show DEBUG messages",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Offline stores the variable from OfflineFlag.
|
|
||||||
var Offline bool
|
|
||||||
|
|
||||||
// DebugFlag turns on/off offline mode.
|
|
||||||
var OfflineFlag = &cli.BoolFlag{
|
|
||||||
Name: "offline, o",
|
|
||||||
Destination: &Offline,
|
|
||||||
Usage: "Prefer offline & filesystem access when possible",
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReleaseNotes stores the variable from ReleaseNotesFlag.
|
|
||||||
var ReleaseNotes bool
|
|
||||||
|
|
||||||
// ReleaseNotesFlag turns on/off printing only release notes when upgrading.
|
|
||||||
var ReleaseNotesFlag = &cli.BoolFlag{
|
|
||||||
Name: "releasenotes, r",
|
|
||||||
Destination: &ReleaseNotes,
|
|
||||||
Usage: "Only show release notes",
|
|
||||||
}
|
|
||||||
|
|
||||||
// MachineReadable stores the variable from MachineReadableFlag
|
|
||||||
var MachineReadable bool
|
|
||||||
|
|
||||||
// MachineReadableFlag turns on/off machine readable output where supported
|
|
||||||
var MachineReadableFlag = &cli.BoolFlag{
|
|
||||||
Name: "machine, m",
|
|
||||||
Destination: &MachineReadable,
|
|
||||||
Usage: "Output in a machine-readable format (where supported)",
|
|
||||||
}
|
|
||||||
|
|
||||||
// RC signifies the latest release candidate
|
|
||||||
var RC bool
|
|
||||||
|
|
||||||
// RCFlag chooses the latest release candidate for install
|
|
||||||
var RCFlag = &cli.BoolFlag{
|
|
||||||
Name: "rc, r",
|
|
||||||
Destination: &RC,
|
|
||||||
Usage: "Install the latest release candidate",
|
|
||||||
}
|
|
||||||
|
|
||||||
var Major bool
|
|
||||||
var MajorFlag = &cli.BoolFlag{
|
|
||||||
Name: "major, x",
|
|
||||||
Usage: "Increase the major part of the version",
|
|
||||||
Destination: &Major,
|
|
||||||
}
|
|
||||||
|
|
||||||
var Minor bool
|
|
||||||
var MinorFlag = &cli.BoolFlag{
|
|
||||||
Name: "minor, y",
|
|
||||||
Usage: "Increase the minor part of the version",
|
|
||||||
Destination: &Minor,
|
|
||||||
}
|
|
||||||
|
|
||||||
var Patch bool
|
|
||||||
var PatchFlag = &cli.BoolFlag{
|
|
||||||
Name: "patch, z",
|
|
||||||
Usage: "Increase the patch part of the version",
|
|
||||||
Destination: &Patch,
|
|
||||||
}
|
|
||||||
|
|
||||||
var Dry bool
|
|
||||||
var DryFlag = &cli.BoolFlag{
|
|
||||||
Name: "dry-run, r",
|
|
||||||
Usage: "Only reports changes that would be made",
|
|
||||||
Destination: &Dry,
|
|
||||||
}
|
|
||||||
|
|
||||||
var Publish bool
|
|
||||||
var PublishFlag = &cli.BoolFlag{
|
|
||||||
Name: "publish, p",
|
|
||||||
Usage: "Publish changes to git.coopcloud.tech",
|
|
||||||
Destination: &Publish,
|
|
||||||
}
|
|
||||||
|
|
||||||
var Domain string
|
|
||||||
var DomainFlag = &cli.StringFlag{
|
|
||||||
Name: "domain, D",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Choose a domain name",
|
|
||||||
Destination: &Domain,
|
|
||||||
}
|
|
||||||
|
|
||||||
var NewAppServer string
|
|
||||||
var NewAppServerFlag = &cli.StringFlag{
|
|
||||||
Name: "server, s",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Show apps of a specific server",
|
|
||||||
Destination: &NewAppServer,
|
|
||||||
}
|
|
||||||
|
|
||||||
var NoDomainChecks bool
|
|
||||||
var NoDomainChecksFlag = &cli.BoolFlag{
|
|
||||||
Name: "no-domain-checks, D",
|
|
||||||
Usage: "Disable public DNS checks",
|
|
||||||
Destination: &NoDomainChecks,
|
|
||||||
}
|
|
||||||
|
|
||||||
var StdErrOnly bool
|
|
||||||
var StdErrOnlyFlag = &cli.BoolFlag{
|
|
||||||
Name: "stderr, s",
|
|
||||||
Usage: "Only tail stderr",
|
|
||||||
Destination: &StdErrOnly,
|
|
||||||
}
|
|
||||||
|
|
||||||
var SinceLogs string
|
|
||||||
var SinceLogsFlag = &cli.StringFlag{
|
|
||||||
Name: "since, S",
|
|
||||||
Value: "",
|
|
||||||
Usage: "tail logs since YYYY-MM-DDTHH:MM:SSZ",
|
|
||||||
Destination: &SinceLogs,
|
|
||||||
}
|
|
||||||
|
|
||||||
var DontWaitConverge bool
|
|
||||||
var DontWaitConvergeFlag = &cli.BoolFlag{
|
|
||||||
Name: "no-converge-checks, c",
|
|
||||||
Usage: "Don't wait for converge logic checks",
|
|
||||||
Destination: &DontWaitConverge,
|
|
||||||
}
|
|
||||||
|
|
||||||
var Watch bool
|
|
||||||
var WatchFlag = &cli.BoolFlag{
|
|
||||||
Name: "watch, w",
|
|
||||||
Usage: "Watch status by polling repeatedly",
|
|
||||||
Destination: &Watch,
|
|
||||||
}
|
|
||||||
|
|
||||||
var OnlyErrors bool
|
|
||||||
var OnlyErrorFlag = &cli.BoolFlag{
|
|
||||||
Name: "errors, e",
|
|
||||||
Usage: "Only show errors",
|
|
||||||
Destination: &OnlyErrors,
|
|
||||||
}
|
|
||||||
|
|
||||||
var SkipUpdates bool
|
|
||||||
var SkipUpdatesFlag = &cli.BoolFlag{
|
|
||||||
Name: "skip-updates, s",
|
|
||||||
Usage: "Skip updating recipe repositories",
|
|
||||||
Destination: &SkipUpdates,
|
|
||||||
}
|
|
||||||
|
|
||||||
var AllTags bool
|
|
||||||
var AllTagsFlag = &cli.BoolFlag{
|
|
||||||
Name: "all-tags, a",
|
|
||||||
Usage: "List all tags, not just upgrades",
|
|
||||||
Destination: &AllTags,
|
|
||||||
}
|
|
||||||
|
|
||||||
var LocalCmd bool
|
|
||||||
var LocalCmdFlag = &cli.BoolFlag{
|
|
||||||
Name: "local, l",
|
|
||||||
Usage: "Run command locally",
|
|
||||||
Destination: &LocalCmd,
|
|
||||||
}
|
|
||||||
|
|
||||||
var RemoteUser string
|
|
||||||
var RemoteUserFlag = &cli.StringFlag{
|
|
||||||
Name: "user, u",
|
|
||||||
Value: "",
|
|
||||||
Usage: "User to run command within a service context",
|
|
||||||
Destination: &RemoteUser,
|
|
||||||
}
|
|
||||||
|
|
||||||
var GitName string
|
|
||||||
var GitNameFlag = &cli.StringFlag{
|
|
||||||
Name: "git-name, gn",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Git (user) name to do commits with",
|
|
||||||
Destination: &GitName,
|
|
||||||
}
|
|
||||||
|
|
||||||
var GitEmail string
|
|
||||||
var GitEmailFlag = &cli.StringFlag{
|
|
||||||
Name: "git-email, ge",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Git email name to do commits with",
|
|
||||||
Destination: &GitEmail,
|
|
||||||
}
|
|
||||||
|
|
||||||
var AllServices bool
|
|
||||||
var AllServicesFlag = &cli.BoolFlag{
|
|
||||||
Name: "all-services, a",
|
|
||||||
Usage: "Restart all services",
|
|
||||||
Destination: &AllServices,
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubCommandBefore wires up pre-action machinery (e.g. --debug handling).
|
|
||||||
func SubCommandBefore(c *cli.Context) error {
|
|
||||||
if Debug {
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
log.SetOutput(os.Stderr)
|
|
||||||
log.SetReportCaller(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -21,7 +21,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// RunCmdRemote executes an abra.sh command in the target service
|
// RunCmdRemote executes an abra.sh command in the target service
|
||||||
func RunCmdRemote(cl *dockerClient.Client, app appPkg.App, abraSh, serviceName, cmdName, cmdArgs string) error {
|
func RunCmdRemote(
|
||||||
|
cl *dockerClient.Client,
|
||||||
|
app appPkg.App,
|
||||||
|
requestTTY bool,
|
||||||
|
abraSh, serviceName, cmdName, cmdArgs, remoteUser string) error {
|
||||||
filters := filters.NewArgs()
|
filters := filters.NewArgs()
|
||||||
filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), serviceName))
|
filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), serviceName))
|
||||||
|
|
||||||
@ -74,15 +78,15 @@ func RunCmdRemote(cl *dockerClient.Client, app appPkg.App, abraSh, serviceName,
|
|||||||
|
|
||||||
log.Debugf("running command: %s", strings.Join(cmd, " "))
|
log.Debugf("running command: %s", strings.Join(cmd, " "))
|
||||||
|
|
||||||
if RemoteUser != "" {
|
if remoteUser != "" {
|
||||||
log.Debugf("running command with user %s", RemoteUser)
|
log.Debugf("running command with user %s", remoteUser)
|
||||||
execCreateOpts.User = RemoteUser
|
execCreateOpts.User = remoteUser
|
||||||
}
|
}
|
||||||
|
|
||||||
execCreateOpts.Cmd = cmd
|
execCreateOpts.Cmd = cmd
|
||||||
execCreateOpts.Tty = true
|
execCreateOpts.Tty = requestTTY
|
||||||
if Tty {
|
if !requestTTY {
|
||||||
execCreateOpts.Tty = false
|
log.Debugf("not requesting a remote TTY")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil {
|
if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil {
|
||||||
|
@ -3,10 +3,14 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
|
"coopcloud.tech/abra/pkg/config"
|
||||||
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
|
"coopcloud.tech/tagcmp"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
@ -20,7 +24,8 @@ var borderStyle = lipgloss.NewStyle().
|
|||||||
|
|
||||||
var headerStyle = lipgloss.NewStyle().
|
var headerStyle = lipgloss.NewStyle().
|
||||||
Underline(true).
|
Underline(true).
|
||||||
Bold(true)
|
Bold(true).
|
||||||
|
PaddingBottom(1)
|
||||||
|
|
||||||
var leftStyle = lipgloss.NewStyle().
|
var leftStyle = lipgloss.NewStyle().
|
||||||
Bold(true)
|
Bold(true)
|
||||||
@ -37,13 +42,13 @@ func NewVersionOverview(
|
|||||||
app appPkg.App,
|
app appPkg.App,
|
||||||
warnMessages []string,
|
warnMessages []string,
|
||||||
kind,
|
kind,
|
||||||
currentVersion,
|
deployedVersion,
|
||||||
chaosVersion,
|
deployedChaosVersion,
|
||||||
newVersion,
|
toDeployVersion,
|
||||||
releaseNotes string) error {
|
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 = composeFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
server := app.Server
|
server := app.Server
|
||||||
@ -51,32 +56,35 @@ func NewVersionOverview(
|
|||||||
server = "local"
|
server = "local"
|
||||||
}
|
}
|
||||||
|
|
||||||
body := strings.Builder{}
|
domain := app.Domain
|
||||||
body.WriteString(
|
if domain == "" {
|
||||||
borderStyle.Render(
|
domain = config.NO_DOMAIN_DEFAULT
|
||||||
lipgloss.JoinVertical(
|
}
|
||||||
lipgloss.Center,
|
|
||||||
headerStyle.Render(fmt.Sprintf("%s OVERVIEW", strings.ToUpper(kind))),
|
|
||||||
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(currentVersion)),
|
|
||||||
horizontal(leftStyle.Render("CHAOS"), " ", rightStyle.Render(chaosVersion)),
|
|
||||||
horizontal(leftStyle.Render("DEPLOY"), " ", rightStyle.Padding(0).Render(newVersion)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
fmt.Println(body.String())
|
|
||||||
|
|
||||||
if releaseNotes != "" && newVersion != "" {
|
rows := [][]string{
|
||||||
fmt.Println()
|
[]string{"APP", domain},
|
||||||
|
[]string{"RECIPE", app.Recipe.Name},
|
||||||
|
[]string{"SERVER", server},
|
||||||
|
[]string{"DEPLOYED", deployedVersion},
|
||||||
|
[]string{"CURRENT CHAOS ", deployedChaosVersion},
|
||||||
|
[]string{fmt.Sprintf("TO %s", strings.ToUpper(kind)), toDeployVersion},
|
||||||
|
[]string{"CONFIG", deployConfig},
|
||||||
|
}
|
||||||
|
|
||||||
|
overview := formatter.CreateOverview(
|
||||||
|
fmt.Sprintf("%s OVERVIEW", strings.ToUpper(kind)),
|
||||||
|
rows,
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt.Println(overview)
|
||||||
|
|
||||||
|
if releaseNotes != "" && toDeployVersion != "" {
|
||||||
fmt.Print(releaseNotes)
|
fmt.Print(releaseNotes)
|
||||||
} else {
|
} else {
|
||||||
warnMessages = append(warnMessages, fmt.Sprintf("no release notes available for %s", newVersion))
|
warnMessages = append(
|
||||||
|
warnMessages,
|
||||||
|
fmt.Sprintf("no release notes available for %s", toDeployVersion),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, msg := range warnMessages {
|
for _, msg := range warnMessages {
|
||||||
@ -101,10 +109,16 @@ func NewVersionOverview(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeployOverview shows a deployment overview
|
// DeployOverview shows a deployment overview
|
||||||
func DeployOverview(app appPkg.App, warnMessages []string, version, chaosVersion string) error {
|
func DeployOverview(
|
||||||
|
app appPkg.App,
|
||||||
|
warnMessages []string,
|
||||||
|
deployedVersion string,
|
||||||
|
deployedChaosVersion string,
|
||||||
|
toDeployVersion,
|
||||||
|
toDeployChaosVersion 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 = composeFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
server := app.Server
|
server := app.Server
|
||||||
@ -112,25 +126,25 @@ func DeployOverview(app appPkg.App, warnMessages []string, version, chaosVersion
|
|||||||
server = "local"
|
server = "local"
|
||||||
}
|
}
|
||||||
|
|
||||||
body := strings.Builder{}
|
domain := app.Domain
|
||||||
body.WriteString(
|
if domain == "" {
|
||||||
borderStyle.Render(
|
domain = config.NO_DOMAIN_DEFAULT
|
||||||
lipgloss.JoinVertical(
|
}
|
||||||
lipgloss.Center,
|
|
||||||
headerStyle.Render("DEPLOY OVERVIEW"),
|
rows := [][]string{
|
||||||
lipgloss.JoinVertical(
|
[]string{"APP", domain},
|
||||||
lipgloss.Left,
|
[]string{"RECIPE", app.Recipe.Name},
|
||||||
horizontal(leftStyle.Render("SERVER"), " ", rightStyle.Render(server)),
|
[]string{"SERVER", server},
|
||||||
horizontal(leftStyle.Render("DOMAIN"), " ", rightStyle.Render(app.Domain)),
|
[]string{"DEPLOYED", deployedVersion},
|
||||||
horizontal(leftStyle.Render("RECIPE"), " ", rightStyle.Render(app.Recipe.Name)),
|
[]string{"CURRENT CHAOS ", deployedChaosVersion},
|
||||||
horizontal(leftStyle.Render("CONFIG"), " ", rightStyle.Render(deployConfig)),
|
[]string{"TO DEPLOY", toDeployVersion},
|
||||||
horizontal(leftStyle.Render("VERSION"), " ", rightStyle.Render(version)),
|
[]string{"NEW CHAOS", toDeployChaosVersion},
|
||||||
horizontal(leftStyle.Render("CHAOS"), " ", rightStyle.Padding(0).Render(chaosVersion)),
|
[]string{"CONFIG", deployConfig},
|
||||||
),
|
}
|
||||||
),
|
|
||||||
),
|
overview := formatter.CreateOverview("DEPLOY OVERVIEW", rows)
|
||||||
)
|
|
||||||
fmt.Println(body.String())
|
fmt.Println(overview)
|
||||||
|
|
||||||
for _, msg := range warnMessages {
|
for _, msg := range warnMessages {
|
||||||
log.Warn(msg)
|
log.Warn(msg)
|
||||||
@ -153,6 +167,56 @@ func DeployOverview(app appPkg.App, warnMessages []string, version, chaosVersion
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UndeployOverview shows an undeployment overview
|
||||||
|
func UndeployOverview(
|
||||||
|
app appPkg.App,
|
||||||
|
version,
|
||||||
|
chaosVersion string) error {
|
||||||
|
deployConfig := "compose.yml"
|
||||||
|
if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
|
||||||
|
deployConfig = composeFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
server := app.Server
|
||||||
|
if app.Server == "default" {
|
||||||
|
server = "local"
|
||||||
|
}
|
||||||
|
|
||||||
|
domain := app.Domain
|
||||||
|
if domain == "" {
|
||||||
|
domain = config.NO_DOMAIN_DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := [][]string{
|
||||||
|
[]string{"APP", domain},
|
||||||
|
[]string{"RECIPE", app.Recipe.Name},
|
||||||
|
[]string{"SERVER", server},
|
||||||
|
[]string{"DEPLOYED", version},
|
||||||
|
[]string{"CHAOS", chaosVersion},
|
||||||
|
[]string{"CONFIG", deployConfig},
|
||||||
|
}
|
||||||
|
|
||||||
|
overview := formatter.CreateOverview("UNDEPLOY OVERVIEW", rows)
|
||||||
|
|
||||||
|
fmt.Println(overview)
|
||||||
|
|
||||||
|
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("undeploy cancelled")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// PostCmds parses a string of commands and executes them inside of the respective services
|
// PostCmds parses a string of commands and executes them inside of the respective services
|
||||||
// the commands string must have the following format:
|
// the commands string must have the following format:
|
||||||
// "<service> <command> <arguments>|<service> <command> <arguments>|... "
|
// "<service> <command> <arguments>|<service> <command> <arguments>|... "
|
||||||
@ -199,10 +263,33 @@ func PostCmds(cl *dockerClient.Client, app appPkg.App, commands string) error {
|
|||||||
|
|
||||||
log.Debugf("running command %s %s within the context of %s_%s", cmdName, parsedCmdArgs, app.StackName(), targetServiceName)
|
log.Debugf("running command %s %s within the context of %s_%s", cmdName, parsedCmdArgs, app.StackName(), targetServiceName)
|
||||||
|
|
||||||
Tty = true
|
requestTTY := true
|
||||||
if err := RunCmdRemote(cl, app, app.Recipe.AbraShPath, targetServiceName, cmdName, parsedCmdArgs); err != nil {
|
if err := RunCmdRemote(
|
||||||
|
cl,
|
||||||
|
app,
|
||||||
|
requestTTY,
|
||||||
|
app.Recipe.AbraShPath, targetServiceName, cmdName, parsedCmdArgs, ""); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SortVersionsDesc sorts versions in descending order.
|
||||||
|
func SortVersionsDesc(versions []string) []string {
|
||||||
|
var tags []tagcmp.Tag
|
||||||
|
|
||||||
|
for _, v := range versions {
|
||||||
|
parsed, _ := tagcmp.Parse(v) // skips unsupported tags
|
||||||
|
tags = append(tags, parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(tagcmp.ByTagDesc(tags))
|
||||||
|
|
||||||
|
var desc []string
|
||||||
|
for _, t := range tags {
|
||||||
|
desc = append(desc, t.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return desc
|
||||||
|
}
|
||||||
|
@ -4,13 +4,13 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ShowSubcommandHelpAndError exits the program on error, logs the error to the
|
// ShowSubcommandHelpAndError exits the program on error, logs the error to the
|
||||||
// terminal, and shows the help command.
|
// terminal, and shows the help command.
|
||||||
func ShowSubcommandHelpAndError(c *cli.Context, err interface{}) {
|
func ShowSubcommandHelpAndError(cmd *cli.Command, err interface{}) {
|
||||||
if err2 := cli.ShowSubcommandHelp(c); err2 != nil {
|
if err2 := cli.ShowSubcommandHelp(cmd); err2 != nil {
|
||||||
log.Error(err2)
|
log.Error(err2)
|
||||||
}
|
}
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
// ReverseStringList reverses a list of a strings. Roll on Go generics.
|
|
||||||
func ReverseStringList(strings []string) []string {
|
|
||||||
for i, j := 0, len(strings)-1; i < j; i, j = i+1, j-1 {
|
|
||||||
strings[i], strings[j] = strings[j], strings[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/app"
|
"coopcloud.tech/abra/pkg/app"
|
||||||
@ -9,12 +8,14 @@ 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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateRecipe ensures the recipe arg is valid.
|
// ValidateRecipe ensures the recipe arg is valid.
|
||||||
func ValidateRecipe(c *cli.Context) recipe.Recipe {
|
func ValidateRecipe(args []string, cmdName string) recipe.Recipe {
|
||||||
recipeName := c.Args().First()
|
var recipeName string
|
||||||
|
if len(args) > 0 {
|
||||||
|
recipeName = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
if recipeName == "" && !NoInput {
|
if recipeName == "" && !NoInput {
|
||||||
var recipes []string
|
var recipes []string
|
||||||
@ -54,7 +55,7 @@ func ValidateRecipe(c *cli.Context) recipe.Recipe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if recipeName == "" {
|
if recipeName == "" {
|
||||||
ShowSubcommandHelpAndError(c, errors.New("no recipe name provided"))
|
log.Fatal("no recipe name provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
chosenRecipe := recipe.Get(recipeName)
|
chosenRecipe := recipe.Get(recipeName)
|
||||||
@ -64,7 +65,7 @@ func ValidateRecipe(c *cli.Context) recipe.Recipe {
|
|||||||
}
|
}
|
||||||
_, err = chosenRecipe.GetComposeConfig(nil)
|
_, err = chosenRecipe.GetComposeConfig(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if c.Command.Name == "generate" {
|
if cmdName == "generate" {
|
||||||
if strings.Contains(err.Error(), "missing a compose") {
|
if strings.Contains(err.Error(), "missing a compose") {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -83,13 +84,13 @@ func ValidateRecipe(c *cli.Context) recipe.Recipe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateApp ensures the app name arg is valid.
|
// ValidateApp ensures the app name arg is valid.
|
||||||
func ValidateApp(c *cli.Context) app.App {
|
func ValidateApp(args []string) app.App {
|
||||||
appName := c.Args().First()
|
if len(args) == 0 {
|
||||||
|
log.Fatal("no app provided")
|
||||||
if appName == "" {
|
|
||||||
ShowSubcommandHelpAndError(c, errors.New("no app provided"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appName := args[0]
|
||||||
|
|
||||||
app, err := app.Get(appName)
|
app, err := app.Get(appName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -101,8 +102,11 @@ func ValidateApp(c *cli.Context) app.App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateDomain ensures the domain name arg is valid.
|
// ValidateDomain ensures the domain name arg is valid.
|
||||||
func ValidateDomain(c *cli.Context) string {
|
func ValidateDomain(args []string) string {
|
||||||
domainName := c.Args().First()
|
var domainName string
|
||||||
|
if len(args) > 0 {
|
||||||
|
domainName = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
if domainName == "" && !NoInput {
|
if domainName == "" && !NoInput {
|
||||||
prompt := &survey.Input{
|
prompt := &survey.Input{
|
||||||
@ -115,7 +119,7 @@ func ValidateDomain(c *cli.Context) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if domainName == "" {
|
if domainName == "" {
|
||||||
ShowSubcommandHelpAndError(c, errors.New("no domain provided"))
|
log.Fatal("no domain provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("validated %s as domain argument", domainName)
|
log.Debugf("validated %s as domain argument", domainName)
|
||||||
@ -123,23 +127,12 @@ func ValidateDomain(c *cli.Context) string {
|
|||||||
return domainName
|
return domainName
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateSubCmdFlags ensures flag order conforms to correct order
|
|
||||||
func ValidateSubCmdFlags(c *cli.Context) bool {
|
|
||||||
for argIdx, arg := range c.Args() {
|
|
||||||
if !strings.HasPrefix(arg, "--") {
|
|
||||||
for _, flag := range c.Args()[argIdx:] {
|
|
||||||
if strings.HasPrefix(flag, "--") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateServer ensures the server name arg is valid.
|
// ValidateServer ensures the server name arg is valid.
|
||||||
func ValidateServer(c *cli.Context) string {
|
func ValidateServer(args []string) string {
|
||||||
serverName := c.Args().First()
|
var serverName string
|
||||||
|
if len(args) > 0 {
|
||||||
|
serverName = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
serverNames, err := config.ReadServerNames()
|
serverNames, err := config.ReadServerNames()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -164,11 +157,11 @@ func ValidateServer(c *cli.Context) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if serverName == "" {
|
if serverName == "" {
|
||||||
ShowSubcommandHelpAndError(c, errors.New("no server provided"))
|
log.Fatal("no server provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !matched {
|
if !matched {
|
||||||
ShowSubcommandHelpAndError(c, errors.New("server doesn't exist?"))
|
log.Fatal("server doesn't exist?")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("validated %s as server argument", serverName)
|
log.Debugf("validated %s as server argument", serverName)
|
||||||
|
@ -5,28 +5,25 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
gitPkg "coopcloud.tech/abra/pkg/git"
|
gitPkg "coopcloud.tech/abra/pkg/git"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var recipeDiffCommand = cli.Command{
|
var RecipeDiffCommand = &cobra.Command{
|
||||||
Name: "diff",
|
Use: "diff <recipe> [flags]",
|
||||||
Usage: "Show unstaged changes in recipe config",
|
Aliases: []string{"d"},
|
||||||
Description: "This command requires /usr/bin/git.",
|
Short: "Show unstaged changes in recipe config",
|
||||||
Aliases: []string{"d"},
|
Long: "This command requires /usr/bin/git.",
|
||||||
ArgsUsage: "<recipe>",
|
Args: cobra.MinimumNArgs(1),
|
||||||
Flags: []cli.Flag{
|
ValidArgsFunction: func(
|
||||||
internal.DebugFlag,
|
cmd *cobra.Command,
|
||||||
internal.NoInputFlag,
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.RecipeNameComplete()
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
BashComplete: autocomplete.RecipeNameComplete,
|
r := internal.ValidateRecipe(args, cmd.Name())
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -6,31 +6,40 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var recipeFetchCommand = cli.Command{
|
var RecipeFetchCommand = &cobra.Command{
|
||||||
Name: "fetch",
|
Use: "fetch [recipe | --all] [flags]",
|
||||||
Usage: "Fetch recipe(s)",
|
Aliases: []string{"f"},
|
||||||
Aliases: []string{"f"},
|
Short: "Clone recipe(s) locally",
|
||||||
ArgsUsage: "[<recipe>]",
|
Args: cobra.RangeArgs(0, 1),
|
||||||
Description: "Retrieves all recipes if no <recipe> argument is passed",
|
ValidArgsFunction: func(
|
||||||
Flags: []cli.Flag{
|
cmd *cobra.Command,
|
||||||
internal.DebugFlag,
|
args []string,
|
||||||
internal.NoInputFlag,
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
internal.OfflineFlag,
|
return autocomplete.RecipeNameComplete()
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
BashComplete: autocomplete.RecipeNameComplete,
|
var recipeName string
|
||||||
Action: func(c *cli.Context) error {
|
if len(args) > 0 {
|
||||||
recipeName := c.Args().First()
|
recipeName = args[0]
|
||||||
r := recipe.Get(recipeName)
|
}
|
||||||
|
|
||||||
|
if recipeName == "" && !fetchAllRecipes {
|
||||||
|
log.Fatal("missing [recipe] or --all/-a")
|
||||||
|
}
|
||||||
|
|
||||||
|
if recipeName != "" && fetchAllRecipes {
|
||||||
|
log.Fatal("cannot use [recipe] and --all/-a together")
|
||||||
|
}
|
||||||
|
|
||||||
if recipeName != "" {
|
if recipeName != "" {
|
||||||
internal.ValidateRecipe(c)
|
r := internal.ValidateRecipe(args, cmd.Name())
|
||||||
if err := r.Ensure(false, false); err != nil {
|
if err := r.Ensure(false, false); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
catalogue, err := recipe.ReadRecipeCatalogue(internal.Offline)
|
catalogue, err := recipe.ReadRecipeCatalogue(internal.Offline)
|
||||||
@ -46,7 +55,19 @@ var recipeFetchCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
catlBar.Add(1)
|
catlBar.Add(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
fetchAllRecipes bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RecipeFetchCommand.Flags().BoolVarP(
|
||||||
|
&fetchAllRecipes,
|
||||||
|
"all",
|
||||||
|
"a",
|
||||||
|
false,
|
||||||
|
"fetch all recipes",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -1,32 +1,27 @@
|
|||||||
package recipe
|
package recipe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"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/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/lint"
|
"coopcloud.tech/abra/pkg/lint"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var recipeLintCommand = cli.Command{
|
var RecipeLintCommand = &cobra.Command{
|
||||||
Name: "lint",
|
Use: "lint <recipe> [flags]",
|
||||||
Usage: "Lint a recipe",
|
Short: "Lint a recipe",
|
||||||
Aliases: []string{"l"},
|
Aliases: []string{"l"},
|
||||||
ArgsUsage: "<recipe>",
|
Args: cobra.MinimumNArgs(1),
|
||||||
Flags: []cli.Flag{
|
ValidArgsFunction: func(
|
||||||
internal.DebugFlag,
|
cmd *cobra.Command,
|
||||||
internal.OnlyErrorFlag,
|
args []string,
|
||||||
internal.OfflineFlag,
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
internal.NoInputFlag,
|
return autocomplete.RecipeNameComplete()
|
||||||
internal.ChaosFlag,
|
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
BashComplete: autocomplete.RecipeNameComplete,
|
recipe := internal.ValidateRecipe(args, cmd.Name())
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
recipe := internal.ValidateRecipe(c)
|
|
||||||
|
|
||||||
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)
|
||||||
@ -53,7 +48,7 @@ var recipeLintCommand = cli.Command{
|
|||||||
var warnMessages []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 onlyError && rule.Level != "error" {
|
||||||
log.Debugf("skipping %s, does not have level \"error\"", rule.Ref)
|
log.Debugf("skipping %s, does not have level \"error\"", rule.Ref)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -107,7 +102,9 @@ var recipeLintCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(rows) > 0 {
|
if len(rows) > 0 {
|
||||||
fmt.Println(table)
|
if err := formatter.PrintTable(table); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
for _, warnMsg := range warnMessages {
|
for _, warnMsg := range warnMessages {
|
||||||
log.Warn(warnMsg)
|
log.Warn(warnMsg)
|
||||||
@ -117,7 +114,27 @@ var recipeLintCommand = cli.Command{
|
|||||||
log.Warnf("critical errors present in %s config", recipe.Name)
|
log.Warnf("critical errors present in %s config", recipe.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
onlyError bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RecipeLintCommand.Flags().BoolVarP(
|
||||||
|
&internal.Chaos,
|
||||||
|
"chaos",
|
||||||
|
"C",
|
||||||
|
false,
|
||||||
|
"ignore uncommitted recipes changes",
|
||||||
|
)
|
||||||
|
|
||||||
|
RecipeLintCommand.Flags().BoolVarP(
|
||||||
|
&onlyError,
|
||||||
|
"error",
|
||||||
|
"e",
|
||||||
|
false,
|
||||||
|
"only show errors",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -10,32 +10,18 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var pattern string
|
var RecipeListCommand = &cobra.Command{
|
||||||
var patternFlag = &cli.StringFlag{
|
Use: "list",
|
||||||
Name: "pattern, p",
|
Short: "List recipes",
|
||||||
Value: "",
|
|
||||||
Usage: "Simple string to filter recipes",
|
|
||||||
Destination: &pattern,
|
|
||||||
}
|
|
||||||
|
|
||||||
var recipeListCommand = cli.Command{
|
|
||||||
Name: "list",
|
|
||||||
Usage: "List available recipes",
|
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Flags: []cli.Flag{
|
Args: cobra.NoArgs,
|
||||||
internal.DebugFlag,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
internal.MachineReadableFlag,
|
|
||||||
patternFlag,
|
|
||||||
internal.OfflineFlag,
|
|
||||||
},
|
|
||||||
Before: internal.SubCommandBefore,
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
recipes := catl.Flatten()
|
recipes := catl.Flatten()
|
||||||
@ -90,13 +76,34 @@ var recipeListCommand = cli.Command{
|
|||||||
log.Fatal("unable to render to JSON: %s", err)
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Println(out)
|
fmt.Println(out)
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(table)
|
if err := formatter.PrintTable(table); err != nil {
|
||||||
log.Infof("total recipes: %v", len(rows))
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
pattern string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RecipeListCommand.Flags().BoolVarP(
|
||||||
|
&internal.MachineReadable,
|
||||||
|
"machine",
|
||||||
|
"m",
|
||||||
|
false,
|
||||||
|
"print machine-readable output",
|
||||||
|
)
|
||||||
|
|
||||||
|
RecipeListCommand.Flags().StringVarP(
|
||||||
|
&pattern,
|
||||||
|
"pattern",
|
||||||
|
"p",
|
||||||
|
"",
|
||||||
|
"filter by recipe",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -2,18 +2,17 @@ package recipe
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/git"
|
"coopcloud.tech/abra/pkg/git"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// recipeMetadata is the recipe metadata for the README.md
|
// recipeMetadata is the recipe metadata for the README.md
|
||||||
@ -30,33 +29,22 @@ type recipeMetadata struct {
|
|||||||
SSO string
|
SSO string
|
||||||
}
|
}
|
||||||
|
|
||||||
var recipeNewCommand = cli.Command{
|
var RecipeNewCommand = &cobra.Command{
|
||||||
Name: "new",
|
Use: "new <recipe> [flags]",
|
||||||
Aliases: []string{"n"},
|
Aliases: []string{"n"},
|
||||||
Flags: []cli.Flag{
|
Short: "Create a new recipe",
|
||||||
internal.DebugFlag,
|
Long: `A community managed recipe template is used.`,
|
||||||
internal.NoInputFlag,
|
Args: cobra.ExactArgs(1),
|
||||||
internal.OfflineFlag,
|
ValidArgsFunction: func(
|
||||||
internal.GitNameFlag,
|
cmd *cobra.Command,
|
||||||
internal.GitEmailFlag,
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.RecipeNameComplete()
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
Usage: "Create a new recipe",
|
recipeName := args[0]
|
||||||
ArgsUsage: "<recipe>",
|
|
||||||
Description: `
|
|
||||||
Create a new recipe.
|
|
||||||
|
|
||||||
Abra uses the built-in example repository which is available here:
|
|
||||||
|
|
||||||
https://git.coopcloud.tech/coop-cloud/example`,
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
recipeName := c.Args().First()
|
|
||||||
r := recipe.Get(recipeName)
|
r := recipe.Get(recipeName)
|
||||||
|
|
||||||
if recipeName == "" {
|
|
||||||
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) {
|
||||||
log.Fatalf("%s recipe directory already exists?", r.Dir)
|
log.Fatalf("%s recipe directory already exists?", r.Dir)
|
||||||
}
|
}
|
||||||
@ -91,14 +79,12 @@ Abra uses the built-in example repository which is available here:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := git.Init(r.Dir, true, internal.GitName, internal.GitEmail); err != nil {
|
if err := git.Init(r.Dir, true, gitName, gitEmail); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("new recipe '%s' created: %s", recipeName, path.Join(r.Dir))
|
log.Infof("new recipe '%s' created: %s", recipeName, path.Join(r.Dir))
|
||||||
log.Info("happy hacking 🎉")
|
log.Info("happy hacking 🎉")
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,3 +103,26 @@ func newRecipeMeta(recipeName string) recipeMetadata {
|
|||||||
SSO: "No",
|
SSO: "No",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
gitName string
|
||||||
|
gitEmail string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RecipeNewCommand.Flags().StringVarP(
|
||||||
|
&gitName,
|
||||||
|
"git-name",
|
||||||
|
"N",
|
||||||
|
"",
|
||||||
|
"Git (user) name to do commits with",
|
||||||
|
)
|
||||||
|
|
||||||
|
RecipeNewCommand.Flags().StringVarP(
|
||||||
|
&gitEmail,
|
||||||
|
"git-email",
|
||||||
|
"e",
|
||||||
|
"",
|
||||||
|
"Git email name to do commits with",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -1,34 +1,19 @@
|
|||||||
package recipe
|
package recipe
|
||||||
|
|
||||||
import (
|
import "github.com/spf13/cobra"
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RecipeCommand defines all recipe related sub-commands.
|
// RecipeCommand defines all recipe related sub-commands.
|
||||||
var RecipeCommand = cli.Command{
|
var RecipeCommand = &cobra.Command{
|
||||||
Name: "recipe",
|
Use: "recipe [cmd] [args] [flags]",
|
||||||
Aliases: []string{"r"},
|
Aliases: []string{"r"},
|
||||||
Usage: "Manage recipes",
|
Short: "Manage recipes",
|
||||||
ArgsUsage: "<recipe>",
|
Long: `A recipe is a blueprint for an app.
|
||||||
Description: `
|
|
||||||
A recipe is a blueprint for an app. It is a bunch of config files which
|
It is a bunch of config files which describe how to deploy and maintain an app.
|
||||||
describe how to deploy and maintain an app. Recipes are maintained by the Co-op
|
Recipes are maintained by the Co-op Cloud community and you can use Abra to
|
||||||
Cloud community and you can use Abra to read them, deploy them and create apps
|
read them, deploy them and create apps for you.
|
||||||
for you.
|
|
||||||
|
|
||||||
Anyone who uses a recipe can become a maintainer. Maintainers typically make
|
Anyone who uses a recipe can become a maintainer. Maintainers typically make
|
||||||
sure the recipe is in good working order and the config upgraded in a timely
|
sure the recipe is in good working order and the config upgraded in a timely
|
||||||
manner.`,
|
manner.`,
|
||||||
Subcommands: []cli.Command{
|
|
||||||
recipeFetchCommand,
|
|
||||||
recipeLintCommand,
|
|
||||||
recipeListCommand,
|
|
||||||
recipeNewCommand,
|
|
||||||
recipeReleaseCommand,
|
|
||||||
recipeSyncCommand,
|
|
||||||
recipeUpgradeCommand,
|
|
||||||
recipeVersionCommand,
|
|
||||||
recipeResetCommand,
|
|
||||||
recipeDiffCommand,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
@ -18,17 +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"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var recipeReleaseCommand = cli.Command{
|
var RecipeReleaseCommand = &cobra.Command{
|
||||||
Name: "release",
|
Use: "release <recipe> [version] [flags]",
|
||||||
Aliases: []string{"rl"},
|
Aliases: []string{"rl"},
|
||||||
Usage: "Release a new recipe version",
|
Short: "Release a new recipe version",
|
||||||
ArgsUsage: "<recipe> [<version>]",
|
Long: `Create a new version of a recipe.
|
||||||
Description: `
|
|
||||||
Create a new version of a recipe. These versions are then published on the
|
These versions are then published on the Co-op Cloud recipe catalogue. These
|
||||||
Co-op Cloud recipe catalogue. These versions take the following form:
|
versions take the following form:
|
||||||
|
|
||||||
a.b.c+x.y.z
|
a.b.c+x.y.z
|
||||||
|
|
||||||
@ -42,23 +42,25 @@ recipe updates are properly communicated. I.e. developers of an app might
|
|||||||
publish a minor version but that might lead to changes in the recipe which are
|
publish a minor version but that might lead to changes in the recipe which are
|
||||||
major and therefore require intervention while doing the upgrade work.
|
major and therefore require intervention while doing the upgrade work.
|
||||||
|
|
||||||
Publish your new release to git.coopcloud.tech with "-p/--publish". This
|
Publish your new release to git.coopcloud.tech with "--publish/-p". 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{
|
Args: cobra.RangeArgs(1, 2),
|
||||||
internal.DebugFlag,
|
ValidArgsFunction: func(
|
||||||
internal.NoInputFlag,
|
cmd *cobra.Command,
|
||||||
internal.DryFlag,
|
args []string,
|
||||||
internal.MajorFlag,
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
internal.MinorFlag,
|
switch l := len(args); l {
|
||||||
internal.PatchFlag,
|
case 0:
|
||||||
internal.PublishFlag,
|
return autocomplete.RecipeNameComplete()
|
||||||
internal.OfflineFlag,
|
case 1:
|
||||||
|
return autocomplete.RecipeVersionComplete(args[0])
|
||||||
|
default:
|
||||||
|
return nil, cobra.ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
BashComplete: autocomplete.RecipeNameComplete,
|
recipe := internal.ValidateRecipe(args, cmd.Name())
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
recipe := internal.ValidateRecipe(c)
|
|
||||||
|
|
||||||
imagesTmp, err := getImageVersions(recipe)
|
imagesTmp, err := getImageVersions(recipe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -75,7 +77,11 @@ your SSH keys configured on your account.`,
|
|||||||
log.Fatalf("main app service version for %s is empty?", recipe.Name)
|
log.Fatalf("main app service version for %s is empty?", recipe.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
tagString := c.Args().Get(1)
|
var tagString string
|
||||||
|
if len(args) == 2 {
|
||||||
|
tagString = args[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)
|
||||||
@ -133,7 +139,7 @@ your SSH keys configured on your account.`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,7 +252,14 @@ func getTagCreateOptions(tag string) (git.CreateTagOptions, error) {
|
|||||||
// addReleaseNotes checks if the release/next release note exists and moves the
|
// addReleaseNotes checks if the release/next release note exists and moves the
|
||||||
// file to release/<tag>.
|
// file to release/<tag>.
|
||||||
func addReleaseNotes(recipe recipe.Recipe, tag string) error {
|
func addReleaseNotes(recipe recipe.Recipe, tag string) error {
|
||||||
tagReleaseNotePath := path.Join(recipe.Dir, "release", tag)
|
releaseDir := path.Join(recipe.Dir, "release")
|
||||||
|
if _, err := os.Stat(releaseDir); errors.Is(err, os.ErrNotExist) {
|
||||||
|
if err := os.Mkdir(releaseDir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tagReleaseNotePath := path.Join(releaseDir, tag)
|
||||||
if _, err := os.Stat(tagReleaseNotePath); err == nil {
|
if _, err := os.Stat(tagReleaseNotePath); err == nil {
|
||||||
// Release note for current tag already exist exists.
|
// Release note for current tag already exist exists.
|
||||||
return nil
|
return nil
|
||||||
@ -254,13 +267,14 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
nextReleaseNotePath := path.Join(recipe.Dir, "release", "next")
|
nextReleaseNotePath := path.Join(releaseDir, "next")
|
||||||
if _, err := os.Stat(nextReleaseNotePath); err == nil {
|
if _, err := os.Stat(nextReleaseNotePath); err == nil {
|
||||||
// release/next note exists. Move it to release/<tag>
|
// release/next note exists. Move it to release/<tag>
|
||||||
if internal.Dry {
|
if internal.Dry {
|
||||||
log.Debugf("dry run: move release note from 'next' to %s", tag)
|
log.Debugf("dry run: move release note from 'next' to %s", tag)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !internal.NoInput {
|
if !internal.NoInput {
|
||||||
prompt := &survey.Input{
|
prompt := &survey.Input{
|
||||||
Message: "Use release note in release/next?",
|
Message: "Use release note in release/next?",
|
||||||
@ -273,14 +287,17 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := os.Rename(nextReleaseNotePath, tagReleaseNotePath)
|
err := os.Rename(nextReleaseNotePath, tagReleaseNotePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = gitPkg.Add(recipe.Dir, path.Join("release", "next"), internal.Dry)
|
err = gitPkg.Add(recipe.Dir, path.Join("release", "next"), internal.Dry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = gitPkg.Add(recipe.Dir, path.Join("release", tag), internal.Dry)
|
err = gitPkg.Add(recipe.Dir, path.Join("release", tag), internal.Dry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -297,6 +314,7 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
|
|||||||
prompt := &survey.Input{
|
prompt := &survey.Input{
|
||||||
Message: "Release Note (leave empty for no release note)",
|
Message: "Release Note (leave empty for no release note)",
|
||||||
}
|
}
|
||||||
|
|
||||||
var releaseNote string
|
var releaseNote string
|
||||||
if err := survey.AskOne(prompt, &releaseNote); err != nil {
|
if err := survey.AskOne(prompt, &releaseNote); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -306,12 +324,11 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := os.WriteFile(tagReleaseNotePath, []byte(releaseNote), 0o644)
|
if err := os.WriteFile(tagReleaseNotePath, []byte(releaseNote), 0o644); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = gitPkg.Add(recipe.Dir, path.Join("release", tag), internal.Dry)
|
|
||||||
if err != nil {
|
if err := gitPkg.Add(recipe.Dir, path.Join("release", tag), internal.Dry); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,17 +393,17 @@ func pushRelease(recipe recipe.Recipe, tagString string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !internal.Publish && !internal.NoInput {
|
if !publish && !internal.NoInput {
|
||||||
prompt := &survey.Confirm{
|
prompt := &survey.Confirm{
|
||||||
Message: "publish new release?",
|
Message: "publish new release?",
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := survey.AskOne(prompt, &internal.Publish); err != nil {
|
if err := survey.AskOne(prompt, &publish); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.Publish {
|
if publish {
|
||||||
if err := recipe.Push(internal.Dry); err != nil {
|
if err := recipe.Push(internal.Dry); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -546,3 +563,50 @@ func getLabelVersion(recipe recipe.Recipe, prompt bool) (string, error) {
|
|||||||
|
|
||||||
return initTag, nil
|
return initTag, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
publish bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RecipeReleaseCommand.Flags().BoolVarP(
|
||||||
|
&internal.Dry,
|
||||||
|
"dry-run",
|
||||||
|
"r",
|
||||||
|
false,
|
||||||
|
"report changes that would be made",
|
||||||
|
)
|
||||||
|
|
||||||
|
RecipeReleaseCommand.Flags().BoolVarP(
|
||||||
|
&internal.Major,
|
||||||
|
"major",
|
||||||
|
"x",
|
||||||
|
false,
|
||||||
|
"increase the major part of the version",
|
||||||
|
)
|
||||||
|
|
||||||
|
RecipeReleaseCommand.Flags().BoolVarP(
|
||||||
|
&internal.Minor,
|
||||||
|
"minor",
|
||||||
|
"y",
|
||||||
|
false,
|
||||||
|
"increase the minor part of the version",
|
||||||
|
)
|
||||||
|
|
||||||
|
RecipeReleaseCommand.Flags().BoolVarP(
|
||||||
|
&internal.Patch,
|
||||||
|
"patch",
|
||||||
|
"z",
|
||||||
|
false,
|
||||||
|
"increase the patch part of the version",
|
||||||
|
)
|
||||||
|
|
||||||
|
RecipeReleaseCommand.Flags().BoolVarP(
|
||||||
|
&publish,
|
||||||
|
"publish",
|
||||||
|
"p",
|
||||||
|
false,
|
||||||
|
"publish changes to git.coopcloud.tech",
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -4,30 +4,24 @@ import (
|
|||||||
"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"
|
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var recipeResetCommand = cli.Command{
|
var RecipeResetCommand = &cobra.Command{
|
||||||
Name: "reset",
|
Use: "reset <recipe> [flags]",
|
||||||
Usage: "Remove all unstaged changes from recipe config",
|
Aliases: []string{"rs"},
|
||||||
Description: "WARNING: this will delete your changes. Be Careful.",
|
Short: "Remove all unstaged changes from recipe config",
|
||||||
Aliases: []string{"rs"},
|
Long: "WARNING: this will delete your changes. Be Careful.",
|
||||||
ArgsUsage: "<recipe>",
|
Args: cobra.ExactArgs(1),
|
||||||
Flags: []cli.Flag{
|
ValidArgsFunction: func(
|
||||||
internal.DebugFlag,
|
cmd *cobra.Command,
|
||||||
internal.NoInputFlag,
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.RecipeNameComplete()
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
BashComplete: autocomplete.RecipeNameComplete,
|
r := internal.ValidateRecipe(args, cmd.Name())
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
recipeName := c.Args().First()
|
|
||||||
r := recipe.Get(recipeName)
|
|
||||||
|
|
||||||
if recipeName != "" {
|
|
||||||
internal.ValidateRecipe(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
repo, err := git.PlainOpen(r.Dir)
|
repo, err := git.PlainOpen(r.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -48,7 +42,5 @@ var recipeResetCommand = cli.Command{
|
|||||||
if err := worktree.Reset(opts); err != nil {
|
if err := worktree.Reset(opts); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -12,35 +12,38 @@ import (
|
|||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var recipeSyncCommand = cli.Command{
|
var RecipeSyncCommand = &cobra.Command{
|
||||||
Name: "sync",
|
Use: "sync <recipe> [version] [flags]",
|
||||||
Aliases: []string{"s"},
|
Aliases: []string{"s"},
|
||||||
Usage: "Sync recipe version label",
|
Short: "Sync recipe version label",
|
||||||
ArgsUsage: "<recipe> [<version>]",
|
Long: `Generate labels for the main recipe service.
|
||||||
Flags: []cli.Flag{
|
|
||||||
internal.DebugFlag,
|
By convention, the service named "app" using the following format:
|
||||||
internal.NoInputFlag,
|
|
||||||
internal.DryFlag,
|
|
||||||
internal.MajorFlag,
|
|
||||||
internal.MinorFlag,
|
|
||||||
internal.PatchFlag,
|
|
||||||
},
|
|
||||||
Before: internal.SubCommandBefore,
|
|
||||||
Description: `
|
|
||||||
Generate labels for the main recipe service (i.e. by convention, the service
|
|
||||||
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.`,
|
||||||
BashComplete: autocomplete.RecipeNameComplete,
|
Args: cobra.RangeArgs(1, 2),
|
||||||
Action: func(c *cli.Context) error {
|
ValidArgsFunction: func(
|
||||||
recipe := internal.ValidateRecipe(c)
|
cmd *cobra.Command,
|
||||||
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
switch l := len(args); l {
|
||||||
|
case 0:
|
||||||
|
return autocomplete.RecipeNameComplete()
|
||||||
|
case 1:
|
||||||
|
return autocomplete.RecipeVersionComplete(args[0])
|
||||||
|
default:
|
||||||
|
return nil, cobra.ShellCompDirectiveError
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
recipe := internal.ValidateRecipe(args, cmd.Name())
|
||||||
|
|
||||||
mainApp, err := internal.GetMainAppImage(recipe)
|
mainApp, err := internal.GetMainAppImage(recipe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -59,7 +62,11 @@ local file system.`,
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTag := c.Args().Get(1)
|
var nextTag string
|
||||||
|
if len(args) == 2 {
|
||||||
|
nextTag = args[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 {
|
||||||
@ -205,7 +212,39 @@ likely to change.
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RecipeSyncCommand.Flags().BoolVarP(
|
||||||
|
&internal.Dry,
|
||||||
|
"dry-run",
|
||||||
|
"r",
|
||||||
|
false,
|
||||||
|
"report changes that would be made",
|
||||||
|
)
|
||||||
|
|
||||||
|
RecipeSyncCommand.Flags().BoolVarP(
|
||||||
|
&internal.Major,
|
||||||
|
"major",
|
||||||
|
"x",
|
||||||
|
false,
|
||||||
|
"increase the major part of the version",
|
||||||
|
)
|
||||||
|
|
||||||
|
RecipeSyncCommand.Flags().BoolVarP(
|
||||||
|
&internal.Minor,
|
||||||
|
"minor",
|
||||||
|
"y",
|
||||||
|
false,
|
||||||
|
"increase the minor part of the version",
|
||||||
|
)
|
||||||
|
|
||||||
|
RecipeSyncCommand.Flags().BoolVarP(
|
||||||
|
&internal.Patch,
|
||||||
|
"patch",
|
||||||
|
"z",
|
||||||
|
false,
|
||||||
|
"increase the patch part of the version",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -19,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"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
type imgPin struct {
|
type imgPin struct {
|
||||||
@ -27,8 +27,8 @@ type imgPin struct {
|
|||||||
version tagcmp.Tag
|
version tagcmp.Tag
|
||||||
}
|
}
|
||||||
|
|
||||||
// anUpgrade represents a single service upgrade (as within a recipe), and the list of tags that it can be upgraded to,
|
// anUpgrade represents a single service upgrade (as within a recipe), and the
|
||||||
// for serialization purposes.
|
// list of tags that it can be upgraded to, for serialization purposes.
|
||||||
type anUpgrade struct {
|
type anUpgrade struct {
|
||||||
Service string `json:"service"`
|
Service string `json:"service"`
|
||||||
Image string `json:"image"`
|
Image string `json:"image"`
|
||||||
@ -36,14 +36,13 @@ type anUpgrade struct {
|
|||||||
UpgradeTags []string `json:"upgrades"`
|
UpgradeTags []string `json:"upgrades"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var recipeUpgradeCommand = cli.Command{
|
var RecipeUpgradeCommand = &cobra.Command{
|
||||||
Name: "upgrade",
|
Use: "upgrade <recipe> [flags]",
|
||||||
Aliases: []string{"u"},
|
Aliases: []string{"u"},
|
||||||
Usage: "Upgrade recipe image tags",
|
Short: "Upgrade recipe image tags",
|
||||||
Description: `
|
Long: `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
|
||||||
@ -53,25 +52,16 @@ 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.`,
|
||||||
|
Args: cobra.RangeArgs(0, 1),
|
||||||
EXAMPLE:
|
ValidArgsFunction: func(
|
||||||
|
cmd *cobra.Command,
|
||||||
abra recipe upgrade`,
|
args []string,
|
||||||
ArgsUsage: "<recipe>",
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
Flags: []cli.Flag{
|
return autocomplete.RecipeNameComplete()
|
||||||
internal.DebugFlag,
|
|
||||||
internal.NoInputFlag,
|
|
||||||
internal.PatchFlag,
|
|
||||||
internal.MinorFlag,
|
|
||||||
internal.MajorFlag,
|
|
||||||
internal.MachineReadableFlag,
|
|
||||||
internal.AllTagsFlag,
|
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
BashComplete: autocomplete.RecipeNameComplete,
|
recipe := internal.ValidateRecipe(args, cmd.Name())
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
recipe := internal.ValidateRecipe(c)
|
|
||||||
|
|
||||||
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)
|
||||||
@ -182,7 +172,7 @@ EXAMPLE:
|
|||||||
|
|
||||||
sort.Sort(tagcmp.ByTagDesc(compatible))
|
sort.Sort(tagcmp.ByTagDesc(compatible))
|
||||||
|
|
||||||
if len(compatible) == 0 && !internal.AllTags {
|
if len(compatible) == 0 && !allTags {
|
||||||
log.Info(fmt.Sprintf("no new versions available for %s, assuming %s is the latest (use -a/--all-tags to see all anyway)", image, tag))
|
log.Info(fmt.Sprintf("no new versions available for %s, assuming %s is the latest (use -a/--all-tags to see all anyway)", image, tag))
|
||||||
continue // skip on to the next tag and don't update any compose files
|
continue // skip on to the next tag and don't update any compose files
|
||||||
}
|
}
|
||||||
@ -236,7 +226,7 @@ EXAMPLE:
|
|||||||
for _, upTag := range compatible {
|
for _, upTag := range compatible {
|
||||||
upElement, err := tag.UpgradeDelta(upTag)
|
upElement, err := tag.UpgradeDelta(upTag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
delta := upElement.UpgradeType()
|
delta := upElement.UpgradeType()
|
||||||
if delta <= bumpType {
|
if delta <= bumpType {
|
||||||
@ -250,9 +240,9 @@ EXAMPLE:
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
msg := fmt.Sprintf("upgrade to which tag? (service: %s, image: %s, tag: %s)", service.Name, image, tag)
|
msg := fmt.Sprintf("upgrade to which tag? (service: %s, image: %s, tag: %s)", service.Name, image, tag)
|
||||||
if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) || internal.AllTags {
|
if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) || allTags {
|
||||||
tag := img.(reference.NamedTagged).Tag()
|
tag := img.(reference.NamedTagged).Tag()
|
||||||
if !internal.AllTags {
|
if !allTags {
|
||||||
log.Warn(fmt.Sprintf("unable to determine versioning semantics of %s, listing all tags", tag))
|
log.Warn(fmt.Sprintf("unable to determine versioning semantics of %s, listing all tags", tag))
|
||||||
}
|
}
|
||||||
msg = fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag)
|
msg = fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag)
|
||||||
@ -320,7 +310,7 @@ EXAMPLE:
|
|||||||
|
|
||||||
fmt.Println(string(jsonstring))
|
fmt.Println(string(jsonstring))
|
||||||
|
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, upgrade := range upgradeList {
|
for _, upgrade := range upgradeList {
|
||||||
@ -341,7 +331,51 @@ EXAMPLE:
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
allTags bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RecipeUpgradeCommand.Flags().BoolVarP(
|
||||||
|
&internal.Major,
|
||||||
|
"major",
|
||||||
|
"x",
|
||||||
|
false,
|
||||||
|
"increase the major part of the version",
|
||||||
|
)
|
||||||
|
|
||||||
|
RecipeUpgradeCommand.Flags().BoolVarP(
|
||||||
|
&internal.Minor,
|
||||||
|
"minor",
|
||||||
|
"y",
|
||||||
|
false,
|
||||||
|
"increase the minor part of the version",
|
||||||
|
)
|
||||||
|
|
||||||
|
RecipeUpgradeCommand.Flags().BoolVarP(
|
||||||
|
&internal.Patch,
|
||||||
|
"patch",
|
||||||
|
"z",
|
||||||
|
false,
|
||||||
|
"increase the patch part of the version",
|
||||||
|
)
|
||||||
|
|
||||||
|
RecipeUpgradeCommand.Flags().BoolVarP(
|
||||||
|
&internal.MachineReadable,
|
||||||
|
"machine",
|
||||||
|
"m",
|
||||||
|
false,
|
||||||
|
"print machine-readable output",
|
||||||
|
)
|
||||||
|
|
||||||
|
RecipeUpgradeCommand.Flags().BoolVarP(
|
||||||
|
&allTags,
|
||||||
|
"all-tags",
|
||||||
|
"a",
|
||||||
|
false,
|
||||||
|
"list all tags, not just upgrades",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -9,36 +9,24 @@ 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/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func sortServiceByName(versions [][]string) func(i, j int) bool {
|
var RecipeVersionCommand = &cobra.Command{
|
||||||
return func(i, j int) bool {
|
Use: "versions <recipe> [flags]",
|
||||||
// NOTE(d1): corresponds to the `tableCol` definition below
|
Aliases: []string{"v"},
|
||||||
if versions[i][1] == "app" {
|
Short: "List recipe versions",
|
||||||
return true
|
Args: cobra.ExactArgs(1),
|
||||||
}
|
ValidArgsFunction: func(
|
||||||
return versions[i][1] < versions[j][1]
|
cmd *cobra.Command,
|
||||||
}
|
args []string,
|
||||||
}
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.RecipeNameComplete()
|
||||||
var recipeVersionCommand = cli.Command{
|
|
||||||
Name: "versions",
|
|
||||||
Aliases: []string{"v"},
|
|
||||||
Usage: "List recipe versions",
|
|
||||||
ArgsUsage: "<recipe>",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
internal.DebugFlag,
|
|
||||||
internal.OfflineFlag,
|
|
||||||
internal.NoInputFlag,
|
|
||||||
internal.MachineReadableFlag,
|
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
BashComplete: autocomplete.RecipeNameComplete,
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
var warnMessages []string
|
var warnMessages []string
|
||||||
|
|
||||||
recipe := internal.ValidateRecipe(c)
|
recipe := internal.ValidateRecipe(args, cmd.Name())
|
||||||
|
|
||||||
catl, err := recipePkg.ReadRecipeCatalogue(internal.Offline)
|
catl, err := recipePkg.ReadRecipeCatalogue(internal.Offline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -67,15 +55,32 @@ var recipeVersionCommand = cli.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Headers("SERVICE", "NAME", "TAG")
|
table.Headers("SERVICE", "IMAGE", "TAG", "VERSION")
|
||||||
|
|
||||||
for version, meta := range recipeMeta.Versions[i] {
|
for version, meta := range recipeMeta.Versions[i] {
|
||||||
var allRows [][]string
|
var allRows [][]string
|
||||||
var rows [][]string
|
var rows [][]string
|
||||||
|
|
||||||
for service, serviceMeta := range meta {
|
for service, serviceMeta := range meta {
|
||||||
rows = append(rows, []string{service, serviceMeta.Image, serviceMeta.Tag})
|
recipeVersion := version
|
||||||
allRows = append(allRows, []string{version, service, serviceMeta.Image, serviceMeta.Tag})
|
if service != "app" {
|
||||||
|
recipeVersion = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
rows = append(rows, []string{
|
||||||
|
service,
|
||||||
|
serviceMeta.Image,
|
||||||
|
serviceMeta.Tag,
|
||||||
|
recipeVersion,
|
||||||
|
})
|
||||||
|
|
||||||
|
allRows = append(allRows, []string{
|
||||||
|
version,
|
||||||
|
service,
|
||||||
|
serviceMeta.Image,
|
||||||
|
serviceMeta.Tag,
|
||||||
|
recipeVersion,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(rows, sortServiceByName(rows))
|
sort.Slice(rows, sortServiceByName(rows))
|
||||||
@ -83,9 +88,9 @@ var recipeVersionCommand = cli.Command{
|
|||||||
table.Rows(rows...)
|
table.Rows(rows...)
|
||||||
|
|
||||||
if !internal.MachineReadable {
|
if !internal.MachineReadable {
|
||||||
fmt.Println(table)
|
if err := formatter.PrintTable(table); err != nil {
|
||||||
log.Infof("VERSION: %s", version)
|
log.Fatal(err)
|
||||||
fmt.Println()
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +112,21 @@ var recipeVersionCommand = cli.Command{
|
|||||||
log.Warn(warnMsg)
|
log.Warn(warnMsg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sortServiceByName(versions [][]string) func(i, j int) bool {
|
||||||
|
return func(i, j int) bool {
|
||||||
|
return versions[i][0] < versions[j][0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RecipeVersionCommand.Flags().BoolVarP(
|
||||||
|
&internal.MachineReadable,
|
||||||
|
"machine",
|
||||||
|
"m",
|
||||||
|
false,
|
||||||
|
"print machine-readable output",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
195
cli/run.go
Normal file
195
cli/run.go
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"coopcloud.tech/abra/cli/app"
|
||||||
|
"coopcloud.tech/abra/cli/catalogue"
|
||||||
|
"coopcloud.tech/abra/cli/internal"
|
||||||
|
"coopcloud.tech/abra/cli/recipe"
|
||||||
|
"coopcloud.tech/abra/cli/server"
|
||||||
|
"coopcloud.tech/abra/pkg/config"
|
||||||
|
"coopcloud.tech/abra/pkg/log"
|
||||||
|
charmLog "github.com/charmbracelet/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/cobra/doc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Run(version, commit string) {
|
||||||
|
rootCmd := &cobra.Command{
|
||||||
|
Use: "abra [cmd] [args] [flags]",
|
||||||
|
Short: "The Co-op Cloud command-line utility belt 🎩🐇",
|
||||||
|
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
|
||||||
|
ValidArgs: []string{
|
||||||
|
"app",
|
||||||
|
"autocomplete",
|
||||||
|
"catalogue",
|
||||||
|
"man",
|
||||||
|
"recipe",
|
||||||
|
"server",
|
||||||
|
"upgrade",
|
||||||
|
},
|
||||||
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
paths := []string{
|
||||||
|
config.ABRA_DIR,
|
||||||
|
config.SERVERS_DIR,
|
||||||
|
config.RECIPES_DIR,
|
||||||
|
config.VENDOR_DIR, // TODO(d1): remove > 0.9.x
|
||||||
|
config.BACKUP_DIR, // TODO(d1): remove > 0.9.x
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range paths {
|
||||||
|
if err := os.Mkdir(path, 0764); err != nil {
|
||||||
|
if !os.IsExist(err) {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Logger.SetStyles(log.Styles())
|
||||||
|
charmLog.SetDefault(log.Logger)
|
||||||
|
|
||||||
|
if internal.Debug {
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
log.SetOutput(os.Stderr)
|
||||||
|
log.SetReportCaller(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("abra version %s, commit %s", version, commit)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCmd.CompletionOptions.DisableDefaultCmd = true
|
||||||
|
|
||||||
|
manCommand := &cobra.Command{
|
||||||
|
Use: "man [flags]",
|
||||||
|
Aliases: []string{"m"},
|
||||||
|
Short: "Generate manpage",
|
||||||
|
Example: ` # generate the man pages into /usr/local/share/man/man1
|
||||||
|
sudo abra man
|
||||||
|
sudo mandb
|
||||||
|
|
||||||
|
# read the man pages
|
||||||
|
man abra
|
||||||
|
man abra-app-deploy`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
header := &doc.GenManHeader{
|
||||||
|
Title: "ABRA",
|
||||||
|
Section: "1",
|
||||||
|
}
|
||||||
|
|
||||||
|
manDir := "/usr/local/share/man/man1"
|
||||||
|
if _, err := os.Stat(manDir); os.IsNotExist(err) {
|
||||||
|
log.Fatalf("unable to proceed, '%s' does not exist?")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := doc.GenManTree(rootCmd, header, manDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("don't forget to run 'sudo mandb'")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(
|
||||||
|
&internal.Debug, "debug", "d", false,
|
||||||
|
"show debug messages",
|
||||||
|
)
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(
|
||||||
|
&internal.NoInput, "no-input", "n", false,
|
||||||
|
"toggle non-interactive mode",
|
||||||
|
)
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(
|
||||||
|
&internal.Offline, "offline", "o", false,
|
||||||
|
"prefer offline & filesystem access",
|
||||||
|
)
|
||||||
|
|
||||||
|
catalogue.CatalogueCommand.AddCommand(
|
||||||
|
catalogue.CatalogueGenerateCommand,
|
||||||
|
)
|
||||||
|
|
||||||
|
server.ServerCommand.AddCommand(
|
||||||
|
server.ServerAddCommand,
|
||||||
|
server.ServerListCommand,
|
||||||
|
server.ServerPruneCommand,
|
||||||
|
server.ServerRemoveCommand,
|
||||||
|
)
|
||||||
|
|
||||||
|
recipe.RecipeCommand.AddCommand(
|
||||||
|
recipe.RecipeDiffCommand,
|
||||||
|
recipe.RecipeFetchCommand,
|
||||||
|
recipe.RecipeLintCommand,
|
||||||
|
recipe.RecipeListCommand,
|
||||||
|
recipe.RecipeNewCommand,
|
||||||
|
recipe.RecipeReleaseCommand,
|
||||||
|
recipe.RecipeResetCommand,
|
||||||
|
recipe.RecipeSyncCommand,
|
||||||
|
recipe.RecipeUpgradeCommand,
|
||||||
|
recipe.RecipeVersionCommand,
|
||||||
|
)
|
||||||
|
|
||||||
|
rootCmd.AddCommand(
|
||||||
|
UpgradeCommand,
|
||||||
|
AutocompleteCommand,
|
||||||
|
manCommand,
|
||||||
|
app.AppCommand,
|
||||||
|
catalogue.CatalogueCommand,
|
||||||
|
server.ServerCommand,
|
||||||
|
recipe.RecipeCommand,
|
||||||
|
)
|
||||||
|
|
||||||
|
app.AppCmdCommand.AddCommand(
|
||||||
|
app.AppCmdListCommand,
|
||||||
|
)
|
||||||
|
|
||||||
|
app.AppSecretCommand.AddCommand(
|
||||||
|
app.AppSecretGenerateCommand,
|
||||||
|
app.AppSecretInsertCommand,
|
||||||
|
app.AppSecretRmCommand,
|
||||||
|
app.AppSecretLsCommand,
|
||||||
|
)
|
||||||
|
|
||||||
|
app.AppVolumeCommand.AddCommand(
|
||||||
|
app.AppVolumeListCommand,
|
||||||
|
app.AppVolumeRemoveCommand,
|
||||||
|
)
|
||||||
|
|
||||||
|
app.AppBackupCommand.AddCommand(
|
||||||
|
app.AppBackupListCommand,
|
||||||
|
app.AppBackupDownloadCommand,
|
||||||
|
app.AppBackupCreateCommand,
|
||||||
|
app.AppBackupSnapshotsCommand,
|
||||||
|
)
|
||||||
|
|
||||||
|
app.AppCommand.AddCommand(
|
||||||
|
app.AppBackupCommand,
|
||||||
|
app.AppCheckCommand,
|
||||||
|
app.AppCmdCommand,
|
||||||
|
app.AppConfigCommand,
|
||||||
|
app.AppCpCommand,
|
||||||
|
app.AppDeployCommand,
|
||||||
|
app.AppListCommand,
|
||||||
|
app.AppLogsCommand,
|
||||||
|
app.AppNewCommand,
|
||||||
|
app.AppPsCommand,
|
||||||
|
app.AppRemoveCommand,
|
||||||
|
app.AppRestartCommand,
|
||||||
|
app.AppRestoreCommand,
|
||||||
|
app.AppRollbackCommand,
|
||||||
|
app.AppRunCommand,
|
||||||
|
app.AppSecretCommand,
|
||||||
|
app.AppServicesCommand,
|
||||||
|
app.AppUndeployCommand,
|
||||||
|
app.AppUpgradeCommand,
|
||||||
|
app.AppVolumeCommand,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
|
"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"
|
||||||
contextPkg "coopcloud.tech/abra/pkg/context"
|
contextPkg "coopcloud.tech/abra/pkg/context"
|
||||||
@ -13,14 +13,112 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/server"
|
"coopcloud.tech/abra/pkg/server"
|
||||||
sshPkg "coopcloud.tech/abra/pkg/ssh"
|
sshPkg "coopcloud.tech/abra/pkg/ssh"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var local bool
|
var ServerAddCommand = &cobra.Command{
|
||||||
var localFlag = &cli.BoolFlag{
|
Use: "add [[server] | --local] [flags]",
|
||||||
Name: "local, l",
|
Aliases: []string{"a"},
|
||||||
Usage: "Use local server",
|
Short: "Add a new server",
|
||||||
Destination: &local,
|
Long: `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
|
||||||
|
connection details. You must configure an entry per-host in your ~/.ssh/config
|
||||||
|
for each server:
|
||||||
|
|
||||||
|
Host 1312.net 1312
|
||||||
|
Hostname 1312.net
|
||||||
|
User antifa
|
||||||
|
Port 12345
|
||||||
|
IdentityFile ~/.ssh/antifa@somewhere
|
||||||
|
|
||||||
|
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
|
||||||
|
Co-op Cloud config located on the server itself, and not on your local
|
||||||
|
developer machine. The domain is then set to "default".`,
|
||||||
|
Example: " abra server add 1312.net",
|
||||||
|
Args: cobra.RangeArgs(0, 1),
|
||||||
|
ValidArgsFunction: func(
|
||||||
|
cmd *cobra.Command,
|
||||||
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
if !local {
|
||||||
|
return autocomplete.ServerNameComplete()
|
||||||
|
}
|
||||||
|
return nil, cobra.ShellCompDirectiveDefault
|
||||||
|
},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) > 0 && local {
|
||||||
|
log.Fatal("cannot use [server] and --local together")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) == 0 && !local {
|
||||||
|
log.Fatal("missing argument or --local/-l flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
name := "default"
|
||||||
|
if !local {
|
||||||
|
name = internal.ValidateDomain(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(d1): reasonable 5 second timeout for connections which can't
|
||||||
|
// succeed. The connection is attempted twice, so this results in 10
|
||||||
|
// seconds.
|
||||||
|
timeout := client.WithTimeout(5)
|
||||||
|
|
||||||
|
if local {
|
||||||
|
created, err := createServerDir(name)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("attempting to create client for %s", name)
|
||||||
|
|
||||||
|
if _, err := client.New(name, timeout); err != nil {
|
||||||
|
cleanUp(name)
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if created {
|
||||||
|
log.Info("local server successfully added")
|
||||||
|
} else {
|
||||||
|
log.Warn("local server already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := createServerDir(name)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
created, err := newContext(name)
|
||||||
|
if err != nil {
|
||||||
|
cleanUp(name)
|
||||||
|
log.Fatalf("unable to create local context: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("attempting to create client for %s", name)
|
||||||
|
|
||||||
|
if _, err := client.New(name, timeout); err != nil {
|
||||||
|
cleanUp(name)
|
||||||
|
log.Debugf("ssh %s error: %s", name, sshPkg.Fatal(name, err))
|
||||||
|
log.Fatalf("can't ssh to %s, make sure \"ssh %s\" works", name, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if created {
|
||||||
|
log.Infof("%s successfully added", name)
|
||||||
|
|
||||||
|
if _, err := dns.EnsureIPv4(name); err != nil {
|
||||||
|
log.Warnf("unable to resolve IPv4 for %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Warnf("%s already exists", name)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanUp cleans up the partially created context/client details for a failed
|
// cleanUp cleans up the partially created context/client details for a failed
|
||||||
@ -91,114 +189,16 @@ func createServerDir(name string) (bool, error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverAddCommand = cli.Command{
|
var (
|
||||||
Name: "add",
|
local bool
|
||||||
Aliases: []string{"a"},
|
)
|
||||||
Usage: "Add a new server to your configuration",
|
|
||||||
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
|
func init() {
|
||||||
connection details. You must configure an entry per-host in your ~/.ssh/config
|
ServerAddCommand.Flags().BoolVarP(
|
||||||
for each server. For example:
|
&local,
|
||||||
|
"local",
|
||||||
Host example.com example
|
"l",
|
||||||
Hostname example.com
|
false,
|
||||||
User exampleUser
|
"use local server",
|
||||||
Port 12345
|
)
|
||||||
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
|
|
||||||
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
|
|
||||||
developer machine. The domain is then set to "default".
|
|
||||||
|
|
||||||
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
|
|
||||||
of your ~/.ssh/config. Checks for a valid online domain will be skipped:
|
|
||||||
|
|
||||||
abra server add -D example`,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
internal.DebugFlag,
|
|
||||||
internal.NoInputFlag,
|
|
||||||
internal.NoDomainChecksFlag,
|
|
||||||
localFlag,
|
|
||||||
},
|
|
||||||
Before: internal.SubCommandBefore,
|
|
||||||
ArgsUsage: "<name>",
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
if len(c.Args()) > 0 && local || !internal.ValidateSubCmdFlags(c) {
|
|
||||||
err := errors.New("cannot use <name> and --local together")
|
|
||||||
internal.ShowSubcommandHelpAndError(c, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var name string
|
|
||||||
if local {
|
|
||||||
name = "default"
|
|
||||||
} else {
|
|
||||||
name = internal.ValidateDomain(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE(d1): reasonable 5 second timeout for connections which can't
|
|
||||||
// succeed. The connection is attempted twice, so this results in 10
|
|
||||||
// seconds.
|
|
||||||
timeout := client.WithTimeout(5)
|
|
||||||
|
|
||||||
if local {
|
|
||||||
created, err := createServerDir(name)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("attempting to create client for %s", name)
|
|
||||||
|
|
||||||
if _, err := client.New(name, timeout); err != nil {
|
|
||||||
cleanUp(name)
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if created {
|
|
||||||
log.Info("local server successfully added")
|
|
||||||
} else {
|
|
||||||
log.Warn("local server already exists")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !internal.NoDomainChecks {
|
|
||||||
if _, err := dns.EnsureIPv4(name); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := createServerDir(name)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
created, err := newContext(name)
|
|
||||||
if err != nil {
|
|
||||||
cleanUp(name)
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("attempting to create client for %s", name)
|
|
||||||
if _, err := client.New(name, timeout); err != nil {
|
|
||||||
cleanUp(name)
|
|
||||||
log.Fatal(sshPkg.Fatal(name, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if created {
|
|
||||||
log.Infof("%s successfully added", name)
|
|
||||||
} else {
|
|
||||||
log.Warnf("%s already exists", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
@ -6,25 +6,20 @@ import (
|
|||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/context"
|
contextPkg "coopcloud.tech/abra/pkg/context"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/docker/cli/cli/connhelper/ssh"
|
"github.com/docker/cli/cli/connhelper/ssh"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var serverListCommand = cli.Command{
|
var ServerListCommand = &cobra.Command{
|
||||||
Name: "list",
|
Use: "list [flags]",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Usage: "List managed servers",
|
Short: "List managed servers",
|
||||||
Flags: []cli.Flag{
|
Args: cobra.NoArgs,
|
||||||
internal.DebugFlag,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
internal.MachineReadableFlag,
|
dockerContextStore := contextPkg.NewDefaultDockerContextStore()
|
||||||
internal.OfflineFlag,
|
|
||||||
},
|
|
||||||
Before: internal.SubCommandBefore,
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
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)
|
||||||
@ -46,14 +41,14 @@ var serverListCommand = cli.Command{
|
|||||||
var rows [][]string
|
var rows [][]string
|
||||||
for _, serverName := range serverNames {
|
for _, serverName := range serverNames {
|
||||||
var row []string
|
var row []string
|
||||||
for _, ctx := range contexts {
|
for _, dockerCtx := range contexts {
|
||||||
endpoint, err := context.GetContextEndpoint(ctx)
|
endpoint, err := contextPkg.GetContextEndpoint(dockerCtx)
|
||||||
if err != nil && strings.Contains(err.Error(), "does not exist") {
|
if err != nil && strings.Contains(err.Error(), "does not exist") {
|
||||||
// No local context found, we can continue safely
|
// No local context found, we can continue safely
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Name == serverName {
|
if dockerCtx.Name == serverName {
|
||||||
sp, err := ssh.ParseURL(endpoint)
|
sp, err := ssh.ParseURL(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -85,12 +80,24 @@ var serverListCommand = cli.Command{
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("unable to render to JSON: %s", err)
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(out)
|
fmt.Println(out)
|
||||||
return nil
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(table)
|
if err := formatter.PrintTable(table); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
return nil
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ServerListCommand.Flags().BoolVarP(
|
||||||
|
&internal.MachineReadable,
|
||||||
|
"machine",
|
||||||
|
"m",
|
||||||
|
false,
|
||||||
|
"print machine-readable output",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -1,64 +1,41 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
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/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var allFilter bool
|
var ServerPruneCommand = &cobra.Command{
|
||||||
|
Use: "prune <server> [flags]",
|
||||||
var allFilterFlag = &cli.BoolFlag{
|
|
||||||
Name: "all, a",
|
|
||||||
Usage: "Remove all unused images not just dangling ones",
|
|
||||||
Destination: &allFilter,
|
|
||||||
}
|
|
||||||
|
|
||||||
var volumesFilter bool
|
|
||||||
|
|
||||||
var volumesFilterFlag = &cli.BoolFlag{
|
|
||||||
Name: "volumes, v",
|
|
||||||
Usage: "Prune volumes. This will remove app data, Be Careful!",
|
|
||||||
Destination: &volumesFilter,
|
|
||||||
}
|
|
||||||
|
|
||||||
var serverPruneCommand = cli.Command{
|
|
||||||
Name: "prune",
|
|
||||||
Aliases: []string{"p"},
|
Aliases: []string{"p"},
|
||||||
Usage: "Prune resources on a server",
|
Short: "Prune resources on a server",
|
||||||
Description: `
|
Long: `Prunes unused containers, networks, and dangling images.
|
||||||
Prunes unused containers, networks, and dangling images.
|
|
||||||
|
|
||||||
Use "-v/--volumes" to remove volumes that are not associated with a deployed
|
Use "--volumes/-v" 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.`,
|
||||||
ArgsUsage: "[<server>]",
|
Args: cobra.ExactArgs(1),
|
||||||
Flags: []cli.Flag{
|
ValidArgsFunction: func(
|
||||||
allFilterFlag,
|
cmd *cobra.Command,
|
||||||
volumesFilterFlag,
|
args []string,
|
||||||
internal.DebugFlag,
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
internal.OfflineFlag,
|
return autocomplete.ServerNameComplete()
|
||||||
internal.NoInputFlag,
|
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
BashComplete: autocomplete.ServerNameComplete,
|
serverName := internal.ValidateServer(args)
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
serverName := internal.ValidateServer(c)
|
|
||||||
|
|
||||||
cl, err := client.New(serverName)
|
cl, err := client.New(serverName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var args filters.Args
|
var filterArgs filters.Args
|
||||||
|
|
||||||
ctx := context.Background()
|
cr, err := cl.ContainersPrune(cmd.Context(), filterArgs)
|
||||||
cr, err := cl.ContainersPrune(ctx, args)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -66,7 +43,7 @@ app. This can result in unwanted data loss if not used carefully.`,
|
|||||||
cntSpaceReclaimed := formatter.ByteCountSI(cr.SpaceReclaimed)
|
cntSpaceReclaimed := formatter.ByteCountSI(cr.SpaceReclaimed)
|
||||||
log.Infof("containers pruned: %d; space reclaimed: %s", len(cr.ContainersDeleted), cntSpaceReclaimed)
|
log.Infof("containers pruned: %d; space reclaimed: %s", len(cr.ContainersDeleted), cntSpaceReclaimed)
|
||||||
|
|
||||||
nr, err := cl.NetworksPrune(ctx, args)
|
nr, err := cl.NetworksPrune(cmd.Context(), filterArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -79,7 +56,7 @@ app. This can result in unwanted data loss if not used carefully.`,
|
|||||||
pruneFilters.Add("dangling", "false")
|
pruneFilters.Add("dangling", "false")
|
||||||
}
|
}
|
||||||
|
|
||||||
ir, err := cl.ImagesPrune(ctx, pruneFilters)
|
ir, err := cl.ImagesPrune(cmd.Context(), pruneFilters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -88,7 +65,7 @@ app. This can result in unwanted data loss if not used carefully.`,
|
|||||||
log.Infof("images pruned: %d; space reclaimed: %s", len(ir.ImagesDeleted), imgSpaceReclaimed)
|
log.Infof("images pruned: %d; space reclaimed: %s", len(ir.ImagesDeleted), imgSpaceReclaimed)
|
||||||
|
|
||||||
if volumesFilter {
|
if volumesFilter {
|
||||||
vr, err := cl.VolumesPrune(ctx, args)
|
vr, err := cl.VolumesPrune(cmd.Context(), filterArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -97,6 +74,29 @@ app. This can result in unwanted data loss if not used carefully.`,
|
|||||||
log.Infof("volumes pruned: %d; space reclaimed: %s", len(vr.VolumesDeleted), volSpaceReclaimed)
|
log.Infof("volumes pruned: %d; space reclaimed: %s", len(vr.VolumesDeleted), volSpaceReclaimed)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
allFilter bool
|
||||||
|
volumesFilter bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ServerPruneCommand.Flags().BoolVarP(
|
||||||
|
&allFilter,
|
||||||
|
"all",
|
||||||
|
"a",
|
||||||
|
false,
|
||||||
|
"remove all unused images",
|
||||||
|
)
|
||||||
|
|
||||||
|
ServerPruneCommand.Flags().BoolVarP(
|
||||||
|
&volumesFilter,
|
||||||
|
"volumes",
|
||||||
|
"v",
|
||||||
|
false,
|
||||||
|
"remove volumes",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -9,29 +9,27 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var serverRemoveCommand = cli.Command{
|
var ServerRemoveCommand = &cobra.Command{
|
||||||
Name: "remove",
|
Use: "remove <server> [flags]",
|
||||||
Aliases: []string{"rm"},
|
Aliases: []string{"rm"},
|
||||||
ArgsUsage: "<server>",
|
Short: "Remove a managed server",
|
||||||
Usage: "Remove a managed server",
|
Long: `Remove a managed server.
|
||||||
Description: `
|
|
||||||
Remove a managed server.
|
|
||||||
|
|
||||||
Abra will remove the internal bookkeeping (~/.abra/servers/...) and underlying
|
Abra will remove the internal bookkeeping ($ABRA_DIR/servers/...) and
|
||||||
client connection context. This server will then be lost in time, like tears in
|
underlying client connection context. This server will then be lost in time,
|
||||||
rain.`,
|
like tears in rain.`,
|
||||||
Flags: []cli.Flag{
|
Args: cobra.ExactArgs(1),
|
||||||
internal.DebugFlag,
|
ValidArgsFunction: func(
|
||||||
internal.NoInputFlag,
|
cmd *cobra.Command,
|
||||||
internal.OfflineFlag,
|
args []string,
|
||||||
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return autocomplete.ServerNameComplete()
|
||||||
},
|
},
|
||||||
Before: internal.SubCommandBefore,
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
BashComplete: autocomplete.ServerNameComplete,
|
serverName := internal.ValidateServer(args)
|
||||||
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)
|
||||||
@ -43,6 +41,6 @@ rain.`,
|
|||||||
|
|
||||||
log.Infof("%s is now lost in time, like tears in rain", serverName)
|
log.Infof("%s is now lost in time, like tears in rain", serverName)
|
||||||
|
|
||||||
return nil
|
return
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,10 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import "github.com/spf13/cobra"
|
||||||
"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 = &cobra.Command{
|
||||||
Name: "server",
|
Use: "server [cmd] [args] [flags]",
|
||||||
Aliases: []string{"s"},
|
Aliases: []string{"s"},
|
||||||
Usage: "Manage servers",
|
Short: "Manage servers",
|
||||||
Subcommands: []cli.Command{
|
|
||||||
serverAddCommand,
|
|
||||||
serverListCommand,
|
|
||||||
serverRemoveCommand,
|
|
||||||
serverPruneCommand,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
@ -21,46 +21,25 @@ import (
|
|||||||
"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"
|
||||||
dockerclient "github.com/docker/docker/client"
|
dockerclient "github.com/docker/docker/client"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const SERVER = "localhost"
|
const SERVER = "localhost"
|
||||||
|
|
||||||
var majorUpdate bool
|
// NotifyCommand checks for available upgrades.
|
||||||
var majorFlag = &cli.BoolFlag{
|
var NotifyCommand = &cobra.Command{
|
||||||
Name: "major, m",
|
Use: "notify [flags]",
|
||||||
Usage: "Also check for major updates",
|
|
||||||
Destination: &majorUpdate,
|
|
||||||
}
|
|
||||||
|
|
||||||
var updateAll bool
|
|
||||||
var allFlag = &cli.BoolFlag{
|
|
||||||
Name: "all, a",
|
|
||||||
Usage: "Update all deployed apps",
|
|
||||||
Destination: &updateAll,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify checks for available upgrades
|
|
||||||
var Notify = cli.Command{
|
|
||||||
Name: "notify",
|
|
||||||
Aliases: []string{"n"},
|
Aliases: []string{"n"},
|
||||||
Usage: "Check for available upgrades",
|
Short: "Check for available upgrades",
|
||||||
Flags: []cli.Flag{
|
Long: `Notify on new versions for deployed apps.
|
||||||
internal.DebugFlag,
|
|
||||||
majorFlag,
|
|
||||||
internal.OfflineFlag,
|
|
||||||
},
|
|
||||||
Before: internal.SubCommandBefore,
|
|
||||||
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/-m" to include new major versions.`,
|
||||||
Action: func(c *cli.Context) error {
|
Args: cobra.NoArgs,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
cl, err := client.New("default")
|
cl, err := client.New("default")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -85,27 +64,15 @@ Use "--major" to include new major versions.`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpgradeApp upgrades apps.
|
// UpgradeCommand upgrades apps.
|
||||||
var UpgradeApp = cli.Command{
|
var UpgradeCommand = &cobra.Command{
|
||||||
Name: "upgrade",
|
Use: "upgrade [[stack] [recipe] | --all] [flags]",
|
||||||
Aliases: []string{"u"},
|
Aliases: []string{"u"},
|
||||||
Usage: "Upgrade apps",
|
Short: "Upgrade apps",
|
||||||
ArgsUsage: "<stack-name> <recipe>",
|
Long: `Upgrade an app by specifying stack name and recipe.
|
||||||
Flags: []cli.Flag{
|
|
||||||
internal.DebugFlag,
|
|
||||||
internal.ChaosFlag,
|
|
||||||
majorFlag,
|
|
||||||
allFlag,
|
|
||||||
internal.OfflineFlag,
|
|
||||||
},
|
|
||||||
Before: internal.SubCommandBefore,
|
|
||||||
Description: `
|
|
||||||
Upgrade an app by specifying stack name and recipe.
|
|
||||||
|
|
||||||
Use "--all" to upgrade every deployed app.
|
Use "--all" to upgrade every deployed app.
|
||||||
|
|
||||||
@ -113,24 +80,37 @@ For each app with auto updates enabled, the deployed version is compared with
|
|||||||
the current recipe catalogue version. If a new patch/minor version is
|
the current recipe catalogue version. If a new patch/minor version is
|
||||||
available, the app is upgraded.
|
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/-m" flag. You probably don't want
|
||||||
as it will break things. Only apps that are not deployed with "--chaos" are
|
that as it will break things. Only apps that are not deployed with "--chaos/-C"
|
||||||
upgraded, to update chaos deployments use the "--chaos" flag. Use it with care.`,
|
are upgraded, to update chaos deployments use the "--chaos/-C" flag. Use it
|
||||||
Action: func(c *cli.Context) error {
|
with care.`,
|
||||||
|
Args: cobra.RangeArgs(0, 2),
|
||||||
|
// TODO(d1): complete stack/recipe
|
||||||
|
// ValidArgsFunction: func(
|
||||||
|
// cmd *cobra.Command,
|
||||||
|
// args []string,
|
||||||
|
// toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
// },
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
cl, err := client.New("default")
|
cl, err := client.New("default")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !updateAll && len(args) != 2 {
|
||||||
|
log.Fatal("missing arguments or --all/-a flag")
|
||||||
|
}
|
||||||
|
|
||||||
if !updateAll {
|
if !updateAll {
|
||||||
stackName := c.Args().Get(0)
|
stackName := args[0]
|
||||||
recipeName := c.Args().Get(1)
|
recipeName := args[1]
|
||||||
|
|
||||||
err = tryUpgrade(cl, stackName, recipeName)
|
err = tryUpgrade(cl, stackName, recipeName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stacks, err := stack.GetStacks(cl)
|
stacks, err := stack.GetStacks(cl)
|
||||||
@ -150,8 +130,6 @@ upgraded, to update chaos deployments use the "--chaos" flag. Use it with care.`
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,7 +289,7 @@ func getAvailableUpgrades(cl *dockerclient.Client, stackName string, recipeName
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if 0 < versionDelta.UpgradeType() && (versionDelta.UpgradeType() < 4 || majorUpdate) {
|
if 0 < versionDelta.UpgradeType() && (versionDelta.UpgradeType() < 4 || includeMajorUpdates) {
|
||||||
availableUpgrades = append(availableUpgrades, version)
|
availableUpgrades = append(availableUpgrades, version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -468,41 +446,87 @@ func upgrade(cl *dockerclient.Client, stackName, recipeName, upgradeVersion stri
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAbraApp(version, commit string) *cli.App {
|
func newKadabraApp(version, commit string) *cobra.Command {
|
||||||
app := &cli.App{
|
rootCmd := &cobra.Command{
|
||||||
Name: "kadabra",
|
Use: "kadabra [cmd] [flags]",
|
||||||
Usage: `The Co-op Cloud auto-updater
|
|
||||||
____ ____ _ _
|
|
||||||
/ ___|___ ___ _ __ / ___| | ___ _ _ __| |
|
|
||||||
| | / _ \ _____ / _ \| '_ \ | | | |/ _ \| | | |/ _' |
|
|
||||||
| |__| (_) |_____| (_) | |_) | | |___| | (_) | |_| | (_| |
|
|
||||||
\____\___/ \___/| .__/ \____|_|\___/ \__,_|\__,_|
|
|
||||||
|_|
|
|
||||||
`,
|
|
||||||
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
|
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
|
||||||
Commands: []cli.Command{
|
Short: "The Co-op Cloud auto-updater 🤖 🚀",
|
||||||
Notify,
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||||
UpgradeApp,
|
log.Logger.SetStyles(log.Styles())
|
||||||
|
charmLog.SetDefault(log.Logger)
|
||||||
|
|
||||||
|
if internal.Debug {
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
log.SetOutput(os.Stderr)
|
||||||
|
log.SetReportCaller(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("kadabra version %s, commit %s", version, commit)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Before = func(c *cli.Context) error {
|
rootCmd.PersistentFlags().BoolVarP(
|
||||||
log.Logger.SetStyles(log.Styles())
|
&internal.Debug, "debug", "d", false,
|
||||||
charmLog.SetDefault(log.Logger)
|
"show debug messages",
|
||||||
|
)
|
||||||
|
|
||||||
log.Debugf("kadabra version %s, commit %s", version, commit)
|
rootCmd.PersistentFlags().BoolVarP(
|
||||||
|
&internal.NoInput, "no-input", "n", false,
|
||||||
|
"toggle non-interactive mode",
|
||||||
|
)
|
||||||
|
|
||||||
return nil
|
rootCmd.AddCommand(
|
||||||
}
|
NotifyCommand,
|
||||||
|
UpgradeCommand,
|
||||||
|
)
|
||||||
|
|
||||||
return app
|
return rootCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunApp runs CLI abra app.
|
// RunApp runs CLI abra app.
|
||||||
func RunApp(version, commit string) {
|
func RunApp(version, commit string) {
|
||||||
app := newAbraApp(version, commit)
|
app := newKadabraApp(version, commit)
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
if err := app.Execute(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
includeMajorUpdates bool
|
||||||
|
updateAll bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
NotifyCommand.Flags().BoolVarP(
|
||||||
|
&includeMajorUpdates,
|
||||||
|
"major",
|
||||||
|
"m",
|
||||||
|
false,
|
||||||
|
"check for major updates",
|
||||||
|
)
|
||||||
|
|
||||||
|
UpgradeCommand.Flags().BoolVarP(
|
||||||
|
&internal.Chaos,
|
||||||
|
"chaos",
|
||||||
|
"C",
|
||||||
|
false,
|
||||||
|
"ignore uncommitted recipes changes",
|
||||||
|
)
|
||||||
|
|
||||||
|
UpgradeCommand.Flags().BoolVarP(
|
||||||
|
&includeMajorUpdates,
|
||||||
|
"major",
|
||||||
|
"m",
|
||||||
|
false,
|
||||||
|
"check for major updates",
|
||||||
|
)
|
||||||
|
|
||||||
|
UpgradeCommand.Flags().BoolVarP(
|
||||||
|
&updateAll,
|
||||||
|
"all",
|
||||||
|
"a",
|
||||||
|
false,
|
||||||
|
"update all deployed apps",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
56
cli/upgrade.go
Normal file
56
cli/upgrade.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Package cli provides the interface for the command-line.
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"coopcloud.tech/abra/cli/internal"
|
||||||
|
"coopcloud.tech/abra/pkg/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpgradeCommand upgrades abra in-place.
|
||||||
|
var UpgradeCommand = &cobra.Command{
|
||||||
|
Use: "upgrade [flags]",
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Short: "Upgrade abra",
|
||||||
|
Long: `Upgrade abra in-place with the latest stable or release candidate.
|
||||||
|
|
||||||
|
By default, the latest stable release is downloaded.
|
||||||
|
|
||||||
|
Use "--rc/-r" to install the latest release candidate. Please bear in mind that
|
||||||
|
it may contain absolutely catastrophic deal-breaker bugs. Thank you very much
|
||||||
|
for the testing efforts 💗`,
|
||||||
|
Example: " abra upgrade --rc",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
mainURL := "https://install.abra.coopcloud.tech"
|
||||||
|
c := exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash", mainURL))
|
||||||
|
|
||||||
|
if releaseCandidate {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("attempting to run %s", c)
|
||||||
|
|
||||||
|
if err := internal.RunCmd(c); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
releaseCandidate bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
UpgradeCommand.Flags().BoolVarP(
|
||||||
|
&releaseCandidate,
|
||||||
|
"rc",
|
||||||
|
"r",
|
||||||
|
false,
|
||||||
|
"install release candidate (may contain bugs)",
|
||||||
|
)
|
||||||
|
}
|
@ -19,5 +19,5 @@ func main() {
|
|||||||
Commit = " "
|
Commit = " "
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.RunApp(Version, Commit)
|
cli.Run(Version, Commit)
|
||||||
}
|
}
|
||||||
|
109
go.mod
109
go.mod
@ -1,43 +1,49 @@
|
|||||||
module coopcloud.tech/abra
|
module coopcloud.tech/abra
|
||||||
|
|
||||||
go 1.21
|
go 1.22.7
|
||||||
|
|
||||||
|
toolchain go1.23.1
|
||||||
|
|
||||||
|
replace github.com/urfave/cli/v3 => github.com/urfave/cli/v3 v3.0.0-alpha9.1.0.20241019193437-5053ec708a44
|
||||||
|
|
||||||
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/lipgloss v1.0.0
|
||||||
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.4.1+incompatible
|
||||||
github.com/docker/docker v27.0.3+incompatible
|
github.com/docker/docker v27.4.1+incompatible
|
||||||
github.com/docker/go-units v0.5.0
|
github.com/docker/go-units v0.5.0
|
||||||
github.com/go-git/go-git/v5 v5.12.0
|
github.com/go-git/go-git/v5 v5.12.0
|
||||||
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.1
|
||||||
github.com/moby/term v0.5.0
|
github.com/moby/term v0.5.0
|
||||||
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.17.1
|
||||||
golang.org/x/term v0.22.0
|
github.com/urfave/cli/v3 v3.0.0-alpha9
|
||||||
|
golang.org/x/term v0.27.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
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
dario.cat/mergo v1.0.0 // indirect
|
dario.cat/mergo v1.0.1 // indirect
|
||||||
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
github.com/ProtonMail/go-crypto v1.1.3 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
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/x/ansi v0.1.3 // indirect
|
github.com/charmbracelet/x/ansi v0.6.0 // indirect
|
||||||
github.com/cloudflare/circl v1.3.9 // indirect
|
github.com/cloudflare/circl v1.5.0 // 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/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
|
github.com/cyphar/filepath-securejoin v0.3.6 // 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
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||||
@ -46,77 +52,83 @@ require (
|
|||||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
|
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
github.com/go-git/go-billy/v5 v5.6.0 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||||
github.com/klauspost/compress v1.17.9 // indirect
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
github.com/moby/sys/user v0.1.0 // indirect
|
github.com/moby/sys/mountinfo v0.6.2 // indirect
|
||||||
|
github.com/moby/sys/user v0.3.0 // indirect
|
||||||
|
github.com/moby/sys/userns v0.1.0 // indirect
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/muesli/termenv v0.15.2 // indirect
|
github.com/muesli/termenv v0.15.2 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/runc v1.1.13 // indirect
|
github.com/opencontainers/runc v1.1.13 // indirect
|
||||||
|
github.com/opencontainers/runtime-spec v1.1.0 // indirect
|
||||||
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/common v0.55.0 // indirect
|
github.com/prometheus/common v0.61.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/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.3.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.28.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect
|
go.opentelemetry.io/otel v1.33.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.33.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.28.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.28.0 // indirect
|
go.opentelemetry.io/otel/metric v1.33.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
go.opentelemetry.io/otel/sdk v1.33.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
go.opentelemetry.io/otel/sdk/metric v1.33.0 // indirect
|
||||||
golang.org/x/crypto v0.25.0 // indirect
|
go.opentelemetry.io/otel/trace v1.33.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
go.opentelemetry.io/proto/otlp v1.4.0 // indirect
|
||||||
golang.org/x/net v0.27.0 // indirect
|
golang.org/x/crypto v0.31.0 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/net v0.33.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/sync v0.10.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
|
golang.org/x/time v0.8.0 // indirect
|
||||||
google.golang.org/grpc v1.65.0 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8 // indirect
|
||||||
google.golang.org/protobuf v1.34.2 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
|
||||||
|
google.golang.org/grpc v1.69.2 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.1 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/containerd/containerd v1.7.19 // indirect
|
|
||||||
github.com/containers/image v3.0.2+incompatible
|
github.com/containers/image v3.0.2+incompatible
|
||||||
github.com/containers/storage v1.38.2 // indirect
|
github.com/containers/storage v1.38.2 // indirect
|
||||||
github.com/decentral1se/passgen v1.0.1
|
github.com/decentral1se/passgen v1.0.1
|
||||||
@ -126,14 +138,13 @@ require (
|
|||||||
github.com/gorilla/mux v1.8.1 // indirect
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.7
|
github.com/hashicorp/go-retryablehttp v0.7.7
|
||||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||||
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.20.5 // 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.10.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.28.0
|
||||||
)
|
)
|
||||||
|
287
go.sum
287
go.sum
@ -24,8 +24,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
|||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||||
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb h1:Ws6WEwKXeaYEkfdkX6AqX1XLPuaCeyStEtxbmEJPllk=
|
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb h1:Ws6WEwKXeaYEkfdkX6AqX1XLPuaCeyStEtxbmEJPllk=
|
||||||
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
|
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
|
||||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355 h1:tCv2B4qoN6RMheKDnCzIafOkWS5BB1h7hwhmo+9bVeE=
|
git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355 h1:tCv2B4qoN6RMheKDnCzIafOkWS5BB1h7hwhmo+9bVeE=
|
||||||
git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355/go.mod h1:Q8V1zbtPAlzYSr/Dvky3wS6x58IQAl3rot2me1oSO2Q=
|
git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355/go.mod h1:Q8V1zbtPAlzYSr/Dvky3wS6x58IQAl3rot2me1oSO2Q=
|
||||||
@ -49,7 +49,6 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
|
|||||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
|
||||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
@ -80,8 +79,8 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0
|
|||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
|
||||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
@ -104,6 +103,8 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l
|
|||||||
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
|
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
|
||||||
|
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
|
||||||
github.com/beorn7/perks v0.0.0-20150223135152-b965b613227f/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20150223135152-b965b613227f/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
@ -126,24 +127,30 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembj
|
|||||||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
|
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
|
||||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=
|
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=
|
||||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
|
||||||
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
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.1 h1:a8KgVPHa7kOoP95vm2tQQrjD2AKhbWmfr4uJ2RW6kNk=
|
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
|
||||||
github.com/charmbracelet/lipgloss v0.11.1/go.mod h1:beLlcmkF7MWA+5UrKKIRo/VJ21xGXr7YJ9miWfdMRIU=
|
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
|
||||||
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.3 h1:RBh/eleNWML5R524mjUF0yVRePTwqN9tPtV+DPgO5Lw=
|
github.com/charmbracelet/x/ansi v0.5.2 h1:dEa1x2qdOZXD/6439s+wF7xjV+kZLu/iN00GuXXrU9E=
|
||||||
github.com/charmbracelet/x/ansi v0.1.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
github.com/charmbracelet/x/ansi v0.5.2/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q=
|
||||||
|
github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA=
|
||||||
|
github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q=
|
||||||
|
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
|
||||||
|
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||||
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=
|
||||||
|
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
|
||||||
|
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
@ -156,9 +163,8 @@ github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2u
|
|||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ=
|
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ=
|
||||||
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
||||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
|
||||||
github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE=
|
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
@ -197,8 +203,6 @@ github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09Zvgq
|
|||||||
github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s=
|
github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s=
|
||||||
github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
|
github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
|
||||||
github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c=
|
github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c=
|
||||||
github.com/containerd/containerd v1.7.19 h1:/xQ4XRJ0tamDkdzrrBAUy/LE5nCcxFKdBm4EcPrSMEE=
|
|
||||||
github.com/containerd/containerd v1.7.19/go.mod h1:h4FtNYUUMB4Phr6v+xG89RYKj9XccvbNSCKjdufCrkc=
|
|
||||||
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||||
github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||||
github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||||
@ -276,6 +280,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
|
|||||||
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 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/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
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=
|
||||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
@ -283,8 +289,10 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
|||||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
|
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
|
||||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||||
github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo=
|
github.com/cyphar/filepath-securejoin v0.3.4 h1:VBWugsJh2ZxJmLFSM06/0qzQyiQX2Qs0ViKrUAcqdZ8=
|
||||||
github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM=
|
||||||
|
github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
|
||||||
|
github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
|
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
|
||||||
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
|
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
|
||||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
|
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
|
||||||
@ -303,16 +311,20 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
|||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||||
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/cli v27.0.3+incompatible h1:usGs0/BoBW8MWxGeEtqPMkzOY56jZ6kYlSN5BLDioCQ=
|
github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ=
|
||||||
github.com/docker/cli v27.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
|
github.com/docker/cli v27.4.1+incompatible h1:VzPiUlRJ/xh+otB75gva3r05isHMo5wXDfPRi5/b4hI=
|
||||||
|
github.com/docker/cli v27.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
||||||
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker v27.0.3+incompatible h1:aBGI9TeQ4MPlhquTQKq9XbK79rKFVwXNUAYz9aXyEBE=
|
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
|
||||||
github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/docker v27.4.1+incompatible h1:ZJvcY7gfwHn1JF48PfbyXg7Jyt9ZCWDW+GGXOIxEwp4=
|
||||||
|
github.com/docker/docker v27.4.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
||||||
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
||||||
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||||
@ -374,8 +386,8 @@ github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
|||||||
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||||
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
|
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8=
|
||||||
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
|
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||||
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
|
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
|
||||||
@ -411,8 +423,8 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
|
|||||||
github.com/go-sql-driver/mysql v1.3.0 h1:pgwjLi/dvffoP9aabwkT3AKpXQM93QARkjFhDDqC1UE=
|
github.com/go-sql-driver/mysql v1.3.0 h1:pgwjLi/dvffoP9aabwkT3AKpXQM93QARkjFhDDqC1UE=
|
||||||
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc=
|
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||||
github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||||
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||||
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
||||||
@ -436,8 +448,8 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er
|
|||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
@ -517,9 +529,12 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de
|
|||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
|
||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||||
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
@ -570,7 +585,6 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
|
|||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
|
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||||
@ -582,8 +596,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||||||
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
@ -599,6 +613,8 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
|
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
@ -621,8 +637,8 @@ 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.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/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=
|
||||||
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
@ -656,13 +672,15 @@ github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2J
|
|||||||
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
|
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
|
||||||
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
||||||
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||||
github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI=
|
github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0=
|
||||||
github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg=
|
github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8=
|
||||||
github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
|
github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
|
||||||
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
|
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
|
||||||
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
|
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||||
|
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||||
|
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||||
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
|
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
|
||||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||||
@ -702,8 +720,8 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
|
|||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
||||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||||
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||||
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||||
@ -760,8 +778,8 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
|
|||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||||
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
@ -775,8 +793,10 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
|
|||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc=
|
||||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
|
||||||
|
github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ=
|
||||||
|
github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s=
|
||||||
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
@ -797,15 +817,16 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
|
|||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
|
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
|
||||||
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 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=
|
||||||
github.com/schollz/progressbar/v3 v3.14.4 h1:W9ZrDSJk7eqmQhd3uxFNNcTr0QL+xuGNI9dEMrw0r74=
|
github.com/schollz/progressbar/v3 v3.17.1 h1:bI1MTaoQO+v5kzklBjYNRQLoVpe0zbyRZNK6DFkVC5U=
|
||||||
github.com/schollz/progressbar/v3 v3.14.4/go.mod h1:aT3UQ7yGm+2ZjeXPqsjTenwL3ddUiuZ0kfQ/2tHlyNI=
|
github.com/schollz/progressbar/v3 v3.17.1/go.mod h1:RzqpnsPQNjUyIgdglUjRLgD7sVnxN1wpmBMV+UiEbL4=
|
||||||
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
|
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
|
||||||
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||||
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
||||||
@ -822,8 +843,8 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
|
|||||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
|
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
|
||||||
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
@ -858,9 +879,6 @@ github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
@ -868,11 +886,8 @@ 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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
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/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=
|
||||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
|
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
|
||||||
@ -890,8 +905,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/v3 v3.0.0-alpha9.1.0.20241019193437-5053ec708a44 h1:BeSTAZEDkDVNv9EOrycIGCkEg+6EhRRgSsbdc93Q3OM=
|
||||||
github.com/urfave/cli v1.22.15/go.mod h1:wSan1hmo5zeyLGBjRJbzRTNk8gwoYa2B9n4q9dmRIc0=
|
github.com/urfave/cli/v3 v3.0.0-alpha9.1.0.20241019193437-5053ec708a44/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
|
||||||
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=
|
||||||
@ -930,29 +945,51 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
|||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw=
|
||||||
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
|
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
|
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s=
|
go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw=
|
go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.33.0 h1:7F29RDmnlqk6B5d+sUqemt8TBfDqxryYW5gX6L74RFA=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.33.0/go.mod h1:ZiGDq7xwDMKmWDrN1XsXAj0iC7hns+2DhxBFSncNHSE=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||||
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
|
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
|
||||||
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
|
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
|
||||||
go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
|
go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=
|
||||||
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
|
go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08=
|
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg=
|
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
|
||||||
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM=
|
||||||
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCtNbsP3JkNqU=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q=
|
||||||
|
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
|
||||||
|
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
||||||
|
go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
|
||||||
|
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
|
||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
@ -977,10 +1014,10 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP
|
|||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@ -991,8 +1028,10 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
|
||||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
|
||||||
|
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
|
||||||
|
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@ -1015,7 +1054,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@ -1057,11 +1095,10 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
|||||||
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@ -1079,9 +1116,10 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@ -1159,22 +1197,19 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
|
||||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@ -1184,18 +1219,18 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@ -1241,7 +1276,6 @@ golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4X
|
|||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@ -1290,10 +1324,14 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG
|
|||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 h1:pgr/4QbFyktUv9CtQ/Fq4gzEE6/Xs7iCXbktaGzLHbQ=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8 h1:st3LcW/BPi75W4q1jJTEor/QWwbNlPlDG0JTn6XhZu0=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:klhJGKFyG8Tn50enBn7gizg4nXGXJ+jqEREdCWaPcV4=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
|
||||||
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
@ -1313,8 +1351,10 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp
|
|||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
|
||||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
|
||||||
|
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
|
||||||
|
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@ -1328,8 +1368,10 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
|||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
|
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||||
|
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII=
|
gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII=
|
||||||
@ -1369,6 +1411,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||||
|
@ -89,6 +89,8 @@ type App struct {
|
|||||||
Env envfile.AppEnv
|
Env envfile.AppEnv
|
||||||
Server string
|
Server string
|
||||||
Path string
|
Path string
|
||||||
|
|
||||||
|
status *Status
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type aliases to make code hints easier to understand
|
// Type aliases to make code hints easier to understand
|
||||||
@ -132,6 +134,19 @@ func StackName(appName string) string {
|
|||||||
return stackName
|
return stackName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a App) Status() (Status, error) {
|
||||||
|
if a.status != nil {
|
||||||
|
return *a.status, nil
|
||||||
|
}
|
||||||
|
appStatuses, err := GetAppStatuses([]App{a}, true)
|
||||||
|
if err != nil {
|
||||||
|
return Status{}, err
|
||||||
|
}
|
||||||
|
appStatus := appStatuses[a.StackName()]
|
||||||
|
a.status = &appStatus
|
||||||
|
return appStatus, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Filters retrieves app filters for querying the container runtime. By default
|
// Filters retrieves app filters for querying the container runtime. By default
|
||||||
// it filters on all services in the app. It is also possible to pass an
|
// it filters on all services in the app. It is also possible to pass an
|
||||||
// otional list of service names, which get filtered instead.
|
// otional list of service names, which get filtered instead.
|
||||||
@ -397,9 +412,17 @@ func SanitiseAppName(name string) string {
|
|||||||
return strings.ReplaceAll(name, ".", "_")
|
return strings.ReplaceAll(name, ".", "_")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Status struct {
|
||||||
|
Status string
|
||||||
|
Version string
|
||||||
|
Chaos bool
|
||||||
|
ChaosVersion string
|
||||||
|
AutoUpdate bool
|
||||||
|
}
|
||||||
|
|
||||||
// GetAppStatuses queries servers to check the deployment status of given apps.
|
// GetAppStatuses queries servers to check the deployment status of given apps.
|
||||||
func GetAppStatuses(apps []App, MachineReadable bool) (map[string]map[string]string, error) {
|
func GetAppStatuses(apps []App, MachineReadable bool) (map[string]Status, error) {
|
||||||
statuses := make(map[string]map[string]string)
|
statuses := make(map[string]Status)
|
||||||
|
|
||||||
servers := make(map[string]struct{})
|
servers := make(map[string]struct{})
|
||||||
for _, app := range apps {
|
for _, app := range apps {
|
||||||
@ -435,36 +458,32 @@ func GetAppStatuses(apps []App, MachineReadable bool) (map[string]map[string]str
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, service := range status.Services {
|
for _, service := range status.Services {
|
||||||
result := make(map[string]string)
|
result := Status{}
|
||||||
name := service.Spec.Labels[convert.LabelNamespace]
|
name := service.Spec.Labels[convert.LabelNamespace]
|
||||||
|
|
||||||
if _, ok := statuses[name]; !ok {
|
if _, ok := statuses[name]; !ok {
|
||||||
result["status"] = "deployed"
|
result.Status = "deployed"
|
||||||
}
|
}
|
||||||
|
|
||||||
labelKey := fmt.Sprintf("coop-cloud.%s.chaos", name)
|
labelKey := fmt.Sprintf("coop-cloud.%s.chaos", name)
|
||||||
chaos, ok := service.Spec.Labels[labelKey]
|
chaos, ok := service.Spec.Labels[labelKey]
|
||||||
if ok {
|
if ok {
|
||||||
result["chaos"] = chaos
|
result.Chaos = chaos == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
labelKey = fmt.Sprintf("coop-cloud.%s.chaos-version", name)
|
labelKey = fmt.Sprintf("coop-cloud.%s.chaos-version", name)
|
||||||
if chaosVersion, ok := service.Spec.Labels[labelKey]; ok {
|
if chaosVersion, ok := service.Spec.Labels[labelKey]; ok {
|
||||||
result["chaosVersion"] = chaosVersion
|
result.ChaosVersion = chaosVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
labelKey = fmt.Sprintf("coop-cloud.%s.autoupdate", name)
|
labelKey = fmt.Sprintf("coop-cloud.%s.autoupdate", name)
|
||||||
if autoUpdate, ok := service.Spec.Labels[labelKey]; ok {
|
if autoUpdate, ok := service.Spec.Labels[labelKey]; ok {
|
||||||
result["autoUpdate"] = autoUpdate
|
result.AutoUpdate = autoUpdate == "true"
|
||||||
} else {
|
|
||||||
result["autoUpdate"] = "false"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
labelKey = fmt.Sprintf("coop-cloud.%s.version", name)
|
labelKey = fmt.Sprintf("coop-cloud.%s.version", name)
|
||||||
if version, ok := service.Spec.Labels[labelKey]; ok {
|
if version, ok := service.Spec.Labels[labelKey]; ok {
|
||||||
result["version"] = version
|
result.Version = version
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
statuses[name] = result
|
statuses[name] = result
|
||||||
@ -615,7 +634,7 @@ func (a App) WriteRecipeVersion(version string, dryRun bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !skipped {
|
if !skipped {
|
||||||
log.Infof("version %s saved to %s.env", version, a.Domain)
|
log.Debugf("version %s saved to %s.env", version, a.Domain)
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("skipping version %s write as already exists in %s.env", version, a.Domain)
|
log.Debugf("skipping version %s write as already exists in %s.env", version, a.Domain)
|
||||||
}
|
}
|
||||||
|
@ -2,101 +2,123 @@ package autocomplete
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/app"
|
"coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/urfave/cli"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AppNameComplete copletes app names.
|
// AppNameComplete copletes app names.
|
||||||
func AppNameComplete(c *cli.Context) {
|
func AppNameComplete() ([]string, cobra.ShellCompDirective) {
|
||||||
appNames, err := app.GetAppNames()
|
appFiles, err := app.LoadAppFiles("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(err)
|
err := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
|
return []string{err}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.NArg() > 0 {
|
var appNames []string
|
||||||
return
|
for appName := range appFiles {
|
||||||
|
appNames = append(appNames, appName)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, a := range appNames {
|
return appNames, cobra.ShellCompDirectiveDefault
|
||||||
fmt.Println(a)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServiceNameComplete(appName string) {
|
func ServiceNameComplete(appName string) ([]string, cobra.ShellCompDirective) {
|
||||||
serviceNames, err := app.GetAppServiceNames(appName)
|
serviceNames, err := app.GetAppServiceNames(appName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
err := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
}
|
return []string{err}, cobra.ShellCompDirectiveError
|
||||||
for _, s := range serviceNames {
|
|
||||||
fmt.Println(s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return serviceNames, cobra.ShellCompDirectiveDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecipeNameComplete completes recipe names.
|
// RecipeNameComplete completes recipe names.
|
||||||
func RecipeNameComplete(c *cli.Context) {
|
func RecipeNameComplete() ([]string, cobra.ShellCompDirective) {
|
||||||
catl, err := recipe.ReadRecipeCatalogue(false)
|
catl, err := recipe.ReadRecipeCatalogue(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(err)
|
err := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
}
|
return []string{err}, cobra.ShellCompDirectiveError
|
||||||
|
|
||||||
if c.NArg() > 0 {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var recipeNames []string
|
||||||
for name := range catl {
|
for name := range catl {
|
||||||
fmt.Println(name)
|
recipeNames = append(recipeNames, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return recipeNames, cobra.ShellCompDirectiveDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecipeVersionComplete completes versions for the recipe.
|
// RecipeVersionComplete completes versions for the recipe.
|
||||||
func RecipeVersionComplete(recipeName string) {
|
func RecipeVersionComplete(recipeName string) ([]string, cobra.ShellCompDirective) {
|
||||||
catl, err := recipe.ReadRecipeCatalogue(false)
|
catl, err := recipe.ReadRecipeCatalogue(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(err)
|
err := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
|
return []string{err}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var recipeVersions []string
|
||||||
for _, v := range catl[recipeName].Versions {
|
for _, v := range catl[recipeName].Versions {
|
||||||
for v2 := range v {
|
for v2 := range v {
|
||||||
fmt.Println(v2)
|
recipeVersions = append(recipeVersions, v2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return recipeVersions, cobra.ShellCompDirectiveDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerNameComplete completes server names.
|
// ServerNameComplete completes server names.
|
||||||
func ServerNameComplete(c *cli.Context) {
|
func ServerNameComplete() ([]string, cobra.ShellCompDirective) {
|
||||||
files, err := app.LoadAppFiles("")
|
files, err := app.LoadAppFiles("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
err := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
}
|
return []string{err}, cobra.ShellCompDirectiveError
|
||||||
|
|
||||||
if c.NArg() > 0 {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var serverNames []string
|
||||||
for _, appFile := range files {
|
for _, appFile := range files {
|
||||||
fmt.Println(appFile.Server)
|
serverNames = append(serverNames, appFile.Server)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return serverNames, cobra.ShellCompDirectiveDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubcommandComplete completes sub-commands.
|
// CommandNameComplete completes recipe commands.
|
||||||
func SubcommandComplete(c *cli.Context) {
|
func CommandNameComplete(appName string) ([]string, cobra.ShellCompDirective) {
|
||||||
if c.NArg() > 0 {
|
app, err := app.Get(appName)
|
||||||
return
|
if err != nil {
|
||||||
|
err := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
|
return []string{err}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
|
||||||
subcmds := []string{
|
cmdNames, err := appPkg.ReadAbraShCmdNames(app.Recipe.AbraShPath)
|
||||||
"app",
|
if err != nil {
|
||||||
"autocomplete",
|
err := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
"catalogue",
|
return []string{err}, cobra.ShellCompDirectiveError
|
||||||
"recipe",
|
|
||||||
"server",
|
|
||||||
"upgrade",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cmd := range subcmds {
|
sort.Strings(cmdNames)
|
||||||
fmt.Println(cmd)
|
|
||||||
}
|
return cmdNames, cobra.ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecretsComplete completes recipe secrets.
|
||||||
|
func SecretComplete(recipeName string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
r := recipe.Get(recipeName)
|
||||||
|
|
||||||
|
config, err := r.GetComposeConfig(nil)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
|
return []string{err}, cobra.ShellCompDirectiveError
|
||||||
|
}
|
||||||
|
|
||||||
|
var secretNames []string
|
||||||
|
for name := range config.Secrets {
|
||||||
|
secretNames = append(secretNames, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return secretNames, cobra.ShellCompDirectiveDefault
|
||||||
}
|
}
|
||||||
|
@ -107,5 +107,13 @@ 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"
|
|
||||||
|
// NOTE(d1): please note, this was done purely out of laziness on our part
|
||||||
|
// AFAICR. it's easy to punt the value into the label because that is what is
|
||||||
|
// expects. it's not particularly useful in terms of UI/UX but hey, nobody
|
||||||
|
// complained yet!
|
||||||
|
CHAOS_DEFAULT = "false"
|
||||||
|
|
||||||
|
NO_DOMAIN_DEFAULT = "N/A"
|
||||||
|
NO_VERSION_DEFAULT = "N/A"
|
||||||
)
|
)
|
||||||
|
@ -9,12 +9,11 @@ import (
|
|||||||
func EnsureIPv4(domainName string) (string, error) {
|
func EnsureIPv4(domainName string) (string, error) {
|
||||||
ipv4, err := net.ResolveIPAddr("ip4", domainName)
|
ipv4, err := net.ResolveIPAddr("ip4", domainName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("unable to resolve ipv4 address for %s, %s", domainName, err)
|
return "", fmt.Errorf("%s: unable to resolve IPv4 address: %s", domainName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE(d1): e.g. when there is only an ipv6 record available
|
|
||||||
if ipv4 == nil {
|
if ipv4 == nil {
|
||||||
return "", fmt.Errorf("unable to resolve ipv4 address for %s", domainName)
|
return "", fmt.Errorf("%s: no IPv4 available", domainName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ipv4.String(), nil
|
return ipv4.String(), nil
|
||||||
|
@ -15,10 +15,10 @@ func TestEnsureDomainsResolveSameIPv4(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
// NOTE(d1): DNS records get checked, so use something that is maintained
|
// NOTE(d1): DNS records get checked, so use something that is maintained
|
||||||
// within the federation. if you're here because of a failing test, try
|
// within the federation. if you're here because of a failing test, try
|
||||||
// `dig +short <domain>` to ensure stuff matches first! If flakyness
|
// `dig +short <app>` to ensure stuff matches first! If flakyness
|
||||||
// becomes an issue we can look into mocking
|
// becomes an issue we can look into mocking
|
||||||
|
{"docs.coopcloud.tech", "swarm-0.coopcloud.tech", true},
|
||||||
{"docs.coopcloud.tech", "coopcloud.tech", true},
|
{"docs.coopcloud.tech", "coopcloud.tech", true},
|
||||||
{"docs.coopcloud.tech", "swarm.autonomic.zone", true},
|
|
||||||
|
|
||||||
// NOTE(d1): special case handling for "--local"
|
// NOTE(d1): special case handling for "--local"
|
||||||
{"", "default", true},
|
{"", "default", true},
|
||||||
@ -43,7 +43,7 @@ func TestEnsureDomainsResolveSameIPv4(t *testing.T) {
|
|||||||
func TestEnsureIpv4(t *testing.T) {
|
func TestEnsureIpv4(t *testing.T) {
|
||||||
// NOTE(d1): DNS records get checked, so use something that is maintained
|
// NOTE(d1): DNS records get checked, so use something that is maintained
|
||||||
// within the federation. if you're here because of a failing test, try `dig
|
// within the federation. if you're here because of a failing test, try `dig
|
||||||
// +short <domain>` to ensure stuff matches first! If flakyness becomes an
|
// +short <app>` to ensure stuff matches first! If flakyness becomes an
|
||||||
// issue we can look into mocking
|
// issue we can look into mocking
|
||||||
domainName := "collabora.ostrom.collective.tools"
|
domainName := "collabora.ostrom.collective.tools"
|
||||||
serverName := "ostrom.collective.tools"
|
serverName := "ostrom.collective.tools"
|
||||||
|
@ -43,33 +43,122 @@ func HumanDuration(timestamp int64) string {
|
|||||||
|
|
||||||
// CreateTable prepares a table layout for output.
|
// CreateTable prepares a table layout for output.
|
||||||
func CreateTable() (*table.Table, error) {
|
func CreateTable() (*table.Table, error) {
|
||||||
|
var (
|
||||||
|
renderer = lipgloss.NewRenderer(os.Stdout)
|
||||||
|
headerStyle = renderer.NewStyle().Bold(true).Align(lipgloss.Center)
|
||||||
|
cellStyle = renderer.NewStyle().Padding(0, 1)
|
||||||
|
borderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("63"))
|
||||||
|
)
|
||||||
|
|
||||||
table := table.New().
|
table := table.New().
|
||||||
Border(lipgloss.ThickBorder()).
|
Border(lipgloss.ThickBorder()).
|
||||||
BorderStyle(
|
BorderStyle(borderStyle).
|
||||||
lipgloss.NewStyle().
|
StyleFunc(func(row, col int) lipgloss.Style {
|
||||||
Foreground(lipgloss.Color("63")),
|
var style lipgloss.Style
|
||||||
)
|
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case row == table.HeaderRow:
|
||||||
|
return headerStyle
|
||||||
|
default:
|
||||||
|
style = cellStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
return style
|
||||||
|
})
|
||||||
|
|
||||||
|
return table, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrintTable(t *table.Table) error {
|
||||||
if isAbraCI, ok := os.LookupEnv("ABRA_CI"); ok && isAbraCI == "1" {
|
if isAbraCI, ok := os.LookupEnv("ABRA_CI"); ok && isAbraCI == "1" {
|
||||||
// NOTE(d1): no width limits for CI testing since we test against outputs
|
// NOTE(d1): no width limits for CI testing since we test against outputs
|
||||||
log.Debug("detected ABRA_CI=1")
|
log.Debug("detected ABRA_CI=1")
|
||||||
return table, nil
|
fmt.Println(t)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tWidth, _ := lipgloss.Size(t.String())
|
||||||
|
|
||||||
width, _, err := term.GetSize(0)
|
width, _, err := term.GetSize(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if width-10 < 79 {
|
if tWidth > width {
|
||||||
// NOTE(d1): maintain standard minimum width
|
t.Width(width - 10)
|
||||||
table.Width(79)
|
|
||||||
} else {
|
|
||||||
// NOTE(d1): tests show that this produces stable border drawing
|
|
||||||
table.Width(width - 10)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return table, nil
|
fmt.Println(t)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// horizontal is a JoinHorizontal helper function.
|
||||||
|
func horizontal(left, mid, right string) string {
|
||||||
|
return lipgloss.JoinHorizontal(lipgloss.Right, left, mid, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateOverview(header string, rows [][]string) string {
|
||||||
|
var borderStyle = lipgloss.NewStyle().
|
||||||
|
BorderStyle(lipgloss.ThickBorder()).
|
||||||
|
Padding(0, 1, 0, 1).
|
||||||
|
MaxWidth(79).
|
||||||
|
BorderForeground(lipgloss.Color("63"))
|
||||||
|
|
||||||
|
var headerStyle = lipgloss.NewStyle().
|
||||||
|
Underline(true).
|
||||||
|
Bold(true).
|
||||||
|
PaddingBottom(1)
|
||||||
|
|
||||||
|
var leftStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true)
|
||||||
|
|
||||||
|
var rightStyle = lipgloss.NewStyle()
|
||||||
|
|
||||||
|
var longest int
|
||||||
|
for _, row := range rows {
|
||||||
|
if len(row[0]) > longest {
|
||||||
|
longest = len(row[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var renderedRows []string
|
||||||
|
for _, row := range rows {
|
||||||
|
if len(row) > 2 {
|
||||||
|
panic("CreateOverview: only accepts rows of len == 2")
|
||||||
|
}
|
||||||
|
|
||||||
|
lenOffset := 4
|
||||||
|
if len(row[0]) < longest {
|
||||||
|
lenOffset += longest - len(row[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := ""
|
||||||
|
for range lenOffset {
|
||||||
|
offset = offset + " "
|
||||||
|
}
|
||||||
|
|
||||||
|
renderedRows = append(
|
||||||
|
renderedRows,
|
||||||
|
horizontal(leftStyle.Render(row[0]), offset, rightStyle.Render(row[1])),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
body := strings.Builder{}
|
||||||
|
body.WriteString(
|
||||||
|
borderStyle.Render(
|
||||||
|
lipgloss.JoinVertical(
|
||||||
|
lipgloss.Center,
|
||||||
|
headerStyle.Render(header),
|
||||||
|
lipgloss.JoinVertical(
|
||||||
|
lipgloss.Left,
|
||||||
|
renderedRows...,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return body.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToJSON converts a lipgloss.Table to JSON representation. It's not a robust
|
// ToJSON converts a lipgloss.Table to JSON representation. It's not a robust
|
||||||
|
@ -5,14 +5,19 @@ import (
|
|||||||
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"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/object"
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Init inits a new repo and commits all the stuff if you want
|
// Init inits a new repo and commits all the stuff if you want
|
||||||
func Init(repoPath string, commit bool, gitName, gitEmail string) error {
|
func Init(repoPath string, commit bool, gitName, gitEmail string) error {
|
||||||
if _, err := git.PlainInit(repoPath, false); err != nil {
|
repo, err := git.PlainInit(repoPath, false)
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("git init: %s", err)
|
return fmt.Errorf("git init: %s", err)
|
||||||
}
|
}
|
||||||
|
if err = SwitchToMain(repo); err != nil {
|
||||||
|
return fmt.Errorf("git branch rename: %s", err)
|
||||||
|
}
|
||||||
log.Debugf("initialised new git repo in %s", repoPath)
|
log.Debugf("initialised new git repo in %s", repoPath)
|
||||||
|
|
||||||
if commit {
|
if commit {
|
||||||
@ -42,3 +47,21 @@ func Init(repoPath string, commit bool, gitName, gitEmail string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SwitchToMain sets the default branch to "main".
|
||||||
|
func SwitchToMain(repo *git.Repository) error {
|
||||||
|
ref := plumbing.NewSymbolicReference(plumbing.HEAD, plumbing.ReferenceName("refs/heads/main"))
|
||||||
|
if err := repo.Storer.SetReference(ref); err != nil {
|
||||||
|
return fmt.Errorf("set reference: %s", err)
|
||||||
|
}
|
||||||
|
cfg, err := repo.Config()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("repo config: %s", err)
|
||||||
|
}
|
||||||
|
cfg.Init.DefaultBranch = "main"
|
||||||
|
if err := repo.SetConfig(cfg); err != nil {
|
||||||
|
return fmt.Errorf("repo set config: %s", err)
|
||||||
|
}
|
||||||
|
log.Debug("set 'main' as the default branch.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
35
pkg/git/init_test.go
Normal file
35
pkg/git/init_test.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
"github.com/go-git/go-git/v5/storage/memory"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSwitchToMain(t *testing.T) {
|
||||||
|
repo, err := git.Init(memory.NewStorage(), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create in-memory repository: %v", err)
|
||||||
|
}
|
||||||
|
if err = SwitchToMain(repo); err != nil {
|
||||||
|
t.Fatalf("SwitchToMain failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, err := repo.Reference(plumbing.HEAD, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get HEAD reference: %v", err)
|
||||||
|
}
|
||||||
|
if ref.Target().String() != "refs/heads/main" {
|
||||||
|
t.Errorf("expected HEAD to point to 'refs/heads/main', got %s", ref.Target().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := repo.Config()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get repository config: %v", err)
|
||||||
|
}
|
||||||
|
if cfg.Init.DefaultBranch != "main" {
|
||||||
|
t.Errorf("expected default branch to be 'main', got %s", cfg.Init.DefaultBranch)
|
||||||
|
}
|
||||||
|
}
|
@ -137,6 +137,13 @@ var LintRules = map[string][]LintRule{
|
|||||||
HowToResolve: "name a servce 'app'",
|
HowToResolve: "name a servce 'app'",
|
||||||
Function: LintAppService,
|
Function: LintAppService,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Ref: "R015",
|
||||||
|
Level: "error",
|
||||||
|
Description: "deploy labels stanza present",
|
||||||
|
HowToResolve: "include \"deploy: labels: ...\" stanza",
|
||||||
|
Function: LintDeployLabelsPresent,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Ref: "R010",
|
Ref: "R010",
|
||||||
Level: "error",
|
Level: "error",
|
||||||
@ -269,6 +276,21 @@ func LintTraefikEnabled(recipe recipe.Recipe) (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LintDeployLabelsPresent(recipe recipe.Recipe) (bool, error) {
|
||||||
|
config, err := recipe.GetComposeConfig(nil)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, service := range config.Services {
|
||||||
|
if service.Name == "app" && service.Deploy.Labels != nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
func LintHealthchecks(recipe recipe.Recipe) (bool, error) {
|
func LintHealthchecks(recipe recipe.Recipe) (bool, error) {
|
||||||
config, err := recipe.GetComposeConfig(nil)
|
config, err := recipe.GetComposeConfig(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/envfile"
|
"coopcloud.tech/abra/pkg/envfile"
|
||||||
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r Recipe) SampleEnv() (map[string]string, error) {
|
func (r Recipe) SampleEnv() (map[string]string, error) {
|
||||||
@ -29,7 +30,10 @@ func (r Recipe) GetReleaseNotes(version string) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
withTitle := fmt.Sprintf("%s release notes:\n%s", version, string(releaseNotes))
|
|
||||||
|
title := formatter.BoldStyle.Render(fmt.Sprintf("%s release notes:", version))
|
||||||
|
withTitle := fmt.Sprintf("%s\n%s\n", title, releaseNotes)
|
||||||
|
|
||||||
return withTitle, nil
|
return withTitle, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,7 +247,7 @@ func (r Recipe) ChaosVersion() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !isClean {
|
if !isClean {
|
||||||
version = fmt.Sprintf("%s + unstaged changes", version)
|
version = fmt.Sprintf("%s+U", version)
|
||||||
}
|
}
|
||||||
|
|
||||||
return version, nil
|
return version, nil
|
||||||
|
0
scripts/autocomplete/bash
Executable file → Normal file
0
scripts/autocomplete/bash
Executable file → Normal file
@ -1,188 +0,0 @@
|
|||||||
#!/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
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "retrieve recipe if missing" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE"
|
|
||||||
|
|
||||||
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE"
|
|
||||||
assert_success
|
|
||||||
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE"
|
|
||||||
|
|
||||||
run $ABRA app backup "$TEST_APP_DOMAIN"
|
|
||||||
assert_failure
|
|
||||||
assert_output --partial 'no containers matching'
|
|
||||||
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "bail if unstaged changes and no --chaos" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
run bash -c "echo foo >> $ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
assert_success
|
|
||||||
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
|
||||||
assert_success
|
|
||||||
assert_output --partial 'foo'
|
|
||||||
|
|
||||||
run $ABRA app backup "$TEST_APP_DOMAIN" app
|
|
||||||
assert_failure
|
|
||||||
assert_output --partial 'locally unstaged changes'
|
|
||||||
|
|
||||||
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
|
|
||||||
_checkout_recipe
|
|
||||||
}
|
|
||||||
|
|
||||||
# bats test_tags=slow
|
|
||||||
@test "do not bail if unstaged changes and --chaos" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
run bash -c 'echo "unstaged changes" >> "$ABRA_DIR/recipes/$TEST_RECIPE/foo"'
|
|
||||||
assert_success
|
|
||||||
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
|
||||||
assert_success
|
|
||||||
assert_output --partial 'foo'
|
|
||||||
|
|
||||||
run $ABRA app deploy "$TEST_APP_DOMAIN" --chaos --no-input
|
|
||||||
assert_success
|
|
||||||
|
|
||||||
run $ABRA app backup "$TEST_APP_DOMAIN" app --chaos
|
|
||||||
assert_success
|
|
||||||
assert_output --partial 'running backup for the app service'
|
|
||||||
|
|
||||||
_undeploy_app
|
|
||||||
|
|
||||||
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "ensure recipe up to date if no --offline" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3
|
|
||||||
assert_success
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
|
||||||
assert_output --partial 'behind 3'
|
|
||||||
|
|
||||||
run $ABRA app backup "$TEST_APP_DOMAIN" --debug
|
|
||||||
assert_failure
|
|
||||||
assert_output --partial 'no containers matching'
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
|
||||||
refute_output --partial 'behind 3'
|
|
||||||
|
|
||||||
_reset_recipe
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "ensure recipe not up to date if --offline" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
latestCommit="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)"
|
|
||||||
refute [ -z "$latestCommit" ];
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3
|
|
||||||
assert_success
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
|
||||||
assert_output --partial 'behind 3'
|
|
||||||
|
|
||||||
run $ABRA app backup "$TEST_APP_DOMAIN" --debug --offline
|
|
||||||
assert_failure
|
|
||||||
assert_output --partial 'no containers matching'
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
|
||||||
assert_output --partial 'behind 3'
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$latestCommit"
|
|
||||||
assert_success
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
|
||||||
refute_output --partial 'behind 3'
|
|
||||||
|
|
||||||
_reset_recipe
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "detect backup labels" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
run $ABRA app backup "$TEST_APP_DOMAIN" --debug
|
|
||||||
assert_failure
|
|
||||||
assert_output --partial 'no containers matching'
|
|
||||||
|
|
||||||
assert_output --partial 'detected backup paths'
|
|
||||||
assert_output --partial 'detected pre-hook command'
|
|
||||||
assert_output --partial 'detected post-hook command'
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "error if backups not enabled" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
run sed -i '/backupbot.backup=true/d' "$ABRA_DIR/recipes/$TEST_RECIPE/compose.yml"
|
|
||||||
assert_success
|
|
||||||
|
|
||||||
run $ABRA app backup "$TEST_APP_DOMAIN" app --chaos
|
|
||||||
assert_failure
|
|
||||||
assert_output --partial 'no backup config for app'
|
|
||||||
|
|
||||||
_checkout_recipe
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "error if backup paths not configured" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
run sed -i '/backupbot.backup.path=.*/d' "$ABRA_DIR/recipes/$TEST_RECIPE/compose.yml"
|
|
||||||
assert_success
|
|
||||||
|
|
||||||
run $ABRA app backup "$TEST_APP_DOMAIN" app --chaos
|
|
||||||
assert_failure
|
|
||||||
assert_output --partial 'backup paths are empty for app?'
|
|
||||||
|
|
||||||
_checkout_recipe
|
|
||||||
}
|
|
||||||
|
|
||||||
# bats test_tags=slow
|
|
||||||
@test "backup single service" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
_deploy_app
|
|
||||||
|
|
||||||
run $ABRA app backup "$TEST_APP_DOMAIN" app
|
|
||||||
assert_success
|
|
||||||
assert_output --partial 'running backup for the app service'
|
|
||||||
|
|
||||||
sanitisedDomainName="${TEST_APP_DOMAIN//./_}"
|
|
||||||
assert_output --partial "_$sanitisedDomainName_app"
|
|
||||||
|
|
||||||
assert_exists "$ABRA_DIR/backups"
|
|
||||||
assert bash -c "ls $ABRA_DIR/backups | grep -q $1_$sanitisedDomainName_app"
|
|
||||||
|
|
||||||
_undeploy_app
|
|
||||||
}
|
|
@ -26,11 +26,9 @@ teardown(){
|
|||||||
@test "validate app argument" {
|
@test "validate app argument" {
|
||||||
run $ABRA app check
|
run $ABRA app check
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app check DOESNTEXIST
|
run $ABRA app check DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "retrieve recipe if missing" {
|
@test "retrieve recipe if missing" {
|
||||||
|
@ -24,32 +24,12 @@ teardown(){
|
|||||||
_undeploy_app
|
_undeploy_app
|
||||||
}
|
}
|
||||||
|
|
||||||
# bats test_tags=slow
|
|
||||||
@test "autocomplete" {
|
|
||||||
run $ABRA app cmd --generate-bash-completion
|
|
||||||
assert_success
|
|
||||||
assert_output "$TEST_APP_DOMAIN"
|
|
||||||
|
|
||||||
run $ABRA app cmd "$TEST_APP_DOMAIN" --generate-bash-completion
|
|
||||||
assert_success
|
|
||||||
assert_output "app"
|
|
||||||
|
|
||||||
run $ABRA app cmd "$TEST_APP_DOMAIN" app --generate-bash-completion
|
|
||||||
assert_success
|
|
||||||
assert_output "test_cmd
|
|
||||||
test_cmd_arg
|
|
||||||
test_cmd_args
|
|
||||||
test_cmd_export"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "validate app argument" {
|
@test "validate app argument" {
|
||||||
run $ABRA app cmd
|
run $ABRA app cmd
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app cmd DOESNTEXIST
|
run $ABRA app cmd DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "retrieve recipe if missing" {
|
@test "retrieve recipe if missing" {
|
||||||
@ -123,13 +103,11 @@ test_cmd_export"
|
|||||||
@test "error if missing arguments without passing --local" {
|
@test "error if missing arguments without passing --local" {
|
||||||
run $ABRA app cmd "$TEST_APP_DOMAIN"
|
run $ABRA app cmd "$TEST_APP_DOMAIN"
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'missing arguments'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "error if missing arguments when passing --local" {
|
@test "error if missing arguments when passing --local" {
|
||||||
run $ABRA app cmd --local "$TEST_APP_DOMAIN"
|
run $ABRA app cmd --local "$TEST_APP_DOMAIN"
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'missing arguments'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "cannot use --local and --user at same time" {
|
@test "cannot use --local and --user at same time" {
|
||||||
@ -162,17 +140,13 @@ test_cmd_export"
|
|||||||
}
|
}
|
||||||
|
|
||||||
@test "run command with single arg" {
|
@test "run command with single arg" {
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/581"
|
|
||||||
|
|
||||||
run $ABRA app cmd --local "$TEST_APP_DOMAIN" test_cmd_arg -- bing
|
run $ABRA app cmd --local "$TEST_APP_DOMAIN" test_cmd_arg -- bing
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial 'bing'
|
assert_output --partial 'bing'
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "run command with several args" {
|
@test "run command with several args" {
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/581"
|
run $ABRA app cmd --local "$TEST_APP_DOMAIN" test_cmd_args bong bang
|
||||||
|
|
||||||
run $ABRA app cmd --local "$TEST_APP_DOMAIN" test_cmd_args -- bong bang
|
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial 'bong bang'
|
assert_output --partial 'bong bang'
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,7 @@ setup(){
|
|||||||
@test "validate app argument" {
|
@test "validate app argument" {
|
||||||
run $ABRA app config
|
run $ABRA app config
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app config DOESNTEXIST
|
run $ABRA app config DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
}
|
||||||
|
@ -29,21 +29,17 @@ teardown(){
|
|||||||
@test "validate app argument" {
|
@test "validate app argument" {
|
||||||
run $ABRA app cp
|
run $ABRA app cp
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app cp DOESNTEXIST
|
run $ABRA app cp DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "error if missing src/dest arguments" {
|
@test "error if missing src/dest arguments" {
|
||||||
run $ABRA app cp "$TEST_APP_DOMAIN"
|
run $ABRA app cp "$TEST_APP_DOMAIN"
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'missing <src> argument'
|
|
||||||
|
|
||||||
run $ABRA app cp "$TEST_APP_DOMAIN" myfile.txt
|
run $ABRA app cp "$TEST_APP_DOMAIN" myfile.txt
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'missing <dest> argument'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "either src/dest has correct syntax" {
|
@test "either src/dest has correct syntax" {
|
||||||
@ -159,7 +155,7 @@ teardown(){
|
|||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@test "copy container file to local directory" {
|
@test "copy container file to local directory" {
|
||||||
run $ABRA app run "$TEST_APP_DOMAIN" app bash -c "echo foo > /etc/myfile.txt"
|
run $ABRA app run "$TEST_APP_DOMAIN" app -- bash -c "echo foo > /etc/myfile.txt"
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
run $ABRA app cp "$TEST_APP_DOMAIN" app:/etc/myfile.txt "$BATS_TMPDIR"
|
run $ABRA app cp "$TEST_APP_DOMAIN" app:/etc/myfile.txt "$BATS_TMPDIR"
|
||||||
@ -170,7 +166,7 @@ teardown(){
|
|||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@test "copy container file to local file" {
|
@test "copy container file to local file" {
|
||||||
run $ABRA app run "$TEST_APP_DOMAIN" app bash -c "echo foo > /etc/myfile.txt"
|
run $ABRA app run "$TEST_APP_DOMAIN" app -- bash -c "echo foo > /etc/myfile.txt"
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
run $ABRA app cp "$TEST_APP_DOMAIN" app:/etc/myfile.txt "$BATS_TMPDIR/myfile.txt"
|
run $ABRA app cp "$TEST_APP_DOMAIN" app:/etc/myfile.txt "$BATS_TMPDIR/myfile.txt"
|
||||||
@ -181,7 +177,7 @@ teardown(){
|
|||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@test "copy container file to local file and rename" {
|
@test "copy container file to local file and rename" {
|
||||||
run $ABRA app run "$TEST_APP_DOMAIN" app bash -c "echo foo > /etc/myfile.txt"
|
run $ABRA app run "$TEST_APP_DOMAIN" app -- bash -c "echo foo > /etc/myfile.txt"
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
run $ABRA app cp "$TEST_APP_DOMAIN" app:/etc/myfile.txt "$BATS_TMPDIR/myfile2.txt"
|
run $ABRA app cp "$TEST_APP_DOMAIN" app:/etc/myfile.txt "$BATS_TMPDIR/myfile2.txt"
|
||||||
@ -192,10 +188,10 @@ teardown(){
|
|||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@test "copy container directory to local directory" {
|
@test "copy container directory to local directory" {
|
||||||
run $ABRA app run "$TEST_APP_DOMAIN" app bash -c "echo foo > /etc/myfile.txt"
|
run $ABRA app run "$TEST_APP_DOMAIN" app -- bash -c "echo foo > /etc/myfile.txt"
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
run $ABRA app run "$TEST_APP_DOMAIN" app bash -c "echo bar > /etc/myfile2.txt"
|
run $ABRA app run "$TEST_APP_DOMAIN" app -- bash -c "echo bar > /etc/myfile2.txt"
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
mkdir "$BATS_TMPDIR/mydir"
|
mkdir "$BATS_TMPDIR/mydir"
|
||||||
@ -209,10 +205,10 @@ teardown(){
|
|||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@test "copy container files to local directory" {
|
@test "copy container files to local directory" {
|
||||||
run $ABRA app run "$TEST_APP_DOMAIN" app bash -c "echo foo > /etc/myfile.txt"
|
run $ABRA app run "$TEST_APP_DOMAIN" app -- bash -c "echo foo > /etc/myfile.txt"
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
run $ABRA app run "$TEST_APP_DOMAIN" app bash -c "echo bar > /etc/myfile2.txt"
|
run $ABRA app run "$TEST_APP_DOMAIN" app -- bash -c "echo bar > /etc/myfile2.txt"
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
mkdir "$BATS_TMPDIR/mydir"
|
mkdir "$BATS_TMPDIR/mydir"
|
||||||
|
@ -32,11 +32,9 @@ teardown(){
|
|||||||
@test "validate app argument" {
|
@test "validate app argument" {
|
||||||
run $ABRA app deploy
|
run $ABRA app deploy
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app deploy DOESNTEXIST
|
run $ABRA app deploy DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "bail if unstaged changes and no --chaos" {
|
@test "bail if unstaged changes and no --chaos" {
|
||||||
@ -67,7 +65,7 @@ teardown(){
|
|||||||
run $ABRA app deploy "$TEST_APP_DOMAIN" \
|
run $ABRA app deploy "$TEST_APP_DOMAIN" \
|
||||||
--chaos --no-input --no-converge-checks
|
--chaos --no-input --no-converge-checks
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial 'chaos'
|
assert_output --partial 'NEW CHAOS'
|
||||||
}
|
}
|
||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@ -113,8 +111,6 @@ teardown(){
|
|||||||
--no-input --no-converge-checks --offline
|
--no-input --no-converge-checks --offline
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial "$latestCommit"
|
assert_output --partial "$latestCommit"
|
||||||
assert_output --partial 'using latest commit'
|
|
||||||
refute_output --partial 'chaos'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@ -132,7 +128,7 @@ teardown(){
|
|||||||
--no-input --no-converge-checks --chaos
|
--no-input --no-converge-checks --chaos
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial "${wantHash:0:8}"
|
assert_output --partial "${wantHash:0:8}"
|
||||||
assert_output --partial 'chaos'
|
assert_output --partial 'NEW CHAOS'
|
||||||
}
|
}
|
||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@ -174,12 +170,10 @@ 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'
|
|
||||||
|
|
||||||
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'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@ -230,7 +224,6 @@ teardown(){
|
|||||||
|
|
||||||
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial 'no DOMAIN=... configured for app'
|
|
||||||
|
|
||||||
run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input
|
run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input
|
||||||
assert_success
|
assert_success
|
||||||
@ -264,7 +257,6 @@ teardown(){
|
|||||||
run $ABRA app deploy "$TEST_APP_DOMAIN" \
|
run $ABRA app deploy "$TEST_APP_DOMAIN" \
|
||||||
--no-input --no-converge-checks --no-domain-checks
|
--no-input --no-converge-checks --no-domain-checks
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial 'skipping domain checks as requested'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "error if specific version does not exist" {
|
@test "error if specific version does not exist" {
|
||||||
|
@ -60,11 +60,6 @@ teardown(){
|
|||||||
assert_output --partial "$TEST_SERVER"
|
assert_output --partial "$TEST_SERVER"
|
||||||
assert_output --partial "foo.com"
|
assert_output --partial "foo.com"
|
||||||
|
|
||||||
run $ABRA app ls --server foo.com
|
|
||||||
assert_success
|
|
||||||
refute_output --partial "SERVER: $TEST_SERVER"
|
|
||||||
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
|
||||||
assert_not_exists "$ABRA_DIR/servers/foo.com"
|
assert_not_exists "$ABRA_DIR/servers/foo.com"
|
||||||
@ -94,33 +89,6 @@ teardown(){
|
|||||||
assert_output --partial "foo-recipe"
|
assert_output --partial "foo-recipe"
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "server stats are correct" {
|
|
||||||
run $ABRA app ls
|
|
||||||
assert_success
|
|
||||||
assert_output --partial "SERVER: $TEST_SERVER"
|
|
||||||
assert_output --partial "TOTAL APPS: 1"
|
|
||||||
|
|
||||||
run mkdir -p "$ABRA_DIR/servers/foo.com"
|
|
||||||
assert_success
|
|
||||||
assert_exists "$ABRA_DIR/servers/foo.com"
|
|
||||||
|
|
||||||
run cp \
|
|
||||||
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" \
|
|
||||||
"$ABRA_DIR/servers/foo.com/app.foo.com.env"
|
|
||||||
assert_exists "$ABRA_DIR/servers/foo.com/app.foo.com.env"
|
|
||||||
|
|
||||||
run $ABRA app ls
|
|
||||||
assert_success
|
|
||||||
assert_output --partial "$TEST_SERVER"
|
|
||||||
assert_output --partial "foo.com"
|
|
||||||
assert_output --partial "TOTAL SERVERS: 2"
|
|
||||||
assert_output --partial "TOTAL APPS: 2"
|
|
||||||
|
|
||||||
run rm -rf "$ABRA_DIR/servers/foo.com"
|
|
||||||
assert_success
|
|
||||||
assert_not_exists "$ABRA_DIR/servers/foo.com"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "output is machine readable" {
|
@test "output is machine readable" {
|
||||||
run $ABRA app ls --machine
|
run $ABRA app ls --machine
|
||||||
|
|
||||||
|
@ -20,11 +20,9 @@ setup(){
|
|||||||
@test "validate app argument" {
|
@test "validate app argument" {
|
||||||
run $ABRA app logs
|
run $ABRA app logs
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app logs DOESNTEXIST
|
run $ABRA app logs DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "error if not deployed" {
|
@test "error if not deployed" {
|
||||||
|
@ -22,12 +22,6 @@ teardown(){
|
|||||||
_reset_recipe
|
_reset_recipe
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "autocomplete" {
|
|
||||||
run $ABRA app new --generate-bash-completion
|
|
||||||
assert_success
|
|
||||||
assert_output --partial "traefik"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "create new app" {
|
@test "create new app" {
|
||||||
run $ABRA app new "$TEST_RECIPE" \
|
run $ABRA app new "$TEST_RECIPE" \
|
||||||
--no-input \
|
--no-input \
|
||||||
@ -179,8 +173,6 @@ teardown(){
|
|||||||
--domain "$TEST_APP_DOMAIN" \
|
--domain "$TEST_APP_DOMAIN" \
|
||||||
--secrets
|
--secrets
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial 'generated secrets'
|
|
||||||
assert_output --partial 'test_pass_one'
|
|
||||||
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
|
||||||
run $ABRA app secret ls "$TEST_APP_DOMAIN"
|
run $ABRA app secret ls "$TEST_APP_DOMAIN"
|
||||||
|
@ -24,11 +24,9 @@ teardown(){
|
|||||||
@test "validate app argument" {
|
@test "validate app argument" {
|
||||||
run $ABRA app ps
|
run $ABRA app ps
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app ps DOESNTEXIST
|
run $ABRA app ps DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
|
@ -28,11 +28,9 @@ teardown(){
|
|||||||
@test "validate app argument" {
|
@test "validate app argument" {
|
||||||
run $ABRA app deploy
|
run $ABRA app deploy
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app deploy DOESNTEXIST
|
run $ABRA app deploy DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "do not show ALERTA warning if --force / --no-input" {
|
@test "do not show ALERTA warning if --force / --no-input" {
|
||||||
|
@ -24,17 +24,14 @@ teardown(){
|
|||||||
@test "validate app argument" {
|
@test "validate app argument" {
|
||||||
run $ABRA app restart
|
run $ABRA app restart
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app restart DOESNTEXIST
|
run $ABRA app restart DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "error if service missing" {
|
@test "error if service missing" {
|
||||||
run $ABRA app restart "$TEST_APP_DOMAIN"
|
run $ABRA app restart "$TEST_APP_DOMAIN"
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'missing <service>'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "error if not deployed" {
|
@test "error if not deployed" {
|
||||||
|
@ -1,272 +0,0 @@
|
|||||||
#!/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
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "validate app argument" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
run $ABRA app restore
|
|
||||||
assert_failure
|
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app restore DOESNTEXIST
|
|
||||||
assert_failure
|
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "retrieve recipe if missing" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE"
|
|
||||||
assert_success
|
|
||||||
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE"
|
|
||||||
|
|
||||||
run $ABRA app restore "$TEST_APP_DOMAIN" app DOESNTEXIST
|
|
||||||
assert_failure
|
|
||||||
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "bail if unstaged changes and no --chaos" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
run bash -c "echo foo >> $ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
assert_success
|
|
||||||
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
|
||||||
assert_success
|
|
||||||
assert_output --partial 'foo'
|
|
||||||
|
|
||||||
run $ABRA app restore "$TEST_APP_DOMAIN" app DOESNTEXIST
|
|
||||||
assert_failure
|
|
||||||
assert_output --partial 'locally unstaged changes'
|
|
||||||
|
|
||||||
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
|
|
||||||
_checkout_recipe
|
|
||||||
}
|
|
||||||
|
|
||||||
# bats test_tags=slow
|
|
||||||
@test "do not bail if unstaged changes and --chaos" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
run bash -c 'echo "unstaged changes" >> "$ABRA_DIR/recipes/$TEST_RECIPE/foo"'
|
|
||||||
assert_success
|
|
||||||
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
|
||||||
assert_success
|
|
||||||
assert_output --partial 'foo'
|
|
||||||
|
|
||||||
run touch "$BATS_TMPDIR/foo.txt"
|
|
||||||
assert_success
|
|
||||||
assert_exists "$BATS_TMPDIR/foo.txt"
|
|
||||||
|
|
||||||
run tar -cvf "$BATS_TMPDIR/foo.tar.gz" "$BATS_TMPDIR/foo.txt"
|
|
||||||
assert_success
|
|
||||||
assert_exists "$BATS_TMPDIR/foo.tar.gz"
|
|
||||||
|
|
||||||
run $ABRA app deploy "$TEST_APP_DOMAIN" --chaos --no-input
|
|
||||||
assert_success
|
|
||||||
|
|
||||||
run $ABRA app restore "$TEST_APP_DOMAIN" app "$BATS_TMPDIR/foo.tar.gz" \
|
|
||||||
--debug --chaos
|
|
||||||
assert_success
|
|
||||||
assert_output --partial 'restore config detected'
|
|
||||||
assert_output --partial 'detected pre-hook command'
|
|
||||||
assert_output --partial 'detected post-hook command'
|
|
||||||
|
|
||||||
run $ABRA app run "$TEST_APP_DOMAIN" app ls "$BATS_TMPDIR"
|
|
||||||
assert_success
|
|
||||||
assert_output --partial 'foo.txt'
|
|
||||||
|
|
||||||
_undeploy_app
|
|
||||||
|
|
||||||
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
|
|
||||||
_checkout_recipe
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "ensure recipe up to date if no --offline" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3
|
|
||||||
assert_success
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
|
||||||
assert_output --regexp 'behind .* 3 commits'
|
|
||||||
|
|
||||||
run $ABRA app restore "$TEST_APP_DOMAIN" app
|
|
||||||
assert_failure
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
|
||||||
assert_output --partial "up to date"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "ensure recipe not up to date if --offline" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
latestCommit="$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-parse --short HEAD)"
|
|
||||||
refute [ -z "$latestCommit" ];
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3
|
|
||||||
assert_success
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
|
||||||
assert_output --regexp 'behind .* 3 commits'
|
|
||||||
|
|
||||||
run $ABRA app restore "$TEST_APP_DOMAIN" app --offline
|
|
||||||
assert_failure
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
|
||||||
assert_output --regexp 'behind .* 3 commits'
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$latestCommit"
|
|
||||||
assert_success
|
|
||||||
|
|
||||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
|
||||||
assert_output --partial "HEAD detached at $latestCommit"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "error if missing service" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
run $ABRA app restore "$TEST_APP_DOMAIN"
|
|
||||||
assert_failure
|
|
||||||
assert_output --partial 'missing <service>'
|
|
||||||
|
|
||||||
run $ABRA app restore "$TEST_APP_DOMAIN" app
|
|
||||||
assert_failure
|
|
||||||
assert_output --partial 'missing <file>'
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "error if file doesn't exist" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
run $ABRA app restore "$TEST_APP_DOMAIN" app DOESNTEXIST
|
|
||||||
assert_failure
|
|
||||||
assert_output --partial "doesn't exist"
|
|
||||||
}
|
|
||||||
|
|
||||||
# bats test_tags=slow
|
|
||||||
@test "detect labels if restore enabled" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
run touch "$BATS_TMPDIR/foo.txt"
|
|
||||||
assert_success
|
|
||||||
assert_exists "$BATS_TMPDIR/foo.txt"
|
|
||||||
|
|
||||||
run tar -cvf "$BATS_TMPDIR/foo.tar.gz" "$BATS_TMPDIR/foo.txt"
|
|
||||||
assert_success
|
|
||||||
assert_exists "$BATS_TMPDIR/foo.tar.gz"
|
|
||||||
|
|
||||||
_deploy_app
|
|
||||||
|
|
||||||
run $ABRA app restore "$TEST_APP_DOMAIN" app "$BATS_TMPDIR/foo.tar.gz" --debug
|
|
||||||
assert_success
|
|
||||||
assert_output --partial 'restore config detected'
|
|
||||||
assert_output --partial 'detected pre-hook command'
|
|
||||||
assert_output --partial 'detected post-hook command'
|
|
||||||
|
|
||||||
_undeploy_app
|
|
||||||
}
|
|
||||||
|
|
||||||
# bats test_tags=slow
|
|
||||||
@test "no error if restore not enabled" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
run sed -i '/backupbot.restore=.*/d' "$ABRA_DIR/recipes/$TEST_RECIPE/compose.yml"
|
|
||||||
assert_success
|
|
||||||
|
|
||||||
run touch "$BATS_TMPDIR/foo.txt"
|
|
||||||
assert_success
|
|
||||||
assert_exists "$BATS_TMPDIR/foo.txt"
|
|
||||||
|
|
||||||
run tar -cvf "$BATS_TMPDIR/foo.tar.gz" "$BATS_TMPDIR/foo.txt"
|
|
||||||
assert_success
|
|
||||||
assert_exists "$BATS_TMPDIR/foo.tar.gz"
|
|
||||||
|
|
||||||
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --chaos
|
|
||||||
assert_success
|
|
||||||
|
|
||||||
run $ABRA app restore "$TEST_APP_DOMAIN" app "$BATS_TMPDIR/foo.tar.gz" \
|
|
||||||
--debug --chaos
|
|
||||||
assert_success
|
|
||||||
refute_output --partial 'restore config detected'
|
|
||||||
refute_output --partial 'detected pre-hook command'
|
|
||||||
refute_output --partial 'detected post-hook command'
|
|
||||||
|
|
||||||
_undeploy_app
|
|
||||||
|
|
||||||
_checkout_recipe
|
|
||||||
}
|
|
||||||
|
|
||||||
# bats test_tags=slow
|
|
||||||
@test "error if service doesn't exist" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
run touch "$BATS_TMPDIR/foo.txt"
|
|
||||||
assert_success
|
|
||||||
assert_exists "$BATS_TMPDIR/foo.txt"
|
|
||||||
|
|
||||||
run tar -cvf "$BATS_TMPDIR/foo.tar.gz" "$BATS_TMPDIR/foo.txt"
|
|
||||||
assert_success
|
|
||||||
assert_exists "$BATS_TMPDIR/foo.tar.gz"
|
|
||||||
|
|
||||||
_deploy_app
|
|
||||||
|
|
||||||
run $ABRA app restore "$TEST_APP_DOMAIN" DOESNTEXIST "$BATS_TMPDIR/foo.tar.gz" --debug
|
|
||||||
assert_failure
|
|
||||||
assert_output --partial 'no containers matching'
|
|
||||||
|
|
||||||
_undeploy_app
|
|
||||||
}
|
|
||||||
|
|
||||||
# bats test_tags=slow
|
|
||||||
@test "restore backup" {
|
|
||||||
skip "https://git.coopcloud.tech/coop-cloud/organising/issues/583"
|
|
||||||
|
|
||||||
run touch "$BATS_TMPDIR/foo.txt"
|
|
||||||
assert_success
|
|
||||||
assert_exists "$BATS_TMPDIR/foo.txt"
|
|
||||||
|
|
||||||
run tar -cvf "$BATS_TMPDIR/foo.tar.gz" "$BATS_TMPDIR/foo.txt"
|
|
||||||
assert_success
|
|
||||||
assert_exists "$BATS_TMPDIR/foo.tar.gz"
|
|
||||||
|
|
||||||
_deploy_app
|
|
||||||
|
|
||||||
run $ABRA app restore "$TEST_APP_DOMAIN" app "$BATS_TMPDIR/foo.tar.gz" --debug
|
|
||||||
assert_success
|
|
||||||
assert_output --partial 'restore config detected'
|
|
||||||
assert_output --partial 'detected pre-hook command'
|
|
||||||
assert_output --partial 'detected post-hook command'
|
|
||||||
|
|
||||||
run $ABRA app run "$TEST_APP_DOMAIN" app ls "$BATS_TMPDIR"
|
|
||||||
assert_success
|
|
||||||
assert_output --partial 'foo.txt'
|
|
||||||
|
|
||||||
_undeploy_app
|
|
||||||
}
|
|
@ -26,11 +26,9 @@ teardown(){
|
|||||||
@test "validate app argument" {
|
@test "validate app argument" {
|
||||||
run $ABRA app rollback
|
run $ABRA app rollback
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app rollback DOESNTEXIST
|
run $ABRA app rollback DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "retrieve recipe if missing" {
|
@test "retrieve recipe if missing" {
|
||||||
|
@ -24,21 +24,17 @@ teardown(){
|
|||||||
@test "validate app argument" {
|
@test "validate app argument" {
|
||||||
run $ABRA app run
|
run $ABRA app run
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app run DOESNTEXIST
|
run $ABRA app run DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "error if missing service" {
|
@test "error if missing service" {
|
||||||
run $ABRA app run "$TEST_APP_DOMAIN"
|
run $ABRA app run "$TEST_APP_DOMAIN"
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no <service> provided'
|
|
||||||
|
|
||||||
run $ABRA app run "$TEST_APP_DOMAIN" app
|
run $ABRA app run "$TEST_APP_DOMAIN" app
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no <args> provided'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@ -54,7 +50,7 @@ teardown(){
|
|||||||
@test "run command" {
|
@test "run command" {
|
||||||
_deploy_app
|
_deploy_app
|
||||||
|
|
||||||
run $ABRA app run "$TEST_APP_DOMAIN" app ls /
|
run $ABRA app run "$TEST_APP_DOMAIN" app -- ls /
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial 'root'
|
assert_output --partial 'root'
|
||||||
}
|
}
|
||||||
|
@ -35,15 +35,12 @@ teardown(){
|
|||||||
@test "generate: validate arguments" {
|
@test "generate: validate arguments" {
|
||||||
run $ABRA app secret generate
|
run $ABRA app secret generate
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app secret generate DOESNTEXIST
|
run $ABRA app secret generate DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
|
|
||||||
run $ABRA app secret generate "$TEST_APP_DOMAIN"
|
run $ABRA app secret generate "$TEST_APP_DOMAIN"
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'missing arguments'
|
|
||||||
|
|
||||||
run $ABRA app secret generate "$TEST_APP_DOMAIN" testSecret testVersion --all
|
run $ABRA app secret generate "$TEST_APP_DOMAIN" testSecret testVersion --all
|
||||||
assert_failure
|
assert_failure
|
||||||
@ -89,7 +86,6 @@ teardown(){
|
|||||||
run $ABRA app secret generate "$TEST_APP_DOMAIN" --all
|
run $ABRA app secret generate "$TEST_APP_DOMAIN" --all
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'missing version'
|
assert_output --partial 'missing version'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "generate: use version from app env" {
|
@test "generate: use version from app env" {
|
||||||
@ -180,19 +176,15 @@ teardown(){
|
|||||||
@test "insert: validate arguments" {
|
@test "insert: validate arguments" {
|
||||||
run $ABRA app secret insert
|
run $ABRA app secret insert
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app secret insert "$TEST_APP_DOMAIN"
|
run $ABRA app secret insert "$TEST_APP_DOMAIN"
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'missing arguments'
|
|
||||||
|
|
||||||
run $ABRA app secret insert "$TEST_APP_DOMAIN" bar
|
run $ABRA app secret insert "$TEST_APP_DOMAIN" bar
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'missing arguments'
|
|
||||||
|
|
||||||
run $ABRA app secret insert "$TEST_APP_DOMAIN" bar baz
|
run $ABRA app secret insert "$TEST_APP_DOMAIN" bar baz
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'missing arguments'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "insert: create secret" {
|
@test "insert: create secret" {
|
||||||
@ -230,20 +222,16 @@ teardown(){
|
|||||||
@test "rm: validate arguments" {
|
@test "rm: validate arguments" {
|
||||||
run $ABRA app secret rm
|
run $ABRA app secret rm
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app secret rm DOESNTEXIST
|
run $ABRA app secret rm DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
|
|
||||||
run $ABRA app secret rm "$TEST_APP_DOMAIN"
|
run $ABRA app secret rm "$TEST_APP_DOMAIN"
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no secret(s) specified'
|
|
||||||
|
|
||||||
run $ABRA app secret rm "$TEST_APP_DOMAIN" test_pass_one --all
|
run $ABRA app secret rm "$TEST_APP_DOMAIN" test_pass_one --all
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot use'
|
assert_output --regexp 'cannot use .* together'
|
||||||
assert_output --partial "'--all' together"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "rm: single secret no match" {
|
@test "rm: single secret no match" {
|
||||||
@ -290,11 +278,9 @@ teardown(){
|
|||||||
@test "ls: validate arguments" {
|
@test "ls: validate arguments" {
|
||||||
run $ABRA app secret ls
|
run $ABRA app secret ls
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app secret ls DOESNTEXIST
|
run $ABRA app secret ls DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "ls: show secrets" {
|
@test "ls: show secrets" {
|
||||||
|
@ -25,11 +25,9 @@ teardown(){
|
|||||||
@test "validate app argument" {
|
@test "validate app argument" {
|
||||||
run $ABRA app services
|
run $ABRA app services
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app services DOESNTEXIST
|
run $ABRA app services DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "error if not deployed" {
|
@test "error if not deployed" {
|
||||||
@ -47,5 +45,4 @@ teardown(){
|
|||||||
|
|
||||||
sanitisedDomainName="${TEST_APP_DOMAIN//./_}"
|
sanitisedDomainName="${TEST_APP_DOMAIN//./_}"
|
||||||
assert_output --partial "$sanitisedDomainName_app"
|
assert_output --partial "$sanitisedDomainName_app"
|
||||||
assert_output --partial "nginx"
|
|
||||||
}
|
}
|
||||||
|
@ -25,11 +25,9 @@ teardown(){
|
|||||||
@test "validate app argument" {
|
@test "validate app argument" {
|
||||||
run $ABRA app undeploy
|
run $ABRA app undeploy
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app undeploy DOESNTEXIST
|
run $ABRA app undeploy DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@ -109,10 +107,10 @@ teardown(){
|
|||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
@test "undeploy chaos deployment" {
|
@test "undeploy chaos deployment" {
|
||||||
run $ABRA app deploy "$TEST_APP_DOMAIN" \
|
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --chaos
|
||||||
--no-input --no-converge-checks --chaos
|
|
||||||
|
|
||||||
_undeploy_app
|
run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input
|
||||||
|
assert_success
|
||||||
|
|
||||||
# NOTE(d1): ensure chaos undeploy
|
# NOTE(d1): ensure chaos undeploy
|
||||||
assert_output --partial ${_get_current_hash:0:8}
|
assert_output --partial ${_get_current_hash:0:8}
|
||||||
|
60
tests/integration/app_undeploy_env_version.bats
Normal file
60
tests/integration/app_undeploy_env_version.bats
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#!/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 "undeploy 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
|
||||||
|
|
||||||
|
run sed -i "s/TYPE=$TEST_RECIPE:.*/TYPE=$TEST_RECIPE/g" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app undeploy "$TEST_APP_DOMAIN" --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
|
||||||
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "chaos commit written to env" {
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --chaos
|
||||||
|
|
||||||
|
run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run grep -q "TYPE=$TEST_RECIPE:${_get_current_hash:0:8}" \
|
||||||
|
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||||
|
assert_success
|
||||||
|
}
|
@ -15,22 +15,22 @@ teardown_file(){
|
|||||||
setup(){
|
setup(){
|
||||||
load "$PWD/tests/integration/helpers/common"
|
load "$PWD/tests/integration/helpers/common"
|
||||||
_common_setup
|
_common_setup
|
||||||
|
_ensure_catalogue
|
||||||
}
|
}
|
||||||
|
|
||||||
teardown(){
|
teardown(){
|
||||||
_reset_recipe
|
_reset_recipe
|
||||||
_reset_app
|
_reset_app
|
||||||
_undeploy_app
|
_undeploy_app
|
||||||
|
_reset_app # NOTE(d1): _undeploy_app writes version
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "validate app argument" {
|
@test "validate app argument" {
|
||||||
run $ABRA app upgrade
|
run $ABRA app upgrade
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app upgrade DOESNTEXIST
|
run $ABRA app upgrade DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
|
@ -24,11 +24,9 @@ teardown(){
|
|||||||
@test "ls validate app argument" {
|
@test "ls validate app argument" {
|
||||||
run $ABRA app volume ls
|
run $ABRA app volume ls
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app volume ls DOESNTEXIST
|
run $ABRA app volume ls DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "list no volumes" {
|
@test "list no volumes" {
|
||||||
@ -49,11 +47,9 @@ teardown(){
|
|||||||
@test "rm validate app argument" {
|
@test "rm validate app argument" {
|
||||||
run $ABRA app volume rm
|
run $ABRA app volume rm
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no app provided'
|
|
||||||
|
|
||||||
run $ABRA app volume rm DOESNTEXIST
|
run $ABRA app volume rm DOESNTEXIST
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'cannot find app'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# bats test_tags=slow
|
# bats test_tags=slow
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
setup(){
|
|
||||||
load "$PWD/tests/integration/helpers/common"
|
|
||||||
_common_setup
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "bash autocompletion" {
|
|
||||||
run $ABRA autocomplete bash
|
|
||||||
assert_success
|
|
||||||
assert_exists "$ABRA_DIR/autocompletion/bash"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "zsh autocompletion" {
|
|
||||||
run $ABRA autocomplete zsh
|
|
||||||
assert_success
|
|
||||||
assert_exists "$ABRA_DIR/autocompletion/zsh"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "fish autocompletion" {
|
|
||||||
run $ABRA autocomplete fish
|
|
||||||
assert_success
|
|
||||||
assert_exists "$ABRA_DIR/autocompletion/fish"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "fizsh autocompletion" {
|
|
||||||
run $ABRA autocomplete fizsh
|
|
||||||
assert_success
|
|
||||||
assert_exists "$ABRA_DIR/autocompletion/zsh"
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ _mkfile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_mkfile_remote() {
|
_mkfile_remote() {
|
||||||
run $ABRA app run "$TEST_APP_DOMAIN" app "bash -c \"echo $2 > $1\""
|
run $ABRA app run "$TEST_APP_DOMAIN" app -- "bash -c \"echo $2 > $1\""
|
||||||
assert_success
|
assert_success
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,6 +19,6 @@ _rm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_rm_remote() {
|
_rm_remote() {
|
||||||
run "$ABRA" app run "$TEST_APP_DOMAIN" app rm -rf "$1"
|
run "$ABRA" app run "$TEST_APP_DOMAIN" app -- rm -rf "$1"
|
||||||
assert_success
|
assert_success
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ setup() {
|
|||||||
assert_success
|
assert_success
|
||||||
assert_not_exists "$ABRA_DIR/recipes/matrix-synapse"
|
assert_not_exists "$ABRA_DIR/recipes/matrix-synapse"
|
||||||
|
|
||||||
run $ABRA recipe fetch
|
run $ABRA recipe fetch --all
|
||||||
assert_success
|
assert_success
|
||||||
assert_exists "$ABRA_DIR/recipes/matrix-synapse"
|
assert_exists "$ABRA_DIR/recipes/matrix-synapse"
|
||||||
}
|
}
|
||||||
@ -25,3 +25,13 @@ setup() {
|
|||||||
assert_success
|
assert_success
|
||||||
assert_exists "$ABRA_DIR/recipes/matrix-synapse"
|
assert_exists "$ABRA_DIR/recipes/matrix-synapse"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "error if missing args/flags" {
|
||||||
|
run $ABRA recipe fetch
|
||||||
|
assert_failure
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "error if single recipe and --all" {
|
||||||
|
run $ABRA recipe fetch matrix-synapse --all
|
||||||
|
assert_failure
|
||||||
|
}
|
||||||
|
@ -5,13 +5,6 @@ setup() {
|
|||||||
_common_setup
|
_common_setup
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "recipe list" {
|
|
||||||
run $ABRA recipe list
|
|
||||||
assert_success
|
|
||||||
NUM_RECIPES=$(jq length "$ABRA_DIR/catalogue/recipes.json")
|
|
||||||
assert_output --partial "total recipes: $NUM_RECIPES"
|
|
||||||
}
|
|
||||||
|
|
||||||
@test "recipe list with pattern" {
|
@test "recipe list with pattern" {
|
||||||
run $ABRA recipe list --pattern cloud
|
run $ABRA recipe list --pattern cloud
|
||||||
assert_success
|
assert_success
|
||||||
|
@ -48,11 +48,9 @@ teardown(){
|
|||||||
--server "$TEST_SERVER" \
|
--server "$TEST_SERVER" \
|
||||||
--domain "foobar.$TEST_SERVER"
|
--domain "foobar.$TEST_SERVER"
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial "new app 'foobar' created"
|
|
||||||
|
|
||||||
run $ABRA app deploy "foobar.$TEST_SERVER" --no-input
|
run $ABRA app deploy "foobar.$TEST_SERVER" --no-input
|
||||||
assert_success
|
assert_success
|
||||||
assert_output --partial 'using latest commit'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "create new recipe with git credentials" {
|
@test "create new recipe with git credentials" {
|
||||||
|
@ -25,11 +25,9 @@ teardown() {
|
|||||||
@test "validate recipe argument" {
|
@test "validate recipe argument" {
|
||||||
run $ABRA recipe release --no-input
|
run $ABRA recipe release --no-input
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no recipe name provided'
|
|
||||||
|
|
||||||
run $ABRA recipe release DOESNTEXIST --no-input
|
run $ABRA recipe release DOESNTEXIST --no-input
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'unable to clone DOESNTEXIST'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "release patch bump" {
|
@test "release patch bump" {
|
||||||
|
@ -24,11 +24,9 @@ teardown(){
|
|||||||
@test "validate recipe argument" {
|
@test "validate recipe argument" {
|
||||||
run $ABRA recipe sync --no-input
|
run $ABRA recipe sync --no-input
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no recipe name provided'
|
|
||||||
|
|
||||||
run $ABRA recipe sync DOESNTEXIST --no-input
|
run $ABRA recipe sync DOESNTEXIST --no-input
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'unable to clone DOESNTEXIST'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "allow unstaged changes" {
|
@test "allow unstaged changes" {
|
||||||
|
@ -24,11 +24,9 @@ teardown(){
|
|||||||
@test "validate recipe argument" {
|
@test "validate recipe argument" {
|
||||||
run $ABRA recipe upgrade --no-input
|
run $ABRA recipe upgrade --no-input
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'no recipe name provided'
|
|
||||||
|
|
||||||
run $ABRA recipe upgrade DOESNTEXIST --no-input
|
run $ABRA recipe upgrade DOESNTEXIST --no-input
|
||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'unable to clone DOESNTEXIST'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "retrieve recipe if missing" {
|
@test "retrieve recipe if missing" {
|
||||||
|
@ -32,13 +32,3 @@ setup() {
|
|||||||
assert_success
|
assert_success
|
||||||
assert_output "$latestVersion"
|
assert_output "$latestVersion"
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "app is first service listed" {
|
|
||||||
run bash -c '$ABRA recipe versions gitea --machine | jq -r ".[0].service" | uniq'
|
|
||||||
assert_success
|
|
||||||
assert_output 'app'
|
|
||||||
|
|
||||||
run bash -c '$ABRA recipe versions gitea --machine | jq -r ".[1].service" | uniq'
|
|
||||||
assert_success
|
|
||||||
assert_output 'db'
|
|
||||||
}
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user