forked from toolshed/abra
Compare commits
7 Commits
refactor-r
...
upgrade-cl
Author | SHA1 | Date | |
---|---|---|---|
b81f5651d3 | |||
7d80f4d56b | |||
114bdc5ce9 | |||
8a7d17f37b | |||
deb4293fba | |||
ac39d6ab97 | |||
428426b6b7 |
@ -29,7 +29,7 @@ steps:
|
||||
event: tag
|
||||
|
||||
- name: release
|
||||
image: goreleaser/goreleaser:v1.24.0
|
||||
image: goreleaser/goreleaser:v1.18.2
|
||||
environment:
|
||||
GITEA_TOKEN:
|
||||
from_secret: goreleaser_gitea_token
|
||||
|
@ -29,8 +29,6 @@ builds:
|
||||
ldflags:
|
||||
- "-X 'main.Commit={{ .Commit }}'"
|
||||
- "-X 'main.Version={{ .Version }}'"
|
||||
- "-s"
|
||||
- "-w"
|
||||
|
||||
- id: kadabra
|
||||
binary: kadabra
|
||||
@ -52,8 +50,12 @@ builds:
|
||||
ldflags:
|
||||
- "-X 'main.Commit={{ .Commit }}'"
|
||||
- "-X 'main.Version={{ .Version }}'"
|
||||
- "-s"
|
||||
- "-w"
|
||||
|
||||
archives:
|
||||
- replacements:
|
||||
386: i386
|
||||
amd64: x86_64
|
||||
format: binary
|
||||
|
||||
checksum:
|
||||
name_template: "checksums.txt"
|
||||
|
@ -7,7 +7,6 @@
|
||||
- cassowary
|
||||
- codegod100
|
||||
- decentral1se
|
||||
- fauno
|
||||
- frando
|
||||
- kawaiipunk
|
||||
- knoflook
|
||||
|
14
Dockerfile
14
Dockerfile
@ -1,29 +1,23 @@
|
||||
# Build image
|
||||
FROM golang:1.21-alpine AS build
|
||||
|
||||
ENV GOPRIVATE coopcloud.tech
|
||||
|
||||
RUN apk add --no-cache \
|
||||
ca-certificates \
|
||||
gcc \
|
||||
git \
|
||||
make \
|
||||
musl-dev
|
||||
|
||||
RUN update-ca-certificates
|
||||
|
||||
COPY . /app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN CGO_ENABLED=0 make build
|
||||
|
||||
# Release image ("slim")
|
||||
FROM alpine:3.19.1
|
||||
|
||||
RUN apk add --no-cache \
|
||||
ca-certificates \
|
||||
git \
|
||||
openssh
|
||||
|
||||
RUN update-ca-certificates
|
||||
FROM scratch
|
||||
|
||||
COPY --from=build /app/abra /abra
|
||||
|
||||
|
3
Makefile
3
Makefile
@ -53,6 +53,3 @@ test:
|
||||
|
||||
loc:
|
||||
@find . -name "*.go" | xargs wc -l
|
||||
|
||||
deps:
|
||||
@go get -t -u ./...
|
||||
|
@ -1,7 +1,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var AppCommand = cli.Command{
|
||||
@ -10,28 +10,28 @@ var AppCommand = cli.Command{
|
||||
Usage: "Manage apps",
|
||||
ArgsUsage: "<domain>",
|
||||
Description: "Functionality for managing the life cycle of your apps",
|
||||
Subcommands: []cli.Command{
|
||||
appBackupCommand,
|
||||
appCheckCommand,
|
||||
appCmdCommand,
|
||||
appConfigCommand,
|
||||
appCpCommand,
|
||||
appDeployCommand,
|
||||
appErrorsCommand,
|
||||
appListCommand,
|
||||
appLogsCommand,
|
||||
appNewCommand,
|
||||
appPsCommand,
|
||||
appRemoveCommand,
|
||||
appRestartCommand,
|
||||
appRestoreCommand,
|
||||
appRollbackCommand,
|
||||
appRunCommand,
|
||||
appSecretCommand,
|
||||
appServicesCommand,
|
||||
appUndeployCommand,
|
||||
appUpgradeCommand,
|
||||
appVersionCommand,
|
||||
appVolumeCommand,
|
||||
Subcommands: []*cli.Command{
|
||||
&appBackupCommand,
|
||||
&appCheckCommand,
|
||||
&appCmdCommand,
|
||||
&appConfigCommand,
|
||||
&appCpCommand,
|
||||
&appDeployCommand,
|
||||
&appErrorsCommand,
|
||||
&appListCommand,
|
||||
&appLogsCommand,
|
||||
&appNewCommand,
|
||||
&appPsCommand,
|
||||
&appRemoveCommand,
|
||||
&appRestartCommand,
|
||||
&appRestoreCommand,
|
||||
&appRollbackCommand,
|
||||
&appRunCommand,
|
||||
&appSecretCommand,
|
||||
&appServicesCommand,
|
||||
&appUndeployCommand,
|
||||
&appUpgradeCommand,
|
||||
&appVersionCommand,
|
||||
&appVolumeCommand,
|
||||
},
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var snapshot string
|
||||
@ -47,26 +47,22 @@ var appBackupListCommand = cli.Command{
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
if err := r.EnsureExists(); err != nil {
|
||||
if err := recipe.EnsureExists(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureIsClean(); err != nil {
|
||||
if err := recipe.EnsureIsClean(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := r.EnsureUpToDate(); err != nil {
|
||||
if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.EnsureLatest(); err != nil {
|
||||
if err := recipe.EnsureLatest(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
@ -114,26 +110,22 @@ var appBackupDownloadCommand = cli.Command{
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
if err := r.EnsureExists(); err != nil {
|
||||
if err := recipe.EnsureExists(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureIsClean(); err != nil {
|
||||
if err := recipe.EnsureIsClean(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := r.EnsureUpToDate(); err != nil {
|
||||
if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.EnsureLatest(); err != nil {
|
||||
if err := recipe.EnsureLatest(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
@ -188,27 +180,22 @@ var appBackupCreateCommand = cli.Command{
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := r.EnsureExists(); err != nil {
|
||||
if err := recipe.EnsureExists(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureIsClean(); err != nil {
|
||||
if err := recipe.EnsureIsClean(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := r.EnsureUpToDate(); err != nil {
|
||||
if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.EnsureLatest(); err != nil {
|
||||
if err := recipe.EnsureLatest(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
@ -251,27 +238,22 @@ var appBackupSnapshotsCommand = cli.Command{
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := r.EnsureExists(); err != nil {
|
||||
if err := recipe.EnsureExists(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureIsClean(); err != nil {
|
||||
if err := recipe.EnsureIsClean(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := r.EnsureUpToDate(); err != nil {
|
||||
if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.EnsureLatest(); err != nil {
|
||||
if err := recipe.EnsureLatest(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
@ -305,10 +287,10 @@ var appBackupCommand = cli.Command{
|
||||
Aliases: []string{"b"},
|
||||
Usage: "Manage app backups",
|
||||
ArgsUsage: "<domain>",
|
||||
Subcommands: []cli.Command{
|
||||
appBackupListCommand,
|
||||
appBackupSnapshotsCommand,
|
||||
appBackupDownloadCommand,
|
||||
appBackupCreateCommand,
|
||||
Subcommands: []*cli.Command{
|
||||
&appBackupListCommand,
|
||||
&appBackupSnapshotsCommand,
|
||||
&appBackupDownloadCommand,
|
||||
&appBackupCreateCommand,
|
||||
},
|
||||
}
|
||||
|
@ -6,8 +6,9 @@ import (
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var appCheckCommand = cli.Command{
|
||||
@ -37,26 +38,22 @@ ${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
if err := r.EnsureExists(); err != nil {
|
||||
if err := recipe.EnsureExists(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureIsClean(); err != nil {
|
||||
if err := recipePkg.EnsureIsClean(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := r.EnsureUpToDate(); err != nil {
|
||||
if err := recipePkg.EnsureUpToDate(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.EnsureLatest(); err != nil {
|
||||
if err := recipePkg.EnsureLatest(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,9 @@ import (
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var appCmdCommand = cli.Command{
|
||||
@ -44,10 +45,10 @@ Example:
|
||||
internal.ChaosFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
Subcommands: []cli.Command{appCmdListCommand},
|
||||
Subcommands: []*cli.Command{&appCmdListCommand},
|
||||
BashComplete: func(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
switch len(args) {
|
||||
switch args.Len() {
|
||||
case 0:
|
||||
autocomplete.AppNameComplete(ctx)
|
||||
case 1:
|
||||
@ -59,27 +60,22 @@ Example:
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := r.EnsureExists(); err != nil {
|
||||
if err := recipe.EnsureExists(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureIsClean(); err != nil {
|
||||
if err := recipePkg.EnsureIsClean(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := r.EnsureUpToDate(); err != nil {
|
||||
if err := recipePkg.EnsureUpToDate(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.EnsureLatest(); err != nil {
|
||||
if err := recipePkg.EnsureLatest(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
@ -88,7 +84,7 @@ Example:
|
||||
internal.ShowSubcommandHelpAndError(c, errors.New("cannot use --local & --user together"))
|
||||
}
|
||||
|
||||
hasCmdArgs, parsedCmdArgs := parseCmdArgs(c.Args(), internal.LocalCmd)
|
||||
hasCmdArgs, parsedCmdArgs := parseCmdArgs(c.Args().Slice())
|
||||
|
||||
abraSh := path.Join(config.RECIPES_DIR, app.Recipe, "abra.sh")
|
||||
if _, err := os.Stat(abraSh); err != nil {
|
||||
@ -99,7 +95,7 @@ Example:
|
||||
}
|
||||
|
||||
if internal.LocalCmd {
|
||||
if !(len(c.Args()) >= 2) {
|
||||
if !(c.Args().Len() >= 2) {
|
||||
internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments"))
|
||||
}
|
||||
|
||||
@ -135,7 +131,7 @@ Example:
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
if !(len(c.Args()) >= 3) {
|
||||
if !(c.Args().Len() >= 3) {
|
||||
internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments"))
|
||||
}
|
||||
|
||||
@ -184,23 +180,16 @@ Example:
|
||||
},
|
||||
}
|
||||
|
||||
func parseCmdArgs(args []string, isLocal bool) (bool, string) {
|
||||
var (
|
||||
parsedCmdArgs string
|
||||
hasCmdArgs bool
|
||||
)
|
||||
|
||||
if isLocal {
|
||||
if len(args) > 2 {
|
||||
return true, fmt.Sprintf("%s ", strings.Join(args[2:], " "))
|
||||
}
|
||||
} else {
|
||||
if len(args) > 3 {
|
||||
return true, fmt.Sprintf("%s ", strings.Join(args[3:], " "))
|
||||
}
|
||||
// Parse the command arguments from the cli args.
|
||||
// Arguments should look like this:
|
||||
//
|
||||
// DOMAIN COMMAND -- ARGUMENT1 ARGUMENT2 ...
|
||||
func parseCmdArgs(args []string) (bool, string) {
|
||||
if len(args) < 4 {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return hasCmdArgs, parsedCmdArgs
|
||||
return true, fmt.Sprintf("%s ", strings.Join(args[3:], " "))
|
||||
}
|
||||
|
||||
func cmdNameComplete(appName string) {
|
||||
@ -232,27 +221,22 @@ var appCmdListCommand = cli.Command{
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := r.EnsureExists(); err != nil {
|
||||
if err := recipe.EnsureExists(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureIsClean(); err != nil {
|
||||
if err := recipePkg.EnsureIsClean(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := r.EnsureUpToDate(); err != nil {
|
||||
if err := recipePkg.EnsureUpToDate(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.EnsureLatest(); err != nil {
|
||||
if err := recipePkg.EnsureLatest(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ func TestParseCmdArgs(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
ok, parsed := parseCmdArgs(test.input, false)
|
||||
ok, parsed := parseCmdArgs(test.input)
|
||||
if ok != test.shouldParse {
|
||||
t.Fatalf("[%s] should not parse", strings.Join(test.input, " "))
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var appConfigCommand = cli.Command{
|
||||
|
@ -22,7 +22,7 @@ import (
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var appCpCommand = cli.Command{
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var appDeployCommand = cli.Command{
|
||||
@ -56,32 +56,28 @@ recipes.
|
||||
logrus.Fatal("cannot use <version> and --chaos together")
|
||||
}
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := r.EnsureExists(); err != nil {
|
||||
if err := recipe.EnsureExists(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureIsClean(); err != nil {
|
||||
if err := recipe.EnsureIsClean(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := r.EnsureUpToDate(); err != nil {
|
||||
if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.EnsureLatest(); err != nil {
|
||||
if err := recipe.EnsureLatest(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.LoadConfig(); err != nil {
|
||||
r, err := recipe.Get(app.Recipe, internal.Offline)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
@ -101,19 +97,6 @@ recipes.
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
// NOTE(d1): check out specific version before dealing with secrets. This
|
||||
// is because we need to deal with GetComposeFiles under the hood and these
|
||||
// files change from version to version which therefore affects which
|
||||
// secrets might be generated
|
||||
version := deployedVersion
|
||||
if specificVersion != "" {
|
||||
version = specificVersion
|
||||
logrus.Debugf("choosing %s as version to deploy", version)
|
||||
if err := r.EnsureVersion(version); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
secStats, err := secret.PollSecretsStatus(cl, app)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
@ -133,6 +116,15 @@ recipes.
|
||||
}
|
||||
}
|
||||
|
||||
version := deployedVersion
|
||||
if specificVersion != "" {
|
||||
version = specificVersion
|
||||
logrus.Debugf("choosing %s as version to deploy", version)
|
||||
if err := recipe.EnsureVersion(app.Recipe, version); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if !internal.Chaos && specificVersion == "" {
|
||||
catl, err := recipe.ReadRecipeCatalogue(internal.Offline)
|
||||
if err != nil {
|
||||
@ -145,7 +137,7 @@ recipes.
|
||||
|
||||
if len(versions) == 0 && !internal.Chaos {
|
||||
logrus.Warn("no published versions in catalogue, trying local recipe repository")
|
||||
recipeVersions, err := r.GetVersions(internal.Offline)
|
||||
recipeVersions, err := recipe.GetRecipeVersions(app.Recipe, internal.Offline)
|
||||
if err != nil {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
@ -159,11 +151,11 @@ recipes.
|
||||
if len(versions) > 0 && !internal.Chaos {
|
||||
version = versions[len(versions)-1]
|
||||
logrus.Debugf("choosing %s as version to deploy", version)
|
||||
if err := r.EnsureVersion(version); err != nil {
|
||||
if err := recipe.EnsureVersion(app.Recipe, version); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
head, err := git.GetHead(app.Recipe)
|
||||
head, err := git.GetRecipeHead(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
@ -175,7 +167,7 @@ recipes.
|
||||
if internal.Chaos {
|
||||
logrus.Warnf("chaos mode engaged")
|
||||
var err error
|
||||
version, err = r.ChaosVersion()
|
||||
version, err = recipe.ChaosVersion(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
@ -13,11 +13,11 @@ import (
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||
containerTypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var appErrorsCommand = cli.Command{
|
||||
@ -88,19 +88,16 @@ the logs.
|
||||
}
|
||||
|
||||
func checkErrors(c *cli.Context, cl *dockerClient.Client, app config.App) error {
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
recipe, err := recipe.Get(app.Recipe, internal.Offline)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
if err := r.LoadConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, service := range r.Config.Services {
|
||||
for _, service := range recipe.Config.Services {
|
||||
filters := filters.NewArgs()
|
||||
filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), service.Name))
|
||||
|
||||
containers, err := cl.ContainerList(context.Background(), containerTypes.ListOptions{Filters: filters})
|
||||
containers, err := cl.ContainerList(context.Background(), types.ContainerListOptions{Filters: filters})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -13,19 +13,21 @@ import (
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/tagcmp"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var status bool
|
||||
var statusFlag = &cli.BoolFlag{
|
||||
Name: "status, S",
|
||||
Name: "status",
|
||||
Aliases: []string{"S"},
|
||||
Usage: "Show app deployment status",
|
||||
Destination: &status,
|
||||
}
|
||||
|
||||
var recipeFilter string
|
||||
var recipeFlag = &cli.StringFlag{
|
||||
Name: "recipe, r",
|
||||
Name: "recipe",
|
||||
Aliases: []string{"r"},
|
||||
Value: "",
|
||||
Usage: "Show apps of a specific recipe",
|
||||
Destination: &recipeFilter,
|
||||
@ -33,7 +35,8 @@ var recipeFlag = &cli.StringFlag{
|
||||
|
||||
var listAppServer string
|
||||
var listAppServerFlag = &cli.StringFlag{
|
||||
Name: "server, s",
|
||||
Name: "server",
|
||||
Aliases: []string{"s"},
|
||||
Value: "",
|
||||
Usage: "Show apps of a specific server",
|
||||
Destination: &listAppServer,
|
||||
|
@ -15,12 +15,11 @@ import (
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/docker/docker/api/types"
|
||||
containerTypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var appLogsCommand = cli.Command{
|
||||
@ -39,12 +38,7 @@ var appLogsCommand = cli.Command{
|
||||
app := internal.ValidateApp(c)
|
||||
stackName := app.StackName()
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := r.EnsureExists(); err != nil {
|
||||
if err := recipe.EnsureExists(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
@ -116,7 +110,7 @@ func tailLogs(cl *dockerClient.Client, app config.App, serviceNames []string) er
|
||||
// collected in parallel.
|
||||
wg.Add(1)
|
||||
go func(serviceID string) {
|
||||
logs, err := cl.ServiceLogs(context.Background(), serviceID, containerTypes.LogsOptions{
|
||||
logs, err := cl.ServiceLogs(context.Background(), serviceID, types.ContainerLogsOptions{
|
||||
ShowStderr: true,
|
||||
ShowStdout: !internal.StdErrOnly,
|
||||
Since: internal.SinceLogs,
|
||||
|
@ -10,11 +10,13 @@ import (
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"coopcloud.tech/abra/pkg/jsontable"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/secret"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var appNewDescription = `
|
||||
@ -56,7 +58,7 @@ var appNewCommand = cli.Command{
|
||||
ArgsUsage: "[<recipe>] [<version>]",
|
||||
BashComplete: func(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
switch len(args) {
|
||||
switch args.Len() {
|
||||
case 0:
|
||||
autocomplete.RecipeNameComplete(ctx)
|
||||
case 1:
|
||||
@ -64,43 +66,23 @@ var appNewCommand = cli.Command{
|
||||
}
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
r := internal.ValidateRecipe(c)
|
||||
recipe := internal.ValidateRecipe(c)
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureIsClean(); err != nil {
|
||||
if err := recipePkg.EnsureIsClean(recipe.Name); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
if !internal.Offline {
|
||||
if err := r.EnsureUpToDate(); err != nil {
|
||||
if err := recipePkg.EnsureUpToDate(recipe.Name); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
if c.Args().Get(1) == "" {
|
||||
var version string
|
||||
|
||||
recipeVersions, err := r.GetVersions(internal.Offline)
|
||||
if err != nil {
|
||||
if err := recipePkg.EnsureLatest(recipe.Name); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
// NOTE(d1): determine whether recipe versions exist or not and check
|
||||
// out the latest version or current HEAD
|
||||
if len(recipeVersions) > 0 {
|
||||
latest := recipeVersions[len(recipeVersions)-1]
|
||||
for tag := range latest {
|
||||
version = tag
|
||||
}
|
||||
|
||||
if err := r.EnsureVersion(version); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
if err := r.EnsureLatest(); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := r.EnsureVersion(c.Args().Get(1)); err != nil {
|
||||
if err := recipePkg.EnsureVersion(recipe.Name, c.Args().Get(1)); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
@ -110,7 +92,7 @@ var appNewCommand = cli.Command{
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := ensureDomainFlag(r.Name, internal.NewAppServer); err != nil {
|
||||
if err := ensureDomainFlag(recipe, internal.NewAppServer); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
@ -118,7 +100,7 @@ var appNewCommand = cli.Command{
|
||||
logrus.Debugf("%s sanitised as %s for new app", internal.Domain, sanitisedAppName)
|
||||
|
||||
if err := config.TemplateAppEnvSample(
|
||||
r.Name,
|
||||
recipe.Name,
|
||||
internal.Domain,
|
||||
internal.NewAppServer,
|
||||
internal.Domain,
|
||||
@ -129,23 +111,23 @@ var appNewCommand = cli.Command{
|
||||
var secrets AppSecrets
|
||||
var secretTable *jsontable.JSONTable
|
||||
if internal.Secrets {
|
||||
sampleEnv, err := r.SampleEnv()
|
||||
sampleEnv, err := recipe.SampleEnv()
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
composeFiles, err := config.GetComposeFiles(r.NameEscaped, sampleEnv)
|
||||
composeFiles, err := config.GetComposeFiles(recipe.Name, sampleEnv)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
envSamplePath := path.Join(r.Dir, ".env.sample")
|
||||
envSamplePath := path.Join(config.RECIPES_DIR, recipe.Name, ".env.sample")
|
||||
secretsConfig, err := secret.ReadSecretsConfig(envSamplePath, composeFiles, config.StackName(internal.Domain))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := promptForSecrets(r.Name, secretsConfig); err != nil {
|
||||
if err := promptForSecrets(recipe.Name, secretsConfig); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
@ -172,9 +154,9 @@ var appNewCommand = cli.Command{
|
||||
|
||||
tableCol := []string{"server", "recipe", "domain"}
|
||||
table := formatter.CreateTable(tableCol)
|
||||
table.Append([]string{internal.NewAppServer, r.Name, internal.Domain})
|
||||
table.Append([]string{internal.NewAppServer, recipe.Name, internal.Domain})
|
||||
|
||||
fmt.Println(fmt.Sprintf("A new %s app has been created! Here is an overview:", r.Name))
|
||||
fmt.Println(fmt.Sprintf("A new %s app has been created! Here is an overview:", recipe.Name))
|
||||
fmt.Println("")
|
||||
table.Render()
|
||||
fmt.Println("")
|
||||
@ -201,12 +183,6 @@ type AppSecrets map[string]string
|
||||
|
||||
// createSecrets creates all secrets for a new app.
|
||||
func createSecrets(cl *dockerClient.Client, secretsConfig map[string]secret.Secret, sanitisedAppName string) (AppSecrets, error) {
|
||||
// NOTE(d1): trim to match app.StackName() implementation
|
||||
if len(sanitisedAppName) > config.MAX_SANITISED_APP_NAME_LENGTH {
|
||||
logrus.Debugf("trimming %s to %s to avoid runtime limits", sanitisedAppName, sanitisedAppName[:config.MAX_SANITISED_APP_NAME_LENGTH])
|
||||
sanitisedAppName = sanitisedAppName[:config.MAX_SANITISED_APP_NAME_LENGTH]
|
||||
}
|
||||
|
||||
secrets, err := secret.GenerateSecrets(cl, secretsConfig, internal.NewAppServer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -230,11 +206,11 @@ func createSecrets(cl *dockerClient.Client, secretsConfig map[string]secret.Secr
|
||||
}
|
||||
|
||||
// ensureDomainFlag checks if the domain flag was used. if not, asks the user for it/
|
||||
func ensureDomainFlag(recipeName string, server string) error {
|
||||
func ensureDomainFlag(recipe recipe.Recipe, server string) error {
|
||||
if internal.Domain == "" && !internal.NoInput {
|
||||
prompt := &survey.Input{
|
||||
Message: "Specify app domain",
|
||||
Default: fmt.Sprintf("%s.%s", recipeName, server),
|
||||
Default: fmt.Sprintf("%s.%s", recipe.Name, server),
|
||||
}
|
||||
if err := survey.AskOne(prompt, &internal.Domain); err != nil {
|
||||
return err
|
||||
|
107
cli/app/ps.go
107
cli/app/ps.go
@ -2,8 +2,7 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
@ -11,16 +10,14 @@ import (
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
abraService "coopcloud.tech/abra/pkg/service"
|
||||
"coopcloud.tech/abra/pkg/service"
|
||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/buger/goterm"
|
||||
dockerFormatter "github.com/docker/cli/cli/command/formatter"
|
||||
containerTypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var appPsCommand = cli.Command{
|
||||
@ -30,7 +27,6 @@ var appPsCommand = cli.Command{
|
||||
ArgsUsage: "<domain>",
|
||||
Description: "Show a more detailed status output of a specific deployed app",
|
||||
Flags: []cli.Flag{
|
||||
internal.MachineReadableFlag,
|
||||
internal.WatchFlag,
|
||||
internal.DebugFlag,
|
||||
},
|
||||
@ -39,17 +35,12 @@ var appPsCommand = cli.Command{
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, app.StackName())
|
||||
isDeployed, _, err := stack.IsDeployed(context.Background(), cl, app.StackName())
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
@ -58,15 +49,6 @@ var appPsCommand = cli.Command{
|
||||
logrus.Fatalf("%s is not deployed?", app.Name)
|
||||
}
|
||||
|
||||
statuses, err := config.GetAppStatuses([]config.App{app}, true)
|
||||
if statusMeta, ok := statuses[app.StackName()]; ok {
|
||||
if _, exists := statusMeta["chaos"]; !exists {
|
||||
if err := r.EnsureVersion(deployedVersion); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !internal.Watch {
|
||||
showPSOutput(c, app, cl)
|
||||
return nil
|
||||
@ -84,77 +66,36 @@ var appPsCommand = cli.Command{
|
||||
|
||||
// showPSOutput renders ps output.
|
||||
func showPSOutput(c *cli.Context, app config.App, cl *dockerClient.Client) {
|
||||
composeFiles, err := config.GetComposeFiles(app.Recipe, app.Env)
|
||||
filters, err := app.Filters(true, true)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
deployOpts := stack.Deploy{
|
||||
Composefiles: composeFiles,
|
||||
Namespace: app.StackName(),
|
||||
Prune: false,
|
||||
ResolveImage: stack.ResolveImageAlways,
|
||||
}
|
||||
compose, err := config.GetAppComposeConfig(app.Name, deployOpts, app.Env)
|
||||
containers, err := cl.ContainerList(context.Background(), types.ContainerListOptions{Filters: filters})
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
var tablerows [][]string
|
||||
allContainerStats := make(map[string]map[string]string)
|
||||
for _, service := range compose.Services {
|
||||
filters := filters.NewArgs()
|
||||
filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), service.Name))
|
||||
tableCol := []string{"service name", "image", "created", "status", "state", "ports"}
|
||||
table := formatter.CreateTable(tableCol)
|
||||
|
||||
containers, err := cl.ContainerList(context.Background(), containerTypes.ListOptions{Filters: filters})
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
return
|
||||
for _, container := range containers {
|
||||
var containerNames []string
|
||||
for _, containerName := range container.Names {
|
||||
trimmed := strings.TrimPrefix(containerName, "/")
|
||||
containerNames = append(containerNames, trimmed)
|
||||
}
|
||||
|
||||
var containerStats map[string]string
|
||||
|
||||
if len(containers) == 0 {
|
||||
containerStats = map[string]string{
|
||||
"service name": service.Name,
|
||||
"image": "unknown",
|
||||
"created": "unknown",
|
||||
"status": "unknown",
|
||||
"state": "unknown",
|
||||
"ports": "unknown",
|
||||
}
|
||||
} else {
|
||||
container := containers[0]
|
||||
containerStats = map[string]string{
|
||||
"service name": abraService.ContainerToServiceName(container.Names, app.StackName()),
|
||||
"image": formatter.RemoveSha(container.Image),
|
||||
"created": formatter.HumanDuration(container.Created),
|
||||
"status": container.Status,
|
||||
"state": container.State,
|
||||
"ports": dockerFormatter.DisplayablePorts(container.Ports),
|
||||
}
|
||||
tableRow := []string{
|
||||
service.ContainerToServiceName(container.Names, app.StackName()),
|
||||
formatter.RemoveSha(container.Image),
|
||||
formatter.HumanDuration(container.Created),
|
||||
container.Status,
|
||||
container.State,
|
||||
dockerFormatter.DisplayablePorts(container.Ports),
|
||||
}
|
||||
allContainerStats[containerStats["service name"]] = containerStats
|
||||
|
||||
var tablerow []string = []string{containerStats["service name"], containerStats["image"], containerStats["created"], containerStats["status"], containerStats["state"], containerStats["ports"]}
|
||||
tablerows = append(tablerows, tablerow)
|
||||
}
|
||||
if internal.MachineReadable {
|
||||
jsonstring, err := json.Marshal(allContainerStats)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
} else {
|
||||
fmt.Println(string(jsonstring))
|
||||
}
|
||||
return
|
||||
} else {
|
||||
tableCol := []string{"service name", "image", "created", "status", "state", "ports"}
|
||||
table := formatter.CreateTable(tableCol)
|
||||
for _, row := range tablerows {
|
||||
table.Append(row)
|
||||
}
|
||||
table.Render()
|
||||
table.Append(tableRow)
|
||||
}
|
||||
|
||||
table.Render()
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
@ -12,8 +13,9 @@ import (
|
||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var appRemoveCommand = cli.Command{
|
||||
@ -110,19 +112,28 @@ flag.
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
volumeList, err := client.GetVolumes(cl, context.Background(), app.Server, fs)
|
||||
volumeListOptions := volume.ListOptions{fs}
|
||||
volumeListOKBody, err := cl.VolumeList(context.Background(), volumeListOptions)
|
||||
volumeList := volumeListOKBody.Volumes
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
volumeNames := client.GetVolumeNames(volumeList)
|
||||
|
||||
if len(volumeNames) > 0 {
|
||||
err := client.RemoveVolumes(cl, context.Background(), volumeNames, internal.Force, 5)
|
||||
if err != nil {
|
||||
log.Fatalf("removing volumes failed: %s", err)
|
||||
var vols []string
|
||||
for _, vol := range volumeList {
|
||||
vols = append(vols, vol.Name)
|
||||
}
|
||||
|
||||
if len(vols) > 0 {
|
||||
for _, vol := range vols {
|
||||
err = retryFunc(5, func() error {
|
||||
return cl.VolumeRemove(context.Background(), vol, internal.Force) // last argument is for force removing
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("removing volumes failed: %s", err)
|
||||
}
|
||||
logrus.Info(fmt.Sprintf("volume %s removed", vol))
|
||||
}
|
||||
|
||||
logrus.Infof("%d volumes removed successfully", len(volumeNames))
|
||||
} else {
|
||||
logrus.Info("no volumes to remove")
|
||||
}
|
||||
@ -136,3 +147,21 @@ flag.
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// retryFunc retries the given function for the given retries. After the nth
|
||||
// retry it waits (n + 1)^2 seconds before the next retry (starting with n=0).
|
||||
// It returns an error if the function still failed after the last retry.
|
||||
func retryFunc(retries int, fn func() error) error {
|
||||
for i := 0; i < retries; i++ {
|
||||
err := fn()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if i+1 < retries {
|
||||
sleep := time.Duration(i+1) * time.Duration(i+1)
|
||||
logrus.Infof("%s: waiting %d seconds before next retry", err, sleep)
|
||||
time.Sleep(sleep * time.Second)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%d retries failed", retries)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package client
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -11,7 +11,7 @@ import (
|
||||
upstream "coopcloud.tech/abra/pkg/upstream/service"
|
||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var appRestartCommand = cli.Command{
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var targetPath string
|
||||
@ -33,27 +33,22 @@ var appRestoreCommand = cli.Command{
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := r.EnsureExists(); err != nil {
|
||||
if err := recipe.EnsureExists(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureIsClean(); err != nil {
|
||||
if err := recipe.EnsureIsClean(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := r.EnsureUpToDate(); err != nil {
|
||||
if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.EnsureLatest(); err != nil {
|
||||
if err := recipe.EnsureLatest(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var appRollbackCommand = cli.Command{
|
||||
@ -51,37 +51,33 @@ recipes.
|
||||
app := internal.ValidateApp(c)
|
||||
stackName := app.StackName()
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
specificVersion := c.Args().Get(1)
|
||||
if specificVersion != "" && internal.Chaos {
|
||||
logrus.Fatal("cannot use <version> and --chaos together")
|
||||
}
|
||||
|
||||
if err := r.EnsureExists(); err != nil {
|
||||
if err := recipe.EnsureExists(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureIsClean(); err != nil {
|
||||
if err := recipe.EnsureIsClean(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := r.EnsureUpToDate(); err != nil {
|
||||
if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.EnsureLatest(); err != nil {
|
||||
if err := recipe.EnsureLatest(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.LoadConfig(); err != nil {
|
||||
r, err := recipe.Get(app.Recipe, internal.Offline)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
@ -117,7 +113,7 @@ recipes.
|
||||
|
||||
if len(versions) == 0 && !internal.Chaos {
|
||||
logrus.Warn("no published versions in catalogue, trying local recipe repository")
|
||||
recipeVersions, err := r.GetVersions(internal.Offline)
|
||||
recipeVersions, err := recipe.GetRecipeVersions(app.Recipe, internal.Offline)
|
||||
if err != nil {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
@ -187,7 +183,7 @@ recipes.
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureVersion(chosenDowngrade); err != nil {
|
||||
if err := recipe.EnsureVersion(app.Recipe, chosenDowngrade); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
@ -195,13 +191,13 @@ recipes.
|
||||
if internal.Chaos {
|
||||
logrus.Warn("chaos mode engaged")
|
||||
var err error
|
||||
chosenDowngrade, err = r.ChaosVersion()
|
||||
chosenDowngrade, err = recipe.ChaosVersion(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
abraShPath := fmt.Sprintf("%s/%s/%s", r.Dir, "abra.sh")
|
||||
abraShPath := fmt.Sprintf("%s/%s/%s", config.RECIPES_DIR, app.Recipe, "abra.sh")
|
||||
abraShEnv, err := config.ReadAbraShEnvVars(abraShPath)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var user string
|
||||
@ -45,11 +45,11 @@ var appRunCommand = cli.Command{
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
|
||||
if len(c.Args()) < 2 {
|
||||
if c.Args().Len() < 2 {
|
||||
internal.ShowSubcommandHelpAndError(c, errors.New("no <service> provided?"))
|
||||
}
|
||||
|
||||
if len(c.Args()) < 3 {
|
||||
if c.Args().Len() < 3 {
|
||||
internal.ShowSubcommandHelpAndError(c, errors.New("no <args> provided?"))
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ var appRunCommand = cli.Command{
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
cmd := c.Args()[2:]
|
||||
cmd := c.Args().Slice()[2:]
|
||||
execCreateOpts := types.ExecConfig{
|
||||
AttachStderr: true,
|
||||
AttachStdin: true,
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
@ -18,7 +17,7 @@ import (
|
||||
"github.com/docker/docker/api/types"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -57,32 +56,27 @@ var appSecretGenerateCommand = cli.Command{
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := r.EnsureExists(); err != nil {
|
||||
if err := recipe.EnsureExists(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureIsClean(); err != nil {
|
||||
if err := recipe.EnsureIsClean(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := r.EnsureUpToDate(); err != nil {
|
||||
if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.EnsureLatest(); err != nil {
|
||||
if err := recipe.EnsureLatest(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.Args()) == 1 && !allSecrets {
|
||||
if c.Args().Len() == 1 && !allSecrets {
|
||||
err := errors.New("missing arguments <secret>/<version> or '--all'")
|
||||
internal.ShowSubcommandHelpAndError(c, err)
|
||||
}
|
||||
@ -162,8 +156,6 @@ var appSecretInsertCommand = cli.Command{
|
||||
Flags: []cli.Flag{
|
||||
internal.DebugFlag,
|
||||
internal.PassFlag,
|
||||
internal.FileFlag,
|
||||
internal.TrimFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
ArgsUsage: "<domain> <secret-name> <version> <data>",
|
||||
@ -183,7 +175,7 @@ Example:
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
|
||||
if len(c.Args()) != 4 {
|
||||
if c.Args().Len() != 4 {
|
||||
internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments?"))
|
||||
}
|
||||
|
||||
@ -196,18 +188,6 @@ Example:
|
||||
version := c.Args().Get(2)
|
||||
data := c.Args().Get(3)
|
||||
|
||||
if internal.File {
|
||||
raw, err := os.ReadFile(data)
|
||||
if err != nil {
|
||||
logrus.Fatalf("reading secret from file: %s", err)
|
||||
}
|
||||
data = string(raw)
|
||||
}
|
||||
|
||||
if internal.Trim {
|
||||
data = strings.TrimSpace(data)
|
||||
}
|
||||
|
||||
secretName := fmt.Sprintf("%s_%s_%s", app.StackName(), name, version)
|
||||
if err := client.StoreSecret(cl, secretName, data, app.Server); err != nil {
|
||||
logrus.Fatal(err)
|
||||
@ -269,27 +249,22 @@ Example:
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := r.EnsureExists(); err != nil {
|
||||
if err := recipe.EnsureExists(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureIsClean(); err != nil {
|
||||
if err := recipe.EnsureIsClean(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := r.EnsureUpToDate(); err != nil {
|
||||
if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.EnsureLatest(); err != nil {
|
||||
if err := recipe.EnsureLatest(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
@ -382,27 +357,22 @@ var appSecretLsCommand = cli.Command{
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := r.EnsureExists(); err != nil {
|
||||
if err := recipe.EnsureExists(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureIsClean(); err != nil {
|
||||
if err := recipe.EnsureIsClean(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := r.EnsureUpToDate(); err != nil {
|
||||
if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.EnsureLatest(); err != nil {
|
||||
if err := recipe.EnsureLatest(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
@ -449,10 +419,10 @@ var appSecretCommand = cli.Command{
|
||||
Aliases: []string{"s"},
|
||||
Usage: "Manage app secrets",
|
||||
ArgsUsage: "<domain>",
|
||||
Subcommands: []cli.Command{
|
||||
appSecretGenerateCommand,
|
||||
appSecretInsertCommand,
|
||||
appSecretRmCommand,
|
||||
appSecretLsCommand,
|
||||
Subcommands: []*cli.Command{
|
||||
&appSecretGenerateCommand,
|
||||
&appSecretInsertCommand,
|
||||
&appSecretRmCommand,
|
||||
&appSecretLsCommand,
|
||||
},
|
||||
}
|
||||
|
@ -11,9 +11,9 @@ import (
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"coopcloud.tech/abra/pkg/service"
|
||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||
containerTypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var appServicesCommand = cli.Command{
|
||||
@ -48,7 +48,7 @@ var appServicesCommand = cli.Command{
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
containers, err := cl.ContainerList(context.Background(), containerTypes.ListOptions{Filters: filters})
|
||||
containers, err := cl.ContainerList(context.Background(), types.ContainerListOptions{Filters: filters})
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var prune bool
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
"coopcloud.tech/tagcmp"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var appUpgradeCommand = cli.Command{
|
||||
@ -31,7 +31,6 @@ var appUpgradeCommand = cli.Command{
|
||||
internal.NoDomainChecksFlag,
|
||||
internal.DontWaitConvergeFlag,
|
||||
internal.OfflineFlag,
|
||||
internal.ReleaseNotesFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
Description: `
|
||||
@ -62,32 +61,28 @@ recipes.
|
||||
logrus.Fatal("cannot use <version> and --chaos together")
|
||||
}
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureIsClean(); err != nil {
|
||||
if err := recipe.EnsureIsClean(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := r.EnsureUpToDate(); err != nil {
|
||||
if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.EnsureLatest(); err != nil {
|
||||
if err := recipe.EnsureLatest(app.Recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.LoadConfig(); err != nil {
|
||||
recipe, err := recipePkg.Get(app.Recipe, internal.Offline)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := lint.LintForErrors(r); err != nil {
|
||||
if err := lint.LintForErrors(recipe); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
@ -119,7 +114,7 @@ recipes.
|
||||
|
||||
if len(versions) == 0 && !internal.Chaos {
|
||||
logrus.Warn("no published versions in catalogue, trying local recipe repository")
|
||||
recipeVersions, err := r.GetVersions(internal.Offline)
|
||||
recipeVersions, err := recipePkg.GetRecipeVersions(app.Recipe, internal.Offline)
|
||||
if err != nil {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
@ -198,30 +193,29 @@ recipes.
|
||||
// check out the tag and then they'll appear to be missing. this covers
|
||||
// when we obviously will forget to write release notes before publishing
|
||||
var releaseNotes string
|
||||
if chosenUpgrade != "" {
|
||||
for _, version := range versions {
|
||||
parsedVersion, err := tagcmp.Parse(version)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
parsedChosenUpgrade, err := tagcmp.Parse(chosenUpgrade)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
for _, version := range versions {
|
||||
parsedVersion, err := tagcmp.Parse(version)
|
||||
|
||||
if !(parsedVersion.Equals(parsedDeployedVersion)) && parsedVersion.IsLessThan(parsedChosenUpgrade) {
|
||||
note, err := internal.GetReleaseNotes(app.Recipe, version)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
return err
|
||||
}
|
||||
if parsedVersion.IsGreaterThan(parsedDeployedVersion) && parsedVersion.IsLessThan(parsedChosenUpgrade) {
|
||||
note, err := internal.GetReleaseNotes(app.Recipe, version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if note != "" {
|
||||
releaseNotes += fmt.Sprintf("%s\n", note)
|
||||
}
|
||||
if note != "" {
|
||||
releaseNotes += fmt.Sprintf("%s\n", note)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureVersion(chosenUpgrade); err != nil {
|
||||
if err := recipePkg.EnsureVersion(app.Recipe, chosenUpgrade); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
@ -229,13 +223,13 @@ recipes.
|
||||
if internal.Chaos {
|
||||
logrus.Warn("chaos mode engaged")
|
||||
var err error
|
||||
chosenUpgrade, err = r.ChaosVersion()
|
||||
chosenUpgrade, err = recipePkg.ChaosVersion(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
abraShPath := fmt.Sprintf("%s/%s/%s", r.Dir, "abra.sh")
|
||||
abraShPath := fmt.Sprintf("%s/%s/%s", config.RECIPES_DIR, app.Recipe, "abra.sh")
|
||||
abraShEnv, err := config.ReadAbraShEnvVars(abraShPath)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
@ -275,12 +269,6 @@ recipes.
|
||||
}
|
||||
}
|
||||
|
||||
if internal.ReleaseNotes {
|
||||
fmt.Println()
|
||||
fmt.Print(releaseNotes)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := internal.NewVersionOverview(app, deployedVersion, chosenUpgrade, releaseNotes); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
@ -10,10 +10,10 @@ import (
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func sortServiceByName(versions [][]string) func(i, j int) bool {
|
||||
@ -58,11 +58,6 @@ var appVersionCommand = cli.Command{
|
||||
app := internal.ValidateApp(c)
|
||||
stackName := app.StackName()
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
@ -83,7 +78,7 @@ var appVersionCommand = cli.Command{
|
||||
logrus.Fatalf("failed to determine version of deployed %s", app.Name)
|
||||
}
|
||||
|
||||
recipeMeta, err := r.GetRecipeMeta(internal.Offline)
|
||||
recipeMeta, err := recipe.GetRecipeMeta(app.Recipe, internal.Offline)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
@ -11,7 +10,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var appVolumeListCommand = cli.Command{
|
||||
@ -132,12 +131,12 @@ Passing "--force/-f" will select all volumes for removal. Be careful.
|
||||
}
|
||||
|
||||
if len(volumesToRemove) > 0 {
|
||||
err := client.RemoveVolumes(cl, context.Background(), volumesToRemove, internal.Force, 5)
|
||||
err = client.RemoveVolumes(cl, context.Background(), app.Server, volumesToRemove, internal.Force)
|
||||
if err != nil {
|
||||
log.Fatalf("removing volumes failed: %s", err)
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
logrus.Infof("%d volumes removed successfully", len(volumesToRemove))
|
||||
logrus.Info("volumes removed successfully")
|
||||
} else {
|
||||
logrus.Info("no volumes removed")
|
||||
}
|
||||
@ -151,8 +150,8 @@ var appVolumeCommand = cli.Command{
|
||||
Aliases: []string{"vl"},
|
||||
Usage: "Manage app volumes",
|
||||
ArgsUsage: "<domain>",
|
||||
Subcommands: []cli.Command{
|
||||
appVolumeListCommand,
|
||||
appVolumeRemoveCommand,
|
||||
Subcommands: []*cli.Command{
|
||||
&appVolumeListCommand,
|
||||
&appVolumeRemoveCommand,
|
||||
},
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
@ -16,7 +15,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var catalogueGenerateCommand = cli.Command{
|
||||
@ -58,10 +57,9 @@ keys configured on your account.
|
||||
BashComplete: autocomplete.RecipeNameComplete,
|
||||
Action: func(c *cli.Context) error {
|
||||
recipeName := c.Args().First()
|
||||
r := recipe.Recipe{}
|
||||
|
||||
if recipeName != "" {
|
||||
r = internal.ValidateRecipe(c)
|
||||
internal.ValidateRecipe(c)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
@ -100,12 +98,12 @@ keys configured on your account.
|
||||
continue
|
||||
}
|
||||
|
||||
versions, err := r.GetVersions(internal.Offline)
|
||||
versions, err := recipe.GetRecipeVersions(recipeMeta.Name, internal.Offline)
|
||||
if err != nil {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
|
||||
features, category, err := r.GetRecipeFeaturesAndCategory()
|
||||
features, category, err := recipe.GetRecipeFeaturesAndCategory(recipeMeta.Name)
|
||||
if err != nil {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
@ -132,7 +130,7 @@ keys configured on your account.
|
||||
}
|
||||
|
||||
if recipeName == "" {
|
||||
if err := os.WriteFile(config.RECIPES_JSON, recipesJSON, 0764); err != nil {
|
||||
if err := ioutil.WriteFile(config.RECIPES_JSON, recipesJSON, 0764); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
@ -219,7 +217,7 @@ var CatalogueCommand = cli.Command{
|
||||
Aliases: []string{"c"},
|
||||
ArgsUsage: "<recipe>",
|
||||
Description: "This command helps recipe packagers interact with the recipe catalogue",
|
||||
Subcommands: []cli.Command{
|
||||
catalogueGenerateCommand,
|
||||
Subcommands: []*cli.Command{
|
||||
&catalogueGenerateCommand,
|
||||
},
|
||||
}
|
||||
|
16
cli/cli.go
16
cli/cli.go
@ -18,7 +18,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/web"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// AutoCompleteCommand helps people set up auto-complete in their shells
|
||||
@ -153,13 +153,13 @@ func newAbraApp(version, commit string) *cli.App {
|
||||
|_|
|
||||
`,
|
||||
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
|
||||
Commands: []cli.Command{
|
||||
app.AppCommand,
|
||||
server.ServerCommand,
|
||||
recipe.RecipeCommand,
|
||||
catalogue.CatalogueCommand,
|
||||
UpgradeCommand,
|
||||
AutoCompleteCommand,
|
||||
Commands: []*cli.Command{
|
||||
&app.AppCommand,
|
||||
&server.ServerCommand,
|
||||
&recipe.RecipeCommand,
|
||||
&catalogue.CatalogueCommand,
|
||||
&UpgradeCommand,
|
||||
&AutoCompleteCommand,
|
||||
},
|
||||
BashComplete: autocomplete.SubcommandComplete,
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
|
||||
logrusStack "github.com/Gurpartap/logrus-stack"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Secrets stores the variable from SecretsFlag
|
||||
@ -13,7 +13,8 @@ var Secrets bool
|
||||
|
||||
// SecretsFlag turns on/off automatically generating secrets
|
||||
var SecretsFlag = &cli.BoolFlag{
|
||||
Name: "secrets, S",
|
||||
Name: "secrets",
|
||||
Aliases: []string{"S"},
|
||||
Usage: "Automatically generate secrets",
|
||||
Destination: &Secrets,
|
||||
}
|
||||
@ -23,7 +24,8 @@ var Pass bool
|
||||
|
||||
// PassFlag turns on/off storing generated secrets in pass
|
||||
var PassFlag = &cli.BoolFlag{
|
||||
Name: "pass, p",
|
||||
Name: "pass",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "Store the generated secrets in a local pass store",
|
||||
Destination: &Pass,
|
||||
}
|
||||
@ -33,31 +35,19 @@ var PassRemove bool
|
||||
|
||||
// PassRemoveFlag turns on/off removing generated secrets from pass
|
||||
var PassRemoveFlag = &cli.BoolFlag{
|
||||
Name: "pass, p",
|
||||
Name: "pass",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "Remove generated secrets from a local pass store",
|
||||
Destination: &PassRemove,
|
||||
}
|
||||
|
||||
var File bool
|
||||
var FileFlag = &cli.BoolFlag{
|
||||
Name: "file, f",
|
||||
Usage: "Treat input as a file",
|
||||
Destination: &File,
|
||||
}
|
||||
|
||||
var Trim bool
|
||||
var TrimFlag = &cli.BoolFlag{
|
||||
Name: "trim, t",
|
||||
Usage: "Trim input",
|
||||
Destination: &Trim,
|
||||
}
|
||||
|
||||
// Force force functionality without asking.
|
||||
var Force bool
|
||||
|
||||
// ForceFlag turns on/off force functionality.
|
||||
var ForceFlag = &cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Name: "force",
|
||||
Aliases: []string{"f"},
|
||||
Usage: "Perform action without further prompt. Use with care!",
|
||||
Destination: &Force,
|
||||
}
|
||||
@ -67,7 +57,8 @@ var Chaos bool
|
||||
|
||||
// ChaosFlag turns on/off chaos functionality.
|
||||
var ChaosFlag = &cli.BoolFlag{
|
||||
Name: "chaos, C",
|
||||
Name: "chaos",
|
||||
Aliases: []string{"C"},
|
||||
Usage: "Proceed with uncommitted recipes changes. Use with care!",
|
||||
Destination: &Chaos,
|
||||
}
|
||||
@ -77,24 +68,29 @@ var Tty bool
|
||||
|
||||
// TtyFlag turns on/off tty mode.
|
||||
var TtyFlag = &cli.BoolFlag{
|
||||
Name: "tty, T",
|
||||
Name: "tty",
|
||||
Aliases: []string{"T"},
|
||||
Usage: "Disables TTY mode to run this command from a script.",
|
||||
Destination: &Tty,
|
||||
}
|
||||
|
||||
var NoInput bool
|
||||
var NoInputFlag = &cli.BoolFlag{
|
||||
Name: "no-input, n",
|
||||
Usage: "Toggle non-interactive mode",
|
||||
Destination: &NoInput,
|
||||
}
|
||||
var (
|
||||
NoInput bool
|
||||
NoInputFlag = &cli.BoolFlag{
|
||||
Name: "no-input",
|
||||
Aliases: []string{"n"},
|
||||
Usage: "Toggle non-interactive mode",
|
||||
Destination: &NoInput,
|
||||
}
|
||||
)
|
||||
|
||||
// Debug stores the variable from DebugFlag.
|
||||
var Debug bool
|
||||
|
||||
// DebugFlag turns on/off verbose logging down to the DEBUG level.
|
||||
var DebugFlag = &cli.BoolFlag{
|
||||
Name: "debug, d",
|
||||
Name: "debug",
|
||||
Aliases: []string{"d"},
|
||||
Destination: &Debug,
|
||||
Usage: "Show DEBUG messages",
|
||||
}
|
||||
@ -104,27 +100,19 @@ var Offline bool
|
||||
|
||||
// DebugFlag turns on/off offline mode.
|
||||
var OfflineFlag = &cli.BoolFlag{
|
||||
Name: "offline, o",
|
||||
Name: "offline",
|
||||
Aliases: []string{"o"},
|
||||
Destination: &Offline,
|
||||
Usage: "Prefer offline & filesystem access when possible",
|
||||
}
|
||||
|
||||
// ReleaseNotes stores the variable from ReleaseNotesFlag.
|
||||
var ReleaseNotes bool
|
||||
|
||||
// ReleaseNotesFlag turns on/off printing only release notes when upgrading.
|
||||
var ReleaseNotesFlag = &cli.BoolFlag{
|
||||
Name: "releasenotes, r",
|
||||
Destination: &ReleaseNotes,
|
||||
Usage: "Only show release notes",
|
||||
}
|
||||
|
||||
// MachineReadable stores the variable from MachineReadableFlag
|
||||
var MachineReadable bool
|
||||
|
||||
// MachineReadableFlag turns on/off machine readable output where supported
|
||||
var MachineReadableFlag = &cli.BoolFlag{
|
||||
Name: "machine, m",
|
||||
Name: "machine",
|
||||
Aliases: []string{"m"},
|
||||
Destination: &MachineReadable,
|
||||
Usage: "Output in a machine-readable format (where supported)",
|
||||
}
|
||||
@ -134,149 +122,185 @@ var RC bool
|
||||
|
||||
// RCFlag chooses the latest release candidate for install
|
||||
var RCFlag = &cli.BoolFlag{
|
||||
Name: "rc, r",
|
||||
Name: "rc",
|
||||
Aliases: []string{"c"},
|
||||
Destination: &RC,
|
||||
Usage: "Install the latest release candidate",
|
||||
}
|
||||
|
||||
var Major bool
|
||||
var MajorFlag = &cli.BoolFlag{
|
||||
Name: "major, x",
|
||||
Usage: "Increase the major part of the version",
|
||||
Destination: &Major,
|
||||
}
|
||||
var (
|
||||
Major bool
|
||||
MajorFlag = &cli.BoolFlag{
|
||||
Name: "major",
|
||||
Aliases: []string{"x"},
|
||||
Usage: "Increase the major part of the version",
|
||||
Destination: &Major,
|
||||
}
|
||||
)
|
||||
|
||||
var Minor bool
|
||||
var MinorFlag = &cli.BoolFlag{
|
||||
Name: "minor, y",
|
||||
Usage: "Increase the minor part of the version",
|
||||
Destination: &Minor,
|
||||
}
|
||||
var (
|
||||
Minor bool
|
||||
MinorFlag = &cli.BoolFlag{
|
||||
Name: "minor",
|
||||
Aliases: []string{"y"},
|
||||
Usage: "Increase the minor part of the version",
|
||||
Destination: &Minor,
|
||||
}
|
||||
)
|
||||
|
||||
var Patch bool
|
||||
var PatchFlag = &cli.BoolFlag{
|
||||
Name: "patch, z",
|
||||
Usage: "Increase the patch part of the version",
|
||||
Destination: &Patch,
|
||||
}
|
||||
var (
|
||||
Patch bool
|
||||
PatchFlag = &cli.BoolFlag{
|
||||
Name: "patch",
|
||||
Aliases: []string{"z"},
|
||||
Usage: "Increase the patch part of the version",
|
||||
Destination: &Patch,
|
||||
}
|
||||
)
|
||||
|
||||
var Dry bool
|
||||
var DryFlag = &cli.BoolFlag{
|
||||
Name: "dry-run, r",
|
||||
Usage: "Only reports changes that would be made",
|
||||
Destination: &Dry,
|
||||
}
|
||||
var (
|
||||
Dry bool
|
||||
DryFlag = &cli.BoolFlag{
|
||||
Name: "dry-run",
|
||||
Aliases: []string{"r"},
|
||||
Usage: "Only reports changes that would be made",
|
||||
Destination: &Dry,
|
||||
}
|
||||
)
|
||||
|
||||
var Publish bool
|
||||
var PublishFlag = &cli.BoolFlag{
|
||||
Name: "publish, p",
|
||||
Usage: "Publish changes to git.coopcloud.tech",
|
||||
Destination: &Publish,
|
||||
}
|
||||
var (
|
||||
Publish bool
|
||||
PublishFlag = &cli.BoolFlag{
|
||||
Name: "publish",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "Publish changes to git.coopcloud.tech",
|
||||
Destination: &Publish,
|
||||
}
|
||||
)
|
||||
|
||||
var Domain string
|
||||
var DomainFlag = &cli.StringFlag{
|
||||
Name: "domain, D",
|
||||
Value: "",
|
||||
Usage: "Choose a domain name",
|
||||
Destination: &Domain,
|
||||
}
|
||||
var (
|
||||
Domain string
|
||||
DomainFlag = &cli.StringFlag{
|
||||
Name: "domain",
|
||||
Aliases: []string{"D"},
|
||||
Value: "",
|
||||
Usage: "Choose a domain name",
|
||||
Destination: &Domain,
|
||||
}
|
||||
)
|
||||
|
||||
var NewAppServer string
|
||||
var NewAppServerFlag = &cli.StringFlag{
|
||||
Name: "server, s",
|
||||
Value: "",
|
||||
Usage: "Show apps of a specific server",
|
||||
Destination: &NewAppServer,
|
||||
}
|
||||
var (
|
||||
NewAppServer string
|
||||
NewAppServerFlag = &cli.StringFlag{
|
||||
Name: "server",
|
||||
Aliases: []string{"s"},
|
||||
Value: "",
|
||||
Usage: "Show apps of a specific server",
|
||||
Destination: &NewAppServer,
|
||||
}
|
||||
)
|
||||
|
||||
var NoDomainChecks bool
|
||||
var NoDomainChecksFlag = &cli.BoolFlag{
|
||||
Name: "no-domain-checks, D",
|
||||
Usage: "Disable app domain sanity checks",
|
||||
Destination: &NoDomainChecks,
|
||||
}
|
||||
var (
|
||||
NoDomainChecks bool
|
||||
NoDomainChecksFlag = &cli.BoolFlag{
|
||||
Name: "no-domain-checks",
|
||||
Aliases: []string{"D"},
|
||||
Usage: "Disable app domain sanity checks",
|
||||
Destination: &NoDomainChecks,
|
||||
}
|
||||
)
|
||||
|
||||
var StdErrOnly bool
|
||||
var StdErrOnlyFlag = &cli.BoolFlag{
|
||||
Name: "stderr, s",
|
||||
Usage: "Only tail stderr",
|
||||
Destination: &StdErrOnly,
|
||||
}
|
||||
var (
|
||||
StdErrOnly bool
|
||||
StdErrOnlyFlag = &cli.BoolFlag{
|
||||
Name: "stderr",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "Only tail stderr",
|
||||
Destination: &StdErrOnly,
|
||||
}
|
||||
)
|
||||
|
||||
var SinceLogs string
|
||||
var SinceLogsFlag = &cli.StringFlag{
|
||||
Name: "since, S",
|
||||
Value: "",
|
||||
Usage: "tail logs since YYYY-MM-DDTHH:MM:SSZ",
|
||||
Destination: &SinceLogs,
|
||||
}
|
||||
var (
|
||||
SinceLogs string
|
||||
SinceLogsFlag = &cli.StringFlag{
|
||||
Name: "since",
|
||||
Aliases: []string{"S"},
|
||||
Value: "",
|
||||
Usage: "tail logs since YYYY-MM-DDTHH:MM:SSZ",
|
||||
Destination: &SinceLogs,
|
||||
}
|
||||
)
|
||||
|
||||
var DontWaitConverge bool
|
||||
var DontWaitConvergeFlag = &cli.BoolFlag{
|
||||
Name: "no-converge-checks, c",
|
||||
Usage: "Don't wait for converge logic checks",
|
||||
Destination: &DontWaitConverge,
|
||||
}
|
||||
var (
|
||||
DontWaitConverge bool
|
||||
DontWaitConvergeFlag = &cli.BoolFlag{
|
||||
Name: "no-converge-checks",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Don't wait for converge logic checks",
|
||||
Destination: &DontWaitConverge,
|
||||
}
|
||||
)
|
||||
|
||||
var Watch bool
|
||||
var WatchFlag = &cli.BoolFlag{
|
||||
Name: "watch, w",
|
||||
Usage: "Watch status by polling repeatedly",
|
||||
Destination: &Watch,
|
||||
}
|
||||
var (
|
||||
Watch bool
|
||||
WatchFlag = &cli.BoolFlag{
|
||||
Name: "watch",
|
||||
Aliases: []string{"w"},
|
||||
Usage: "Watch status by polling repeatedly",
|
||||
Destination: &Watch,
|
||||
}
|
||||
)
|
||||
|
||||
var OnlyErrors bool
|
||||
var OnlyErrorFlag = &cli.BoolFlag{
|
||||
Name: "errors, e",
|
||||
Usage: "Only show errors",
|
||||
Destination: &OnlyErrors,
|
||||
}
|
||||
var (
|
||||
OnlyErrors bool
|
||||
OnlyErrorFlag = &cli.BoolFlag{
|
||||
Name: "errors",
|
||||
Aliases: []string{"e"},
|
||||
Usage: "Only show errors",
|
||||
Destination: &OnlyErrors,
|
||||
}
|
||||
)
|
||||
|
||||
var SkipUpdates bool
|
||||
var SkipUpdatesFlag = &cli.BoolFlag{
|
||||
Name: "skip-updates, s",
|
||||
Usage: "Skip updating recipe repositories",
|
||||
Destination: &SkipUpdates,
|
||||
}
|
||||
var (
|
||||
SkipUpdates bool
|
||||
SkipUpdatesFlag = &cli.BoolFlag{
|
||||
Name: "skip-updates",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "Skip updating recipe repositories",
|
||||
Destination: &SkipUpdates,
|
||||
}
|
||||
)
|
||||
|
||||
var AllTags bool
|
||||
var AllTagsFlag = &cli.BoolFlag{
|
||||
Name: "all-tags, a",
|
||||
Usage: "List all tags, not just upgrades",
|
||||
Destination: &AllTags,
|
||||
}
|
||||
var (
|
||||
AllTags bool
|
||||
AllTagsFlag = &cli.BoolFlag{
|
||||
Name: "all-tags",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "List all tags, not just upgrades",
|
||||
Destination: &AllTags,
|
||||
}
|
||||
)
|
||||
|
||||
var LocalCmd bool
|
||||
var LocalCmdFlag = &cli.BoolFlag{
|
||||
Name: "local, l",
|
||||
Usage: "Run command locally",
|
||||
Destination: &LocalCmd,
|
||||
}
|
||||
var (
|
||||
LocalCmd bool
|
||||
LocalCmdFlag = &cli.BoolFlag{
|
||||
Name: "local",
|
||||
Aliases: []string{"l"},
|
||||
Usage: "Run command locally",
|
||||
Destination: &LocalCmd,
|
||||
}
|
||||
)
|
||||
|
||||
var RemoteUser string
|
||||
var RemoteUserFlag = &cli.StringFlag{
|
||||
Name: "user, u",
|
||||
Value: "",
|
||||
Usage: "User to run command within a service context",
|
||||
Destination: &RemoteUser,
|
||||
}
|
||||
|
||||
var GitName string
|
||||
var GitNameFlag = &cli.StringFlag{
|
||||
Name: "git-name, gn",
|
||||
Value: "",
|
||||
Usage: "Git (user) name to do commits with",
|
||||
Destination: &GitName,
|
||||
}
|
||||
|
||||
var GitEmail string
|
||||
var GitEmailFlag = &cli.StringFlag{
|
||||
Name: "git-email, ge",
|
||||
Value: "",
|
||||
Usage: "Git email name to do commits with",
|
||||
Destination: &GitEmail,
|
||||
}
|
||||
var (
|
||||
RemoteUser string
|
||||
RemoteUserFlag = &cli.StringFlag{
|
||||
Name: "user",
|
||||
Aliases: []string{"u"},
|
||||
Value: "",
|
||||
Usage: "User to run command within a service context",
|
||||
Destination: &RemoteUser,
|
||||
}
|
||||
)
|
||||
|
||||
// SubCommandBefore wires up pre-action machinery (e.g. --debug handling).
|
||||
func SubCommandBefore(c *cli.Context) error {
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// ShowSubcommandHelpAndError exits the program on error, logs the error to the
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// ValidateRecipe ensures the recipe arg is valid.
|
||||
@ -57,10 +57,7 @@ func ValidateRecipe(c *cli.Context) recipe.Recipe {
|
||||
ShowSubcommandHelpAndError(c, errors.New("no recipe name provided"))
|
||||
}
|
||||
|
||||
r, err := recipe.Get(recipeName)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
chosenRecipe, err := recipe.Get(recipeName, Offline)
|
||||
if err != nil {
|
||||
if c.Command.Name == "generate" {
|
||||
if strings.Contains(err.Error(), "missing a compose") {
|
||||
@ -77,7 +74,7 @@ func ValidateRecipe(c *cli.Context) recipe.Recipe {
|
||||
|
||||
logrus.Debugf("validated %s as recipe argument", recipeName)
|
||||
|
||||
return r
|
||||
return chosenRecipe
|
||||
}
|
||||
|
||||
// ValidateApp ensures the app name arg is valid.
|
||||
@ -123,9 +120,9 @@ func ValidateDomain(c *cli.Context) string {
|
||||
|
||||
// ValidateSubCmdFlags ensures flag order conforms to correct order
|
||||
func ValidateSubCmdFlags(c *cli.Context) bool {
|
||||
for argIdx, arg := range c.Args() {
|
||||
for argIdx, arg := range c.Args().Slice() {
|
||||
if !strings.HasPrefix(arg, "--") {
|
||||
for _, flag := range c.Args()[argIdx:] {
|
||||
for _, flag := range c.Args().Slice()[argIdx:] {
|
||||
if strings.HasPrefix(flag, "--") {
|
||||
return false
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
gitPkg "coopcloud.tech/abra/pkg/git"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var recipeDiffCommand = cli.Command{
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var recipeFetchCommand = cli.Command{
|
||||
@ -24,14 +24,9 @@ var recipeFetchCommand = cli.Command{
|
||||
BashComplete: autocomplete.RecipeNameComplete,
|
||||
Action: func(c *cli.Context) error {
|
||||
recipeName := c.Args().First()
|
||||
|
||||
if recipeName != "" {
|
||||
internal.ValidateRecipe(c)
|
||||
r, err := recipe.Get(recipeName)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
if err := r.Ensure(); err != nil {
|
||||
if err := recipe.Ensure(recipeName); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
@ -44,11 +39,7 @@ var recipeFetchCommand = cli.Command{
|
||||
|
||||
catlBar := formatter.CreateProgressbar(len(catalogue), "fetching latest recipes...")
|
||||
for recipeName := range catalogue {
|
||||
r, err := recipe.Get(recipeName)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
if err := r.Ensure(); err != nil {
|
||||
if err := recipe.Ensure(recipeName); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
catlBar.Add(1)
|
||||
|
@ -7,8 +7,9 @@ import (
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"coopcloud.tech/abra/pkg/lint"
|
||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var recipeLintCommand = cli.Command{
|
||||
@ -26,27 +27,24 @@ var recipeLintCommand = cli.Command{
|
||||
Before: internal.SubCommandBefore,
|
||||
BashComplete: autocomplete.RecipeNameComplete,
|
||||
Action: func(c *cli.Context) error {
|
||||
r := internal.ValidateRecipe(c)
|
||||
if err := r.LoadConfig(); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
recipe := internal.ValidateRecipe(c)
|
||||
|
||||
if err := r.EnsureExists(); err != nil {
|
||||
if err := recipePkg.EnsureExists(recipe.Name); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := r.EnsureIsClean(); err != nil {
|
||||
if err := recipePkg.EnsureIsClean(recipe.Name); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !internal.Offline {
|
||||
if err := r.EnsureUpToDate(); err != nil {
|
||||
if err := recipePkg.EnsureUpToDate(recipe.Name); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.EnsureLatest(); err != nil {
|
||||
if err := recipePkg.EnsureLatest(recipe.Name); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
@ -64,7 +62,7 @@ var recipeLintCommand = cli.Command{
|
||||
}
|
||||
|
||||
skipped := false
|
||||
if rule.Skip(r) {
|
||||
if rule.Skip(recipe) {
|
||||
skipped = true
|
||||
}
|
||||
|
||||
@ -75,7 +73,7 @@ var recipeLintCommand = cli.Command{
|
||||
|
||||
satisfied := false
|
||||
if !skipped {
|
||||
ok, err := rule.Function(r)
|
||||
ok, err := rule.Function(recipe)
|
||||
if err != nil {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var pattern string
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"text/template"
|
||||
@ -12,7 +13,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/git"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// recipeMetadata is the recipe metadata for the README.md
|
||||
@ -36,8 +37,6 @@ var recipeNewCommand = cli.Command{
|
||||
internal.DebugFlag,
|
||||
internal.NoInputFlag,
|
||||
internal.OfflineFlag,
|
||||
internal.GitNameFlag,
|
||||
internal.GitEmailFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
Usage: "Create a new recipe",
|
||||
@ -93,14 +92,14 @@ recipe and domain in the sample environment config).
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, templated.Bytes(), 0644); err != nil {
|
||||
if err := ioutil.WriteFile(path, templated.Bytes(), 0644); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
newGitRepo := path.Join(config.RECIPES_DIR, recipeName)
|
||||
if err := git.Init(newGitRepo, true, internal.GitName, internal.GitEmail); err != nil {
|
||||
if err := git.Init(newGitRepo, true); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package recipe
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// RecipeCommand defines all recipe related sub-commands.
|
||||
@ -21,16 +21,16 @@ 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
|
||||
"abra recipe upgrade", "abra recipe sync" and "abra recipe release" commands.
|
||||
`,
|
||||
Subcommands: []cli.Command{
|
||||
recipeFetchCommand,
|
||||
recipeLintCommand,
|
||||
recipeListCommand,
|
||||
recipeNewCommand,
|
||||
recipeReleaseCommand,
|
||||
recipeSyncCommand,
|
||||
recipeUpgradeCommand,
|
||||
recipeVersionCommand,
|
||||
recipeResetCommand,
|
||||
recipeDiffCommand,
|
||||
Subcommands: []*cli.Command{
|
||||
&recipeFetchCommand,
|
||||
&recipeLintCommand,
|
||||
&recipeListCommand,
|
||||
&recipeNewCommand,
|
||||
&recipeReleaseCommand,
|
||||
&recipeSyncCommand,
|
||||
&recipeUpgradeCommand,
|
||||
&recipeVersionCommand,
|
||||
&recipeResetCommand,
|
||||
&recipeDiffCommand,
|
||||
},
|
||||
}
|
||||
|
@ -17,10 +17,10 @@ import (
|
||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/tagcmp"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var recipeReleaseCommand = cli.Command{
|
||||
@ -108,14 +108,14 @@ your SSH keys configured on your account.
|
||||
}
|
||||
}
|
||||
|
||||
isClean, err := gitPkg.IsClean(recipe.Dir)
|
||||
isClean, err := gitPkg.IsClean(recipe.Dir())
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if !isClean {
|
||||
logrus.Infof("%s currently has these unstaged changes 👇", recipe.Name)
|
||||
if err := gitPkg.DiffUnstaged(recipe.Dir); err != nil {
|
||||
if err := gitPkg.DiffUnstaged(recipe.Dir()); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
@ -184,7 +184,7 @@ func getImageVersions(recipe recipe.Recipe) (map[string]string, error) {
|
||||
func createReleaseFromTag(recipe recipe.Recipe, tagString, mainAppVersion string) error {
|
||||
var err error
|
||||
|
||||
directory := path.Join(recipe.Dir)
|
||||
directory := path.Join(config.RECIPES_DIR, recipe.Name)
|
||||
repo, err := git.PlainOpen(directory)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -246,7 +246,8 @@ func getTagCreateOptions(tag string) (git.CreateTagOptions, error) {
|
||||
// addReleaseNotes checks if the release/next release note exists and moves the
|
||||
// file to release/<tag>.
|
||||
func addReleaseNotes(recipe recipe.Recipe, tag string) error {
|
||||
tagReleaseNotePath := path.Join(recipe.Dir, "release", tag)
|
||||
repoPath := path.Join(config.RECIPES_DIR, recipe.Name)
|
||||
tagReleaseNotePath := path.Join(repoPath, "release", tag)
|
||||
if _, err := os.Stat(tagReleaseNotePath); err == nil {
|
||||
// Release note for current tag already exist exists.
|
||||
return nil
|
||||
@ -254,7 +255,7 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
nextReleaseNotePath := path.Join(recipe.Dir, "release", "next")
|
||||
nextReleaseNotePath := path.Join(repoPath, "release", "next")
|
||||
if _, err := os.Stat(nextReleaseNotePath); err == nil {
|
||||
// release/next note exists. Move it to release/<tag>
|
||||
if internal.Dry {
|
||||
@ -277,11 +278,11 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = gitPkg.Add(recipe.Dir, path.Join("release", "next"), internal.Dry)
|
||||
err = gitPkg.Add(repoPath, path.Join("release", "next"), internal.Dry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = gitPkg.Add(recipe.Dir, path.Join("release", tag), internal.Dry)
|
||||
err = gitPkg.Add(repoPath, path.Join("release", tag), internal.Dry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -310,7 +311,7 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = gitPkg.Add(recipe.Dir, path.Join("release", tag), internal.Dry)
|
||||
err = gitPkg.Add(repoPath, path.Join("release", tag), internal.Dry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -324,14 +325,14 @@ func commitRelease(recipe recipe.Recipe, tag string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
isClean, err := gitPkg.IsClean(recipe.Dir)
|
||||
isClean, err := gitPkg.IsClean(recipe.Dir())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isClean {
|
||||
if !internal.Dry {
|
||||
return fmt.Errorf("no changes discovered in %s, nothing to publish?", recipe.Dir)
|
||||
return fmt.Errorf("no changes discovered in %s, nothing to publish?", recipe.Dir())
|
||||
}
|
||||
}
|
||||
|
||||
@ -401,7 +402,8 @@ func pushRelease(recipe recipe.Recipe, tagString string) error {
|
||||
}
|
||||
|
||||
func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recipe.Recipe, tags []string) error {
|
||||
repo, err := git.PlainOpen(recipe.Dir)
|
||||
directory := path.Join(config.RECIPES_DIR, recipe.Name)
|
||||
repo, err := git.PlainOpen(directory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var recipeResetCommand = cli.Command{
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var recipeSyncCommand = cli.Command{
|
||||
@ -199,13 +199,13 @@ likely to change.
|
||||
logrus.Infof("dry run: not syncing label %s for recipe %s", nextTag, recipe.Name)
|
||||
}
|
||||
|
||||
isClean, err := gitPkg.IsClean(recipe.Dir)
|
||||
isClean, err := gitPkg.IsClean(recipe.Dir())
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
if !isClean {
|
||||
logrus.Infof("%s currently has these unstaged changes 👇", recipe.Name)
|
||||
if err := gitPkg.DiffUnstaged(recipe.Dir); err != nil {
|
||||
if err := gitPkg.DiffUnstaged(recipe.Dir()); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
@ -18,9 +18,9 @@ import (
|
||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/tagcmp"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
type imgPin struct {
|
||||
@ -73,19 +73,19 @@ You may invoke this command in "wizard" mode and be prompted for input:
|
||||
Action: func(c *cli.Context) error {
|
||||
recipe := internal.ValidateRecipe(c)
|
||||
|
||||
if err := recipe.EnsureIsClean(); err != nil {
|
||||
if err := recipePkg.EnsureIsClean(recipe.Name); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := recipe.EnsureExists(); err != nil {
|
||||
if err := recipePkg.EnsureExists(recipe.Name); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := recipe.EnsureUpToDate(); err != nil {
|
||||
if err := recipePkg.EnsureUpToDate(recipe.Name); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := recipe.EnsureLatest(); err != nil {
|
||||
if err := recipePkg.EnsureLatest(recipe.Name); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func sortServiceByName(versions [][]string) func(i, j int) bool {
|
||||
@ -54,9 +54,8 @@ var recipeVersionCommand = cli.Command{
|
||||
logrus.Fatalf("%s has no catalogue published versions?", recipe.Name)
|
||||
}
|
||||
|
||||
tableCols := []string{"version", "service", "image", "tag"}
|
||||
aggregated_table := formatter.CreateTable(tableCols)
|
||||
for i := len(recipeMeta.Versions) - 1; i >= 0; i-- {
|
||||
tableCols := []string{"version", "service", "image", "tag"}
|
||||
table := formatter.CreateTable(tableCols)
|
||||
for version, meta := range recipeMeta.Versions[i] {
|
||||
var versions [][]string
|
||||
@ -68,10 +67,11 @@ var recipeVersionCommand = cli.Command{
|
||||
|
||||
for _, version := range versions {
|
||||
table.Append(version)
|
||||
aggregated_table.Append(version)
|
||||
}
|
||||
|
||||
if !internal.MachineReadable {
|
||||
if internal.MachineReadable {
|
||||
table.JSONRender()
|
||||
} else {
|
||||
table.SetAutoMergeCellsByColumnIndex([]int{0})
|
||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
||||
table.Render()
|
||||
@ -79,9 +79,6 @@ var recipeVersionCommand = cli.Command{
|
||||
}
|
||||
}
|
||||
}
|
||||
if internal.MachineReadable {
|
||||
aggregated_table.JSONRender()
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
|
@ -13,12 +13,13 @@ import (
|
||||
"coopcloud.tech/abra/pkg/server"
|
||||
sshPkg "coopcloud.tech/abra/pkg/ssh"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var local bool
|
||||
var localFlag = &cli.BoolFlag{
|
||||
Name: "local, l",
|
||||
Name: "local",
|
||||
Aliases: []string{"l"},
|
||||
Usage: "Use local server",
|
||||
Destination: &local,
|
||||
}
|
||||
@ -53,7 +54,7 @@ func cleanUp(domainName string) {
|
||||
// Docker manages SSH connection details. These are stored to disk in
|
||||
// ~/.docker. Abra can manage this completely for the user, so it's an
|
||||
// implementation detail.
|
||||
func newContext(c *cli.Context, domainName string) error {
|
||||
func newContext(c *cli.Context, domainName, username, port string) error {
|
||||
store := contextPkg.NewDefaultDockerContextStore()
|
||||
contexts, err := store.Store.List()
|
||||
if err != nil {
|
||||
@ -67,9 +68,9 @@ func newContext(c *cli.Context, domainName string) error {
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Debugf("creating context with domain %s", domainName)
|
||||
logrus.Debugf("creating context with domain %s, username %s and port %s", domainName, username, port)
|
||||
|
||||
if err := client.CreateContext(domainName); err != nil {
|
||||
if err := client.CreateContext(domainName, username, port); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -122,7 +123,7 @@ developer machine.
|
||||
Before: internal.SubCommandBefore,
|
||||
ArgsUsage: "<domain>",
|
||||
Action: func(c *cli.Context) error {
|
||||
if len(c.Args()) > 0 && local || !internal.ValidateSubCmdFlags(c) {
|
||||
if c.Args().Len() > 0 && local || !internal.ValidateSubCmdFlags(c) {
|
||||
err := errors.New("cannot use <domain> and --local together")
|
||||
internal.ShowSubcommandHelpAndError(c, err)
|
||||
}
|
||||
@ -158,7 +159,12 @@ developer machine.
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := newContext(c, domainName); err != nil {
|
||||
hostConfig, err := sshPkg.GetHostConfig(domainName)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := newContext(c, domainName, hostConfig.User, hostConfig.Port); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"github.com/docker/cli/cli/connhelper/ssh"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var problemsFilter bool
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var allFilter bool
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var serverRemoveCommand = cli.Command{
|
||||
|
@ -1,7 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// ServerCommand defines the `abra server` command and its subcommands
|
||||
@ -9,10 +9,10 @@ var ServerCommand = cli.Command{
|
||||
Name: "server",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "Manage servers",
|
||||
Subcommands: []cli.Command{
|
||||
serverAddCommand,
|
||||
serverListCommand,
|
||||
serverRemoveCommand,
|
||||
serverPruneCommand,
|
||||
Subcommands: []*cli.Command{
|
||||
&serverAddCommand,
|
||||
&serverListCommand,
|
||||
&serverRemoveCommand,
|
||||
&serverPruneCommand,
|
||||
},
|
||||
}
|
||||
|
@ -21,24 +21,28 @@ import (
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const SERVER = "localhost"
|
||||
|
||||
var majorUpdate bool
|
||||
var majorFlag = &cli.BoolFlag{
|
||||
Name: "major, m",
|
||||
Usage: "Also check for major updates",
|
||||
Destination: &majorUpdate,
|
||||
}
|
||||
var (
|
||||
majorUpdate bool
|
||||
majorFlag = &cli.BoolFlag{
|
||||
Name: "major",
|
||||
Aliases: []string{"m"},
|
||||
Usage: "Also check for major updates",
|
||||
Destination: &majorUpdate,
|
||||
}
|
||||
|
||||
var updateAll bool
|
||||
var allFlag = &cli.BoolFlag{
|
||||
Name: "all, a",
|
||||
Usage: "Update all deployed apps",
|
||||
Destination: &updateAll,
|
||||
}
|
||||
updateAll bool
|
||||
allFlag = &cli.BoolFlag{
|
||||
Name: "all",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "Update all deployed apps",
|
||||
Destination: &updateAll,
|
||||
}
|
||||
)
|
||||
|
||||
// Notify checks for available upgrades
|
||||
var Notify = cli.Command{
|
||||
@ -271,7 +275,8 @@ func getDeployedVersion(cl *dockerclient.Client, stackName string, recipeName st
|
||||
// than the deployed version. It only includes major upgrades if the "--major"
|
||||
// flag is set.
|
||||
func getAvailableUpgrades(cl *dockerclient.Client, stackName string, recipeName string,
|
||||
deployedVersion string) ([]string, error) {
|
||||
deployedVersion string,
|
||||
) ([]string, error) {
|
||||
catl, err := recipe.ReadRecipeCatalogue(internal.Offline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -316,23 +321,22 @@ func getAvailableUpgrades(cl *dockerclient.Client, stackName string, recipeName
|
||||
|
||||
// processRecipeRepoVersion clones, pulls, checks out the version and lints the
|
||||
// recipe repository.
|
||||
func processRecipeRepoVersion(recipe recipe.Recipe, version string) error {
|
||||
if err := recipe.EnsureExists(); err != nil {
|
||||
func processRecipeRepoVersion(recipeName, version string) error {
|
||||
if err := recipe.EnsureExists(recipeName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := recipe.EnsureUpToDate(); err != nil {
|
||||
if err := recipe.EnsureUpToDate(recipeName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := recipe.EnsureVersion(version); err != nil {
|
||||
if err := recipe.EnsureVersion(recipeName, version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := recipe.LoadConfig(); err != nil {
|
||||
if r, err := recipe.Get(recipeName, internal.Offline); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := lint.LintForErrors(recipe); err != nil {
|
||||
} else if err := lint.LintForErrors(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -430,7 +434,8 @@ func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string) error {
|
||||
|
||||
// upgrade performs all necessary steps to upgrade an app.
|
||||
func upgrade(cl *dockerclient.Client, stackName, recipeName,
|
||||
upgradeVersion string) error {
|
||||
upgradeVersion string,
|
||||
) error {
|
||||
env, err := getEnv(cl, stackName)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -443,12 +448,7 @@ func upgrade(cl *dockerclient.Client, stackName, recipeName,
|
||||
Env: env,
|
||||
}
|
||||
|
||||
recipe, err := recipe.Get(recipeName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = processRecipeRepoVersion(recipe, upgradeVersion); err != nil {
|
||||
if err = processRecipeRepoVersion(recipeName, upgradeVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -480,9 +480,9 @@ func newAbraApp(version, commit string) *cli.App {
|
||||
|_|
|
||||
`,
|
||||
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
|
||||
Commands: []cli.Command{
|
||||
Notify,
|
||||
UpgradeApp,
|
||||
Commands: []*cli.Command{
|
||||
&Notify,
|
||||
&UpgradeApp,
|
||||
},
|
||||
}
|
||||
|
||||
|
135
go.mod
135
go.mod
@ -3,131 +3,120 @@ module coopcloud.tech/abra
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb
|
||||
coopcloud.tech/tagcmp v0.0.0-20211103052201-885b22f77d52
|
||||
git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||
github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4
|
||||
github.com/distribution/distribution v2.8.3+incompatible
|
||||
github.com/docker/cli v26.1.4+incompatible
|
||||
github.com/docker/docker v26.1.4+incompatible
|
||||
github.com/docker/cli v24.0.7+incompatible
|
||||
github.com/docker/distribution v2.8.3+incompatible
|
||||
github.com/docker/docker v24.0.7+incompatible
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/go-git/go-git/v5 v5.12.0
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/go-git/go-git/v5 v5.10.0
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/moby/sys/signal v0.7.0
|
||||
github.com/moby/term v0.5.0
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/schollz/progressbar/v3 v3.14.4
|
||||
github.com/schollz/progressbar/v3 v3.14.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
gotest.tools/v3 v3.5.1
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/Microsoft/hcsshim v0.9.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.9 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.3 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/klauspost/compress v1.14.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/miekg/pkcs11 v1.0.3 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/sys/user v0.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/runc v1.1.13 // indirect
|
||||
github.com/opencontainers/runc v1.1.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.54.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.10.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/skeema/knownhosts v1.2.2 // indirect
|
||||
github.com/skeema/knownhosts v1.2.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/urfave/cli/v2 v2.27.1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect
|
||||
go.opentelemetry.io/otel v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.27.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/term v0.21.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect
|
||||
google.golang.org/grpc v1.64.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/term v0.14.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
|
||||
github.com/buger/goterm v1.0.4
|
||||
github.com/containerd/containerd v1.7.18 // indirect
|
||||
github.com/containerd/containerd v1.5.9 // indirect
|
||||
github.com/containers/image v3.0.2+incompatible
|
||||
github.com/containers/storage v1.38.2 // indirect
|
||||
github.com/decentral1se/passgen v1.0.1
|
||||
github.com/docker/docker-credential-helpers v0.8.2 // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||
github.com/fvbommel/sortorder v1.1.0 // indirect
|
||||
github.com/fvbommel/sortorder v1.0.2 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5
|
||||
github.com/klauspost/pgzip v1.2.6
|
||||
github.com/moby/patternmatcher v0.5.0 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/spf13/cobra v1.8.1 // indirect
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20211202193544-a5463b7f9c84 // indirect
|
||||
github.com/prometheus/client_golang v1.16.0 // indirect
|
||||
github.com/sergi/go-diff v1.2.0 // indirect
|
||||
github.com/spf13/cobra v1.3.0 // indirect
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
github.com/urfave/cli v1.22.15
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
golang.org/x/sys v0.21.0
|
||||
github.com/urfave/cli v1.22.9
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190809123943-df4f5c81cb3b // indirect
|
||||
golang.org/x/sys v0.14.0
|
||||
)
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// AppNameComplete copletes app names.
|
||||
|
@ -14,16 +14,19 @@ import (
|
||||
|
||||
type Context = contextStore.Metadata
|
||||
|
||||
// CreateContext creates a new Docker context.
|
||||
func CreateContext(contextName string) error {
|
||||
host := fmt.Sprintf("ssh://%s", contextName)
|
||||
|
||||
func CreateContext(contextName string, user string, port string) error {
|
||||
host := contextName
|
||||
if user != "" {
|
||||
host = fmt.Sprintf("%s@%s", user, host)
|
||||
}
|
||||
if port != "" {
|
||||
host = fmt.Sprintf("%s:%s", host, port)
|
||||
}
|
||||
host = fmt.Sprintf("ssh://%s", host)
|
||||
if err := createContext(contextName, host); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Debugf("created the %s context", contextName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/containers/image/docker"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/distribution/reference"
|
||||
)
|
||||
|
||||
// GetRegistryTags retrieves all tags of an image from a container registry.
|
||||
|
@ -2,17 +2,15 @@ package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func GetVolumes(cl *client.Client, ctx context.Context, server string, fs filters.Args) ([]*volume.Volume, error) {
|
||||
volumeListOKBody, err := cl.VolumeList(ctx, volume.ListOptions{Filters: fs})
|
||||
volumeListOptions := volume.ListOptions{fs}
|
||||
volumeListOKBody, err := cl.VolumeList(ctx, volumeListOptions)
|
||||
volumeList := volumeListOKBody.Volumes
|
||||
if err != nil {
|
||||
return volumeList, err
|
||||
@ -31,32 +29,13 @@ func GetVolumeNames(volumes []*volume.Volume) []string {
|
||||
return volumeNames
|
||||
}
|
||||
|
||||
func RemoveVolumes(cl *client.Client, ctx context.Context, volumeNames []string, force bool, retries int) error {
|
||||
func RemoveVolumes(cl *client.Client, ctx context.Context, server string, volumeNames []string, force bool) error {
|
||||
for _, volName := range volumeNames {
|
||||
err := retryFunc(5, func() error {
|
||||
return cl.VolumeRemove(context.Background(), volName, force)
|
||||
})
|
||||
err := cl.VolumeRemove(ctx, volName, force)
|
||||
if err != nil {
|
||||
return fmt.Errorf("volume %s: %s", volName, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// retryFunc retries the given function for the given retries. After the nth
|
||||
// retry it waits (n + 1)^2 seconds before the next retry (starting with n=0).
|
||||
// It returns an error if the function still failed after the last retry.
|
||||
func retryFunc(retries int, fn func() error) error {
|
||||
for i := 0; i < retries; i++ {
|
||||
err := fn()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if i+1 < retries {
|
||||
sleep := time.Duration(i+1) * time.Duration(i+1)
|
||||
logrus.Infof("%s: waiting %d seconds before next retry", err, sleep)
|
||||
time.Sleep(sleep * time.Second)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%d retries failed", retries)
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ import (
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
loader "coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/distribution/reference"
|
||||
composetypes "github.com/docker/cli/cli/compose/types"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@ -69,9 +69,9 @@ func (a App) StackName() string {
|
||||
func StackName(appName string) string {
|
||||
stackName := SanitiseAppName(appName)
|
||||
|
||||
if len(stackName) > MAX_SANITISED_APP_NAME_LENGTH {
|
||||
logrus.Debugf("trimming %s to %s to avoid runtime limits", stackName, stackName[:MAX_SANITISED_APP_NAME_LENGTH])
|
||||
stackName = stackName[:MAX_SANITISED_APP_NAME_LENGTH]
|
||||
if len(stackName) > 45 {
|
||||
logrus.Debugf("trimming %s to %s to avoid runtime limits", stackName, stackName[:45])
|
||||
stackName = stackName[:45]
|
||||
}
|
||||
|
||||
return stackName
|
||||
|
@ -45,7 +45,7 @@ func TestGetApp(t *testing.T) {
|
||||
|
||||
func TestGetComposeFiles(t *testing.T) {
|
||||
offline := true
|
||||
r, err := recipe.Get2("abra-test-recipe", offline)
|
||||
r, err := recipe.Get("abra-test-recipe", offline)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -92,7 +92,7 @@ func TestGetComposeFiles(t *testing.T) {
|
||||
|
||||
func TestGetComposeFilesError(t *testing.T) {
|
||||
offline := true
|
||||
r, err := recipe.Get2("abra-test-recipe", offline)
|
||||
r, err := recipe.Get("abra-test-recipe", offline)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -36,9 +36,6 @@ var REPOS_BASE_URL = "https://git.coopcloud.tech/coop-cloud"
|
||||
var CATALOGUE_JSON_REPO_NAME = "recipes-catalogue-json"
|
||||
var SSH_URL_TEMPLATE = "ssh://git@git.coopcloud.tech:2222/coop-cloud/%s.git"
|
||||
|
||||
const MAX_SANITISED_APP_NAME_LENGTH = 45
|
||||
const MAX_DOCKER_SECRET_LENGTH = 64
|
||||
|
||||
var BackupbotLabel = "coop-cloud.backupbot.enabled"
|
||||
|
||||
// envVarModifiers is a list of env var modifier strings. These are added to
|
||||
|
@ -94,7 +94,7 @@ func TestReadEnv(t *testing.T) {
|
||||
|
||||
func TestReadAbraShEnvVars(t *testing.T) {
|
||||
offline := true
|
||||
r, err := recipe.Get2("abra-test-recipe", offline)
|
||||
r, err := recipe.Get("abra-test-recipe", offline)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -124,7 +124,7 @@ func TestReadAbraShEnvVars(t *testing.T) {
|
||||
|
||||
func TestReadAbraShCmdNames(t *testing.T) {
|
||||
offline := true
|
||||
r, err := recipe.Get2("abra-test-recipe", offline)
|
||||
r, err := recipe.Get("abra-test-recipe", offline)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -149,7 +149,7 @@ func TestReadAbraShCmdNames(t *testing.T) {
|
||||
|
||||
func TestCheckEnv(t *testing.T) {
|
||||
offline := true
|
||||
r, err := recipe.Get2("abra-test-recipe", offline)
|
||||
r, err := recipe.Get("abra-test-recipe", offline)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -183,7 +183,7 @@ func TestCheckEnv(t *testing.T) {
|
||||
|
||||
func TestCheckEnvError(t *testing.T) {
|
||||
offline := true
|
||||
r, err := recipe.Get2("abra-test-recipe", offline)
|
||||
r, err := recipe.Get("abra-test-recipe", offline)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -219,7 +219,7 @@ func TestCheckEnvError(t *testing.T) {
|
||||
|
||||
func TestEnvVarCommentsRemoved(t *testing.T) {
|
||||
offline := true
|
||||
r, err := recipe.Get2("abra-test-recipe", offline)
|
||||
r, err := recipe.Get("abra-test-recipe", offline)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -251,7 +251,7 @@ func TestEnvVarCommentsRemoved(t *testing.T) {
|
||||
|
||||
func TestEnvVarModifiersIncluded(t *testing.T) {
|
||||
offline := true
|
||||
r, err := recipe.Get2("abra-test-recipe", offline)
|
||||
r, err := recipe.Get("abra-test-recipe", offline)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/docker/docker/api/types"
|
||||
containerTypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -18,7 +17,7 @@ import (
|
||||
// count of containers does not match 1, then a prompt is presented to let the
|
||||
// user choose. A count of 0 is handled gracefully.
|
||||
func GetContainer(c context.Context, cl *client.Client, filters filters.Args, noInput bool) (types.Container, error) {
|
||||
containerOpts := containerTypes.ListOptions{Filters: filters}
|
||||
containerOpts := types.ContainerListOptions{Filters: filters}
|
||||
containers, err := cl.ContainerList(c, containerOpts)
|
||||
if err != nil {
|
||||
return types.Container{}, err
|
||||
|
@ -1,41 +1,35 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
gitPkg "github.com/go-git/go-git/v5"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Init inits a new repo and commits all the stuff if you want
|
||||
func Init(repoPath string, commit bool, gitName, gitEmail string) error {
|
||||
if _, err := git.PlainInit(repoPath, false); err != nil {
|
||||
return fmt.Errorf("git init: %s", err)
|
||||
func Init(repoPath string, commit bool) error {
|
||||
if _, err := gitPkg.PlainInit(repoPath, false); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
logrus.Debugf("initialised new git repo in %s", repoPath)
|
||||
|
||||
if commit {
|
||||
commitRepo, err := git.PlainOpen(repoPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git open: %s", err)
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
commitWorktree, err := commitRepo.Worktree()
|
||||
if err != nil {
|
||||
return fmt.Errorf("git worktree: %s", err)
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := commitWorktree.AddWithOptions(&git.AddOptions{All: true}); err != nil {
|
||||
return fmt.Errorf("git add: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
var author *object.Signature
|
||||
if gitName != "" && gitEmail != "" {
|
||||
author = &object.Signature{Name: gitName, Email: gitEmail}
|
||||
}
|
||||
if _, err = commitWorktree.Commit("init", &git.CommitOptions{Author: author}); err != nil {
|
||||
return fmt.Errorf("git commit: %s", err)
|
||||
if _, err = commitWorktree.Commit("init", &git.CommitOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Debugf("init committed all files for new git repo in %s", repoPath)
|
||||
}
|
||||
|
@ -4,9 +4,11 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"github.com/go-git/go-git/v5"
|
||||
gitConfigPkg "github.com/go-git/go-git/v5/config"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
@ -14,9 +16,11 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// GetHead retrieves latest HEAD metadata.
|
||||
func GetHead(dir string) (*plumbing.Reference, error) {
|
||||
repo, err := git.PlainOpen(dir)
|
||||
// GetRecipeHead retrieves latest HEAD metadata.
|
||||
func GetRecipeHead(recipeName string) (*plumbing.Reference, error) {
|
||||
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
|
||||
|
||||
repo, err := git.PlainOpen(recipeDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/tagcmp"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -115,13 +115,6 @@ var LintRules = map[string][]LintRule{
|
||||
HowToResolve: "upload your recipe to git.coopcloud.tech/coop-cloud/...",
|
||||
Function: LintHasRecipeRepo,
|
||||
},
|
||||
{
|
||||
Ref: "R015",
|
||||
Level: "warn",
|
||||
Description: "long secret names",
|
||||
HowToResolve: "reduce length of secret names to 12 chars",
|
||||
Function: LintSecretLengths,
|
||||
},
|
||||
},
|
||||
"error": {
|
||||
{
|
||||
@ -210,7 +203,7 @@ func LintComposeVersion(recipe recipe.Recipe) (bool, error) {
|
||||
}
|
||||
|
||||
func LintEnvConfigPresent(recipe recipe.Recipe) (bool, error) {
|
||||
envSample := fmt.Sprintf("%s/%s/.env.sample", recipe.Dir)
|
||||
envSample := fmt.Sprintf("%s/%s/.env.sample", config.RECIPES_DIR, recipe.Name)
|
||||
if _, err := os.Stat(envSample); !os.IsNotExist(err) {
|
||||
return true, nil
|
||||
}
|
||||
@ -233,7 +226,7 @@ func LintAppService(recipe recipe.Recipe) (bool, error) {
|
||||
// the recipe. This typically means that no domain is required to deploy and
|
||||
// therefore no matching traefik deploy label will be present.
|
||||
func LintTraefikEnabledSkipCondition(recipe recipe.Recipe) (bool, error) {
|
||||
envSamplePath := path.Join(recipe.Dir, ".env.sample")
|
||||
envSamplePath := path.Join(config.RECIPES_DIR, recipe.Name, ".env.sample")
|
||||
sampleEnv, err := config.ReadEnv(envSamplePath)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Unable to discover .env.sample for %s", recipe.Name)
|
||||
@ -358,7 +351,7 @@ func LintHasPublishedVersion(recipe recipe.Recipe) (bool, error) {
|
||||
}
|
||||
|
||||
func LintMetadataFilledIn(r recipe.Recipe) (bool, error) {
|
||||
features, category, err := r.GetRecipeFeaturesAndCategory()
|
||||
features, category, err := recipe.GetRecipeFeaturesAndCategory(r.Name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -408,16 +401,6 @@ func LintHasRecipeRepo(recipe recipe.Recipe) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func LintSecretLengths(recipe recipe.Recipe) (bool, error) {
|
||||
for name := range recipe.Config.Secrets {
|
||||
if len(name) > 12 {
|
||||
return false, fmt.Errorf("secret %s is longer than 12 characters", name)
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func LintValidTags(recipe recipe.Recipe) (bool, error) {
|
||||
recipeDir := path.Join(config.RECIPES_DIR, recipe.Name)
|
||||
|
||||
|
@ -3,7 +3,7 @@ package recipe
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@ -22,8 +22,8 @@ import (
|
||||
loader "coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"coopcloud.tech/abra/pkg/web"
|
||||
"coopcloud.tech/tagcmp"
|
||||
"github.com/distribution/reference"
|
||||
composetypes "github.com/docker/cli/cli/compose/types"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -130,10 +130,17 @@ type Features struct {
|
||||
SSO string `json:"sso"`
|
||||
}
|
||||
|
||||
// Recipe represents a recipe.
|
||||
type Recipe struct {
|
||||
Name string
|
||||
Config *composetypes.Config
|
||||
Meta RecipeMeta
|
||||
}
|
||||
|
||||
// Push pushes the latest changes to a SSH URL remote. You need to have your
|
||||
// local SSH configuration for git.coopcloud.tech working for this to work
|
||||
func (r Recipe) Push(dryRun bool) error {
|
||||
repo, err := git.PlainOpen(r.Dir)
|
||||
repo, err := git.PlainOpen(r.Dir())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -142,16 +149,21 @@ func (r Recipe) Push(dryRun bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gitPkg.Push(r.Dir, "origin-ssh", true, dryRun); err != nil {
|
||||
if err := gitPkg.Push(r.Dir(), "origin-ssh", true, dryRun); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dir retrieves the recipe repository path
|
||||
func (r Recipe) Dir() string {
|
||||
return path.Join(config.RECIPES_DIR, r.Name)
|
||||
}
|
||||
|
||||
// UpdateLabel updates a recipe label
|
||||
func (r Recipe) UpdateLabel(pattern, serviceName, label string) error {
|
||||
fullPattern := fmt.Sprintf("%s/%s", r.Dir, pattern)
|
||||
fullPattern := fmt.Sprintf("%s/%s/%s", config.RECIPES_DIR, r.Name, pattern)
|
||||
if err := compose.UpdateLabel(fullPattern, serviceName, label, r.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -160,7 +172,7 @@ func (r Recipe) UpdateLabel(pattern, serviceName, label string) error {
|
||||
|
||||
// UpdateTag updates a recipe tag
|
||||
func (r Recipe) UpdateTag(image, tag string) (bool, error) {
|
||||
pattern := fmt.Sprintf("%s/compose**yml", r.Dir)
|
||||
pattern := fmt.Sprintf("%s/%s/compose**yml", config.RECIPES_DIR, r.Name)
|
||||
|
||||
image = formatter.StripTagMeta(image)
|
||||
|
||||
@ -176,7 +188,7 @@ func (r Recipe) UpdateTag(image, tag string) (bool, error) {
|
||||
func (r Recipe) Tags() ([]string, error) {
|
||||
var tags []string
|
||||
|
||||
repo, err := git.PlainOpen(r.Dir)
|
||||
repo, err := git.PlainOpen(r.Dir())
|
||||
if err != nil {
|
||||
return tags, err
|
||||
}
|
||||
@ -198,51 +210,49 @@ func (r Recipe) Tags() ([]string, error) {
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// // Get2 retrieves a recipe.
|
||||
// func (r Recipe) Load(offline bool) (Recipe2, error) {
|
||||
//
|
||||
// meta, err := r.GetRecipeMeta(offline)
|
||||
// if err != nil {
|
||||
// switch err.(type) {
|
||||
// case RecipeMissingFromCatalogue:
|
||||
// meta = RecipeMeta{}
|
||||
// default:
|
||||
// return Recipe2{}, err
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return Recipe2{
|
||||
// Name: r.Name,
|
||||
// Config: config,
|
||||
// Meta: meta,
|
||||
// }, nil
|
||||
// }
|
||||
|
||||
func (r Recipe) LoadConfig() error {
|
||||
if err := r.EnsureExists(); err != nil {
|
||||
return err
|
||||
// Get retrieves a recipe.
|
||||
func Get(recipeName string, offline bool) (Recipe, error) {
|
||||
if err := EnsureExists(recipeName); err != nil {
|
||||
return Recipe{}, err
|
||||
}
|
||||
|
||||
pattern := fmt.Sprintf("%s/compose**yml", r.Dir)
|
||||
pattern := fmt.Sprintf("%s/%s/compose**yml", config.RECIPES_DIR, recipeName)
|
||||
composeFiles, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
return err
|
||||
return Recipe{}, err
|
||||
}
|
||||
|
||||
if len(composeFiles) == 0 {
|
||||
return fmt.Errorf("%s is missing a compose.yml or compose.*.yml file?", r.Name)
|
||||
return Recipe{}, fmt.Errorf("%s is missing a compose.yml or compose.*.yml file?", recipeName)
|
||||
}
|
||||
opts := stack.Deploy{Composefiles: composeFiles}
|
||||
sampleEnv, err := r.SampleEnv()
|
||||
|
||||
envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample")
|
||||
sampleEnv, err := config.ReadEnv(envSamplePath)
|
||||
if err != nil {
|
||||
return err
|
||||
return Recipe{}, err
|
||||
}
|
||||
|
||||
opts := stack.Deploy{Composefiles: composeFiles}
|
||||
config, err := loader.LoadComposefile(opts, sampleEnv)
|
||||
if err != nil {
|
||||
return err
|
||||
return Recipe{}, err
|
||||
}
|
||||
r.Config = config
|
||||
return nil
|
||||
|
||||
meta, err := GetRecipeMeta(recipeName, offline)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case RecipeMissingFromCatalogue:
|
||||
meta = RecipeMeta{}
|
||||
default:
|
||||
return Recipe{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return Recipe{
|
||||
Name: recipeName,
|
||||
Config: config,
|
||||
Meta: meta,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r Recipe) SampleEnv() (map[string]string, error) {
|
||||
@ -254,71 +264,33 @@ func (r Recipe) SampleEnv() (map[string]string, error) {
|
||||
return sampleEnv, nil
|
||||
}
|
||||
|
||||
type Recipe struct {
|
||||
Name string
|
||||
NameEscaped string
|
||||
Dir string
|
||||
GitURL string
|
||||
|
||||
Config *composetypes.Config
|
||||
Meta RecipeMeta
|
||||
}
|
||||
|
||||
func Get(recipeName string) (Recipe, error) {
|
||||
if !strings.Contains(recipeName, "/") {
|
||||
return Recipe{
|
||||
Name: recipeName,
|
||||
GitURL: fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, recipeName),
|
||||
}, nil
|
||||
}
|
||||
|
||||
u, err := url.Parse(recipeName)
|
||||
if err != nil {
|
||||
return Recipe{}, err
|
||||
}
|
||||
u.Scheme = "https"
|
||||
u.RawPath, err = url.JoinPath(u.RawPath, ".git")
|
||||
if err != nil {
|
||||
return Recipe{}, err
|
||||
}
|
||||
return Recipe{
|
||||
Name: recipeName,
|
||||
NameEscaped: escapeRecipeName(recipeName),
|
||||
Dir: path.Join(config.RECIPES_DIR, escapeRecipeName(recipeName)),
|
||||
GitURL: u.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func escapeRecipeName(recipeName string) string {
|
||||
recipeName = strings.ReplaceAll(recipeName, "/", "_")
|
||||
recipeName = strings.ReplaceAll(recipeName, ".", "_")
|
||||
return recipeName
|
||||
}
|
||||
|
||||
// Ensure makes sure the recipe exists, is up to date and has the latest version checked out.
|
||||
func (r Recipe) Ensure() error {
|
||||
if err := r.EnsureExists(); err != nil {
|
||||
func Ensure(recipeName string) error {
|
||||
if err := EnsureExists(recipeName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.EnsureUpToDate(); err != nil {
|
||||
if err := EnsureUpToDate(recipeName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.EnsureLatest(); err != nil {
|
||||
if err := EnsureLatest(recipeName); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnsureExists ensures that the recipe is locally cloned
|
||||
func (r Recipe) EnsureExists() error {
|
||||
if _, err := os.Stat(r.Dir); os.IsNotExist(err) {
|
||||
logrus.Debugf("%s does not exist, attemmpting to clone", r.Dir)
|
||||
if err := gitPkg.Clone(r.Dir, r.GitURL); err != nil {
|
||||
// EnsureExists ensures that a recipe is locally cloned
|
||||
func EnsureExists(recipeName string) error {
|
||||
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
|
||||
|
||||
if _, err := os.Stat(recipeDir); os.IsNotExist(err) {
|
||||
logrus.Debugf("%s does not exist, attemmpting to clone", recipeDir)
|
||||
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, recipeName)
|
||||
if err := gitPkg.Clone(recipeDir, url); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := gitPkg.EnsureGitRepo(r.Dir); err != nil {
|
||||
if err := gitPkg.EnsureGitRepo(recipeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -326,12 +298,14 @@ func (r Recipe) EnsureExists() error {
|
||||
}
|
||||
|
||||
// EnsureVersion checks whether a specific version exists for a recipe.
|
||||
func (r Recipe) EnsureVersion(version string) error {
|
||||
if err := gitPkg.EnsureGitRepo(r.Dir); err != nil {
|
||||
func EnsureVersion(recipeName, version string) error {
|
||||
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
|
||||
|
||||
if err := gitPkg.EnsureGitRepo(recipeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repo, err := git.PlainOpen(r.Dir)
|
||||
repo, err := git.PlainOpen(recipeDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -355,11 +329,11 @@ func (r Recipe) EnsureVersion(version string) error {
|
||||
|
||||
joinedTags := strings.Join(parsedTags, ", ")
|
||||
if joinedTags != "" {
|
||||
logrus.Debugf("read %s as tags for recipe %s", joinedTags, r.Name)
|
||||
logrus.Debugf("read %s as tags for recipe %s", joinedTags, recipeName)
|
||||
}
|
||||
|
||||
if tagRef.String() == "" {
|
||||
return fmt.Errorf("the local copy of %s doesn't seem to have version %s available?", r.Name, version)
|
||||
return fmt.Errorf("the local copy of %s doesn't seem to have version %s available?", recipeName, version)
|
||||
}
|
||||
|
||||
worktree, err := repo.Worktree()
|
||||
@ -376,33 +350,37 @@ func (r Recipe) EnsureVersion(version string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Debugf("successfully checked %s out to %s in %s", r.Name, tagRef.Short(), r.Dir)
|
||||
logrus.Debugf("successfully checked %s out to %s in %s", recipeName, tagRef.Short(), recipeDir)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnsureIsClean makes sure that the recipe repository has no unstaged changes.
|
||||
func (r Recipe) EnsureIsClean() error {
|
||||
isClean, err := gitPkg.IsClean(r.Dir)
|
||||
func EnsureIsClean(recipeName string) error {
|
||||
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
|
||||
|
||||
isClean, err := gitPkg.IsClean(recipeDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to check git clean status in %s: %s", r.Dir, err)
|
||||
return fmt.Errorf("unable to check git clean status in %s: %s", recipeDir, err)
|
||||
}
|
||||
|
||||
if !isClean {
|
||||
msg := "%s (%s) has locally unstaged changes? please commit/remove your changes before proceeding"
|
||||
return fmt.Errorf(msg, r.Name, r.Dir)
|
||||
return fmt.Errorf(msg, recipeName, recipeDir)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnsureLatest makes sure the latest commit is checked out for the local recipe repository
|
||||
func (r Recipe) EnsureLatest() error {
|
||||
if err := gitPkg.EnsureGitRepo(r.Dir); err != nil {
|
||||
// EnsureLatest makes sure the latest commit is checked out for a local recipe repository
|
||||
func EnsureLatest(recipeName string) error {
|
||||
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
|
||||
|
||||
if err := gitPkg.EnsureGitRepo(recipeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repo, err := git.PlainOpen(r.Dir)
|
||||
repo, err := git.PlainOpen(recipeDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -412,7 +390,7 @@ func (r Recipe) EnsureLatest() error {
|
||||
return err
|
||||
}
|
||||
|
||||
branch, err := gitPkg.GetDefaultBranch(repo, r.Dir)
|
||||
branch, err := gitPkg.GetDefaultBranch(repo, recipeDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -424,7 +402,7 @@ func (r Recipe) EnsureLatest() error {
|
||||
}
|
||||
|
||||
if err := worktree.Checkout(checkOutOpts); err != nil {
|
||||
logrus.Debugf("failed to check out %s in %s", branch, r.Dir)
|
||||
logrus.Debugf("failed to check out %s in %s", branch, recipeDir)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -432,17 +410,18 @@ func (r Recipe) EnsureLatest() error {
|
||||
}
|
||||
|
||||
// ChaosVersion constructs a chaos mode recipe version.
|
||||
func (r Recipe) ChaosVersion() (string, error) {
|
||||
func ChaosVersion(recipeName string) (string, error) {
|
||||
var version string
|
||||
|
||||
head, err := gitPkg.GetHead(r.Dir)
|
||||
head, err := gitPkg.GetRecipeHead(recipeName)
|
||||
if err != nil {
|
||||
return version, err
|
||||
}
|
||||
|
||||
version = formatter.SmallSHA(head.String())
|
||||
|
||||
isClean, err := gitPkg.IsClean(r.Dir)
|
||||
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
|
||||
isClean, err := gitPkg.IsClean(recipeDir)
|
||||
if err != nil {
|
||||
return version, err
|
||||
}
|
||||
@ -485,22 +464,22 @@ func GetVersionLabelLocal(recipe Recipe) (string, error) {
|
||||
return label, nil
|
||||
}
|
||||
|
||||
func (r Recipe) GetRecipeFeaturesAndCategory() (Features, string, error) {
|
||||
func GetRecipeFeaturesAndCategory(recipeName string) (Features, string, error) {
|
||||
feat := Features{}
|
||||
|
||||
var category string
|
||||
|
||||
readmePath := path.Join(r.Dir, "README.md")
|
||||
readmePath := path.Join(config.RECIPES_DIR, recipeName, "README.md")
|
||||
|
||||
logrus.Debugf("attempting to open %s for recipe metadata parsing", readmePath)
|
||||
|
||||
readmeFS, err := os.ReadFile(readmePath)
|
||||
readmeFS, err := ioutil.ReadFile(readmePath)
|
||||
if err != nil {
|
||||
return feat, category, err
|
||||
}
|
||||
|
||||
readmeMetadata, err := GetStringInBetween( // Find text between delimiters
|
||||
r.Name,
|
||||
recipeName,
|
||||
string(readmeFS),
|
||||
"<!-- metadata -->", "<!-- endmetadata -->",
|
||||
)
|
||||
@ -551,7 +530,7 @@ func (r Recipe) GetRecipeFeaturesAndCategory() (Features, string, error) {
|
||||
if strings.Contains(val, "**Image**") {
|
||||
imageMetadata, err := GetImageMetadata(strings.TrimSpace(
|
||||
strings.TrimPrefix(val, "* **Image**:"),
|
||||
), r.Name)
|
||||
), recipeName)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@ -618,36 +597,38 @@ func GetStringInBetween(recipeName, str, start, end string) (result string, err
|
||||
}
|
||||
|
||||
// EnsureUpToDate ensures that the local repo is synced to the remote
|
||||
func (r Recipe) EnsureUpToDate() error {
|
||||
repo, err := git.PlainOpen(r.Dir)
|
||||
func EnsureUpToDate(recipeName string) error {
|
||||
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
|
||||
|
||||
repo, err := git.PlainOpen(recipeDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to open %s: %s", r.Dir, err)
|
||||
return fmt.Errorf("unable to open %s: %s", recipeDir, err)
|
||||
}
|
||||
|
||||
remotes, err := repo.Remotes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read remotes in %s: %s", r.Dir, err)
|
||||
return fmt.Errorf("unable to read remotes in %s: %s", recipeDir, err)
|
||||
}
|
||||
|
||||
if len(remotes) == 0 {
|
||||
logrus.Debugf("cannot ensure %s is up-to-date, no git remotes configured", r.Name)
|
||||
logrus.Debugf("cannot ensure %s is up-to-date, no git remotes configured", recipeName)
|
||||
return nil
|
||||
}
|
||||
|
||||
worktree, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to open git work tree in %s: %s", r.Dir, err)
|
||||
return fmt.Errorf("unable to open git work tree in %s: %s", recipeDir, err)
|
||||
}
|
||||
|
||||
branch, err := gitPkg.CheckoutDefaultBranch(repo, r.Dir)
|
||||
branch, err := gitPkg.CheckoutDefaultBranch(repo, recipeDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to check out default branch in %s: %s", r.Dir, err)
|
||||
return fmt.Errorf("unable to check out default branch in %s: %s", recipeDir, err)
|
||||
}
|
||||
|
||||
fetchOpts := &git.FetchOptions{Tags: git.AllTags}
|
||||
if err := repo.Fetch(fetchOpts); err != nil {
|
||||
if !strings.Contains(err.Error(), "already up-to-date") {
|
||||
return fmt.Errorf("unable to fetch tags in %s: %s", r.Dir, err)
|
||||
return fmt.Errorf("unable to fetch tags in %s: %s", recipeDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -659,11 +640,11 @@ func (r Recipe) EnsureUpToDate() error {
|
||||
|
||||
if err := worktree.Pull(opts); err != nil {
|
||||
if !strings.Contains(err.Error(), "already up-to-date") {
|
||||
return fmt.Errorf("unable to git pull in %s: %s", r.Dir, err)
|
||||
return fmt.Errorf("unable to git pull in %s: %s", recipeDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Debugf("fetched latest git changes for %s", r.Name)
|
||||
logrus.Debugf("fetched latest git changes for %s", recipeName)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -691,7 +672,7 @@ func ReadRecipeCatalogue(offline bool) (RecipeCatalogue, error) {
|
||||
|
||||
// readRecipeCatalogueFS reads the catalogue from the file system.
|
||||
func readRecipeCatalogueFS(target interface{}) error {
|
||||
recipesJSONFS, err := os.ReadFile(config.RECIPES_JSON)
|
||||
recipesJSONFS, err := ioutil.ReadFile(config.RECIPES_JSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -743,20 +724,20 @@ func (r RecipeMissingFromCatalogue) Error() string {
|
||||
}
|
||||
|
||||
// GetRecipeMeta retrieves the recipe metadata from the recipe catalogue.
|
||||
func (r Recipe) GetRecipeMeta(offline bool) (RecipeMeta, error) {
|
||||
func GetRecipeMeta(recipeName string, offline bool) (RecipeMeta, error) {
|
||||
catl, err := ReadRecipeCatalogue(offline)
|
||||
if err != nil {
|
||||
return RecipeMeta{}, err
|
||||
}
|
||||
|
||||
recipeMeta, ok := catl[r.Name]
|
||||
recipeMeta, ok := catl[recipeName]
|
||||
if !ok {
|
||||
return RecipeMeta{}, RecipeMissingFromCatalogue{
|
||||
err: fmt.Sprintf("recipe %s does not exist?", r.Name),
|
||||
err: fmt.Sprintf("recipe %s does not exist?", recipeName),
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Debugf("recipe metadata retrieved for %s", r.Name)
|
||||
logrus.Debugf("recipe metadata retrieved for %s", recipeName)
|
||||
|
||||
return recipeMeta, nil
|
||||
}
|
||||
@ -883,12 +864,13 @@ func ReadReposMetadata() (RepoCatalogue, error) {
|
||||
}
|
||||
|
||||
// GetRecipeVersions retrieves all recipe versions.
|
||||
func (r Recipe) GetVersions(offline bool) (RecipeVersions, error) {
|
||||
func GetRecipeVersions(recipeName string, offline bool) (RecipeVersions, error) {
|
||||
versions := RecipeVersions{}
|
||||
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
|
||||
|
||||
logrus.Debugf("attempting to open git repository in %s", r.Dir)
|
||||
logrus.Debugf("attempting to open git repository in %s", recipeDir)
|
||||
|
||||
repo, err := git.PlainOpen(r.Dir)
|
||||
repo, err := git.PlainOpen(recipeDir)
|
||||
if err != nil {
|
||||
return versions, err
|
||||
}
|
||||
@ -906,7 +888,7 @@ func (r Recipe) GetVersions(offline bool) (RecipeVersions, error) {
|
||||
if err := gitTags.ForEach(func(ref *plumbing.Reference) (err error) {
|
||||
tag := strings.TrimPrefix(string(ref.Name()), "refs/tags/")
|
||||
|
||||
logrus.Debugf("processing %s for %s", tag, r.Name)
|
||||
logrus.Debugf("processing %s for %s", tag, recipeName)
|
||||
|
||||
checkOutOpts := &git.CheckoutOptions{
|
||||
Create: false,
|
||||
@ -914,18 +896,19 @@ func (r Recipe) GetVersions(offline bool) (RecipeVersions, error) {
|
||||
Branch: plumbing.ReferenceName(ref.Name()),
|
||||
}
|
||||
if err := worktree.Checkout(checkOutOpts); err != nil {
|
||||
logrus.Debugf("failed to check out %s in %s", tag, r.Dir)
|
||||
logrus.Debugf("failed to check out %s in %s", tag, recipeDir)
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Debugf("successfully checked out %s in %s", ref.Name(), r.Dir)
|
||||
logrus.Debugf("successfully checked out %s in %s", ref.Name(), recipeDir)
|
||||
|
||||
if err := r.LoadConfig(); err != nil {
|
||||
recipe, err := Get(recipeName, offline)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
versionMeta := make(map[string]ServiceMeta)
|
||||
for _, service := range r.Config.Services {
|
||||
for _, service := range recipe.Config.Services {
|
||||
|
||||
img, err := reference.ParseNormalizedNamed(service.Image)
|
||||
if err != nil {
|
||||
@ -958,14 +941,13 @@ func (r Recipe) GetVersions(offline bool) (RecipeVersions, error) {
|
||||
return versions, err
|
||||
}
|
||||
|
||||
_, err = gitPkg.CheckoutDefaultBranch(repo, r.Dir)
|
||||
_, err = gitPkg.CheckoutDefaultBranch(repo, recipeDir)
|
||||
if err != nil {
|
||||
return versions, err
|
||||
}
|
||||
|
||||
sortRecipeVersions(versions)
|
||||
|
||||
logrus.Debugf("collected %s for %s", versions, r.Name)
|
||||
logrus.Debugf("collected %s for %s", versions, recipeName)
|
||||
|
||||
return versions, nil
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
|
||||
func TestGetVersionLabelLocalDoesNotUseTimeoutLabel(t *testing.T) {
|
||||
offline := true
|
||||
recipe, err := Get2("traefik", offline)
|
||||
recipe, err := Get("traefik", offline)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
|
||||
appEnv["STACK_NAME"] = stackName
|
||||
|
||||
opts := stack.Deploy{Composefiles: composeFiles}
|
||||
composeConfig, err := loader.LoadComposefile(opts, appEnv)
|
||||
config, err := loader.LoadComposefile(opts, appEnv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -100,7 +100,7 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
|
||||
}
|
||||
|
||||
var enabledSecrets []string
|
||||
for _, service := range composeConfig.Services {
|
||||
for _, service := range config.Services {
|
||||
for _, secret := range service.Secrets {
|
||||
enabledSecrets = append(enabledSecrets, secret.Source)
|
||||
}
|
||||
@ -112,7 +112,7 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
|
||||
}
|
||||
|
||||
secretValues := map[string]Secret{}
|
||||
for secretId, secretConfig := range composeConfig.Secrets {
|
||||
for secretId, secretConfig := range config.Secrets {
|
||||
if string(secretConfig.Name[len(secretConfig.Name)-1]) == "_" {
|
||||
return nil, fmt.Errorf("missing version for secret? (%s)", secretId)
|
||||
}
|
||||
@ -126,10 +126,6 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
|
||||
secretVersion := secretConfig.Name[lastIdx+1:]
|
||||
value := Secret{Version: secretVersion, RemoteName: secretConfig.Name}
|
||||
|
||||
if len(value.RemoteName) > config.MAX_DOCKER_SECRET_LENGTH {
|
||||
return nil, fmt.Errorf("secret %s is > %d chars when combined with %s", secretId, config.MAX_DOCKER_SECRET_LENGTH, stackName)
|
||||
}
|
||||
|
||||
// Check if the length modifier is set for this secret.
|
||||
for envName, modifierValues := range appModifiers {
|
||||
// configWithoutEnv contains the raw name as defined in the compose.yaml
|
||||
|
@ -28,12 +28,3 @@ func TestReadSecretsConfig(t *testing.T) {
|
||||
assert.Equal(t, "v2", secretsFromConfig["test_pass_three"].Version)
|
||||
assert.Equal(t, 0, secretsFromConfig["test_pass_three"].Length)
|
||||
}
|
||||
|
||||
func TestReadSecretsConfigWithLongDomain(t *testing.T) {
|
||||
composeFiles := []string{"./testdir/compose.yaml"}
|
||||
_, err := ReadSecretsConfig("./testdir/.env.sample", composeFiles, "should_break_on_forty_eight_char_stack_nameeeeee")
|
||||
if err == nil {
|
||||
t.Fatal("expected failure, stack name is too long")
|
||||
}
|
||||
assert.Contains(t, err.Error(), "is > 64 chars")
|
||||
}
|
||||
|
@ -2,14 +2,73 @@ package ssh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// HostConfig is a SSH host config.
|
||||
type HostConfig struct {
|
||||
Host string
|
||||
IdentityFile string
|
||||
Port string
|
||||
User string
|
||||
}
|
||||
|
||||
// String presents a human friendly output for the HostConfig.
|
||||
func (h HostConfig) String() string {
|
||||
return fmt.Sprintf(
|
||||
"{host: %s, username: %s, port: %s, identityfile: %s}",
|
||||
h.Host,
|
||||
h.User,
|
||||
h.Port,
|
||||
h.IdentityFile,
|
||||
)
|
||||
}
|
||||
|
||||
// GetHostConfig retrieves a ~/.ssh/config config for a host using /usr/bin/ssh
|
||||
// directly. We therefore maintain consistent interop with this standard
|
||||
// tooling. This is useful because SSH confuses a lot of people and having to
|
||||
// learn how two tools (`ssh` and `abra`) handle SSH connection details instead
|
||||
// of one (just `ssh`) is Not Cool. Here's to less bug reports on this topic!
|
||||
func GetHostConfig(hostname string) (HostConfig, error) {
|
||||
var hostConfig HostConfig
|
||||
|
||||
out, err := exec.Command("ssh", "-G", hostname).Output()
|
||||
if err != nil {
|
||||
return hostConfig, err
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
entries := strings.Split(line, " ")
|
||||
for idx, entry := range entries {
|
||||
if entry == "hostname" {
|
||||
hostConfig.Host = entries[idx+1]
|
||||
}
|
||||
if entry == "user" {
|
||||
hostConfig.User = entries[idx+1]
|
||||
}
|
||||
if entry == "port" {
|
||||
hostConfig.Port = entries[idx+1]
|
||||
}
|
||||
if entry == "identityfile" {
|
||||
if hostConfig.IdentityFile == "" {
|
||||
hostConfig.IdentityFile = entries[idx+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Debugf("retrieved ssh config for %s: %s", hostname, hostConfig.String())
|
||||
|
||||
return hostConfig, nil
|
||||
}
|
||||
|
||||
// Fatal is a error output wrapper which aims to make SSH failures easier to
|
||||
// parse through re-wording.
|
||||
func Fatal(hostname string, err error) error {
|
||||
out := err.Error()
|
||||
|
||||
if strings.Contains(out, "Host key verification failed.") {
|
||||
return fmt.Errorf("SSH host key verification failed for %s", hostname)
|
||||
} else if strings.Contains(out, "Could not resolve hostname") {
|
||||
@ -20,7 +79,7 @@ func Fatal(hostname string, err error) error {
|
||||
return fmt.Errorf("ssh auth: permission denied for %s", hostname)
|
||||
} else if strings.Contains(out, "Network is unreachable") {
|
||||
return fmt.Errorf("unable to connect to %s, network is unreachable?", hostname)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
@ -16,12 +16,12 @@ import (
|
||||
// GetConnectionHelper returns Docker-specific connection helper for the given URL.
|
||||
// GetConnectionHelper returns nil without error when no helper is registered for the scheme.
|
||||
//
|
||||
// ssh://<host> URL requires Docker 18.09 or later on the remote host.
|
||||
// ssh://<user>@<host> URL requires Docker 18.09 or later on the remote host.
|
||||
func GetConnectionHelper(daemonURL string) (*connhelper.ConnectionHelper, error) {
|
||||
return getConnectionHelper(daemonURL)
|
||||
return getConnectionHelper(daemonURL, []string{"-o ConnectTimeout=60"})
|
||||
}
|
||||
|
||||
func getConnectionHelper(daemonURL string) (*connhelper.ConnectionHelper, error) {
|
||||
func getConnectionHelper(daemonURL string, sshFlags []string) (*connhelper.ConnectionHelper, error) {
|
||||
url, err := url.Parse(daemonURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -35,7 +35,7 @@ func getConnectionHelper(daemonURL string) (*connhelper.ConnectionHelper, error)
|
||||
|
||||
return &connhelper.ConnectionHelper{
|
||||
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return New(ctx, "ssh", ctxConnDetails.Args("docker", "system", "dial-stdio")...)
|
||||
return New(ctx, "ssh", append(sshFlags, ctxConnDetails.Args("docker", "system", "dial-stdio")...)...)
|
||||
},
|
||||
Host: "http://docker.example.com",
|
||||
}, nil
|
||||
@ -45,7 +45,6 @@ func getConnectionHelper(daemonURL string) (*connhelper.ConnectionHelper, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// NewConnectionHelper creates new connection helper for a remote docker daemon.
|
||||
func NewConnectionHelper(daemonURL string) (*connhelper.ConnectionHelper, error) {
|
||||
helper, err := GetConnectionHelper(daemonURL)
|
||||
if err != nil {
|
||||
@ -74,7 +73,6 @@ func getDockerEndpoint(host string) (docker.Endpoint, error) {
|
||||
return ep, nil
|
||||
}
|
||||
|
||||
// GetDockerEndpointMetadataAndTLS retrieves the docker endpoint and TLS info for a remote host.
|
||||
func GetDockerEndpointMetadataAndTLS(host string) (docker.EndpointMeta, *dCliContextStore.EndpointTLSData, error) {
|
||||
ep, err := getDockerEndpoint(host)
|
||||
if err != nil {
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
apiclient "github.com/docker/docker/client"
|
||||
"github.com/moby/sys/signal"
|
||||
@ -22,7 +22,7 @@ func resizeTtyTo(ctx context.Context, client client.ContainerAPIClient, id strin
|
||||
return nil
|
||||
}
|
||||
|
||||
options := container.ResizeOptions{
|
||||
options := types.ResizeOptions{
|
||||
Height: height,
|
||||
Width: width,
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ func validateExternalNetworks(ctx context.Context, client dockerClient.NetworkAP
|
||||
network, err := client.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{})
|
||||
switch {
|
||||
case dockerClient.IsErrNotFound(err):
|
||||
return errors.Errorf("network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed, which you can do by running this on the server: docker network create -d overlay proxy", networkName)
|
||||
return errors.Errorf("network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed", networkName)
|
||||
case err != nil:
|
||||
return err
|
||||
case network.Scope != "swarm":
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ABRA_VERSION="0.9.0-beta"
|
||||
ABRA_VERSION="0.8.1-beta"
|
||||
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION"
|
||||
RC_VERSION="0.8.0-rc1-beta"
|
||||
RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$RC_VERSION"
|
||||
@ -45,9 +45,7 @@ function install_abra_release {
|
||||
fi
|
||||
|
||||
ARCH=$(uname -m)
|
||||
if [[ $ARCH =~ "x86_64" ]]; then
|
||||
ARCH="amd64"
|
||||
elif [[ $ARCH =~ "aarch64" ]]; then
|
||||
if [[ $ARCH =~ "aarch64" ]]; then
|
||||
ARCH="arm64"
|
||||
elif [[ $ARCH =~ "armv5l" ]]; then
|
||||
ARCH="armv5"
|
||||
@ -57,7 +55,7 @@ function install_abra_release {
|
||||
ARCH="armv7"
|
||||
fi
|
||||
PLATFORM=$(uname -s | tr '[:upper:]' '[:lower:]')_$ARCH
|
||||
FILENAME="abra_"$ABRA_VERSION"_"$PLATFORM".tar.gz"
|
||||
FILENAME="abra_"$ABRA_VERSION"_"$PLATFORM""
|
||||
sed_command_rel='s/.*"assets":\[\{[^]]*"name":"'$FILENAME'"[^}]*"browser_download_url":"([^"]*)".*\].*/\1/p'
|
||||
sed_command_checksums='s/.*"assets":\[\{[^\]*"name":"checksums.txt"[^}]*"browser_download_url":"([^"]*)".*\].*/\1/p'
|
||||
|
||||
@ -67,7 +65,7 @@ function install_abra_release {
|
||||
|
||||
checksums=$(wget -q -O- $checksums_url)
|
||||
checksum=$(echo "$checksums" | grep "$FILENAME" - | sed -En 's/([0-9a-f]{64})\s+'"$FILENAME"'.*/\1/p')
|
||||
abra_download="/tmp/abra-download.tar.gz"
|
||||
abra_download="/tmp/abra-download"
|
||||
|
||||
echo "downloading $ABRA_VERSION $PLATFORM binary release for abra..."
|
||||
|
||||
@ -79,10 +77,7 @@ function install_abra_release {
|
||||
exit 1
|
||||
fi
|
||||
echo "$(tput setaf 2)check successful!$(tput sgr0)"
|
||||
cd /tmp/
|
||||
tar xf abra-download.tar.gz
|
||||
mv abra "$HOME/.local/bin/abra"
|
||||
tar tf abra-download.tar.gz | xargs rm -f
|
||||
mv "$abra_download" "$HOME/.local/bin/abra"
|
||||
chmod +x "$HOME/.local/bin/abra"
|
||||
|
||||
x=$(echo $PATH | grep $HOME/.local/bin)
|
||||
|
@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
setup_file(){
|
||||
load "$PWD/tests/integration/helpers/git"
|
||||
load "$PWD/tests/integration/helpers/common"
|
||||
_common_setup
|
||||
_add_server
|
||||
@ -363,7 +362,6 @@ teardown(){
|
||||
_reset_app
|
||||
}
|
||||
|
||||
# bats test_tags=slow
|
||||
@test "recipe config comments not present in values" {
|
||||
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input
|
||||
assert_success
|
||||
@ -372,36 +370,3 @@ teardown(){
|
||||
assert_success
|
||||
refute_output --partial 'should be removed'
|
||||
}
|
||||
|
||||
# bats test_tags=slow
|
||||
@test "deploy specific version with incompatible HEAD" {
|
||||
run sed -i 's/COMPOSE_FILE="compose.yml"/COMPOSE_FILE="compose.yml:compose.extra_secret.yml"/g' \
|
||||
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||
assert_success
|
||||
|
||||
run sed -i 's/#SECRET_EXTRA_PASS_VERSION=v1/SECRET_EXTRA_PASS_VERSION=v1/g' \
|
||||
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||
assert_success
|
||||
|
||||
run $ABRA app secret generate "$TEST_APP_DOMAIN" --all
|
||||
assert_success
|
||||
assert_output --partial 'extra_pass'
|
||||
|
||||
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/compose.extra_secret.yml"
|
||||
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/compose.extra_secret.yml"
|
||||
|
||||
_git_commit
|
||||
|
||||
# NOTE(d1): 0.1.1+1.20.2 is a previous version which includes compose.extra_secret.yml
|
||||
run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks
|
||||
assert_success
|
||||
refute_output --partial 'no such file or directory'
|
||||
|
||||
_undeploy_app
|
||||
_reset_app
|
||||
|
||||
run $ABRA app secret rm "$TEST_APP_DOMAIN" --all
|
||||
assert_success
|
||||
|
||||
_reset_recipe
|
||||
}
|
||||
|
@ -13,7 +13,6 @@ teardown_file(){
|
||||
|
||||
setup(){
|
||||
load "$PWD/tests/integration/helpers/common"
|
||||
load "$PWD/tests/integration/helpers/git"
|
||||
_common_setup
|
||||
_fetch_recipe
|
||||
}
|
||||
@ -27,6 +26,14 @@ teardown(){
|
||||
run $ABRA app new --generate-bash-completion
|
||||
assert_success
|
||||
assert_output --partial "traefik"
|
||||
assert_output --partial "abra-test-recipe"
|
||||
|
||||
# Note: this test needs to be updated when a new version of the test recipe is published.
|
||||
run $ABRA app new abra-test-recipe --generate-bash-completion
|
||||
assert_success
|
||||
assert_output "0.1.0+1.20.0
|
||||
0.1.1+1.20.2
|
||||
0.2.0+1.21.0"
|
||||
}
|
||||
|
||||
@test "create new app" {
|
||||
@ -37,9 +44,8 @@ teardown(){
|
||||
assert_success
|
||||
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||
|
||||
_get_head_hash
|
||||
_get_current_hash
|
||||
assert_equal "$headHash" "$currentHash"
|
||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
||||
assert_output --partial "up to date"
|
||||
}
|
||||
|
||||
@test "create new app with version" {
|
||||
@ -50,7 +56,8 @@ teardown(){
|
||||
assert_success
|
||||
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||
|
||||
assert_equal $(_get_tag_hash 0.1.1+1.20.2) $(_get_current_hash)
|
||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" log -1
|
||||
assert_output --partial "453db7121c0a56a7a8f15378f18fe3bf21ccfdef"
|
||||
}
|
||||
|
||||
@test "does not overwrite existing env files" {
|
||||
@ -110,13 +117,11 @@ teardown(){
|
||||
}
|
||||
|
||||
@test "ensure recipe up to date if no --offline" {
|
||||
_reset_recipe
|
||||
wantHash=$(_get_n_hash 3)
|
||||
|
||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3
|
||||
assert_success
|
||||
|
||||
assert_equal $(_get_current_hash) "$wantHash"
|
||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
||||
assert_output --regexp 'behind .* 3 commits'
|
||||
|
||||
run $ABRA app new "$TEST_RECIPE" \
|
||||
--no-input \
|
||||
@ -125,19 +130,18 @@ teardown(){
|
||||
assert_success
|
||||
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||
|
||||
assert_equal $(_get_head_hash) $(_get_current_hash)
|
||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
||||
assert_output --partial "up to date"
|
||||
|
||||
_reset_recipe
|
||||
}
|
||||
|
||||
@test "ensure recipe not up to date if --offline" {
|
||||
_reset_recipe
|
||||
wantHash=$(_get_n_hash 3)
|
||||
|
||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3
|
||||
assert_success
|
||||
|
||||
assert_equal $(_get_current_hash) "$wantHash"
|
||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
||||
assert_output --regexp 'behind .* 3 commits'
|
||||
|
||||
# NOTE(d1): need to use --chaos to force same commit
|
||||
run $ABRA app new "$TEST_RECIPE" \
|
||||
@ -149,12 +153,12 @@ teardown(){
|
||||
assert_success
|
||||
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||
|
||||
assert_equal $(_get_current_hash) "$wantHash"
|
||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" status
|
||||
assert_output --regexp 'behind .* 3 commits'
|
||||
|
||||
_reset_recipe
|
||||
}
|
||||
|
||||
# bats test_tags=slow
|
||||
@test "generate secrets" {
|
||||
run $ABRA app new "$TEST_RECIPE" \
|
||||
--no-input \
|
||||
|
@ -104,6 +104,9 @@ teardown(){
|
||||
|
||||
_undeploy_app
|
||||
|
||||
# TODO: should wait as long as volume is no longer in use
|
||||
sleep 10
|
||||
|
||||
run $ABRA app volume rm "$TEST_APP_DOMAIN" --no-input
|
||||
assert_success
|
||||
|
||||
|
@ -19,13 +19,6 @@ teardown_file(){
|
||||
_reset_recipe
|
||||
}
|
||||
|
||||
teardown() {
|
||||
run $ABRA app secret rm "$TEST_APP_DOMAIN" --all
|
||||
_reset_app
|
||||
_reset_recipe
|
||||
_checkout_recipe
|
||||
}
|
||||
|
||||
setup(){
|
||||
load "$PWD/tests/integration/helpers/common"
|
||||
_common_setup
|
||||
@ -84,6 +77,9 @@ setup(){
|
||||
assert_output --partial 'test_pass_one'
|
||||
assert_output --partial 'test_pass_two'
|
||||
refute_output --partial 'extra_pass'
|
||||
|
||||
run $ABRA app secret rm "$TEST_APP_DOMAIN" --all
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "generate: broken if missing version" {
|
||||
@ -95,6 +91,7 @@ setup(){
|
||||
assert_failure
|
||||
assert_output --partial 'missing version'
|
||||
|
||||
_reset_app
|
||||
}
|
||||
|
||||
@test "generate: use version from app env" {
|
||||
@ -111,6 +108,11 @@ setup(){
|
||||
assert_success
|
||||
assert_output --partial 'v2'
|
||||
refute_output --partial 'v1'
|
||||
|
||||
run $ABRA app secret rm "$TEST_APP_DOMAIN" --all
|
||||
assert_success
|
||||
|
||||
_reset_app
|
||||
}
|
||||
|
||||
@test "generate: generate extra secret based on COMPOSE_FILE" {
|
||||
@ -129,6 +131,11 @@ setup(){
|
||||
run docker -c "$TEST_SERVER" secret ls
|
||||
assert_success
|
||||
assert_output --partial "$TEST_APP_DOMAIN_extra_pass_v1"
|
||||
|
||||
run $ABRA app secret rm "$TEST_APP_DOMAIN" --all
|
||||
assert_success
|
||||
|
||||
_reset_app
|
||||
}
|
||||
|
||||
@test "generate: bail if unstaged changes and no --chaos" {
|
||||
@ -155,6 +162,8 @@ setup(){
|
||||
|
||||
run $ABRA app secret rm "$TEST_APP_DOMAIN" --all --chaos
|
||||
assert_success
|
||||
|
||||
_checkout_recipe
|
||||
}
|
||||
|
||||
@test "generate: ensure secret name uses trimmed stack name" {
|
||||
@ -219,22 +228,9 @@ setup(){
|
||||
run $ABRA app secret ls "$TEST_APP_DOMAIN"
|
||||
assert_success
|
||||
assert_output --partial 'true'
|
||||
}
|
||||
|
||||
@test "insert: create secret from file" {
|
||||
run $ABRA app secret ls "$TEST_APP_DOMAIN"
|
||||
run $ABRA app secret rm "$TEST_APP_DOMAIN" test_pass_one
|
||||
assert_success
|
||||
assert_output --partial 'false'
|
||||
|
||||
run bash -c "echo bar >> $ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||
|
||||
run $ABRA app secret insert --file "$TEST_APP_DOMAIN" test_pass_one v1 "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||
assert_success
|
||||
assert_output --partial 'successfully stored on server'
|
||||
|
||||
run $ABRA app secret ls "$TEST_APP_DOMAIN"
|
||||
assert_success
|
||||
assert_output --partial 'true'
|
||||
}
|
||||
|
||||
@test "rm: validate arguments" {
|
||||
@ -318,6 +314,9 @@ setup(){
|
||||
run $ABRA app secret ls "$TEST_APP_DOMAIN"
|
||||
assert_success
|
||||
assert_output --partial 'true'
|
||||
|
||||
run $ABRA app secret rm "$TEST_APP_DOMAIN" --all
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "ls: show secrets as machine readable" {
|
||||
@ -331,6 +330,9 @@ setup(){
|
||||
run $ABRA app secret ls "$TEST_APP_DOMAIN" --machine
|
||||
assert_success
|
||||
assert_output --partial '"created-on-server":"true"'
|
||||
|
||||
run $ABRA app secret rm "$TEST_APP_DOMAIN" --all
|
||||
assert_success
|
||||
}
|
||||
|
||||
@test "ls: bail if unstaged changes and no --chaos" {
|
||||
|
@ -78,6 +78,9 @@ teardown(){
|
||||
|
||||
_undeploy_app
|
||||
|
||||
# NOTE(d1): to let the stack come down before nuking volumes
|
||||
sleep 10
|
||||
|
||||
run $ABRA app volume rm "$TEST_APP_DOMAIN" --force
|
||||
assert_success
|
||||
assert_output --partial 'volumes removed successfully'
|
||||
@ -89,6 +92,9 @@ teardown(){
|
||||
|
||||
_undeploy_app
|
||||
|
||||
# NOTE(d1): to let the stack come down before nuking volumes
|
||||
sleep 10
|
||||
|
||||
run $ABRA app volume rm "$TEST_APP_DOMAIN" --force
|
||||
assert_success
|
||||
assert_output --partial 'volumes removed successfully'
|
||||
|
@ -1,17 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
_new_app() {
|
||||
run $ABRA app new "$TEST_RECIPE" \
|
||||
run $ABRA app new \
|
||||
--no-input \
|
||||
--server "$TEST_SERVER" \
|
||||
--domain "$TEST_APP_DOMAIN" \
|
||||
--secrets
|
||||
--secrets \
|
||||
"$TEST_RECIPE"
|
||||
assert_success
|
||||
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||
}
|
||||
|
||||
_deploy_app() {
|
||||
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input
|
||||
run $ABRA app deploy --no-input "$TEST_APP_DOMAIN"
|
||||
assert_success
|
||||
|
||||
run $ABRA app ls --server "$TEST_SERVER" --status
|
||||
@ -21,7 +22,7 @@ _deploy_app() {
|
||||
}
|
||||
|
||||
_undeploy_app() {
|
||||
run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input
|
||||
run $ABRA app undeploy --no-input "$TEST_APP_DOMAIN"
|
||||
assert_success
|
||||
|
||||
run $ABRA app ls --server "$TEST_SERVER" --status
|
||||
@ -34,10 +35,10 @@ _rm_app() {
|
||||
# NOTE(d1): not asserting outcomes on teardown here since some might fail
|
||||
# depending on what the test created. all commands run through anyway
|
||||
if [[ -f "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" ]]; then
|
||||
run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input
|
||||
run $ABRA app secret remove "$TEST_APP_DOMAIN" --all --no-input
|
||||
run $ABRA app volume remove "$TEST_APP_DOMAIN" --no-input
|
||||
run $ABRA app remove "$TEST_APP_DOMAIN" --no-input
|
||||
run $ABRA app undeploy --no-input "$TEST_APP_DOMAIN"
|
||||
run $ABRA app secret remove --all --no-input "$TEST_APP_DOMAIN"
|
||||
run $ABRA app volume remove --no-input "$TEST_APP_DOMAIN"
|
||||
run $ABRA app remove --no-input "$TEST_APP_DOMAIN"
|
||||
fi
|
||||
}
|
||||
|
||||
@ -46,10 +47,11 @@ _reset_app(){
|
||||
assert_success
|
||||
assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||
|
||||
run $ABRA app new "$TEST_RECIPE" \
|
||||
run $ABRA app new \
|
||||
--no-input \
|
||||
--server "$TEST_SERVER" \
|
||||
--domain "$TEST_APP_DOMAIN"
|
||||
--domain "$TEST_APP_DOMAIN" \
|
||||
"$TEST_RECIPE"
|
||||
assert_success
|
||||
assert_exists "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
|
||||
}
|
||||
|
@ -32,39 +32,6 @@ _reset_tags() {
|
||||
_set_git_author() {
|
||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" config --local user.email test@example.com
|
||||
assert_success
|
||||
|
||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" config --local user.name test
|
||||
assert_success
|
||||
}
|
||||
|
||||
_git_commit() {
|
||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" add .
|
||||
assert_success
|
||||
|
||||
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" commit -m "test: helpers/git.bash: _git_commit"
|
||||
assert_success
|
||||
}
|
||||
|
||||
_get_tag_hash() {
|
||||
tagHash=$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" rev-list -n 1 "$1")
|
||||
assert_success
|
||||
echo "$tagHash"
|
||||
}
|
||||
|
||||
_get_head_hash() {
|
||||
headHash=$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" show -s --format="%H" HEAD)
|
||||
assert_success
|
||||
echo "$headHash"
|
||||
}
|
||||
|
||||
_get_current_hash() {
|
||||
currentHash=$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" show -s --format="%H")
|
||||
assert_success
|
||||
echo "$currentHash"
|
||||
}
|
||||
|
||||
_get_n_hash() {
|
||||
nHash=$(git -C "$ABRA_DIR/recipes/$TEST_RECIPE" show -s --format="%H" "HEAD~$1")
|
||||
assert_success
|
||||
echo "$nHash"
|
||||
}
|
||||
|
@ -40,16 +40,3 @@ teardown(){
|
||||
assert_success
|
||||
assert_output --partial 'A new foobar app has been created!'
|
||||
}
|
||||
|
||||
@test "create new recipe with git credentials" {
|
||||
run $ABRA recipe new foobar --git-name fooUser --git-email foo@example.com
|
||||
assert_success
|
||||
assert_output --partial 'Your new foobar recipe has been created'
|
||||
assert_exists "$ABRA_DIR/recipes/foobar"
|
||||
|
||||
run bash -c 'git -C "$ABRA_DIR/recipes/foobar" log -n 1'
|
||||
assert_success
|
||||
assert_output --partial 'fooUser'
|
||||
assert_output --partial 'foo@example.com'
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user