Compare commits

...

16 Commits

Author SHA1 Message Date
cff7534bf9 chore: publish 0.4.0-alpha-rc6
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-19 13:33:32 +01:00
13e582349c fix: correctly override with ~/.ssh/config if failing to connect
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-19 13:28:57 +01:00
b1b9612e01 fix: dont try to parse empty values on status lookup
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-19 12:38:41 +01:00
afeee1270e test: break up integration, rejig manual 2022-01-19 12:17:09 +01:00
cb210d0c81 docs: pass on flag/help strings
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-19 11:21:06 +01:00
9f2bb3f74f refactor!: remove auto dns, too magic, too broken 2022-01-19 11:20:51 +01:00
a33767f848 refactor!: drop auto traefik deploy, rarely works
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-19 11:08:43 +01:00
a1abe5c6be refactor!: drop backup/restore for now
This will be done with the bot from now on.
2022-01-19 11:06:54 +01:00
672b44f965 test: remove since we're not supporting that in abra now 2022-01-19 11:04:28 +01:00
6d9573ec7e test: more help for how to do this 2022-01-19 11:04:15 +01:00
53cd3b8b71 fix: drop duplicate flags 2022-01-19 10:58:09 +01:00
b9ec41647b fix: when upgrading, skip over bad tags, don't error out
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-19 10:40:55 +01:00
f4b563528f docs: point to new option for better assurance on tag listing 2022-01-19 10:40:37 +01:00
f9a2c1d58f refactor: put StripTagMeta into formatter package
Avoid circular import.
2022-01-19 10:40:14 +01:00
7a66a90ecb fix!: change dry-run alias to not conflict with debug 2022-01-18 17:13:28 +01:00
0e688f1407 refactor!: migrate to urfave/cli v1
All checks were successful
continuous-integration/drone/push Build is passing
Better flexible flags handling.
2022-01-18 14:38:20 +01:00
78 changed files with 691 additions and 1012 deletions

1
.gitignore vendored
View File

@ -4,4 +4,5 @@
.vscode/ .vscode/
abra abra
dist/ dist/
tests/integration/.abra/catalogue
vendor/ vendor/

View File

@ -43,15 +43,3 @@ loc-author:
sort -f | \ sort -f | \
uniq -ic | \ uniq -ic | \
sort -n sort -n
int-core:
@docker run \
-v $$(pwd):/src \
--env-file .e2e.env \
debian:bullseye-slim \
sh -c "\
apt update && apt install -y wget curl git; echo ""; echo ""; \
git config --global user.email 'e2e@coopcloud.tech'; \
git config --global user.name 'e2e'; \
cd /src/tests/integration && bash core.sh -- --dev \
"

View File

@ -7,7 +7,7 @@
The Co-op Cloud utility belt 🎩🐇 The Co-op Cloud utility belt 🎩🐇
`abra` is a command-line tool for managing your own [Co-op Cloud](https://coopcloud.tech). It can provision new servers, create apps, deploy them, run backup and restore operations and a whole lot of other things. Please see [docs.coopcloud.tech](https://docs.coopcloud.tech) for more extensive documentation. `abra` is a command-line tool for managing your own [Co-op Cloud](https://coopcloud.tech). It can provision new servers, create apps, deploy them and a whole lot of other things. Please see [docs.coopcloud.tech](https://docs.coopcloud.tech) for more extensive documentation.
## Quick install ## Quick install

View File

@ -1,29 +1,22 @@
package app package app
import ( import (
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// AppCommand defines the `abra app` command and ets subcommands var AppCommand = cli.Command{
var AppCommand = &cli.Command{ Name: "app",
Name: "app", Aliases: []string{"a"},
Usage: "Manage apps", Usage: "Manage apps",
Aliases: []string{"a"}, ArgsUsage: "<app>",
ArgsUsage: "<app>", Description: "This command provides functionality for managing the life cycle of your apps",
Description: ` Subcommands: []cli.Command{
This command provides all the functionality you need to manage the life cycle
of your apps. From initial deployment, day-2 operations (e.g. backup/restore)
to scaling apps up and spinning them down.
`,
Subcommands: []*cli.Command{
appNewCommand, appNewCommand,
appConfigCommand, appConfigCommand,
appRestartCommand, appRestartCommand,
appDeployCommand, appDeployCommand,
appUpgradeCommand, appUpgradeCommand,
appUndeployCommand, appUndeployCommand,
appBackupCommand,
appRestoreCommand,
appRemoveCommand, appRemoveCommand,
appCheckCommand, appCheckCommand,
appListCommand, appListCommand,

View File

@ -1,77 +0,0 @@
package app
import (
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"strings"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/config"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
var backupAllServices bool
var backupAllServicesFlag = &cli.BoolFlag{
Name: "all",
Value: false,
Destination: &backupAllServices,
Aliases: []string{"a"},
Usage: "Backup all services",
}
var appBackupCommand = &cli.Command{
Name: "backup",
Usage: "Backup an app",
Aliases: []string{"b"},
Flags: []cli.Flag{backupAllServicesFlag},
ArgsUsage: "<service>",
Action: func(c *cli.Context) error {
app := internal.ValidateApp(c)
if c.Args().Get(1) != "" && backupAllServices {
internal.ShowSubcommandHelpAndError(c, errors.New("cannot use '<service>' and '--all' together"))
}
abraSh := path.Join(config.RECIPES_DIR, app.Type, "abra.sh")
if _, err := os.Stat(abraSh); err != nil {
if os.IsNotExist(err) {
logrus.Fatalf("%s does not exist?", abraSh)
}
logrus.Fatal(err)
}
sourceCmd := fmt.Sprintf("source %s", abraSh)
execCmd := "abra_backup"
if !backupAllServices {
serviceName := c.Args().Get(1)
if serviceName == "" {
internal.ShowSubcommandHelpAndError(c, errors.New("no service(s) target provided"))
}
execCmd = fmt.Sprintf("abra_backup_%s", serviceName)
}
bytes, err := ioutil.ReadFile(abraSh)
if err != nil {
logrus.Fatal(err)
}
if !strings.Contains(string(bytes), execCmd) {
logrus.Fatalf("%s doesn't have a %s function", app.Type, execCmd)
}
sourceAndExec := fmt.Sprintf("%s; %s", sourceCmd, execCmd)
cmd := exec.Command("bash", "-c", sourceAndExec)
if err := internal.RunCmd(cmd); err != nil {
logrus.Fatal(err)
}
return nil
},
BashComplete: autocomplete.AppNameComplete,
}

View File

@ -9,14 +9,19 @@ import (
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var appCheckCommand = &cli.Command{ var appCheckCommand = cli.Command{
Name: "check", Name: "check",
Usage: "Check if app is configured correctly",
Aliases: []string{"c"}, Aliases: []string{"c"},
Usage: "Check if app is configured correctly",
ArgsUsage: "<service>", ArgsUsage: "<service>",
Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
},
Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)

View File

@ -10,13 +10,18 @@ import (
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var appConfigCommand = &cli.Command{ var appConfigCommand = cli.Command{
Name: "config", Name: "config",
Aliases: []string{"c"}, Aliases: []string{"c"},
Usage: "Edit app config", Usage: "Edit app config",
Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
},
Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
appName := c.Args().First() appName := c.Args().First()

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"strings" "strings"
@ -15,14 +16,19 @@ import (
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/archive"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var appCpCommand = &cli.Command{ var appCpCommand = cli.Command{
Name: "cp", Name: "cp",
Aliases: []string{"c"}, Aliases: []string{"c"},
ArgsUsage: "<src> <dst>", ArgsUsage: "<src> <dst>",
Usage: "Copy files to/from a running app service", Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
},
Before: internal.SubCommandBefore,
Usage: "Copy files to/from a running app service",
Description: ` Description: `
This command supports copying files to and from any app service file system. This command supports copying files to and from any app service file system.
@ -118,7 +124,7 @@ func configureAndCp(
filters := filters.NewArgs() filters := filters.NewArgs()
filters.Add("name", fmt.Sprintf("%s_%s", appEnv.StackName(), service)) filters.Add("name", fmt.Sprintf("%s_%s", appEnv.StackName(), service))
container, err := container.GetContainer(c.Context, cl, filters, true) container, err := container.GetContainer(context.Background(), cl, filters, true)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -137,11 +143,11 @@ func configureAndCp(
} }
copyOpts := types.CopyToContainerOptions{AllowOverwriteDirWithFile: false, CopyUIDGID: false} copyOpts := types.CopyToContainerOptions{AllowOverwriteDirWithFile: false, CopyUIDGID: false}
if err := cl.CopyToContainer(c.Context, container.ID, dstPath, content, copyOpts); err != nil { if err := cl.CopyToContainer(context.Background(), container.ID, dstPath, content, copyOpts); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
} else { } else {
content, _, err := cl.CopyFromContainer(c.Context, container.ID, srcPath) content, _, err := cl.CopyFromContainer(context.Background(), container.ID, srcPath)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -3,19 +3,22 @@ package app
import ( import (
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var appDeployCommand = &cli.Command{ var appDeployCommand = cli.Command{
Name: "deploy", Name: "deploy",
Aliases: []string{"d"}, Aliases: []string{"d"},
Usage: "Deploy an app", Usage: "Deploy an app",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.ForceFlag, internal.ForceFlag,
internal.ChaosFlag, internal.ChaosFlag,
internal.NoDomainChecksFlag, internal.NoDomainChecksFlag,
internal.DontWaitConvergeFlag, internal.DontWaitConvergeFlag,
}, },
Before: internal.SubCommandBefore,
Description: ` Description: `
This command deploys an app. It does not support incrementing the version of a This command deploys an app. It does not support incrementing the version of a
deployed app, for this you need to look at the "abra app upgrade <app>" deployed app, for this you need to look at the "abra app upgrade <app>"

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -15,10 +16,10 @@ import (
"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/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var appErrorsCommand = &cli.Command{ var appErrorsCommand = cli.Command{
Name: "errors", Name: "errors",
Usage: "List errors for a deployed app", Usage: "List errors for a deployed app",
Description: ` Description: `
@ -44,8 +45,13 @@ further information which can help you debug the cause of an app failure via
the logs. the logs.
`, `,
Aliases: []string{"e"}, Aliases: []string{"e"},
Flags: []cli.Flag{internal.WatchFlag}, Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.WatchFlag,
},
Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
@ -55,7 +61,7 @@ the logs.
logrus.Fatal(err) logrus.Fatal(err)
} }
isDeployed, _, err := stack.IsDeployed(c.Context, cl, app.StackName()) isDeployed, _, err := stack.IsDeployed(context.Background(), cl, app.StackName())
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -91,7 +97,7 @@ func checkErrors(c *cli.Context, cl *dockerClient.Client, app config.App) error
for _, service := range recipe.Config.Services { for _, service := range recipe.Config.Services {
filters := filters.NewArgs() filters := filters.NewArgs()
filters.Add("name", service.Name) filters.Add("name", service.Name)
containers, err := cl.ContainerList(c.Context, types.ContainerListOptions{Filters: filters}) containers, err := cl.ContainerList(context.Background(), types.ContainerListOptions{Filters: filters})
if err != nil { if err != nil {
return err return err
} }
@ -102,7 +108,7 @@ func checkErrors(c *cli.Context, cl *dockerClient.Client, app config.App) error
} }
container := containers[0] container := containers[0]
containerState, err := cl.ContainerInspect(c.Context, container.ID) containerState, err := cl.ContainerInspect(context.Background(), container.ID)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -12,22 +12,19 @@ import (
"coopcloud.tech/abra/pkg/ssh" "coopcloud.tech/abra/pkg/ssh"
"coopcloud.tech/tagcmp" "coopcloud.tech/tagcmp"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var status bool var status bool
var statusFlag = &cli.BoolFlag{ var statusFlag = &cli.BoolFlag{
Name: "status", Name: "status, S",
Aliases: []string{"S"},
Value: false,
Usage: "Show app deployment status", Usage: "Show app deployment status",
Destination: &status, Destination: &status,
} }
var appType string var appType string
var typeFlag = &cli.StringFlag{ var typeFlag = &cli.StringFlag{
Name: "type", Name: "type, t",
Aliases: []string{"t"},
Value: "", Value: "",
Usage: "Show apps of a specific type", Usage: "Show apps of a specific type",
Destination: &appType, Destination: &appType,
@ -35,8 +32,7 @@ var typeFlag = &cli.StringFlag{
var listAppServer string var listAppServer string
var listAppServerFlag = &cli.StringFlag{ var listAppServerFlag = &cli.StringFlag{
Name: "server", Name: "server, s",
Aliases: []string{"s"},
Value: "", Value: "",
Usage: "Show apps of a specific server", Usage: "Show apps of a specific server",
Destination: &listAppServer, Destination: &listAppServer,
@ -61,9 +57,10 @@ type serverStatus struct {
upgradeCount int upgradeCount int
} }
var appListCommand = &cli.Command{ var appListCommand = cli.Command{
Name: "list", Name: "list",
Usage: "List all managed apps", Aliases: []string{"ls"},
Usage: "List all managed apps",
Description: ` Description: `
This command looks at your local file system listing of apps and servers (e.g. This command looks at your local file system listing of apps and servers (e.g.
in ~/.abra/) to generate a report of all your apps. in ~/.abra/) to generate a report of all your apps.
@ -72,12 +69,14 @@ By passing the "--status/-S" flag, you can query all your servers for the
actual live deployment status. Depending on how many servers you manage, this actual live deployment status. Depending on how many servers you manage, this
can take some time. can take some time.
`, `,
Aliases: []string{"ls"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
statusFlag, statusFlag,
listAppServerFlag, listAppServerFlag,
typeFlag, typeFlag,
}, },
Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
appFiles, err := config.LoadAppFiles(listAppServer) appFiles, err := config.LoadAppFiles(listAppServer)
if err != nil { if err != nil {
@ -145,7 +144,9 @@ can take some time.
version := "unknown" version := "unknown"
if statusMeta, ok := statuses[app.StackName()]; ok { if statusMeta, ok := statuses[app.StackName()]; ok {
if currentVersion, exists := statusMeta["version"]; exists { if currentVersion, exists := statusMeta["version"]; exists {
version = currentVersion if currentVersion != "" {
version = currentVersion
}
} }
if statusMeta["status"] != "" { if statusMeta["status"] != "" {
status = statusMeta["status"] status = statusMeta["status"]

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -15,7 +16,7 @@ import (
"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/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var logOpts = types.ContainerLogsOptions{ var logOpts = types.ContainerLogsOptions{
@ -32,7 +33,7 @@ func stackLogs(c *cli.Context, stackName string, client *dockerClient.Client) {
filters := filters.NewArgs() filters := filters.NewArgs()
filters.Add("name", stackName) filters.Add("name", stackName)
serviceOpts := types.ServiceListOptions{Filters: filters} serviceOpts := types.ServiceListOptions{Filters: filters}
services, err := client.ServiceList(c.Context, serviceOpts) services, err := client.ServiceList(context.Background(), serviceOpts)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -45,7 +46,7 @@ func stackLogs(c *cli.Context, stackName string, client *dockerClient.Client) {
logOpts.ShowStdout = false logOpts.ShowStdout = false
} }
logs, err := client.ServiceLogs(c.Context, s, logOpts) logs, err := client.ServiceLogs(context.Background(), s, logOpts)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -63,14 +64,17 @@ func stackLogs(c *cli.Context, stackName string, client *dockerClient.Client) {
os.Exit(0) os.Exit(0)
} }
var appLogsCommand = &cli.Command{ var appLogsCommand = cli.Command{
Name: "logs", Name: "logs",
Aliases: []string{"l"}, Aliases: []string{"l"},
ArgsUsage: "[<service>]", ArgsUsage: "[<service>]",
Usage: "Tail app logs", Usage: "Tail app logs",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.StdErrOnlyFlag, internal.StdErrOnlyFlag,
internal.DebugFlag,
internal.NoInputFlag,
}, },
Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
@ -98,7 +102,7 @@ var appLogsCommand = &cli.Command{
func tailServiceLogs(c *cli.Context, cl *dockerClient.Client, app config.App, serviceName string) error { func tailServiceLogs(c *cli.Context, cl *dockerClient.Client, app config.App, serviceName 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))
chosenService, err := service.GetService(c.Context, cl, filters, internal.NoInput) chosenService, err := service.GetService(context.Background(), cl, filters, internal.NoInput)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -107,7 +111,7 @@ func tailServiceLogs(c *cli.Context, cl *dockerClient.Client, app config.App, se
logOpts.ShowStdout = false logOpts.ShowStdout = false
} }
logs, err := cl.ServiceLogs(c.Context, chosenService.ID, logOpts) logs, err := cl.ServiceLogs(context.Background(), chosenService.ID, logOpts)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -3,7 +3,7 @@ package app
import ( import (
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var appNewDescription = ` var appNewDescription = `
@ -26,18 +26,21 @@ 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 = cli.Command{
Name: "new", Name: "new",
Usage: "Create a new app",
Aliases: []string{"n"}, Aliases: []string{"n"},
Usage: "Create a new app",
Description: appNewDescription, Description: appNewDescription,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.NewAppServerFlag, internal.NewAppServerFlag,
internal.DomainFlag, internal.DomainFlag,
internal.NewAppNameFlag, internal.NewAppNameFlag,
internal.PassFlag, internal.PassFlag,
internal.SecretsFlag, internal.SecretsFlag,
}, },
Before: internal.SubCommandBefore,
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Action: internal.NewAction, Action: internal.NewAction,
BashComplete: autocomplete.RecipeNameComplete, BashComplete: autocomplete.RecipeNameComplete,

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"strings" "strings"
"time" "time"
@ -17,17 +18,20 @@ import (
"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/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var appPsCommand = &cli.Command{ var appPsCommand = cli.Command{
Name: "ps", Name: "ps",
Aliases: []string{"p"},
Usage: "Check app status", Usage: "Check app status",
Description: "This command shows a more detailed status output of a specific deployed app.", Description: "This command shows a more detailed status output of a specific deployed app.",
Aliases: []string{"p"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.WatchFlag, internal.WatchFlag,
internal.DebugFlag,
internal.NoInputFlag,
}, },
Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
@ -37,7 +41,7 @@ var appPsCommand = &cli.Command{
logrus.Fatal(err) logrus.Fatal(err)
} }
isDeployed, _, err := stack.IsDeployed(c.Context, cl, app.StackName()) isDeployed, _, err := stack.IsDeployed(context.Background(), cl, app.StackName())
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -66,7 +70,7 @@ func showPSOutput(c *cli.Context, app config.App, cl *dockerClient.Client) {
filters := filters.NewArgs() filters := filters.NewArgs()
filters.Add("name", app.StackName()) filters.Add("name", app.StackName())
containers, err := cl.ContainerList(c.Context, types.ContainerListOptions{Filters: filters}) containers, err := cl.ContainerList(context.Background(), types.ContainerListOptions{Filters: filters})
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"fmt" "fmt"
"os" "os"
@ -12,7 +13,7 @@ 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"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// Volumes stores the variable from VolumesFlag // Volumes stores the variable from VolumesFlag
@ -21,18 +22,20 @@ var Volumes bool
// VolumesFlag is used to specify if volumes should be deleted when deleting an app // VolumesFlag is used to specify if volumes should be deleted when deleting an app
var VolumesFlag = &cli.BoolFlag{ var VolumesFlag = &cli.BoolFlag{
Name: "volumes", Name: "volumes",
Value: false,
Destination: &Volumes, Destination: &Volumes,
} }
var appRemoveCommand = &cli.Command{ var appRemoveCommand = cli.Command{
Name: "remove", Name: "remove",
Usage: "Remove an already undeployed app",
Aliases: []string{"rm"}, Aliases: []string{"rm"},
Usage: "Remove an already undeployed app",
Flags: []cli.Flag{ Flags: []cli.Flag{
VolumesFlag, VolumesFlag,
internal.ForceFlag, internal.ForceFlag,
internal.DebugFlag,
internal.NoInputFlag,
}, },
Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
@ -54,7 +57,7 @@ var appRemoveCommand = &cli.Command{
logrus.Fatal(err) logrus.Fatal(err)
} }
isDeployed, _, err := stack.IsDeployed(c.Context, cl, app.StackName()) isDeployed, _, err := stack.IsDeployed(context.Background(), cl, app.StackName())
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -64,7 +67,7 @@ var appRemoveCommand = &cli.Command{
fs := filters.NewArgs() fs := filters.NewArgs()
fs.Add("name", app.StackName()) fs.Add("name", app.StackName())
secretList, err := cl.SecretList(c.Context, types.SecretListOptions{Filters: fs}) secretList, err := cl.SecretList(context.Background(), types.SecretListOptions{Filters: fs})
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -94,7 +97,7 @@ var appRemoveCommand = &cli.Command{
} }
for _, name := range secretNamesToRemove { for _, name := range secretNamesToRemove {
err := cl.SecretRemove(c.Context, secrets[name]) err := cl.SecretRemove(context.Background(), secrets[name])
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -104,7 +107,7 @@ var appRemoveCommand = &cli.Command{
logrus.Info("no secrets to remove") logrus.Info("no secrets to remove")
} }
volumeListOKBody, err := cl.VolumeList(c.Context, fs) volumeListOKBody, err := cl.VolumeList(context.Background(), fs)
volumeList := volumeListOKBody.Volumes volumeList := volumeListOKBody.Volumes
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
@ -131,7 +134,7 @@ var appRemoveCommand = &cli.Command{
} }
} }
for _, vol := range removeVols { for _, vol := range removeVols {
err := cl.VolumeRemove(c.Context, vol, internal.Force) // last argument is for force removing err := cl.VolumeRemove(context.Background(), vol, internal.Force) // last argument is for force removing
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
@ -10,14 +11,19 @@ import (
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/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var appRestartCommand = &cli.Command{ var appRestartCommand = cli.Command{
Name: "restart", Name: "restart",
Usage: "Restart an app", Aliases: []string{"re"},
Aliases: []string{"re"}, Usage: "Restart an app",
ArgsUsage: "<service>", ArgsUsage: "<service>",
Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
},
Before: internal.SubCommandBefore,
Description: `This command restarts a service within a deployed app.`, Description: `This command restarts a service within a deployed app.`,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
@ -37,22 +43,22 @@ var appRestartCommand = &cli.Command{
serviceName := fmt.Sprintf("%s_%s", app.StackName(), serviceNameShort) serviceName := fmt.Sprintf("%s_%s", app.StackName(), serviceNameShort)
logrus.Debugf("attempting to scale %s to 0 (restart logic)", serviceName) logrus.Debugf("attempting to scale %s to 0 (restart logic)", serviceName)
if err := upstream.RunServiceScale(c.Context, cl, serviceName, 0); err != nil { if err := upstream.RunServiceScale(context.Background(), cl, serviceName, 0); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
if err := stack.WaitOnService(c.Context, cl, serviceName, app.Name); err != nil { if err := stack.WaitOnService(context.Background(), cl, serviceName, app.Name); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Debugf("%s has been scaled to 0 (restart logic)", serviceName) logrus.Debugf("%s has been scaled to 0 (restart logic)", serviceName)
logrus.Debugf("attempting to scale %s to 1 (restart logic)", serviceName) logrus.Debugf("attempting to scale %s to 1 (restart logic)", serviceName)
if err := upstream.RunServiceScale(c.Context, cl, serviceName, 1); err != nil { if err := upstream.RunServiceScale(context.Background(), cl, serviceName, 1); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
if err := stack.WaitOnService(c.Context, cl, serviceName, app.Name); err != nil { if err := stack.WaitOnService(context.Background(), cl, serviceName, app.Name); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -1,79 +0,0 @@
package app
import (
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"strings"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/config"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
var restoreAllServices bool
var restoreAllServicesFlag = &cli.BoolFlag{
Name: "all",
Value: false,
Destination: &restoreAllServices,
Aliases: []string{"a"},
Usage: "Restore all services",
}
var appRestoreCommand = &cli.Command{
Name: "restore",
Usage: "Restore an app from a backup",
Aliases: []string{"rs"},
Flags: []cli.Flag{restoreAllServicesFlag},
ArgsUsage: "<service> [<backup file>]",
Action: func(c *cli.Context) error {
app := internal.ValidateApp(c)
if c.Args().Len() > 1 && restoreAllServices {
internal.ShowSubcommandHelpAndError(c, errors.New("cannot use <service>/<backup file> and '--all' together"))
}
abraSh := path.Join(config.RECIPES_DIR, app.Type, "abra.sh")
if _, err := os.Stat(abraSh); err != nil {
if os.IsNotExist(err) {
logrus.Fatalf("%s does not exist?", abraSh)
}
logrus.Fatal(err)
}
sourceCmd := fmt.Sprintf("source %s", abraSh)
execCmd := "abra_restore"
if !restoreAllServices {
serviceName := c.Args().Get(1)
if serviceName == "" {
internal.ShowSubcommandHelpAndError(c, errors.New("no service(s) target provided"))
}
execCmd = fmt.Sprintf("abra_restore_%s", serviceName)
}
bytes, err := ioutil.ReadFile(abraSh)
if err != nil {
logrus.Fatal(err)
}
if !strings.Contains(string(bytes), execCmd) {
logrus.Fatalf("%s doesn't have a %s function", app.Type, execCmd)
}
backupFile := c.Args().Get(2)
if backupFile != "" {
execCmd = fmt.Sprintf("%s %s", execCmd, backupFile)
}
sourceAndExec := fmt.Sprintf("%s; %s", sourceCmd, execCmd)
cmd := exec.Command("bash", "-c", sourceAndExec)
if err := internal.RunCmd(cmd); err != nil {
logrus.Fatal(err)
}
return nil
},
}

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"fmt" "fmt"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
@ -14,19 +15,22 @@ import (
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var appRollbackCommand = &cli.Command{ var appRollbackCommand = cli.Command{
Name: "rollback", Name: "rollback",
Usage: "Roll an app back to a previous version",
Aliases: []string{"rl"}, Aliases: []string{"rl"},
Usage: "Roll an app back to a previous version",
ArgsUsage: "<app>", ArgsUsage: "<app>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.ForceFlag, internal.ForceFlag,
internal.ChaosFlag, internal.ChaosFlag,
internal.DontWaitConvergeFlag, internal.DontWaitConvergeFlag,
}, },
Before: internal.SubCommandBefore,
Description: ` Description: `
This command rolls an app back to a previous version if one exists. This command rolls an app back to a previous version if one exists.
@ -34,7 +38,7 @@ You may pass "--force/-f" to downgrade to the same version again. This can be
useful if the container runtime has gotten into a weird state. useful if the container runtime has gotten into a weird state.
This action could be destructive, please ensure you have a copy of your app This action could be destructive, please ensure you have a copy of your app
data beforehand - see "abra app backup <app>" for more. data beforehand.
Chas mode ("--chaos") will deploy your local checkout of a recipe as-is, Chas mode ("--chaos") will deploy your local checkout of a recipe as-is,
including unstaged changes and can be useful for live hacking and testing new including unstaged changes and can be useful for live hacking and testing new
@ -67,7 +71,7 @@ recipes.
logrus.Debugf("checking whether %s is already deployed", stackName) logrus.Debugf("checking whether %s is already deployed", stackName)
isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName) isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, stackName)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
@ -13,7 +14,7 @@ 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"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var user string var user string
@ -26,28 +27,30 @@ var userFlag = &cli.StringFlag{
var noTTY bool var noTTY bool
var noTTYFlag = &cli.BoolFlag{ var noTTYFlag = &cli.BoolFlag{
Name: "no-tty", Name: "no-tty",
Value: false,
Destination: &noTTY, Destination: &noTTY,
} }
var appRunCommand = &cli.Command{ var appRunCommand = cli.Command{
Name: "run", Name: "run",
Aliases: []string{"r"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
noTTYFlag, noTTYFlag,
userFlag, userFlag,
}, },
Aliases: []string{"r"}, Before: internal.SubCommandBefore,
ArgsUsage: "<service> <args>...", ArgsUsage: "<service> <args>...",
Usage: "Run a command in a service container", Usage: "Run a command in a service container",
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
if c.Args().Len() < 2 { if len(c.Args()) < 2 {
internal.ShowSubcommandHelpAndError(c, errors.New("no <service> provided?")) internal.ShowSubcommandHelpAndError(c, errors.New("no <service> provided?"))
} }
if c.Args().Len() < 3 { if len(c.Args()) < 3 {
internal.ShowSubcommandHelpAndError(c, errors.New("no <args> provided?")) internal.ShowSubcommandHelpAndError(c, errors.New("no <args> provided?"))
} }
@ -61,12 +64,12 @@ var appRunCommand = &cli.Command{
filters := filters.NewArgs() filters := filters.NewArgs()
filters.Add("name", stackAndServiceName) filters.Add("name", stackAndServiceName)
targetContainer, err := containerPkg.GetContainer(c.Context, cl, filters, true) targetContainer, err := containerPkg.GetContainer(context.Background(), cl, filters, true)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
cmd := c.Args().Slice()[2:] cmd := c.Args()[2:]
execCreateOpts := types.ExecConfig{ execCreateOpts := types.ExecConfig{
AttachStderr: true, AttachStderr: true,
AttachStdin: true, AttachStdin: true,

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -14,29 +15,32 @@ 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"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var allSecrets bool var allSecrets bool
var allSecretsFlag = &cli.BoolFlag{ var allSecretsFlag = &cli.BoolFlag{
Name: "all", Name: "all, a",
Aliases: []string{"a"},
Value: false,
Destination: &allSecrets, Destination: &allSecrets,
Usage: "Generate all secrets", Usage: "Generate all secrets",
} }
var appSecretGenerateCommand = &cli.Command{ var appSecretGenerateCommand = cli.Command{
Name: "generate", Name: "generate",
Aliases: []string{"g"}, Aliases: []string{"g"},
Usage: "Generate secrets", Usage: "Generate secrets",
ArgsUsage: "<secret> <version>", ArgsUsage: "<secret> <version>",
Flags: []cli.Flag{allSecretsFlag, internal.PassFlag}, Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
allSecretsFlag, internal.PassFlag,
},
Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
if c.Args().Len() == 1 && !allSecrets { if len(c.Args()) == 1 && !allSecrets {
err := errors.New("missing arguments <secret>/<version> or '--all'") err := errors.New("missing arguments <secret>/<version> or '--all'")
internal.ShowSubcommandHelpAndError(c, err) internal.ShowSubcommandHelpAndError(c, err)
} }
@ -95,11 +99,16 @@ var appSecretGenerateCommand = &cli.Command{
}, },
} }
var appSecretInsertCommand = &cli.Command{ var appSecretInsertCommand = cli.Command{
Name: "insert", Name: "insert",
Aliases: []string{"i"}, Aliases: []string{"i"},
Usage: "Insert secret", Usage: "Insert secret",
Flags: []cli.Flag{internal.PassFlag}, Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.PassFlag,
},
Before: internal.SubCommandBefore,
ArgsUsage: "<app> <secret-name> <version> <data>", ArgsUsage: "<app> <secret-name> <version> <data>",
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Description: ` Description: `
@ -117,7 +126,7 @@ Example:
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
if c.Args().Len() != 4 { if len(c.Args()) != 4 {
internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments?")) internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments?"))
} }
@ -140,11 +149,16 @@ Example:
}, },
} }
var appSecretRmCommand = &cli.Command{ var appSecretRmCommand = cli.Command{
Name: "remove", Name: "remove",
Usage: "Remove a secret", Aliases: []string{"rm"},
Aliases: []string{"rm"}, Usage: "Remove a secret",
Flags: []cli.Flag{allSecretsFlag, internal.PassFlag}, Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
allSecretsFlag, internal.PassFlag,
},
Before: internal.SubCommandBefore,
ArgsUsage: "<app> <secret-name>", ArgsUsage: "<app> <secret-name>",
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Description: ` Description: `
@ -173,7 +187,7 @@ Example:
filters := filters.NewArgs() filters := filters.NewArgs()
filters.Add("name", app.StackName()) filters.Add("name", app.StackName())
secretList, err := cl.SecretList(c.Context, types.SecretListOptions{Filters: filters}) secretList, err := cl.SecretList(context.Background(), types.SecretListOptions{Filters: filters})
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -183,7 +197,7 @@ Example:
secretName := cont.Spec.Annotations.Name secretName := cont.Spec.Annotations.Name
parsed := secret.ParseGeneratedSecretName(secretName, app) parsed := secret.ParseGeneratedSecretName(secretName, app)
if allSecrets { if allSecrets {
if err := cl.SecretRemove(c.Context, secretName); err != nil { if err := cl.SecretRemove(context.Background(), secretName); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Infof("deleted %s successfully from server", secretName) logrus.Infof("deleted %s successfully from server", secretName)
@ -197,7 +211,7 @@ Example:
} }
} else { } else {
if parsed == secretToRm { if parsed == secretToRm {
if err := cl.SecretRemove(c.Context, secretName); err != nil { if err := cl.SecretRemove(context.Background(), secretName); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -218,10 +232,15 @@ Example:
}, },
} }
var appSecretLsCommand = &cli.Command{ var appSecretLsCommand = cli.Command{
Name: "list", Name: "list",
Usage: "List all secrets",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
},
Before: internal.SubCommandBefore,
Usage: "List all secrets",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
secrets := secret.ReadSecretEnvVars(app.Env) secrets := secret.ReadSecretEnvVars(app.Env)
@ -236,7 +255,7 @@ var appSecretLsCommand = &cli.Command{
filters := filters.NewArgs() filters := filters.NewArgs()
filters.Add("name", app.StackName()) filters.Add("name", app.StackName())
secretList, err := cl.SecretList(c.Context, types.SecretListOptions{Filters: filters}) secretList, err := cl.SecretList(context.Background(), types.SecretListOptions{Filters: filters})
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -272,12 +291,12 @@ var appSecretLsCommand = &cli.Command{
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
} }
var appSecretCommand = &cli.Command{ var appSecretCommand = cli.Command{
Name: "secret", Name: "secret",
Aliases: []string{"s"}, Aliases: []string{"s"},
Usage: "Manage app secrets", Usage: "Manage app secrets",
ArgsUsage: "<command>", ArgsUsage: "<command>",
Subcommands: []*cli.Command{ Subcommands: []cli.Command{
appSecretGenerateCommand, appSecretGenerateCommand,
appSecretInsertCommand, appSecretInsertCommand,
appSecretRmCommand, appSecretRmCommand,

View File

@ -1,18 +1,25 @@
package app package app
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"
stack "coopcloud.tech/abra/pkg/upstream/stack" stack "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var appUndeployCommand = &cli.Command{ var appUndeployCommand = cli.Command{
Name: "undeploy", Name: "undeploy",
Aliases: []string{"un"}, Aliases: []string{"un"},
Usage: "Undeploy an app", Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
},
Before: internal.SubCommandBefore,
Usage: "Undeploy an app",
Description: ` Description: `
This does not destroy any of the application data. However, you should remain This does not destroy any of the application data. However, you should remain
vigilant, as your swarm installation will consider any previously attached vigilant, as your swarm installation will consider any previously attached
@ -29,7 +36,7 @@ volumes as eligiblef or pruning once undeployed.
logrus.Debugf("checking whether %s is already deployed", stackName) logrus.Debugf("checking whether %s is already deployed", stackName)
isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName) isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, stackName)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -43,7 +50,7 @@ volumes as eligiblef or pruning once undeployed.
} }
rmOpts := stack.Remove{Namespaces: []string{app.StackName()}} rmOpts := stack.Remove{Namespaces: []string{app.StackName()}}
if err := stack.RunRemove(c.Context, cl, rmOpts); err != nil { if err := stack.RunRemove(context.Background(), cl, rmOpts); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"fmt" "fmt"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
@ -13,19 +14,22 @@ import (
"coopcloud.tech/tagcmp" "coopcloud.tech/tagcmp"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var appUpgradeCommand = &cli.Command{ var appUpgradeCommand = cli.Command{
Name: "upgrade", Name: "upgrade",
Aliases: []string{"up"}, Aliases: []string{"up"},
Usage: "Upgrade an app", Usage: "Upgrade an app",
ArgsUsage: "<app>", ArgsUsage: "<app>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.ForceFlag, internal.ForceFlag,
internal.ChaosFlag, internal.ChaosFlag,
internal.NoDomainChecksFlag, internal.NoDomainChecksFlag,
}, },
Before: internal.SubCommandBefore,
Description: ` Description: `
This command supports upgrading an app. You can use it to choose and roll out a This command supports upgrading an app. You can use it to choose and roll out a
new upgrade to an existing app. new upgrade to an existing app.
@ -38,7 +42,7 @@ You may pass "--force/-f" to upgrade to the same version again. This can be
useful if the container runtime has gotten into a weird state. useful if the container runtime has gotten into a weird state.
This action could be destructive, please ensure you have a copy of your app This action could be destructive, please ensure you have a copy of your app
data beforehand - see "abra app backup <app>" for more. data beforehand.
Chas mode ("--chaos") will deploy your local checkout of a recipe as-is, Chas mode ("--chaos") will deploy your local checkout of a recipe as-is,
including unstaged changes and can be useful for live hacking and testing new including unstaged changes and can be useful for live hacking and testing new
@ -70,7 +74,7 @@ recipes.
logrus.Debugf("checking whether %s is already deployed", stackName) logrus.Debugf("checking whether %s is already deployed", stackName)
isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName) isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, stackName)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -1,6 +1,8 @@
package app package app
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"
@ -9,7 +11,7 @@ import (
"coopcloud.tech/abra/pkg/upstream/stack" "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// getImagePath returns the image name // getImagePath returns the image name
@ -21,17 +23,22 @@ func getImagePath(image string) (string, error) {
path := reference.Path(img) path := reference.Path(img)
path = recipe.StripTagMeta(path) path = formatter.StripTagMeta(path)
logrus.Debugf("parsed %s from %s", path, image) logrus.Debugf("parsed %s from %s", path, image)
return path, nil return path, nil
} }
var appVersionCommand = &cli.Command{ var appVersionCommand = cli.Command{
Name: "version", Name: "version",
Aliases: []string{"v"}, Aliases: []string{"v"},
Usage: "Show app versions", Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
},
Before: internal.SubCommandBefore,
Usage: "Show app versions",
Description: ` Description: `
This command shows all information about versioning related to a deployed app. This command shows all information about versioning related to a deployed app.
This includes the individual image names, tags and digests. But also the Co-op This includes the individual image names, tags and digests. But also the Co-op
@ -48,7 +55,7 @@ Cloud recipe version.
logrus.Debugf("checking whether %s is already deployed", stackName) logrus.Debugf("checking whether %s is already deployed", stackName)
isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName) isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, stackName)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -1,24 +1,31 @@
package app package app
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"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var appVolumeListCommand = &cli.Command{ var appVolumeListCommand = cli.Command{
Name: "list", Name: "list",
Aliases: []string{"ls"},
Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
},
Before: internal.SubCommandBefore,
Usage: "List volumes associated with an app", Usage: "List volumes associated with an app",
Aliases: []string{"ls"},
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
volumeList, err := client.GetVolumes(c.Context, app.Server, app.Name) volumeList, err := client.GetVolumes(context.Background(), app.Server, app.Name)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -45,7 +52,7 @@ var appVolumeListCommand = &cli.Command{
}, },
} }
var appVolumeRemoveCommand = &cli.Command{ var appVolumeRemoveCommand = cli.Command{
Name: "remove", Name: "remove",
Usage: "Remove volume(s) associated with an app", Usage: "Remove volume(s) associated with an app",
Description: ` Description: `
@ -62,12 +69,15 @@ Passing "--force" will select all volumes for removal. Be careful.
ArgsUsage: "<app>", ArgsUsage: "<app>",
Aliases: []string{"rm"}, Aliases: []string{"rm"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.ForceFlag, internal.ForceFlag,
}, },
Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
volumeList, err := client.GetVolumes(c.Context, app.Server, app.Name) volumeList, err := client.GetVolumes(context.Background(), app.Server, app.Name)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -89,7 +99,7 @@ Passing "--force" will select all volumes for removal. Be careful.
volumesToRemove = volumeNames volumesToRemove = volumeNames
} }
err = client.RemoveVolumes(c.Context, app.Server, volumesToRemove, internal.Force) err = client.RemoveVolumes(context.Background(), app.Server, volumesToRemove, internal.Force)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -101,12 +111,12 @@ Passing "--force" will select all volumes for removal. Be careful.
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
} }
var appVolumeCommand = &cli.Command{ var appVolumeCommand = cli.Command{
Name: "volume", Name: "volume",
Aliases: []string{"vl"}, Aliases: []string{"vl"},
Usage: "Manage app volumes", Usage: "Manage app volumes",
ArgsUsage: "<command>", ArgsUsage: "<command>",
Subcommands: []*cli.Command{ Subcommands: []cli.Command{
appVolumeListCommand, appVolumeListCommand,
appVolumeRemoveCommand, appVolumeRemoveCommand,
}, },

View File

@ -15,7 +15,7 @@ import (
"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/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// CatalogueSkipList is all the repos that are not recipes. // CatalogueSkipList is all the repos that are not recipes.
@ -56,17 +56,20 @@ var CatalogueSkipList = map[string]bool{
"tyop": true, "tyop": true,
} }
var catalogueGenerateCommand = &cli.Command{ var catalogueGenerateCommand = cli.Command{
Name: "generate", Name: "generate",
Aliases: []string{"g"}, Aliases: []string{"g"},
Usage: "Generate the recipe catalogue", Usage: "Generate the recipe catalogue",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.PublishFlag, internal.PublishFlag,
internal.DryFlag, internal.DryFlag,
internal.SkipUpdatesFlag, internal.SkipUpdatesFlag,
internal.RegistryUsernameFlag, internal.RegistryUsernameFlag,
internal.RegistryPasswordFlag, internal.RegistryPasswordFlag,
}, },
Before: internal.SubCommandBefore,
Description: ` Description: `
This command generates a new copy of the recipe catalogue which can be found on: This command generates a new copy of the recipe catalogue which can be found on:
@ -247,13 +250,13 @@ keys configured on your account.
} }
// CatalogueCommand defines the `abra catalogue` command and sub-commands. // CatalogueCommand defines the `abra catalogue` command and sub-commands.
var CatalogueCommand = &cli.Command{ var CatalogueCommand = cli.Command{
Name: "catalogue", Name: "catalogue",
Usage: "Manage the recipe catalogue", Usage: "Manage the recipe catalogue",
Aliases: []string{"c"}, Aliases: []string{"c"},
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Description: "This command helps recipe packagers interact with the recipe catalogue", Description: "This command helps recipe packagers interact with the recipe catalogue",
Subcommands: []*cli.Command{ Subcommands: []cli.Command{
catalogueGenerateCommand, catalogueGenerateCommand,
}, },
} }

View File

@ -16,16 +16,15 @@ import (
"coopcloud.tech/abra/cli/server" "coopcloud.tech/abra/cli/server"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/web" "coopcloud.tech/abra/pkg/web"
logrusStack "github.com/Gurpartap/logrus-stack"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// AutoCompleteCommand helps people set up auto-complete in their shells // AutoCompleteCommand helps people set up auto-complete in their shells
var AutoCompleteCommand = &cli.Command{ var AutoCompleteCommand = cli.Command{
Name: "autocomplete", Name: "autocomplete",
Usage: "Configure shell autocompletion (recommended)",
Aliases: []string{"ac"}, Aliases: []string{"ac"},
Usage: "Configure shell autocompletion (recommended)",
Description: ` Description: `
This command helps set up autocompletion in your shell by downloading the This command helps set up autocompletion in your shell by downloading the
relevant autocompletion files and laying out what additional information must relevant autocompletion files and laying out what additional information must
@ -43,6 +42,10 @@ Supported shells are as follows:
`, `,
ArgsUsage: "<shell>", ArgsUsage: "<shell>",
Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
},
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
shellType := c.Args().First() shellType := c.Args().First()
@ -105,10 +108,10 @@ echo "PROG=abra\n_CLI_ZSH_AUTOCOMPLETE_HACK=1\nsource /etc/zsh/completion.d/abra
} }
// UpgradeCommand upgrades abra in-place. // UpgradeCommand upgrades abra in-place.
var UpgradeCommand = &cli.Command{ var UpgradeCommand = cli.Command{
Name: "upgrade", Name: "upgrade",
Usage: "Upgrade Abra itself",
Aliases: []string{"u"}, Aliases: []string{"u"},
Usage: "Upgrade Abra itself",
Description: ` Description: `
This command allows you to upgrade Abra in-place with the latest stable or This command allows you to upgrade Abra in-place with the latest stable or
release candidate. release candidate.
@ -150,7 +153,7 @@ func newAbraApp(version, commit string) *cli.App {
|_| |_|
`, `,
Version: fmt.Sprintf("%s-%s", version, commit[:7]), Version: fmt.Sprintf("%s-%s", version, commit[:7]),
Commands: []*cli.Command{ Commands: []cli.Command{
app.AppCommand, app.AppCommand,
server.ServerCommand, server.ServerCommand,
recipe.RecipeCommand, recipe.RecipeCommand,
@ -159,11 +162,7 @@ func newAbraApp(version, commit string) *cli.App {
UpgradeCommand, UpgradeCommand,
AutoCompleteCommand, AutoCompleteCommand,
}, },
Flags: []cli.Flag{ Authors: []cli.Author{
internal.DebugFlag,
internal.NoInputFlag,
},
Authors: []*cli.Author{
// If you're looking at this and you hack on Abra and you're not listed // If you're looking at this and you hack on Abra and you're not listed
// here, please do add yourself! This is a community project, let's show // here, please do add yourself! This is a community project, let's show
// some love // some love
@ -178,13 +177,6 @@ func newAbraApp(version, commit string) *cli.App {
app.EnableBashCompletion = true app.EnableBashCompletion = true
app.Before = func(c *cli.Context) error { app.Before = func(c *cli.Context) error {
if internal.Debug {
logrus.SetLevel(logrus.DebugLevel)
logrus.SetFormatter(&logrus.TextFormatter{})
logrus.SetOutput(os.Stderr)
logrus.AddHook(logrusStack.StandardHook())
}
paths := []string{ paths := []string{
config.ABRA_DIR, config.ABRA_DIR,
path.Join(config.SERVERS_DIR), path.Join(config.SERVERS_DIR),

View File

@ -1,7 +1,11 @@
package internal package internal
import ( import (
"github.com/urfave/cli/v2" "os"
logrusStack "github.com/Gurpartap/logrus-stack"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
) )
// Secrets stores the variable from SecretsFlag // Secrets stores the variable from SecretsFlag
@ -9,9 +13,7 @@ var Secrets bool
// SecretsFlag turns on/off automatically generating secrets // SecretsFlag turns on/off automatically generating secrets
var SecretsFlag = &cli.BoolFlag{ var SecretsFlag = &cli.BoolFlag{
Name: "secrets", Name: "secrets, ss",
Aliases: []string{"ss"},
Value: false,
Usage: "Automatically generate secrets", Usage: "Automatically generate secrets",
Destination: &Secrets, Destination: &Secrets,
} }
@ -21,9 +23,7 @@ var Pass bool
// PassFlag turns on/off storing generated secrets in pass // PassFlag turns on/off storing generated secrets in pass
var PassFlag = &cli.BoolFlag{ var PassFlag = &cli.BoolFlag{
Name: "pass", Name: "pass, p",
Aliases: []string{"p"},
Value: false,
Usage: "Store the generated secrets in a local pass store", Usage: "Store the generated secrets in a local pass store",
Destination: &Pass, Destination: &Pass,
} }
@ -33,9 +33,8 @@ var Context string
// ContextFlag is temp // ContextFlag is temp
var ContextFlag = &cli.StringFlag{ var ContextFlag = &cli.StringFlag{
Name: "context", Name: "context, c",
Value: "", Value: "",
Aliases: []string{"c"},
Destination: &Context, Destination: &Context,
} }
@ -44,9 +43,7 @@ var Force bool
// ForceFlag turns on/off force functionality. // ForceFlag turns on/off force functionality.
var ForceFlag = &cli.BoolFlag{ var ForceFlag = &cli.BoolFlag{
Name: "force", Name: "force, f",
Value: false,
Aliases: []string{"f"},
Usage: "Perform action without further prompt. Use with care!", Usage: "Perform action without further prompt. Use with care!",
Destination: &Force, Destination: &Force,
} }
@ -56,9 +53,7 @@ var Chaos bool
// ChaosFlag turns on/off chaos functionality. // ChaosFlag turns on/off chaos functionality.
var ChaosFlag = &cli.BoolFlag{ var ChaosFlag = &cli.BoolFlag{
Name: "chaos", Name: "chaos, ch",
Value: false,
Aliases: []string{"ch"},
Usage: "Deploy uncommitted recipes changes. Use with care!", Usage: "Deploy uncommitted recipes changes. Use with care!",
Destination: &Chaos, Destination: &Chaos,
} }
@ -68,18 +63,15 @@ var DNSProvider string
// DNSProviderFlag selects a DNS provider. // DNSProviderFlag selects a DNS provider.
var DNSProviderFlag = &cli.StringFlag{ var DNSProviderFlag = &cli.StringFlag{
Name: "provider", Name: "provider, p",
Value: "", Value: "",
Aliases: []string{"p"},
Usage: "DNS provider", Usage: "DNS provider",
Destination: &DNSProvider, Destination: &DNSProvider,
} }
var NoInput bool var NoInput bool
var NoInputFlag = &cli.BoolFlag{ var NoInputFlag = &cli.BoolFlag{
Name: "no-input", Name: "no-input, n",
Value: false,
Aliases: []string{"n"},
Usage: "Toggle non-interactive mode", Usage: "Toggle non-interactive mode",
Destination: &NoInput, Destination: &NoInput,
} }
@ -87,9 +79,8 @@ var NoInputFlag = &cli.BoolFlag{
var DNSType string var DNSType string
var DNSTypeFlag = &cli.StringFlag{ var DNSTypeFlag = &cli.StringFlag{
Name: "type", Name: "type, t",
Value: "", Value: "",
Aliases: []string{"t"},
Usage: "Domain name record type (e.g. A)", Usage: "Domain name record type (e.g. A)",
Destination: &DNSType, Destination: &DNSType,
} }
@ -97,9 +88,8 @@ var DNSTypeFlag = &cli.StringFlag{
var DNSName string var DNSName string
var DNSNameFlag = &cli.StringFlag{ var DNSNameFlag = &cli.StringFlag{
Name: "name", Name: "name, n",
Value: "", Value: "",
Aliases: []string{"n"},
Usage: "Domain name record name (e.g. mysubdomain)", Usage: "Domain name record name (e.g. mysubdomain)",
Destination: &DNSName, Destination: &DNSName,
} }
@ -107,18 +97,16 @@ var DNSNameFlag = &cli.StringFlag{
var DNSValue string var DNSValue string
var DNSValueFlag = &cli.StringFlag{ var DNSValueFlag = &cli.StringFlag{
Name: "value", Name: "value, v",
Value: "", Value: "",
Aliases: []string{"v"},
Usage: "Domain name record value (e.g. 192.168.1.1)", Usage: "Domain name record value (e.g. 192.168.1.1)",
Destination: &DNSValue, Destination: &DNSValue,
} }
var DNSTTL string var DNSTTL string
var DNSTTLFlag = &cli.StringFlag{ var DNSTTLFlag = &cli.StringFlag{
Name: "ttl", Name: "ttl, T",
Value: "600s", Value: "600s",
Aliases: []string{"T"},
Usage: "Domain name TTL value (seconds)", Usage: "Domain name TTL value (seconds)",
Destination: &DNSTTL, Destination: &DNSTTL,
} }
@ -126,9 +114,8 @@ var DNSTTLFlag = &cli.StringFlag{
var DNSPriority int var DNSPriority int
var DNSPriorityFlag = &cli.IntFlag{ var DNSPriorityFlag = &cli.IntFlag{
Name: "priority", Name: "priority, P",
Value: 10, Value: 10,
Aliases: []string{"P"},
Usage: "Domain name priority value", Usage: "Domain name priority value",
Destination: &DNSPriority, Destination: &DNSPriority,
} }
@ -136,8 +123,7 @@ var DNSPriorityFlag = &cli.IntFlag{
var ServerProvider string var ServerProvider string
var ServerProviderFlag = &cli.StringFlag{ var ServerProviderFlag = &cli.StringFlag{
Name: "provider", Name: "provider, p",
Aliases: []string{"p"},
Usage: "3rd party server provider", Usage: "3rd party server provider",
Destination: &ServerProvider, Destination: &ServerProvider,
} }
@ -145,9 +131,8 @@ var ServerProviderFlag = &cli.StringFlag{
var CapsulInstanceURL string var CapsulInstanceURL string
var CapsulInstanceURLFlag = &cli.StringFlag{ var CapsulInstanceURLFlag = &cli.StringFlag{
Name: "capsul-url", Name: "capsul-url, cu",
Value: "yolo.servers.coop", Value: "yolo.servers.coop",
Aliases: []string{"cu"},
Usage: "capsul instance URL", Usage: "capsul instance URL",
Destination: &CapsulInstanceURL, Destination: &CapsulInstanceURL,
} }
@ -155,9 +140,8 @@ var CapsulInstanceURLFlag = &cli.StringFlag{
var CapsulName string var CapsulName string
var CapsulNameFlag = &cli.StringFlag{ var CapsulNameFlag = &cli.StringFlag{
Name: "capsul-name", Name: "capsul-name, cn",
Value: "", Value: "",
Aliases: []string{"cn"},
Usage: "capsul name", Usage: "capsul name",
Destination: &CapsulName, Destination: &CapsulName,
} }
@ -165,9 +149,8 @@ var CapsulNameFlag = &cli.StringFlag{
var CapsulType string var CapsulType string
var CapsulTypeFlag = &cli.StringFlag{ var CapsulTypeFlag = &cli.StringFlag{
Name: "capsul-type", Name: "capsul-type, ct",
Value: "f1-xs", Value: "f1-xs",
Aliases: []string{"ct"},
Usage: "capsul type", Usage: "capsul type",
Destination: &CapsulType, Destination: &CapsulType,
} }
@ -175,38 +158,33 @@ var CapsulTypeFlag = &cli.StringFlag{
var CapsulImage string var CapsulImage string
var CapsulImageFlag = &cli.StringFlag{ var CapsulImageFlag = &cli.StringFlag{
Name: "capsul-image", Name: "capsul-image, ci",
Value: "debian10", Value: "debian10",
Aliases: []string{"ci"},
Usage: "capsul image", Usage: "capsul image",
Destination: &CapsulImage, Destination: &CapsulImage,
} }
var CapsulSSHKeys cli.StringSlice var CapsulSSHKeys cli.StringSlice
var CapsulSSHKeysFlag = &cli.StringSliceFlag{ var CapsulSSHKeysFlag = &cli.StringSliceFlag{
Name: "capsul-ssh-keys", Name: "capsul-ssh-keys, cs",
Aliases: []string{"cs"}, Usage: "capsul SSH key",
Usage: "capsul SSH key", Value: &CapsulSSHKeys,
Destination: &CapsulSSHKeys,
} }
var CapsulAPIToken string var CapsulAPIToken string
var CapsulAPITokenFlag = &cli.StringFlag{ var CapsulAPITokenFlag = &cli.StringFlag{
Name: "capsul-token", Name: "capsul-token, ca",
Aliases: []string{"ca"},
Usage: "capsul API token", Usage: "capsul API token",
EnvVars: []string{"CAPSUL_TOKEN"}, EnvVar: "CAPSUL_TOKEN",
Destination: &CapsulAPIToken, Destination: &CapsulAPIToken,
} }
var HetznerCloudName string var HetznerCloudName string
var HetznerCloudNameFlag = &cli.StringFlag{ var HetznerCloudNameFlag = &cli.StringFlag{
Name: "hetzner-name", Name: "hetzner-name, hn",
Value: "", Value: "",
Aliases: []string{"hn"},
Usage: "hetzner cloud name", Usage: "hetzner cloud name",
Destination: &HetznerCloudName, Destination: &HetznerCloudName,
} }
@ -214,8 +192,7 @@ var HetznerCloudNameFlag = &cli.StringFlag{
var HetznerCloudType string var HetznerCloudType string
var HetznerCloudTypeFlag = &cli.StringFlag{ var HetznerCloudTypeFlag = &cli.StringFlag{
Name: "hetzner-type", Name: "hetzner-type, ht",
Aliases: []string{"ht"},
Usage: "hetzner cloud type", Usage: "hetzner cloud type",
Destination: &HetznerCloudType, Destination: &HetznerCloudType,
Value: "cx11", Value: "cx11",
@ -224,8 +201,7 @@ var HetznerCloudTypeFlag = &cli.StringFlag{
var HetznerCloudImage string var HetznerCloudImage string
var HetznerCloudImageFlag = &cli.StringFlag{ var HetznerCloudImageFlag = &cli.StringFlag{
Name: "hetzner-image", Name: "hetzner-image, hi",
Aliases: []string{"hi"},
Usage: "hetzner cloud image", Usage: "hetzner cloud image",
Value: "debian-10", Value: "debian-10",
Destination: &HetznerCloudImage, Destination: &HetznerCloudImage,
@ -234,17 +210,15 @@ var HetznerCloudImageFlag = &cli.StringFlag{
var HetznerCloudSSHKeys cli.StringSlice var HetznerCloudSSHKeys cli.StringSlice
var HetznerCloudSSHKeysFlag = &cli.StringSliceFlag{ var HetznerCloudSSHKeysFlag = &cli.StringSliceFlag{
Name: "hetzner-ssh-keys", Name: "hetzner-ssh-keys, hs",
Aliases: []string{"hs"}, Usage: "hetzner cloud SSH keys (e.g. me@foo.com)",
Usage: "hetzner cloud SSH keys (e.g. me@foo.com)", Value: &HetznerCloudSSHKeys,
Destination: &HetznerCloudSSHKeys,
} }
var HetznerCloudLocation string var HetznerCloudLocation string
var HetznerCloudLocationFlag = &cli.StringFlag{ var HetznerCloudLocationFlag = &cli.StringFlag{
Name: "hetzner-location", Name: "hetzner-location, hl",
Aliases: []string{"hl"},
Usage: "hetzner cloud server location", Usage: "hetzner cloud server location",
Value: "hel1", Value: "hel1",
Destination: &HetznerCloudLocation, Destination: &HetznerCloudLocation,
@ -253,10 +227,9 @@ var HetznerCloudLocationFlag = &cli.StringFlag{
var HetznerCloudAPIToken string var HetznerCloudAPIToken string
var HetznerCloudAPITokenFlag = &cli.StringFlag{ var HetznerCloudAPITokenFlag = &cli.StringFlag{
Name: "hetzner-token", Name: "hetzner-token, ha",
Aliases: []string{"ha"},
Usage: "hetzner cloud API token", Usage: "hetzner cloud API token",
EnvVars: []string{"HCLOUD_TOKEN"}, EnvVar: "HCLOUD_TOKEN",
Destination: &HetznerCloudAPIToken, Destination: &HetznerCloudAPIToken,
} }
@ -265,9 +238,7 @@ var Debug bool
// DebugFlag turns on/off verbose logging down to the DEBUG level. // DebugFlag turns on/off verbose logging down to the DEBUG level.
var DebugFlag = &cli.BoolFlag{ var DebugFlag = &cli.BoolFlag{
Name: "debug", Name: "debug, d",
Aliases: []string{"d"},
Value: false,
Destination: &Debug, Destination: &Debug,
Usage: "Show DEBUG messages", Usage: "Show DEBUG messages",
} }
@ -278,60 +249,48 @@ var RC bool
// RCFlag chooses the latest release candidate for install // RCFlag chooses the latest release candidate for install
var RCFlag = &cli.BoolFlag{ var RCFlag = &cli.BoolFlag{
Name: "rc", Name: "rc",
Value: false,
Destination: &RC, Destination: &RC,
Usage: "Insatll the latest release candidate", Usage: "Insatll the latest release candidate",
} }
var Major bool var Major bool
var MajorFlag = &cli.BoolFlag{ var MajorFlag = &cli.BoolFlag{
Name: "major", Name: "major, ma, x",
Usage: "Increase the major part of the version", Usage: "Increase the major part of the version",
Value: false,
Aliases: []string{"ma", "x"},
Destination: &Major, Destination: &Major,
} }
var Minor bool var Minor bool
var MinorFlag = &cli.BoolFlag{ var MinorFlag = &cli.BoolFlag{
Name: "minor", Name: "minor, mi, y",
Usage: "Increase the minor part of the version", Usage: "Increase the minor part of the version",
Value: false,
Aliases: []string{"mi", "y"},
Destination: &Minor, Destination: &Minor,
} }
var Patch bool var Patch bool
var PatchFlag = &cli.BoolFlag{ var PatchFlag = &cli.BoolFlag{
Name: "patch", Name: "patch, pa, z",
Usage: "Increase the patch part of the version", Usage: "Increase the patch part of the version",
Value: false,
Aliases: []string{"pa", "z"},
Destination: &Patch, Destination: &Patch,
} }
var Dry bool var Dry bool
var DryFlag = &cli.BoolFlag{ var DryFlag = &cli.BoolFlag{
Name: "dry-run", Name: "dry-run, dr",
Usage: "Only reports changes that would be made", Usage: "Only reports changes that would be made",
Value: false,
Aliases: []string{"d"},
Destination: &Dry, Destination: &Dry,
} }
var Publish bool var Publish bool
var PublishFlag = &cli.BoolFlag{ var PublishFlag = &cli.BoolFlag{
Name: "publish", Name: "publish, p",
Usage: "Publish changes to git.coopcloud.tech", Usage: "Publish changes to git.coopcloud.tech",
Value: false,
Aliases: []string{"p"},
Destination: &Publish, Destination: &Publish,
} }
var Domain string var Domain string
var DomainFlag = &cli.StringFlag{ var DomainFlag = &cli.StringFlag{
Name: "domain", Name: "domain, dn",
Aliases: []string{"d"},
Value: "", Value: "",
Usage: "Choose a domain name", Usage: "Choose a domain name",
Destination: &Domain, Destination: &Domain,
@ -339,8 +298,7 @@ var DomainFlag = &cli.StringFlag{
var NewAppServer string var NewAppServer string
var NewAppServerFlag = &cli.StringFlag{ var NewAppServerFlag = &cli.StringFlag{
Name: "server", Name: "server, s",
Aliases: []string{"s"},
Value: "", Value: "",
Usage: "Show apps of a specific server", Usage: "Show apps of a specific server",
Destination: &NewAppServer, Destination: &NewAppServer,
@ -348,8 +306,7 @@ var NewAppServerFlag = &cli.StringFlag{
var NewAppName string var NewAppName string
var NewAppNameFlag = &cli.StringFlag{ var NewAppNameFlag = &cli.StringFlag{
Name: "app-name", Name: "app-name, a",
Aliases: []string{"a"},
Value: "", Value: "",
Usage: "Choose an app name", Usage: "Choose an app name",
Destination: &NewAppName, Destination: &NewAppName,
@ -357,92 +314,67 @@ var NewAppNameFlag = &cli.StringFlag{
var NoDomainChecks bool var NoDomainChecks bool
var NoDomainChecksFlag = &cli.BoolFlag{ var NoDomainChecksFlag = &cli.BoolFlag{
Name: "no-domain-checks", Name: "no-domain-checks, nd",
Aliases: []string{"nd"},
Value: false,
Usage: "Disable app domain sanity checks", Usage: "Disable app domain sanity checks",
Destination: &NoDomainChecks, Destination: &NoDomainChecks,
} }
var StdErrOnly bool var StdErrOnly bool
var StdErrOnlyFlag = &cli.BoolFlag{ var StdErrOnlyFlag = &cli.BoolFlag{
Name: "stderr", Name: "stderr, s",
Aliases: []string{"s"},
Value: false,
Usage: "Only tail stderr", Usage: "Only tail stderr",
Destination: &StdErrOnly, Destination: &StdErrOnly,
} }
var AutoDNSRecord bool
var AutoDNSRecordFlag = &cli.BoolFlag{
Name: "auto",
Aliases: []string{"a"},
Value: false,
Usage: "Automatically configure DNS records",
Destination: &AutoDNSRecord,
}
var DontWaitConverge bool var DontWaitConverge bool
var DontWaitConvergeFlag = &cli.BoolFlag{ var DontWaitConvergeFlag = &cli.BoolFlag{
Name: "no-converge-checks", Name: "no-converge-checks, nc",
Aliases: []string{"nc"},
Value: false,
Usage: "Don't wait for converge logic checks", Usage: "Don't wait for converge logic checks",
Destination: &DontWaitConverge, Destination: &DontWaitConverge,
} }
var Watch bool var Watch bool
var WatchFlag = &cli.BoolFlag{ var WatchFlag = &cli.BoolFlag{
Name: "watch", Name: "watch, w",
Aliases: []string{"w"},
Value: false,
Usage: "Watch status by polling repeatedly", Usage: "Watch status by polling repeatedly",
Destination: &Watch, Destination: &Watch,
} }
var OnlyErrors bool var OnlyErrors bool
var OnlyErrorFlag = &cli.BoolFlag{ var OnlyErrorFlag = &cli.BoolFlag{
Name: "errors", Name: "errors, e",
Aliases: []string{"e"},
Value: false,
Usage: "Only show errors", Usage: "Only show errors",
Destination: &OnlyErrors, Destination: &OnlyErrors,
} }
var SkipUpdates bool var SkipUpdates bool
var SkipUpdatesFlag = &cli.BoolFlag{ var SkipUpdatesFlag = &cli.BoolFlag{
Name: "skip-updates", Name: "skip-updates, s",
Aliases: []string{"s"},
Value: false,
Usage: "Skip updating recipe repositories", Usage: "Skip updating recipe repositories",
Destination: &SkipUpdates, Destination: &SkipUpdates,
} }
var RegistryUsername string var RegistryUsername string
var RegistryUsernameFlag = &cli.StringFlag{ var RegistryUsernameFlag = &cli.StringFlag{
Name: "username", Name: "username, user",
Aliases: []string{"user"},
Value: "", Value: "",
Usage: "Registry username", Usage: "Registry username",
EnvVars: []string{"REGISTRY_USERNAME"}, EnvVar: "REGISTRY_USERNAME",
Destination: &RegistryUsername, Destination: &RegistryUsername,
} }
var RegistryPassword string var RegistryPassword string
var RegistryPasswordFlag = &cli.StringFlag{ var RegistryPasswordFlag = &cli.StringFlag{
Name: "password", Name: "password, pass",
Aliases: []string{"pass"},
Value: "", Value: "",
Usage: "Registry password", Usage: "Registry password",
EnvVars: []string{"REGISTRY_PASSWORD"}, EnvVar: "REGISTRY_PASSWORD",
Destination: &RegistryUsername, Destination: &RegistryUsername,
} }
var AllTags bool var AllTags bool
var AllTagsFlag = &cli.BoolFlag{ var AllTagsFlag = &cli.BoolFlag{
Name: "all-tags", Name: "all-tags, a",
Aliases: []string{"a"},
Value: false,
Usage: "List all tags, not just upgrades", Usage: "List all tags, not just upgrades",
Destination: &AllTags, Destination: &AllTags,
} }
@ -495,3 +427,15 @@ Host foo.coopcloud.tech
Good luck! Good luck!
` `
// SubCommandBefore wires up pre-action machinery (e.g. --debug handling).
func SubCommandBefore(c *cli.Context) error {
if Debug {
logrus.SetLevel(logrus.DebugLevel)
logrus.SetFormatter(&logrus.TextFormatter{})
logrus.SetOutput(os.Stderr)
logrus.AddHook(logrusStack.StandardHook())
}
return nil
}

View File

@ -1,6 +1,7 @@
package internal package internal
import ( import (
"context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -17,7 +18,7 @@ import (
"coopcloud.tech/abra/pkg/upstream/stack" "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// DeployAction is the main command-line action for this package // DeployAction is the main command-line action for this package
@ -46,7 +47,7 @@ func DeployAction(c *cli.Context) error {
logrus.Debugf("checking whether %s is already deployed", app.StackName()) logrus.Debugf("checking whether %s is already deployed", app.StackName())
isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, app.StackName()) isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, app.StackName())
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -4,7 +4,7 @@ import (
"os" "os"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// ShowSubcommandHelpAndError exits the program on error, logs the error to the // ShowSubcommandHelpAndError exits the program on error, logs the error to the

View File

@ -12,7 +12,7 @@ import (
"coopcloud.tech/abra/pkg/ssh" "coopcloud.tech/abra/pkg/ssh"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// AppSecrets represents all app secrest // AppSecrets represents all app secrest

View File

@ -3,8 +3,8 @@ package internal
import ( import (
"fmt" "fmt"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
recipePkg "coopcloud.tech/abra/pkg/recipe"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -94,7 +94,7 @@ func GetMainAppImage(recipe recipe.Recipe) (string, error) {
} }
path = reference.Path(img) path = reference.Path(img)
path = recipePkg.StripTagMeta(path) path = formatter.StripTagMeta(path)
return path, nil return path, nil
} }

View File

@ -12,7 +12,7 @@ import (
"coopcloud.tech/abra/pkg/ssh" "coopcloud.tech/abra/pkg/ssh"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// AppName is used for configuring app name programmatically // AppName is used for configuring app name programmatically
@ -160,9 +160,9 @@ func ValidateDomain(c *cli.Context) (string, error) {
// ValidateSubCmdFlags ensures flag order conforms to correct order // ValidateSubCmdFlags ensures flag order conforms to correct order
func ValidateSubCmdFlags(c *cli.Context) bool { func ValidateSubCmdFlags(c *cli.Context) bool {
for argIdx, arg := range c.Args().Slice() { for argIdx, arg := range c.Args() {
if !strings.HasPrefix(arg, "--") { if !strings.HasPrefix(arg, "--") {
for _, flag := range c.Args().Slice()[argIdx:] { for _, flag := range c.Args()[argIdx:] {
if strings.HasPrefix(flag, "--") { if strings.HasPrefix(flag, "--") {
return false return false
} }
@ -369,7 +369,7 @@ func EnsureNewCapsulVPSFlags(c *cli.Context) error {
if err := survey.AskOne(prompt, &CapsulSSHKeys); err != nil { if err := survey.AskOne(prompt, &CapsulSSHKeys); err != nil {
return err return err
} }
CapsulSSHKeys = *cli.NewStringSlice(strings.Split(sshKeys, ",")...) CapsulSSHKeys = cli.StringSlice(strings.Split(sshKeys, ","))
} }
if CapsulAPIToken == "" && !NoInput { if CapsulAPIToken == "" && !NoInput {
@ -448,7 +448,7 @@ func EnsureNewHetznerCloudVPSFlags(c *cli.Context) error {
if err := survey.AskOne(prompt, &sshKeys); err != nil { if err := survey.AskOne(prompt, &sshKeys); err != nil {
return err return err
} }
HetznerCloudSSHKeys = *cli.NewStringSlice(strings.Split(sshKeys, ",")...) HetznerCloudSSHKeys = cli.StringSlice(strings.Split(sshKeys, ","))
} }
if !NoInput { if !NoInput {

View File

@ -9,15 +9,20 @@ import (
"coopcloud.tech/abra/pkg/lint" "coopcloud.tech/abra/pkg/lint"
recipePkg "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var recipeLintCommand = &cli.Command{ var recipeLintCommand = cli.Command{
Name: "lint", Name: "lint",
Usage: "Lint a recipe", Usage: "Lint a recipe",
Aliases: []string{"l"}, Aliases: []string{"l"},
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Flags: []cli.Flag{internal.OnlyErrorFlag}, Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.OnlyErrorFlag,
},
Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete, BashComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipe(c) recipe := internal.ValidateRecipe(c)

View File

@ -6,28 +6,31 @@ import (
"strconv" "strconv"
"strings" "strings"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var pattern string var pattern string
var patternFlag = &cli.StringFlag{ var patternFlag = &cli.StringFlag{
Name: "pattern", Name: "pattern, p",
Value: "", Value: "",
Aliases: []string{"p"},
Usage: "Simple string to filter recipes", Usage: "Simple string to filter recipes",
Destination: &pattern, Destination: &pattern,
} }
var recipeListCommand = &cli.Command{ var recipeListCommand = cli.Command{
Name: "list", Name: "list",
Usage: "List available recipes", Usage: "List available recipes",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
patternFlag, patternFlag,
}, },
Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
catl, err := recipe.ReadRecipeCatalogue() catl, err := recipe.ReadRecipeCatalogue()
if err != nil { if err != nil {

View File

@ -13,7 +13,7 @@ import (
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/git" "coopcloud.tech/abra/pkg/git"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// recipeMetadata is the recipe metadata for the README.md // recipeMetadata is the recipe metadata for the README.md
@ -30,10 +30,15 @@ type recipeMetadata struct {
SSO string SSO string
} }
var recipeNewCommand = &cli.Command{ var recipeNewCommand = cli.Command{
Name: "new", Name: "new",
Aliases: []string{"n"},
Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
},
Before: internal.SubCommandBefore,
Usage: "Create a new recipe", Usage: "Create a new recipe",
Aliases: []string{"n"},
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Description: ` Description: `
This command creates a new recipe. This command creates a new recipe.

View File

@ -1,15 +1,15 @@
package recipe package recipe
import ( import (
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// RecipeCommand defines all recipe related sub-commands. // RecipeCommand defines all recipe related sub-commands.
var RecipeCommand = &cli.Command{ var RecipeCommand = cli.Command{
Name: "recipe", Name: "recipe",
Aliases: []string{"r"},
Usage: "Manage recipes", Usage: "Manage recipes",
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Aliases: []string{"r"},
Description: ` Description: `
A recipe is a blueprint for an app. It is a bunch of config files which A recipe is a blueprint for an app. It is a bunch of config files which
describe how to deploy and maintain an app. Recipes are maintained by the Co-op describe how to deploy and maintain an app. Recipes are maintained by the Co-op
@ -20,7 +20,7 @@ sure the recipe is in good working order and the config upgraded in a timely
manner. Abra supports convenient automation for recipe maintainenace, see the manner. Abra supports convenient automation for recipe maintainenace, see the
"abra recipe upgrade", "abra recipe sync" and "abra recipe release" commands. "abra recipe upgrade", "abra recipe sync" and "abra recipe release" commands.
`, `,
Subcommands: []*cli.Command{ Subcommands: []cli.Command{
recipeListCommand, recipeListCommand,
recipeVersionCommand, recipeVersionCommand,
recipeReleaseCommand, recipeReleaseCommand,

View File

@ -18,13 +18,13 @@ import (
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var recipeReleaseCommand = &cli.Command{ var recipeReleaseCommand = cli.Command{
Name: "release", Name: "release",
Usage: "Release a new recipe version",
Aliases: []string{"rl"}, Aliases: []string{"rl"},
Usage: "Release a new recipe version",
ArgsUsage: "<recipe> [<version>]", ArgsUsage: "<recipe> [<version>]",
Description: ` Description: `
This command is used to specify a new version of a recipe. These versions are This command is used to specify a new version of a recipe. These versions are
@ -48,12 +48,15 @@ requires that you have permission to git push to these repositories and have
your SSH keys configured on your account. your SSH keys configured on your account.
`, `,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.DryFlag, internal.DryFlag,
internal.MajorFlag, internal.MajorFlag,
internal.MinorFlag, internal.MinorFlag,
internal.PatchFlag, internal.PatchFlag,
internal.PublishFlag, internal.PublishFlag,
}, },
Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete, BashComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipeWithPrompt(c) recipe := internal.ValidateRecipeWithPrompt(c)
@ -140,7 +143,7 @@ func getImageVersions(recipe recipe.Recipe) (map[string]string, error) {
path := reference.Path(img) path := reference.Path(img)
path = recipePkg.StripTagMeta(path) path = formatter.StripTagMeta(path)
var tag string var tag string
switch img.(type) { switch img.(type) {

View File

@ -13,20 +13,23 @@ import (
"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/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var recipeSyncCommand = &cli.Command{ var recipeSyncCommand = cli.Command{
Name: "sync", Name: "sync",
Usage: "Sync recipe version label",
Aliases: []string{"s"}, Aliases: []string{"s"},
Usage: "Sync recipe version label",
ArgsUsage: "<recipe> [<version>]", ArgsUsage: "<recipe> [<version>]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.DryFlag, internal.DryFlag,
internal.MajorFlag, internal.MajorFlag,
internal.MinorFlag, internal.MinorFlag,
internal.PatchFlag, internal.PatchFlag,
}, },
Before: internal.SubCommandBefore,
Description: ` Description: `
This command will generate labels for the main recipe service (i.e. by This command will generate labels for the main recipe service (i.e. by
convention, the service named 'app') which corresponds to the following format: convention, the service named 'app') which corresponds to the following format:

View File

@ -12,12 +12,13 @@ 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/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/formatter"
recipePkg "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/tagcmp" "coopcloud.tech/tagcmp"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
type imgPin struct { type imgPin struct {
@ -25,10 +26,10 @@ type imgPin struct {
version tagcmp.Tag version tagcmp.Tag
} }
var recipeUpgradeCommand = &cli.Command{ var recipeUpgradeCommand = cli.Command{
Name: "upgrade", Name: "upgrade",
Usage: "Upgrade recipe image tags",
Aliases: []string{"u"}, Aliases: []string{"u"},
Usage: "Upgrade recipe image tags",
Description: ` Description: `
This command reads and attempts to parse all image tags within the given This command reads and attempts to parse all image tags within the given
<recipe> configuration and prompt with more recent tags to upgrade to. It will <recipe> configuration and prompt with more recent tags to upgrade to. It will
@ -50,11 +51,14 @@ You may invoke this command in "wizard" mode and be prompted for input:
BashComplete: autocomplete.RecipeNameComplete, BashComplete: autocomplete.RecipeNameComplete,
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.PatchFlag, internal.PatchFlag,
internal.MinorFlag, internal.MinorFlag,
internal.MajorFlag, internal.MajorFlag,
internal.AllTagsFlag, internal.AllTagsFlag,
}, },
Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipeWithPrompt(c) recipe := internal.ValidateRecipeWithPrompt(c)
@ -116,7 +120,7 @@ You may invoke this command in "wizard" mode and be prompted for input:
} }
logrus.Debugf("retrieved %s from remote registry for %s", regVersions, image) logrus.Debugf("retrieved %s from remote registry for %s", regVersions, image)
image = recipePkg.StripTagMeta(image) image = formatter.StripTagMeta(image)
switch img.(type) { switch img.(type) {
case reference.NamedTagged: case reference.NamedTagged:
@ -153,7 +157,7 @@ You may invoke this command in "wizard" mode and be prompted for input:
sort.Sort(tagcmp.ByTagDesc(compatible)) sort.Sort(tagcmp.ByTagDesc(compatible))
if len(compatible) == 0 && !internal.AllTags { if len(compatible) == 0 && !internal.AllTags {
logrus.Info(fmt.Sprintf("no new versions available for %s, %s is the latest", image, tag)) logrus.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
} }

View File

@ -6,14 +6,19 @@ import (
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
recipePkg "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var recipeVersionCommand = &cli.Command{ var recipeVersionCommand = cli.Command{
Name: "versions", Name: "versions",
Usage: "List recipe versions", Aliases: []string{"v"},
Aliases: []string{"v"}, Usage: "List recipe versions",
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
},
Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete, BashComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipe(c) recipe := internal.ValidateRecipe(c)

View File

@ -1,6 +1,7 @@
package record package record
import ( import (
"context"
"fmt" "fmt"
"strconv" "strconv"
@ -9,18 +10,21 @@ import (
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"github.com/libdns/gandi" "github.com/libdns/gandi"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// RecordListCommand lists domains. // RecordListCommand lists domains.
var RecordListCommand = &cli.Command{ var RecordListCommand = cli.Command{
Name: "list", Name: "list",
Usage: "List domain name records", Usage: "List domain name records",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
ArgsUsage: "<zone>", ArgsUsage: "<zone>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.DNSProviderFlag, internal.DNSProviderFlag,
}, },
Before: internal.SubCommandBefore,
Description: ` Description: `
This command lists all domain name records managed by a 3rd party provider for This command lists all domain name records managed by a 3rd party provider for
a specific zone. a specific zone.
@ -49,7 +53,7 @@ are listed. This zone must already be created on your provider account.
logrus.Fatalf("%s is not a supported DNS provider", internal.DNSProvider) logrus.Fatalf("%s is not a supported DNS provider", internal.DNSProvider)
} }
records, err := provider.GetRecords(c.Context, zone) records, err := provider.GetRecords(context.Background(), zone)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -1,6 +1,7 @@
package record package record
import ( import (
"context"
"fmt" "fmt"
"strconv" "strconv"
@ -11,24 +12,26 @@ import (
"github.com/libdns/gandi" "github.com/libdns/gandi"
"github.com/libdns/libdns" "github.com/libdns/libdns"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// RecordNewCommand creates a new domain name record. // RecordNewCommand creates a new domain name record.
var RecordNewCommand = &cli.Command{ var RecordNewCommand = cli.Command{
Name: "new", Name: "new",
Usage: "Create a new domain record", Usage: "Create a new domain record",
Aliases: []string{"n"}, Aliases: []string{"n"},
ArgsUsage: "<zone>", ArgsUsage: "<zone>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.DNSProviderFlag, internal.DNSProviderFlag,
internal.DNSTypeFlag, internal.DNSTypeFlag,
internal.DNSNameFlag, internal.DNSNameFlag,
internal.DNSValueFlag, internal.DNSValueFlag,
internal.DNSTTLFlag, internal.DNSTTLFlag,
internal.DNSPriorityFlag, internal.DNSPriorityFlag,
internal.AutoDNSRecordFlag,
}, },
Before: internal.SubCommandBefore,
Description: ` Description: `
This command creates a new domain name record for a specific zone. This command creates a new domain name record for a specific zone.
@ -39,13 +42,7 @@ Example:
abra record new foo.com -p gandi -t A -n myapp -v 192.168.178.44 abra record new foo.com -p gandi -t A -n myapp -v 192.168.178.44
Typically, you need two records, an A record which points at the zone (@.) and You may also invoke this command in "wizard" mode and be prompted for input:
a wildcard record for your apps (*.). Pass "--auto" to have Abra automatically
set this up.
abra record new --auto foo.com -p gandi -v 192.168.178.44
You may also invoke this command in "wizard" mode and be prompted for input
abra record new abra record new
@ -71,25 +68,6 @@ You may also invoke this command in "wizard" mode and be prompted for input
logrus.Fatalf("%s is not a supported DNS provider", internal.DNSProvider) logrus.Fatalf("%s is not a supported DNS provider", internal.DNSProvider)
} }
if internal.AutoDNSRecord {
ipv4, err := dns.EnsureIPv4(zone)
if err != nil {
logrus.Debugf("no ipv4 associated with %s, prompting for input", zone)
if err := internal.EnsureDNSValueFlag(c); err != nil {
logrus.Fatal(err)
}
ipv4 = internal.DNSValue
}
logrus.Infof("automatically configuring @./*. A records for %s for %s (--auto)", zone, ipv4)
if err := autoConfigure(c, &provider, zone, ipv4); err != nil {
logrus.Fatal(err)
}
return nil
}
if err := internal.EnsureDNSTypeFlag(c); err != nil { if err := internal.EnsureDNSTypeFlag(c); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -118,7 +96,7 @@ You may also invoke this command in "wizard" mode and be prompted for input
record.Priority = internal.DNSPriority record.Priority = internal.DNSPriority
} }
records, err := provider.GetRecords(c.Context, zone) records, err := provider.GetRecords(context.Background(), zone)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -132,7 +110,7 @@ You may also invoke this command in "wizard" mode and be prompted for input
} }
createdRecords, err := provider.SetRecords( createdRecords, err := provider.SetRecords(
c.Context, context.Background(),
zone, zone,
[]libdns.Record{record}, []libdns.Record{record},
) )
@ -169,84 +147,3 @@ You may also invoke this command in "wizard" mode and be prompted for input
return nil return nil
}, },
} }
func autoConfigure(c *cli.Context, provider *gandi.Provider, zone, ipv4 string) error {
ttl, err := dns.GetTTL(internal.DNSTTL)
if err != nil {
return err
}
atRecord := libdns.Record{
Type: "A",
Name: "@",
Value: ipv4,
TTL: ttl,
}
wildcardRecord := libdns.Record{
Type: "A",
Name: "*",
Value: ipv4,
TTL: ttl,
}
records := []libdns.Record{atRecord, wildcardRecord}
tableCol := []string{"type", "name", "value", "TTL", "priority"}
table := formatter.CreateTable(tableCol)
for _, record := range records {
existingRecords, err := provider.GetRecords(c.Context, zone)
if err != nil {
return err
}
discovered := false
for _, existingRecord := range existingRecords {
if existingRecord.Type == record.Type &&
existingRecord.Name == record.Name &&
existingRecord.Value == record.Value {
logrus.Warnf("%s record: %s %s for %s already exists?", record.Type, record.Name, record.Value, zone)
discovered = true
}
}
if discovered {
continue
}
createdRecords, err := provider.SetRecords(
c.Context,
zone,
[]libdns.Record{record},
)
if err != nil {
return err
}
if len(createdRecords) == 0 {
return fmt.Errorf("provider library reports that no record was created?")
}
createdRecord := createdRecords[0]
value := createdRecord.Value
if len(createdRecord.Value) > 30 {
value = fmt.Sprintf("%s...", createdRecord.Value[:30])
}
table.Append([]string{
createdRecord.Type,
createdRecord.Name,
value,
createdRecord.TTL.String(),
strconv.Itoa(createdRecord.Priority),
})
}
if table.NumLines() > 0 {
table.Render()
}
return nil
}

View File

@ -1,11 +1,11 @@
package record package record
import ( import (
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// RecordCommand supports managing DNS entries. // RecordCommand supports managing DNS entries.
var RecordCommand = &cli.Command{ var RecordCommand = cli.Command{
Name: "record", Name: "record",
Usage: "Manage domain name records", Usage: "Manage domain name records",
Aliases: []string{"rc"}, Aliases: []string{"rc"},
@ -30,7 +30,7 @@ to implement new provider support easily.
https://pkg.go.dev/github.com/libdns/libdns https://pkg.go.dev/github.com/libdns/libdns
`, `,
Subcommands: []*cli.Command{ Subcommands: []cli.Command{
RecordListCommand, RecordListCommand,
RecordNewCommand, RecordNewCommand,
RecordRemoveCommand, RecordRemoveCommand,

View File

@ -1,6 +1,7 @@
package record package record
import ( import (
"context"
"fmt" "fmt"
"strconv" "strconv"
@ -11,20 +12,23 @@ import (
"github.com/libdns/gandi" "github.com/libdns/gandi"
"github.com/libdns/libdns" "github.com/libdns/libdns"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// RecordRemoveCommand lists domains. // RecordRemoveCommand lists domains.
var RecordRemoveCommand = &cli.Command{ var RecordRemoveCommand = cli.Command{
Name: "remove", Name: "remove",
Usage: "Remove a domain name record", Usage: "Remove a domain name record",
Aliases: []string{"rm"}, Aliases: []string{"rm"},
ArgsUsage: "<zone>", ArgsUsage: "<zone>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.DNSProviderFlag, internal.DNSProviderFlag,
internal.DNSTypeFlag, internal.DNSTypeFlag,
internal.DNSNameFlag, internal.DNSNameFlag,
}, },
Before: internal.SubCommandBefore,
Description: ` Description: `
This command removes a domain name record for a specific zone. This command removes a domain name record for a specific zone.
@ -37,7 +41,7 @@ Example:
abra record remove foo.com -p gandi -t A -n myapp abra record remove foo.com -p gandi -t A -n myapp
You may also invoke this command in "wizard" mode and be prompted for input You may also invoke this command in "wizard" mode and be prompted for input:
abra record rm abra record rm
`, `,
@ -70,7 +74,7 @@ You may also invoke this command in "wizard" mode and be prompted for input
logrus.Fatal(err) logrus.Fatal(err)
} }
records, err := provider.GetRecords(c.Context, zone) records, err := provider.GetRecords(context.Background(), zone)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -120,7 +124,7 @@ You may also invoke this command in "wizard" mode and be prompted for input
} }
} }
_, err = provider.DeleteRecords(c.Context, zone, []libdns.Record{toDelete}) _, err = provider.DeleteRecords(context.Background(), zone, []libdns.Record{toDelete})
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -1,12 +1,12 @@
package server package server
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"os/user" "os/user"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
@ -22,7 +22,7 @@ import (
"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/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var ( var (
@ -47,26 +47,21 @@ source for this script can be seen here:
var local bool var local bool
var localFlag = &cli.BoolFlag{ var localFlag = &cli.BoolFlag{
Name: "local", Name: "local, l",
Aliases: []string{"l"},
Value: false,
Usage: "Use local server", Usage: "Use local server",
Destination: &local, Destination: &local,
} }
var provision bool var provision bool
var provisionFlag = &cli.BoolFlag{ var provisionFlag = &cli.BoolFlag{
Name: "provision", Name: "provision, p",
Aliases: []string{"p"},
Value: false,
Usage: "Provision server so it can deploy apps", Usage: "Provision server so it can deploy apps",
Destination: &provision, Destination: &provision,
} }
var sshAuth string var sshAuth string
var sshAuthFlag = &cli.StringFlag{ var sshAuthFlag = &cli.StringFlag{
Name: "ssh-auth", Name: "ssh-auth, sh",
Aliases: []string{"sh"},
Value: "identity-file", Value: "identity-file",
Usage: "Select SSH authentication method (identity-file, password)", Usage: "Select SSH authentication method (identity-file, password)",
Destination: &sshAuth, Destination: &sshAuth,
@ -74,22 +69,11 @@ var sshAuthFlag = &cli.StringFlag{
var askSudoPass bool var askSudoPass bool
var askSudoPassFlag = &cli.BoolFlag{ var askSudoPassFlag = &cli.BoolFlag{
Name: "ask-sudo-pass", Name: "ask-sudo-pass, as",
Aliases: []string{"as"},
Value: false,
Usage: "Ask for sudo password", Usage: "Ask for sudo password",
Destination: &askSudoPass, Destination: &askSudoPass,
} }
var traefik bool
var traefikFlag = &cli.BoolFlag{
Name: "traefik",
Aliases: []string{"t"},
Value: false,
Usage: "Deploy traefik",
Destination: &traefik,
}
func cleanUp(domainName string) { func cleanUp(domainName string) {
logrus.Warnf("cleaning up context for %s", domainName) logrus.Warnf("cleaning up context for %s", domainName)
if err := client.DeleteContext(domainName); err != nil { if err := client.DeleteContext(domainName); err != nil {
@ -163,12 +147,6 @@ func newLocalServer(c *cli.Context, domainName string) error {
} }
} }
if traefik {
if err := deployTraefik(c, cl, domainName); err != nil {
return err
}
}
logrus.Info("local server has been added") logrus.Info("local server has been added")
return nil return nil
@ -320,7 +298,7 @@ If nothing works, you try running the Docker install script manually on your ser
func initSwarmLocal(c *cli.Context, cl *dockerClient.Client, domainName string) error { func initSwarmLocal(c *cli.Context, cl *dockerClient.Client, domainName string) error {
initReq := swarm.InitRequest{ListenAddr: "0.0.0.0:2377"} initReq := swarm.InitRequest{ListenAddr: "0.0.0.0:2377"}
if _, err := cl.SwarmInit(c.Context, initReq); err != nil { if _, err := cl.SwarmInit(context.Background(), initReq); err != nil {
if strings.Contains(err.Error(), "is already part of a swarm") || if strings.Contains(err.Error(), "is already part of a swarm") ||
strings.Contains(err.Error(), "must specify a listening address") { strings.Contains(err.Error(), "must specify a listening address") {
logrus.Infof("swarm mode already initialised on %s", domainName) logrus.Infof("swarm mode already initialised on %s", domainName)
@ -332,7 +310,7 @@ func initSwarmLocal(c *cli.Context, cl *dockerClient.Client, domainName string)
} }
netOpts := types.NetworkCreate{Driver: "overlay", Scope: "swarm"} netOpts := types.NetworkCreate{Driver: "overlay", Scope: "swarm"}
if _, err := cl.NetworkCreate(c.Context, "proxy", netOpts); err != nil { if _, err := cl.NetworkCreate(context.Background(), "proxy", netOpts); err != nil {
if !strings.Contains(err.Error(), "proxy already exists") { if !strings.Contains(err.Error(), "proxy already exists") {
return err return err
} }
@ -354,7 +332,7 @@ func initSwarm(c *cli.Context, cl *dockerClient.Client, domainName string) error
ListenAddr: "0.0.0.0:2377", ListenAddr: "0.0.0.0:2377",
AdvertiseAddr: ipv4, AdvertiseAddr: ipv4,
} }
if _, err := cl.SwarmInit(c.Context, initReq); err != nil { if _, err := cl.SwarmInit(context.Background(), initReq); err != nil {
if strings.Contains(err.Error(), "is already part of a swarm") || if strings.Contains(err.Error(), "is already part of a swarm") ||
strings.Contains(err.Error(), "must specify a listening address") { strings.Contains(err.Error(), "must specify a listening address") {
logrus.Infof("swarm mode already initialised on %s", domainName) logrus.Infof("swarm mode already initialised on %s", domainName)
@ -366,7 +344,7 @@ func initSwarm(c *cli.Context, cl *dockerClient.Client, domainName string) error
} }
netOpts := types.NetworkCreate{Driver: "overlay", Scope: "swarm"} netOpts := types.NetworkCreate{Driver: "overlay", Scope: "swarm"}
if _, err := cl.NetworkCreate(c.Context, "proxy", netOpts); err != nil { if _, err := cl.NetworkCreate(context.Background(), "proxy", netOpts); err != nil {
if !strings.Contains(err.Error(), "proxy already exists") { if !strings.Contains(err.Error(), "proxy already exists") {
return err return err
} }
@ -388,35 +366,10 @@ func createServerDir(domainName string) error {
return nil return nil
} }
func deployTraefik(c *cli.Context, cl *dockerClient.Client, domainName string) error { var serverAddCommand = cli.Command{
internal.NoInput = true Name: "add",
Aliases: []string{"a"},
internal.RecipeName = "traefik" Usage: "Add a server to your configuration",
internal.NewAppServer = domainName
internal.Domain = fmt.Sprintf("%s.%s", "traefik", domainName)
internal.NewAppName = fmt.Sprintf("%s_%s", "traefik", config.SanitiseAppName(domainName))
appEnvPath := path.Join(config.ABRA_DIR, "servers", internal.Domain, fmt.Sprintf("%s.env", internal.NewAppName))
if _, err := os.Stat(appEnvPath); os.IsNotExist(err) {
logrus.Info(fmt.Sprintf("-t/--traefik specified, automatically deploying traefik to %s", internal.NewAppServer))
if err := internal.NewAction(c); err != nil {
logrus.Fatal(err)
}
} else {
logrus.Infof("%s already exists, not creating again", appEnvPath)
}
internal.AppName = internal.NewAppName
if err := internal.DeployAction(c); err != nil {
logrus.Fatal(err)
}
return nil
}
var serverAddCommand = &cli.Command{
Name: "add",
Usage: "Add a server to your configuration",
Description: ` Description: `
This command adds a new server to your configuration so that it can be managed This command adds a new server to your configuration so that it can be managed
by Abra. This can be useful when you already have a server provisioned and want by Abra. This can be useful when you already have a server provisioned and want
@ -451,7 +404,7 @@ system username to make an initial connection. You can use the <user> and
Example: Example:
abra server add --provision --traefik varia.zone glodemodem 12345 abra server add --provision varia.zone glodemodem 12345
Abra will construct the following SSH connection and Docker context: Abra will construct the following SSH connection and Docker context:
@ -459,25 +412,22 @@ Abra will construct the following SSH connection and Docker context:
All communication between Abra and the server will use this SSH connection. All communication between Abra and the server will use this SSH connection.
In this example, Abra will run the following operations: In this example, Abra will install Docker and initialise swarm mode.
1. Install Docker
2. Initialise Swarm mode
3. Deploy Traefik (core web proxy)
You may omit flags to avoid performing this provisioning logic. You may omit flags to avoid performing this provisioning logic.
`, `,
Aliases: []string{"a"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
localFlag, localFlag,
provisionFlag, provisionFlag,
sshAuthFlag, sshAuthFlag,
askSudoPassFlag, askSudoPassFlag,
traefikFlag,
}, },
Before: internal.SubCommandBefore,
ArgsUsage: "<domain> [<user>] [<port>]", ArgsUsage: "<domain> [<user>] [<port>]",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if c.Args().Len() > 0 && local || !internal.ValidateSubCmdFlags(c) { if len(c.Args()) > 0 && local || !internal.ValidateSubCmdFlags(c) {
err := errors.New("cannot use <domain> and --local together") err := errors.New("cannot use <domain> and --local together")
internal.ShowSubcommandHelpAndError(c, err) internal.ShowSubcommandHelpAndError(c, err)
} }
@ -543,17 +493,11 @@ You may omit flags to avoid performing this provisioning logic.
} }
} }
if _, err := cl.Info(c.Context); err != nil { if _, err := cl.Info(context.Background()); err != nil {
cleanUp(domainName) cleanUp(domainName)
logrus.Fatalf("couldn't make a remote docker connection to %s? use --provision/-p to attempt to install", domainName) logrus.Fatalf("couldn't make a remote docker connection to %s? use --provision/-p to attempt to install", domainName)
} }
if traefik {
if err := deployTraefik(c, cl, domainName); err != nil {
logrus.Fatal(err)
}
}
return nil return nil
}, },
} }

View File

@ -3,20 +3,24 @@ package server
import ( import (
"strings" "strings"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/context" "coopcloud.tech/abra/pkg/context"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"github.com/docker/cli/cli/connhelper/ssh" "github.com/docker/cli/cli/connhelper/ssh"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var serverListCommand = &cli.Command{ var serverListCommand = cli.Command{
Name: "list", Name: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Usage: "List managed servers", Usage: "List managed servers",
ArgsUsage: " ", Flags: []cli.Flag{
HideHelp: true, internal.DebugFlag,
internal.NoInputFlag,
},
Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
dockerContextStore := context.NewDefaultDockerContextStore() dockerContextStore := context.NewDefaultDockerContextStore()
contexts, err := dockerContextStore.Store.List() contexts, err := dockerContextStore.Store.List()

View File

@ -1,6 +1,7 @@
package server package server
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
@ -10,7 +11,7 @@ import (
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/hetznercloud/hcloud-go/hcloud" "github.com/hetznercloud/hcloud-go/hcloud"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
func newHetznerCloudVPS(c *cli.Context) error { func newHetznerCloudVPS(c *cli.Context) error {
@ -27,7 +28,7 @@ func newHetznerCloudVPS(c *cli.Context) error {
continue continue
} }
sshKey, _, err := client.SSHKey.GetByName(c.Context, sshKey) sshKey, _, err := client.SSHKey.GetByName(context.Background(), sshKey)
if err != nil { if err != nil {
return err return err
} }
@ -72,7 +73,7 @@ func newHetznerCloudVPS(c *cli.Context) error {
logrus.Fatal("exiting as requested") logrus.Fatal("exiting as requested")
} }
res, _, err := client.Server.Create(c.Context, serverOpts) res, _, err := client.Server.Create(context.Background(), serverOpts)
if err != nil { if err != nil {
return err return err
} }
@ -110,9 +111,6 @@ bar.example.com).
@ 1800 IN A %s @ 1800 IN A %s
* 1800 IN A %s * 1800 IN A %s
"abra record new --auto" can help you do this quickly if you use a supported
DNS provider.
`, `,
internal.HetznerCloudName, ip, rootPassword, internal.HetznerCloudName, ip, rootPassword,
ip, ip, ip, ip, ip, ip,
@ -200,7 +198,7 @@ bar.example.com).
return nil return nil
} }
var serverNewCommand = &cli.Command{ var serverNewCommand = cli.Command{
Name: "new", Name: "new",
Aliases: []string{"n"}, Aliases: []string{"n"},
Usage: "Create a new server using a 3rd party provider", Usage: "Create a new server using a 3rd party provider",
@ -223,7 +221,12 @@ API tokens are read from the environment if specified, e.g.
Where "$provider_TOKEN" is the expected env var format. Where "$provider_TOKEN" is the expected env var format.
`, `,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.ServerProviderFlag, internal.ServerProviderFlag,
internal.DebugFlag,
internal.NoInputFlag,
// Capsul // Capsul
internal.CapsulInstanceURLFlag, internal.CapsulInstanceURLFlag,
@ -240,6 +243,7 @@ Where "$provider_TOKEN" is the expected env var format.
internal.HetznerCloudLocationFlag, internal.HetznerCloudLocationFlag,
internal.HetznerCloudAPITokenFlag, internal.HetznerCloudAPITokenFlag,
}, },
Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
if err := internal.EnsureServerProvider(); err != nil { if err := internal.EnsureServerProvider(); err != nil {
logrus.Fatal(err) logrus.Fatal(err)

View File

@ -1,6 +1,7 @@
package server package server
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@ -12,14 +13,12 @@ import (
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/hetznercloud/hcloud-go/hcloud" "github.com/hetznercloud/hcloud-go/hcloud"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
var rmServer bool var rmServer bool
var rmServerFlag = &cli.BoolFlag{ var rmServerFlag = &cli.BoolFlag{
Name: "server", Name: "server, s",
Aliases: []string{"s"},
Value: false,
Usage: "remove the actual server also", Usage: "remove the actual server also",
Destination: &rmServer, Destination: &rmServer,
} }
@ -50,7 +49,7 @@ func rmHetznerCloudVPS(c *cli.Context) error {
client := hcloud.NewClient(hcloud.WithToken(internal.HetznerCloudAPIToken)) client := hcloud.NewClient(hcloud.WithToken(internal.HetznerCloudAPIToken))
server, _, err := client.Server.Get(c.Context, internal.HetznerCloudName) server, _, err := client.Server.Get(context.Background(), internal.HetznerCloudName)
if err != nil { if err != nil {
return err return err
} }
@ -89,7 +88,7 @@ destroyed.
logrus.Fatal("exiting as requested") logrus.Fatal("exiting as requested")
} }
_, err = client.Server.Delete(c.Context, server) _, err = client.Server.Delete(context.Background(), server)
if err != nil { if err != nil {
return err return err
} }
@ -99,7 +98,7 @@ destroyed.
return nil return nil
} }
var serverRemoveCommand = &cli.Command{ var serverRemoveCommand = cli.Command{
Name: "remove", Name: "remove",
Aliases: []string{"rm"}, Aliases: []string{"rm"},
ArgsUsage: "[<server>]", ArgsUsage: "[<server>]",
@ -116,6 +115,8 @@ underlying client connection context. This server will then be lost in time,
like tears in rain. like tears in rain.
`, `,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
rmServerFlag, rmServerFlag,
internal.ServerProviderFlag, internal.ServerProviderFlag,
@ -123,6 +124,7 @@ like tears in rain.
internal.HetznerCloudNameFlag, internal.HetznerCloudNameFlag,
internal.HetznerCloudAPITokenFlag, internal.HetznerCloudAPITokenFlag,
}, },
Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
serverName := c.Args().Get(1) serverName := c.Args().Get(1)
if serverName != "" { if serverName != "" {

View File

@ -1,11 +1,11 @@
package server package server
import ( import (
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// ServerCommand defines the `abra server` command and its subcommands // ServerCommand defines the `abra server` command and its subcommands
var ServerCommand = &cli.Command{ var ServerCommand = cli.Command{
Name: "server", Name: "server",
Aliases: []string{"s"}, Aliases: []string{"s"},
Usage: "Manage servers", Usage: "Manage servers",
@ -18,7 +18,7 @@ already have a server, you can add it to your configuration using "abra server
add". Abra can provision servers so that they are ready to deploy Co-op Cloud add". Abra can provision servers so that they are ready to deploy Co-op Cloud
apps, see available flags on "server add" for more. apps, see available flags on "server add" for more.
`, `,
Subcommands: []*cli.Command{ Subcommands: []cli.Command{
serverNewCommand, serverNewCommand,
serverAddCommand, serverAddCommand,
serverListCommand, serverListCommand,

2
go.mod
View File

@ -20,7 +20,6 @@ require (
github.com/schollz/progressbar/v3 v3.8.5 github.com/schollz/progressbar/v3 v3.8.5
github.com/schultz-is/passgen v1.0.1 github.com/schultz-is/passgen v1.0.1
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/urfave/cli/v2 v2.3.0
gotest.tools/v3 v3.1.0 gotest.tools/v3 v3.1.0
) )
@ -43,6 +42,7 @@ require (
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/runc v1.0.2 // indirect github.com/opencontainers/runc v1.0.2 // indirect
github.com/theupdateframework/notary v0.7.0 // indirect github.com/theupdateframework/notary v0.7.0 // indirect
github.com/urfave/cli v1.22.5
github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e

6
go.sum
View File

@ -769,10 +769,9 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
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 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
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/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
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=
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
@ -1149,7 +1148,6 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -6,7 +6,7 @@ import (
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// AppNameComplete copletes app names // AppNameComplete copletes app names

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/upstream/stack" "coopcloud.tech/abra/pkg/upstream/stack"
loader "coopcloud.tech/abra/pkg/upstream/stack" loader "coopcloud.tech/abra/pkg/upstream/stack"
composetypes "github.com/docker/cli/cli/compose/types" composetypes "github.com/docker/cli/cli/compose/types"
@ -53,11 +54,11 @@ func UpdateTag(pattern, image, tag, recipeName string) (bool, error) {
case reference.NamedTagged: case reference.NamedTagged:
composeTag = img.(reference.NamedTagged).Tag() composeTag = img.(reference.NamedTagged).Tag()
default: default:
// unable to parse, typically image missing tag logrus.Debugf("unable to parse %s, skipping", img)
return false, nil continue
} }
composeImage := reference.Path(img) composeImage := formatter.StripTagMeta(reference.Path(img))
logrus.Debugf("parsed %s from %s", composeTag, service.Image) logrus.Debugf("parsed %s from %s", composeTag, service.Image)
@ -74,7 +75,7 @@ func UpdateTag(pattern, image, tag, recipeName string) (bool, error) {
logrus.Debugf("updating %s to %s in %s", old, new, compose.Filename) logrus.Debugf("updating %s to %s in %s", old, new, compose.Filename)
if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0764); err != nil { if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0764); err != nil {
return true, err return false, err
} }
} }
} }

View File

@ -8,6 +8,7 @@ import (
"github.com/docker/go-units" "github.com/docker/go-units"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
"github.com/schollz/progressbar/v3" "github.com/schollz/progressbar/v3"
"github.com/sirupsen/logrus"
) )
func ShortenID(str string) string { func ShortenID(str string) string {
@ -49,3 +50,22 @@ func CreateProgressbar(length int, title string) *progressbar.ProgressBar {
progressbar.OptionSetDescription(title), progressbar.OptionSetDescription(title),
) )
} }
// StripTagMeta strips front-matter image tag data that we don't need for parsing.
func StripTagMeta(image string) string {
originalImage := image
if strings.Contains(image, "docker.io") {
image = strings.Split(image, "/")[1]
}
if strings.Contains(image, "library") {
image = strings.Split(image, "/")[1]
}
if originalImage != image {
logrus.Debugf("stripped %s to %s for parsing", originalImage, image)
}
return image
}

View File

@ -166,7 +166,7 @@ func (r Recipe) UpdateLabel(pattern, serviceName, label string) error {
func (r Recipe) UpdateTag(image, tag string) (bool, error) { func (r Recipe) UpdateTag(image, tag string) (bool, error) {
pattern := fmt.Sprintf("%s/%s/compose**yml", config.RECIPES_DIR, r.Name) pattern := fmt.Sprintf("%s/%s/compose**yml", config.RECIPES_DIR, r.Name)
image = StripTagMeta(image) image = formatter.StripTagMeta(image)
ok, err := compose.UpdateTag(pattern, image, tag, r.Name) ok, err := compose.UpdateTag(pattern, image, tag, r.Name)
if err != nil { if err != nil {
@ -983,7 +983,7 @@ func GetRecipeVersions(recipeName, registryUsername, registryPassword string) (R
path := reference.Path(img) path := reference.Path(img)
path = StripTagMeta(path) path = formatter.StripTagMeta(path)
var tag string var tag string
switch img.(type) { switch img.(type) {
@ -1050,25 +1050,6 @@ func GetRecipeCatalogueVersions(recipeName string, catl RecipeCatalogue) ([]stri
return versions, nil return versions, nil
} }
// StripTagMeta strips front-matter image tag data that we don't need for parsing.
func StripTagMeta(image string) string {
originalImage := image
if strings.Contains(image, "docker.io") {
image = strings.Split(image, "/")[1]
}
if strings.Contains(image, "library") {
image = strings.Split(image, "/")[1]
}
if originalImage != image {
logrus.Debugf("stripped %s to %s for parsing", originalImage, image)
}
return image
}
// EnsureCatalogue ensures that the catalogue is cloned locally & present. // EnsureCatalogue ensures that the catalogue is cloned locally & present.
func EnsureCatalogue() error { func EnsureCatalogue() error {
catalogueDir := path.Join(config.ABRA_DIR, "catalogue") catalogueDir := path.Join(config.ABRA_DIR, "catalogue")

View File

@ -325,7 +325,7 @@ func connect(username, host, port string, authMethod ssh.AuthMethod, timeout tim
conn, err = net.DialTimeout("tcp", hostnameAndPort, timeout) conn, err = net.DialTimeout("tcp", hostnameAndPort, timeout)
if err != nil { if err != nil {
logrus.Debugf("tcp dialing %s failed, trying via ~/.ssh/config", hostnameAndPort) logrus.Debugf("tcp dialing %s failed, trying via ~/.ssh/config", hostnameAndPort)
hostConfig, err := GetHostConfig(host, username, port) hostConfig, err := GetHostConfig(host, username, port, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -452,7 +452,7 @@ func GetContextConnDetails(serverName string) (*dockerSSHPkg.Spec, error) {
} }
} }
hostConfig, err := GetHostConfig(serverName, "", "") hostConfig, err := GetHostConfig(serverName, "", "", false)
if err != nil { if err != nil {
return &dockerSSHPkg.Spec{}, err return &dockerSSHPkg.Spec{}, err
} }
@ -472,30 +472,36 @@ func GetContextConnDetails(serverName string) (*dockerSSHPkg.Spec, error) {
} }
// GetHostConfig retrieves a ~/.ssh/config config for a host. // GetHostConfig retrieves a ~/.ssh/config config for a host.
func GetHostConfig(hostname, username, port string) (HostConfig, error) { func GetHostConfig(hostname, username, port string, override bool) (HostConfig, error) {
var hostConfig HostConfig var hostConfig HostConfig
if hostname == "" { if hostname == "" || override {
if hostname = ssh_config.Get(hostname, "Hostname"); hostname == "" { if sshHost := ssh_config.Get(hostname, "Hostname"); sshHost != "" {
logrus.Debugf("no hostname found in SSH config, assuming %s", hostname) hostname = sshHost
} }
} }
if username == "" { if username == "" || override {
if username = ssh_config.Get(hostname, "User"); username == "" { if sshUser := ssh_config.Get(hostname, "User"); sshUser != "" {
username = sshUser
} else {
systemUser, err := user.Current() systemUser, err := user.Current()
if err != nil { if err != nil {
return hostConfig, err return hostConfig, err
} }
logrus.Debugf("no username found in SSH config or passed on command-line, assuming %s", username)
username = systemUser.Username username = systemUser.Username
} }
} }
if port == "" { if port == "" || override {
if port = ssh_config.Get(hostname, "Port"); port == "" { if sshPort := ssh_config.Get(hostname, "Port"); sshPort != "" {
logrus.Debugf("no port found in SSH config or passed on command-line, assuming 22") // skip override probably correct port with dummy default value from
port = "22" // ssh_config which is 22. only when the original port number is empty
// should we try this default. this might not cover all cases
// unfortunately.
if port != "" && sshPort != "22" {
port = sshPort
}
} }
} }
@ -507,7 +513,6 @@ func GetHostConfig(hostname, username, port string) (HostConfig, error) {
} }
hostConfig.IdentityFile = idf hostConfig.IdentityFile = idf
} else { } else {
logrus.Debugf("no identity file found in SSH config for %s", hostname)
hostConfig.IdentityFile = "" hostConfig.IdentityFile = ""
} }

View File

@ -44,6 +44,7 @@ func getConnectionHelper(daemonURL string, sshFlags []string) (*connhelper.Conne
ctxConnDetails.Host, ctxConnDetails.Host,
ctxConnDetails.User, ctxConnDetails.User,
ctxConnDetails.Port, ctxConnDetails.Port,
false,
) )
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -2,7 +2,7 @@
ABRA_VERSION="0.3.0-alpha" ABRA_VERSION="0.3.0-alpha"
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION" ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION"
RC_VERSION="0.4.0-alpha-rc5" RC_VERSION="0.4.0-alpha-rc6"
RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$RC_VERSION" RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$RC_VERSION"
for arg in "$@"; do for arg in "$@"; do

View File

@ -0,0 +1 @@
TYPE=gitea

View File

@ -0,0 +1 @@
TYPE=wordpress

View File

@ -0,0 +1 @@
TYPE=wordpress

View File

@ -0,0 +1,4 @@
GANDI_TOKEN=...
HCLOUD_TOKEN=...
REGISTRY_PASSWORD=...
REGISTRY_USERNAME=...

View File

@ -0,0 +1,7 @@
FROM debian:bullseye-slim
RUN apt update && apt install -y wget curl git;
RUN git config --global user.email "integration-tests@coopcloud.tech";
RUN git config --global user.name "integration-tests";

View File

@ -0,0 +1,4 @@
# integration tests
- `cp .envrc.sample .envrc` (fill out values && `direnv allow`)
- `TARGET=install.sh make` (ensure `docker context use default`)

15
tests/integration/app.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
source ./common.sh
echo "all apps, all servers"
$ABRA app ls
printf "\\n\\n\\n"
echo "all wordpress apps, all servers"
$ABRA app ls --type wordpress
printf "\\n\\n\\n"
echo "all wordpress apps, only server2"
$ABRA app ls --type wordpress --server server2
printf "\\n\\n\\n"

View File

@ -0,0 +1,9 @@
#!/bin/bash
source ./common.sh
$ABRA autocomplete bash
$ABRA autocomplete fizsh
$ABRA autocomplete zsh

7
tests/integration/catalogue.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
source ./common.sh
$ABRA catalogue generate --debug
$ABRA catalogue generate gitea --debug

22
tests/integration/common.sh Executable file
View File

@ -0,0 +1,22 @@
#!/bin/bash
set -e
function init() {
ABRA="$HOME/.local/bin/abra"
INSTALLER_URL="https://install.abra.coopcloud.tech"
for arg in "$@"; do
if [ "$arg" == "--dev" ]; then
ABRA="/src/abra"
INSTALLER_URL="https://git.coopcloud.tech/coop-cloud/abra/raw/branch/main/scripts/installer/installer"
fi
done
export PATH=$PATH:$HOME/.local/bin
echo "choosing $ABRA as abra command"
echo "choosing $INSTALLER_URL as abra installer url"
}
init "$@"

View File

@ -1,101 +0,0 @@
#!/bin/bash
# the goal of this script is to ensure basic core functionality is working
# before we make new releases. we try to make a balance between manual testing
# and automated testing, i.e. we don't invest too much time in a fragile
# automation that costs us more time to maintain and instead just do the test
# manually (see `../manual/manual.md` for more). it is a balance which we
# figure out together.
set -ex
ABRA="$HOME/.local/bin/abra -d"
INSTALLER_URL="https://install.abra.coopcloud.tech"
for arg in "$@"; do
if [ "$arg" == "--dev" ]; then
ABRA="/src/abra -d"
INSTALLER_URL="https://git.coopcloud.tech/coop-cloud/abra/raw/branch/main/scripts/installer/installer"
fi
done
export PATH=$PATH:$HOME/.local/bin
# ========================================================================
# choosing abra executable for test run
# ========================================================================
echo "choosing $ABRA as abra executable"
echo "choosing $INSTALLER_URL as abra installer url"
# ========================================================================
# latest stable release
# ========================================================================
wget -O- https://install.abra.autonomic.zone | bash
~/.local/bin/abra -v
# ========================================================================
# latest rc release
# ========================================================================
wget -O- https://install.abra.autonomic.zone | bash -s -- --rc
~/.local/bin/abra -v
# ========================================================================
# upgrade to stable in-place
# ========================================================================
$ABRA upgrade
~/.local/bin/abra -v
# ========================================================================
# upgrade to rc in-place
# ========================================================================
$ABRA upgrade --rc
~/.local/bin/abra -v
# ========================================================================
# autocomplete
# ========================================================================
$ABRA autocomplete bash
$ABRA autocomplete fizsh
$ABRA autocomplete zsh
# ========================================================================
# record command
# ========================================================================
$ABRA record new -p gandi -t A -n int-core -v 192.157.2.21 coopcloud.tech
$ABRA record list -p gandi coopcloud.tech | grep -q int-core
$ABRA -n record rm -p gandi -t A -n int-core coopcloud.tech
# ========================================================================
# catalogue command
# ========================================================================
$ABRA catalogue generate
$ABRA catalogue generate -s gitea
# ========================================================================
# recipe command
# ========================================================================
$ABRA recipe new testrecipe
$ABRA recipe list
$ABRA recipe list -p cloud
$ABRA recipe versions peertube
$ABRA recipe lint gitea
# ========================================================================
# server command
# ========================================================================
$ABRA -n server new -p hetzner-cloud --hn int-core
$ABRA server ls | grep -q int-core
$ABRA -n server rm -s -p hetzner-cloud --hn int-core
# ========================================================================
# app command
# ========================================================================
$ABRA app ls
$ABRA app ls -S

15
tests/integration/install.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
source ./common.sh
wget -O- https://install.abra.autonomic.zone | bash
~/.local/bin/abra -v
wget -O- https://install.abra.autonomic.zone | bash -s -- --rc
~/.local/bin/abra -v
$ABRA upgrade
~/.local/bin/abra -v
$ABRA upgrade --rc
~/.local/bin/abra -v

View File

@ -0,0 +1,11 @@
default:
@docker run \
-v $$(pwd)/../../:/src \
-v $$(pwd)/.abra:/root/.abra \
--env-file .envrc \
decentral1se/abra-int:latest \
sh -c '\
echo "Running $(TARGET)..."; \
cd /src/tests/integration; \
bash "$(TARGET)" -- --dev \
'

12
tests/integration/recipe.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/bash
source ./common.sh
$ABRA recipe new testrecipe
$ABRA recipe list
$ABRA recipe list -p cloud
$ABRA recipe versions peertube
$ABRA recipe lint gitea

9
tests/integration/records.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
source ./common.sh
$ABRA record new -p gandi -t A -n int-core -v 192.157.2.21 coopcloud.tech
$ABRA record list -p gandi coopcloud.tech | grep -q int-core
$ABRA -n record rm -p gandi -t A -n int-core coopcloud.tech

9
tests/integration/server.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
source ./common.sh
$ABRA -n server new -p hetzner-cloud --hn int-core
$ABRA server ls | grep -q int-core
$ABRA -n server rm -s -p hetzner-cloud --hn int-core

View File

@ -1 +0,0 @@
TYPE=works

View File

@ -1,84 +0,0 @@
---
# The goal of this compose file is to have a testing ground for understanding
# what cases we need to handle to get stable deployments. For that, we need to
# work with healthchecks and deploy configurations quite closely. If you run
# the `make symlink` target then this will be loaded into a "fake" app on your
# local machine which you can deploy with `abra`.
version: "3.8"
services:
r1_should_work:
image: redis:alpine
deploy:
update_config:
failure_action: rollback
order: start-first
rollback_config:
order: start-first
restart_policy:
max_attempts: 1
healthcheck:
test: redis-cli ping
interval: 2s
retries: 3
start_period: 1s
timeout: 3s
r2_broken_health_check:
image: redis:alpine
deploy:
update_config:
failure_action: rollback
order: start-first
rollback_config:
order: start-first
restart_policy:
max_attempts: 3
healthcheck:
test: foobar
interval: 2s
retries: 3
start_period: 1s
timeout: 3s
r3_no_health_check:
image: redis:alpine
deploy:
update_config:
failure_action: rollback
order: start-first
rollback_config:
order: start-first
restart_policy:
max_attempts: 3
r4_disabled_health_check:
image: redis:alpine
deploy:
update_config:
failure_action: rollback
order: start-first
rollback_config:
order: start-first
restart_policy:
max_attempts: 3
healthcheck:
disable: true
r5_should_also_work:
image: redis:alpine
deploy:
update_config:
failure_action: rollback
order: start-first
rollback_config:
order: start-first
restart_policy:
max_attempts: 1
healthcheck:
test: redis-cli ping
interval: 2s
retries: 3
start_period: 1s
timeout: 3s

View File

@ -1 +0,0 @@
TYPE=works

View File

@ -1,60 +1,40 @@
# manual test plan # manual test plan
Best served after running `make int-core` which assures most core functionality
is still working. These manual tests are for testing things that are hard to
wire up for testing in an automated way.
## recipe publish ## recipe publish
- `abra recipe upgrade <recipe>` - `abra recipe upgrade <recipe>`
- `cd ~/.abra/apps/<recipe>/ && git diff` to ensure changes made
- `abra recipe sync <recipe>` - `abra recipe sync <recipe>`
- `abra recipe release --publish <recipe>` - `cd ~/.abra/apps/<recipe>/ && git diff` to ensure changes made
## automagic traefik deploy - `abra recipe release <recipe> --dry-run`
- prompts should be correct, read what `abra` asks you carefully
- `abra server add -p -t <server>`
## deploy, upgrade, rollback ## deploy, upgrade, rollback
- `abra app deploy <app>`
- `abra app deploy --force <app>`
- `abra app deploy --chaos <app>` - `abra app deploy --chaos <app>`
- `abra app upgrade <app>` - `abra app deploy --force <app>`
- `abra app deploy <app>`
- `abra app rollback <app>` - `abra app rollback <app>`
- `abra app upgrade <app>`
## backup & restore
- `abra app deploy <app>`
- `abra app backup <app>`
- `abra app undeploy <app>`
- `abra app volume remove --force <app>`
- `abra app deploy <app>`
- `abra app restore <app>`
## app day-to-day ops ## app day-to-day ops
### easy mode
- `abra app ls -t <recipe>`
- `abra app ls -s <server>`
- `abra app ls -s <server> -t <recipe>`
- `abra app ls -s <server> -t <recipe> -S`
- `abra app config <app>`
- `abra app check <app>` - `abra app check <app>`
- `abra app ps <app>` - `abra app config <app>`
- `abra app logs <app>`
- `abra app cp <app>` - `abra app cp <app>`
- `abra app run <app>`
- `abra app secret ls <app>`
- `abra app volume ls <app>`
- `abra app new --secrets <recipe>`
### hard mode
- `abra app restart <app>`
- `abra app remove <app>`
- `abra app secret insert <app> foo v1 bar`
- `abra app secret remove <app> foo`
- `abra app secret generate --all`
- `abra app volume remove --force <app>`
- `abra app errors -w <app>` - `abra app errors -w <app>`
- `abra app logs <app>`
- `abra app ls --status <app>`
- `abra app new --secrets <recipe>`
- `abra app ps <app>`
- `abra app remove <app>`
- `abra app restart <app>`
- `abra app run <app>`
- `abra app secret generate --all`
- `abra app secret insert <app> foo v1 bar`
- `abra app secret ls <app>`
- `abra app secret remove <app> foo`
- `abra app volume ls <app>`
- `abra app volume remove --force <app>`