Compare commits
31 Commits
0.7.0-rc2-
...
0.7.0-beta
Author | SHA1 | Date | |
---|---|---|---|
eb07617e73
|
|||
9fca4e56fb
|
|||
f17523010a
|
|||
3058178d84
|
|||
d62c4e3400
|
|||
5739758c3a
|
|||
a6b5566fa6
|
|||
4dbe1362a8
|
|||
98fc36c830
|
|||
b8abc8705c
|
|||
636261934f
|
|||
6381b73a6a
|
|||
1a72e27045
|
|||
9754c1b2d1
|
|||
b14ec0cda4 | |||
c7730ba604 | |||
47c61df444
|
|||
312b93e794
|
|||
992e675921
|
|||
d4f3a7be31
|
|||
d619f399e7 | |||
96a8cb7aff
|
|||
9b51d22c20 | |||
d789830ce4 | |||
e4b4084dfd
|
|||
ff58646cfc
|
|||
eec6469ba1
|
|||
e94f947d20
|
|||
cccbe4a2ec
|
|||
f53cfb6c36
|
|||
f55f01a25c
|
@ -5,9 +5,12 @@
|
||||
|
||||
- 3wordchant
|
||||
- cassowary
|
||||
- codegod100
|
||||
- decentral1se
|
||||
- frando
|
||||
- kawaiipunk
|
||||
- knoflook
|
||||
- moritz
|
||||
- roxxers
|
||||
- vera
|
||||
- yksflip
|
||||
|
6
Makefile
6
Makefile
@ -16,11 +16,11 @@ install:
|
||||
@go install -ldflags=$(LDFLAGS) $(ABRA)
|
||||
|
||||
build-dev:
|
||||
@go build -ldflags=$(LDFLAGS) $(ABRA)
|
||||
@go build -v -ldflags=$(LDFLAGS) $(ABRA)
|
||||
|
||||
build:
|
||||
@go build -ldflags=$(DIST_LDFLAGS) $(ABRA)
|
||||
@go build -ldflags=$(DIST_LDFLAGS) $(KADABRA)
|
||||
@go build -v -ldflags=$(DIST_LDFLAGS) $(ABRA)
|
||||
@go build -v -ldflags=$(DIST_LDFLAGS) $(KADABRA)
|
||||
|
||||
clean:
|
||||
@rm '$(GOPATH)/bin/abra'
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
containerPkg "coopcloud.tech/abra/pkg/container"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
"coopcloud.tech/abra/pkg/upstream/container"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types"
|
||||
@ -72,13 +73,14 @@ This single file can be used to restore your app. See "abra app restore" for mor
|
||||
`,
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
conf := runtime.New()
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
recipe, err := recipe.Get(app.Recipe)
|
||||
recipe, err := recipe.Get(app.Recipe, conf)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
@ -55,6 +56,7 @@ the logs.
|
||||
BashComplete: autocomplete.AppNameComplete,
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
conf := runtime.New()
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
@ -71,14 +73,14 @@ the logs.
|
||||
}
|
||||
|
||||
if !internal.Watch {
|
||||
if err := checkErrors(c, cl, app); err != nil {
|
||||
if err := checkErrors(c, cl, app, conf); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
if err := checkErrors(c, cl, app); err != nil {
|
||||
if err := checkErrors(c, cl, app, conf); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
@ -88,8 +90,8 @@ the logs.
|
||||
},
|
||||
}
|
||||
|
||||
func checkErrors(c *cli.Context, cl *dockerClient.Client, app config.App) error {
|
||||
recipe, err := recipe.Get(app.Recipe)
|
||||
func checkErrors(c *cli.Context, cl *dockerClient.Client, app config.App, conf *runtime.Config) error {
|
||||
recipe, err := recipe.Get(app.Recipe, conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
"coopcloud.tech/abra/pkg/service"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
@ -20,12 +21,14 @@ import (
|
||||
)
|
||||
|
||||
var logOpts = types.ContainerLogsOptions{
|
||||
Details: false,
|
||||
Follow: true,
|
||||
ShowStderr: true,
|
||||
ShowStdout: true,
|
||||
Tail: "20",
|
||||
Since: "",
|
||||
Until: "",
|
||||
Timestamps: true,
|
||||
Follow: true,
|
||||
Tail: "20",
|
||||
Details: false,
|
||||
}
|
||||
|
||||
// stackLogs lists logs for all stack services
|
||||
@ -74,18 +77,21 @@ var appLogsCommand = cli.Command{
|
||||
Usage: "Tail app logs",
|
||||
Flags: []cli.Flag{
|
||||
internal.StdErrOnlyFlag,
|
||||
internal.SinceLogsFlag,
|
||||
internal.DebugFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
BashComplete: autocomplete.AppNameComplete,
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
app := internal.ValidateApp(c, runtime.WithEnsureRecipeExists(false))
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
logOpts.Since = internal.SinceLogs
|
||||
|
||||
serviceName := c.Args().Get(1)
|
||||
if serviceName == "" {
|
||||
logrus.Debugf("tailing logs for all %s services", app.Recipe)
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
containerPkg "coopcloud.tech/abra/pkg/container"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
"coopcloud.tech/abra/pkg/upstream/container"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types"
|
||||
@ -55,6 +56,7 @@ Example:
|
||||
`,
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
conf := runtime.New()
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
@ -77,7 +79,7 @@ Example:
|
||||
}
|
||||
}
|
||||
|
||||
recipe, err := recipe.Get(app.Recipe)
|
||||
recipe, err := recipe.Get(app.Recipe, conf)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/lint"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"coopcloud.tech/tagcmp"
|
||||
|
||||
@ -49,14 +50,15 @@ recipes.
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
stackName := app.StackName()
|
||||
conf := runtime.New()
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
|
||||
if err := recipe.EnsureUpToDate(app.Recipe, conf); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
r, err := recipe.Get(app.Recipe, conf)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
@ -2,15 +2,82 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var prune bool
|
||||
|
||||
var pruneFlag = &cli.BoolFlag{
|
||||
Name: "prune, p",
|
||||
Destination: &prune,
|
||||
Usage: "Prunes unused containers, networks, and dangling images for an app",
|
||||
}
|
||||
|
||||
// pruneApp runs the equivalent of a "docker system prune" but only filtering
|
||||
// against resources connected with the app deployment. It is not a system wide
|
||||
// prune. Volumes are not pruned to avoid unwated data loss.
|
||||
func pruneApp(c *cli.Context, cl *dockerClient.Client, app config.App) error {
|
||||
stackName := app.StackName()
|
||||
ctx := context.Background()
|
||||
|
||||
for {
|
||||
logrus.Debugf("polling for %s stack, waiting to be undeployed...", stackName)
|
||||
|
||||
services, err := stack.GetStackServices(ctx, cl, stackName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(services) == 0 {
|
||||
logrus.Debugf("%s undeployed, moving on with pruning logic", stackName)
|
||||
time.Sleep(time.Second) // give runtime more time to tear down related state
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
pruneFilters := filters.NewArgs()
|
||||
stackSearch := fmt.Sprintf("%s*", stackName)
|
||||
pruneFilters.Add("label", stackSearch)
|
||||
cr, err := cl.ContainersPrune(ctx, pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cntSpaceReclaimed := formatter.ByteCountSI(cr.SpaceReclaimed)
|
||||
logrus.Infof("containers pruned: %d; space reclaimed: %s", len(cr.ContainersDeleted), cntSpaceReclaimed)
|
||||
|
||||
nr, err := cl.NetworksPrune(ctx, pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Infof("networks pruned: %d", len(nr.NetworksDeleted))
|
||||
|
||||
ir, err := cl.ImagesPrune(ctx, pruneFilters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imgSpaceReclaimed := formatter.ByteCountSI(ir.SpaceReclaimed)
|
||||
logrus.Infof("images pruned: %d; space reclaimed: %s", len(ir.ImagesDeleted), imgSpaceReclaimed)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var appUndeployCommand = cli.Command{
|
||||
Name: "undeploy",
|
||||
Aliases: []string{"un"},
|
||||
@ -18,18 +85,22 @@ var appUndeployCommand = cli.Command{
|
||||
Flags: []cli.Flag{
|
||||
internal.DebugFlag,
|
||||
internal.NoInputFlag,
|
||||
pruneFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
Usage: "Undeploy an app",
|
||||
BashComplete: autocomplete.AppNameComplete,
|
||||
Description: `
|
||||
This does not destroy any of the application data. However, you should remain
|
||||
vigilant, as your swarm installation will consider any previously attached
|
||||
volumes as eligiblef or pruning once undeployed.
|
||||
This does not destroy any of the application data.
|
||||
|
||||
However, you should remain vigilant, as your swarm installation will consider
|
||||
any previously attached volumes as eligible for pruning once undeployed.
|
||||
|
||||
Passing "-p/--prune" does not remove those volumes.
|
||||
`,
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
stackName := app.StackName()
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
@ -55,7 +126,12 @@ volumes as eligiblef or pruning once undeployed.
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if prune {
|
||||
if err := pruneApp(c, cl, app); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
BashComplete: autocomplete.AppNameComplete,
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/lint"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"coopcloud.tech/tagcmp"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
@ -52,6 +53,7 @@ recipes.
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
stackName := app.StackName()
|
||||
conf := runtime.New()
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
@ -59,12 +61,12 @@ recipes.
|
||||
}
|
||||
|
||||
if !internal.Chaos {
|
||||
if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
|
||||
if err := recipe.EnsureUpToDate(app.Recipe, conf); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
r, err := recipe.Get(app.Recipe, conf)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -48,6 +49,7 @@ version.
|
||||
Action: func(c *cli.Context) error {
|
||||
app := internal.ValidateApp(c)
|
||||
stackName := app.StackName()
|
||||
conf := runtime.New()
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
@ -69,7 +71,7 @@ version.
|
||||
logrus.Fatalf("%s is not deployed?", app.Name)
|
||||
}
|
||||
|
||||
recipeMeta, err := recipe.GetRecipeMeta(app.Recipe)
|
||||
recipeMeta, err := recipe.GetRecipeMeta(app.Recipe, conf)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
gitPkg "coopcloud.tech/abra/pkg/git"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
@ -54,8 +55,10 @@ keys configured on your account.
|
||||
ArgsUsage: "[<recipe>]",
|
||||
Action: func(c *cli.Context) error {
|
||||
recipeName := c.Args().First()
|
||||
conf := runtime.New()
|
||||
|
||||
if recipeName != "" {
|
||||
internal.ValidateRecipe(c, true)
|
||||
internal.ValidateRecipe(c)
|
||||
}
|
||||
|
||||
if err := catalogue.EnsureUpToDate(); err != nil {
|
||||
@ -79,7 +82,7 @@ keys configured on your account.
|
||||
|
||||
if !internal.SkipUpdates {
|
||||
logrus.Warn(logMsg)
|
||||
if err := recipe.UpdateRepositories(repos, recipeName); err != nil {
|
||||
if err := recipe.UpdateRepositories(repos, recipeName, conf); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
@ -97,7 +100,7 @@ keys configured on your account.
|
||||
continue
|
||||
}
|
||||
|
||||
versions, err := recipe.GetRecipeVersions(recipeMeta.Name)
|
||||
versions, err := recipe.GetRecipeVersions(recipeMeta.Name, conf)
|
||||
if err != nil {
|
||||
logrus.Warn(err)
|
||||
}
|
||||
|
@ -328,6 +328,14 @@ var StdErrOnlyFlag = &cli.BoolFlag{
|
||||
Destination: &StdErrOnly,
|
||||
}
|
||||
|
||||
var SinceLogs string
|
||||
var SinceLogsFlag = &cli.StringFlag{
|
||||
Name: "since, S",
|
||||
Value: "",
|
||||
Usage: "tail logs since YYYY-MM-DDTHH:MM:SSZ",
|
||||
Destination: &SinceLogs,
|
||||
}
|
||||
|
||||
var DontWaitConverge bool
|
||||
var DontWaitConvergeFlag = &cli.BoolFlag{
|
||||
Name: "no-converge-checks, c",
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/git"
|
||||
"coopcloud.tech/abra/pkg/lint"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -24,6 +25,7 @@ import (
|
||||
// DeployAction is the main command-line action for this package
|
||||
func DeployAction(c *cli.Context) error {
|
||||
app := ValidateApp(c)
|
||||
conf := runtime.New()
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
@ -31,12 +33,12 @@ func DeployAction(c *cli.Context) error {
|
||||
}
|
||||
|
||||
if !Chaos {
|
||||
if err := recipe.EnsureUpToDate(app.Recipe); err != nil {
|
||||
if err := recipe.EnsureUpToDate(app.Recipe, conf); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
r, err := recipe.Get(app.Recipe)
|
||||
r, err := recipe.Get(app.Recipe, conf)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
@ -83,7 +85,7 @@ func DeployAction(c *cli.Context) error {
|
||||
}
|
||||
version = formatter.SmallSHA(head.String())
|
||||
logrus.Warn("no versions detected, using latest commit")
|
||||
if err := recipe.EnsureLatest(app.Recipe); err != nil {
|
||||
if err := recipe.EnsureLatest(app.Recipe, conf); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/jsontable"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
"coopcloud.tech/abra/pkg/secret"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
@ -119,9 +120,10 @@ func ensureServerFlag() error {
|
||||
|
||||
// NewAction is the new app creation logic
|
||||
func NewAction(c *cli.Context) error {
|
||||
recipe := ValidateRecipeWithPrompt(c, false)
|
||||
recipe := ValidateRecipeWithPrompt(c, runtime.WithEnsureRecipeLatest(false))
|
||||
|
||||
if err := recipePkg.EnsureUpToDate(recipe.Name); err != nil {
|
||||
conf := runtime.New(runtime.WithEnsureRecipeLatest(false))
|
||||
if err := recipePkg.EnsureUpToDate(recipe.Name, conf); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/app"
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
@ -18,14 +19,15 @@ import (
|
||||
var AppName string
|
||||
|
||||
// ValidateRecipe ensures the recipe arg is valid.
|
||||
func ValidateRecipe(c *cli.Context, ensureLatest bool) recipe.Recipe {
|
||||
func ValidateRecipe(c *cli.Context, opts ...runtime.Option) recipe.Recipe {
|
||||
recipeName := c.Args().First()
|
||||
conf := runtime.New(opts...)
|
||||
|
||||
if recipeName == "" {
|
||||
ShowSubcommandHelpAndError(c, errors.New("no recipe name provided"))
|
||||
}
|
||||
|
||||
chosenRecipe, err := recipe.Get(recipeName)
|
||||
chosenRecipe, err := recipe.Get(recipeName, conf)
|
||||
if err != nil {
|
||||
if c.Command.Name == "generate" {
|
||||
if strings.Contains(err.Error(), "missing a compose") {
|
||||
@ -40,11 +42,9 @@ func ValidateRecipe(c *cli.Context, ensureLatest bool) recipe.Recipe {
|
||||
}
|
||||
}
|
||||
|
||||
if ensureLatest {
|
||||
if err := recipe.EnsureLatest(recipeName); err != nil {
|
||||
if err := recipe.EnsureLatest(recipeName, conf); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Debugf("validated %s as recipe argument", recipeName)
|
||||
|
||||
@ -53,8 +53,9 @@ func ValidateRecipe(c *cli.Context, ensureLatest bool) recipe.Recipe {
|
||||
|
||||
// ValidateRecipeWithPrompt ensures a recipe argument is present before
|
||||
// validating, asking for input if required.
|
||||
func ValidateRecipeWithPrompt(c *cli.Context, ensureLatest bool) recipe.Recipe {
|
||||
func ValidateRecipeWithPrompt(c *cli.Context, opts ...runtime.Option) recipe.Recipe {
|
||||
recipeName := c.Args().First()
|
||||
conf := runtime.New(opts...)
|
||||
|
||||
if recipeName == "" && !NoInput {
|
||||
var recipes []string
|
||||
@ -102,16 +103,14 @@ func ValidateRecipeWithPrompt(c *cli.Context, ensureLatest bool) recipe.Recipe {
|
||||
ShowSubcommandHelpAndError(c, errors.New("no recipe name provided"))
|
||||
}
|
||||
|
||||
chosenRecipe, err := recipe.Get(recipeName)
|
||||
chosenRecipe, err := recipe.Get(recipeName, conf)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if ensureLatest {
|
||||
if err := recipe.EnsureLatest(recipeName); err != nil {
|
||||
if err := recipe.EnsureLatest(recipeName, conf); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Debugf("validated %s as recipe argument", recipeName)
|
||||
|
||||
@ -119,8 +118,9 @@ func ValidateRecipeWithPrompt(c *cli.Context, ensureLatest bool) recipe.Recipe {
|
||||
}
|
||||
|
||||
// ValidateApp ensures the app name arg is valid.
|
||||
func ValidateApp(c *cli.Context) config.App {
|
||||
func ValidateApp(c *cli.Context, opts ...runtime.Option) config.App {
|
||||
appName := c.Args().First()
|
||||
conf := runtime.New(opts...)
|
||||
|
||||
if AppName != "" {
|
||||
appName = AppName
|
||||
@ -136,7 +136,7 @@ func ValidateApp(c *cli.Context) config.App {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := recipe.EnsureExists(app.Recipe); err != nil {
|
||||
if err := recipe.EnsureExists(app.Recipe, conf); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -21,8 +22,10 @@ var recipeFetchCommand = cli.Command{
|
||||
BashComplete: autocomplete.RecipeNameComplete,
|
||||
Action: func(c *cli.Context) error {
|
||||
recipeName := c.Args().First()
|
||||
conf := runtime.New()
|
||||
|
||||
if recipeName != "" {
|
||||
internal.ValidateRecipe(c, true)
|
||||
internal.ValidateRecipe(c)
|
||||
return nil // ValidateRecipe ensures latest checkout
|
||||
}
|
||||
|
||||
@ -31,7 +34,7 @@ var recipeFetchCommand = cli.Command{
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := recipe.UpdateRepositories(repos, recipeName); err != nil {
|
||||
if err := recipe.UpdateRepositories(repos, recipeName, conf); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"coopcloud.tech/abra/pkg/lint"
|
||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -24,9 +25,10 @@ var recipeLintCommand = cli.Command{
|
||||
Before: internal.SubCommandBefore,
|
||||
BashComplete: autocomplete.RecipeNameComplete,
|
||||
Action: func(c *cli.Context) error {
|
||||
recipe := internal.ValidateRecipe(c, true)
|
||||
recipe := internal.ValidateRecipe(c)
|
||||
conf := runtime.New()
|
||||
|
||||
if err := recipePkg.EnsureUpToDate(recipe.Name); err != nil {
|
||||
if err := recipePkg.EnsureUpToDate(recipe.Name, conf); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
gitPkg "coopcloud.tech/abra/pkg/git"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
"coopcloud.tech/tagcmp"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/docker/distribution/reference"
|
||||
@ -58,7 +59,7 @@ your SSH keys configured on your account.
|
||||
Before: internal.SubCommandBefore,
|
||||
BashComplete: autocomplete.RecipeNameComplete,
|
||||
Action: func(c *cli.Context) error {
|
||||
recipe := internal.ValidateRecipeWithPrompt(c, false)
|
||||
recipe := internal.ValidateRecipeWithPrompt(c, runtime.WithEnsureRecipeLatest(false))
|
||||
|
||||
imagesTmp, err := getImageVersions(recipe)
|
||||
if err != nil {
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
"coopcloud.tech/tagcmp"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/go-git/go-git/v5"
|
||||
@ -41,7 +42,7 @@ auto-generate it for you. The <recipe> configuration will be updated on the
|
||||
local file system.
|
||||
`,
|
||||
Action: func(c *cli.Context) error {
|
||||
recipe := internal.ValidateRecipeWithPrompt(c, false)
|
||||
recipe := internal.ValidateRecipeWithPrompt(c, runtime.WithEnsureRecipeLatest(false))
|
||||
|
||||
mainApp, err := internal.GetMainAppImage(recipe)
|
||||
if err != nil {
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
"coopcloud.tech/tagcmp"
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/docker/distribution/reference"
|
||||
@ -59,9 +60,10 @@ You may invoke this command in "wizard" mode and be prompted for input:
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
Action: func(c *cli.Context) error {
|
||||
recipe := internal.ValidateRecipeWithPrompt(c, true)
|
||||
recipe := internal.ValidateRecipeWithPrompt(c)
|
||||
conf := runtime.New()
|
||||
|
||||
if err := recipePkg.EnsureUpToDate(recipe.Name); err != nil {
|
||||
if err := recipePkg.EnsureUpToDate(recipe.Name, conf); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@ -20,7 +21,7 @@ var recipeVersionCommand = cli.Command{
|
||||
Before: internal.SubCommandBefore,
|
||||
BashComplete: autocomplete.RecipeNameComplete,
|
||||
Action: func(c *cli.Context) error {
|
||||
recipe := internal.ValidateRecipe(c, false)
|
||||
recipe := internal.ValidateRecipe(c, runtime.WithEnsureRecipeLatest(false))
|
||||
|
||||
catalogue, err := recipePkg.ReadRecipeCatalogue()
|
||||
if err != nil {
|
||||
|
@ -39,11 +39,7 @@ var serverListCommand = cli.Command{
|
||||
|
||||
tableColumns := []string{"name", "host", "user", "port"}
|
||||
table := formatter.CreateTable(tableColumns)
|
||||
if internal.MachineReadable {
|
||||
defer table.JSONRender()
|
||||
} else {
|
||||
defer table.Render()
|
||||
}
|
||||
|
||||
serverNames, err := config.ReadServerNames()
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
@ -84,6 +80,16 @@ var serverListCommand = cli.Command{
|
||||
}
|
||||
}
|
||||
|
||||
if internal.MachineReadable {
|
||||
table.JSONRender()
|
||||
} else {
|
||||
if problemsFilter && table.NumLines() == 0 {
|
||||
logrus.Info("all servers wired up correctly 👏")
|
||||
} else {
|
||||
table.Render()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
100
cli/server/prune.go
Normal file
100
cli/server/prune.go
Normal file
@ -0,0 +1,100 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var allFilter bool
|
||||
|
||||
var allFilterFlag = &cli.BoolFlag{
|
||||
Name: "all, a",
|
||||
Usage: "Remove all unused images not just dangling ones",
|
||||
Destination: &allFilter,
|
||||
}
|
||||
|
||||
var volunesFilter bool
|
||||
|
||||
var volumesFilterFlag = &cli.BoolFlag{
|
||||
Name: "volumes, v",
|
||||
Usage: "Prune volumes. This will remove app data, Be Careful!",
|
||||
Destination: &volunesFilter,
|
||||
}
|
||||
|
||||
var serverPruneCommand = cli.Command{
|
||||
Name: "prune",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "Prune a managed server; Runs a docker system prune",
|
||||
Description: `
|
||||
Prunes unused containers, networks, and dangling images.
|
||||
|
||||
If passing "-v/--volumes" then volumes not connected with a deployed app will
|
||||
also be removed. This can result in unwanted data loss if not used carefully.
|
||||
`,
|
||||
ArgsUsage: "[<server>]",
|
||||
Flags: []cli.Flag{
|
||||
allFilterFlag,
|
||||
volumesFilterFlag,
|
||||
internal.DebugFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
BashComplete: autocomplete.ServerNameComplete,
|
||||
Action: func(c *cli.Context) error {
|
||||
var args filters.Args
|
||||
|
||||
serverName := internal.ValidateServer(c)
|
||||
|
||||
cl, err := client.New(serverName)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
cr, err := cl.ContainersPrune(ctx, args)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
cntSpaceReclaimed := formatter.ByteCountSI(cr.SpaceReclaimed)
|
||||
logrus.Infof("containers pruned: %d; space reclaimed: %s", len(cr.ContainersDeleted), cntSpaceReclaimed)
|
||||
|
||||
nr, err := cl.NetworksPrune(ctx, args)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
logrus.Infof("networks pruned: %d", len(nr.NetworksDeleted))
|
||||
|
||||
pruneFilters := filters.NewArgs()
|
||||
if allFilter {
|
||||
pruneFilters.Add("dangling", "false")
|
||||
}
|
||||
|
||||
ir, err := cl.ImagesPrune(ctx, pruneFilters)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
imgSpaceReclaimed := formatter.ByteCountSI(ir.SpaceReclaimed)
|
||||
logrus.Infof("images pruned: %d; space reclaimed: %s", len(ir.ImagesDeleted), imgSpaceReclaimed)
|
||||
|
||||
if volunesFilter {
|
||||
vr, err := cl.VolumesPrune(ctx, args)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
volSpaceReclaimed := formatter.ByteCountSI(vr.SpaceReclaimed)
|
||||
logrus.Infof("volumes pruned: %d; space reclaimed: %s", len(vr.VolumesDeleted), volSpaceReclaimed)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
"coopcloud.tech/abra/pkg/autocomplete"
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
@ -125,6 +126,7 @@ like tears in rain.
|
||||
internal.HetznerCloudAPITokenFlag,
|
||||
},
|
||||
Before: internal.SubCommandBefore,
|
||||
BashComplete: autocomplete.ServerNameComplete,
|
||||
Action: func(c *cli.Context) error {
|
||||
serverName := internal.ValidateServer(c)
|
||||
|
||||
|
@ -22,5 +22,6 @@ recipes, see available flags on "abra server add" for more.
|
||||
serverAddCommand,
|
||||
serverListCommand,
|
||||
serverRemoveCommand,
|
||||
serverPruneCommand,
|
||||
},
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/lint"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
"coopcloud.tech/abra/pkg/upstream/convert"
|
||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"coopcloud.tech/tagcmp"
|
||||
@ -113,10 +114,12 @@ update chaos deployments use the "--chaos" flag. Use it with care.
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
conf := runtime.New()
|
||||
|
||||
if !updateAll {
|
||||
stackName := c.Args().Get(0)
|
||||
recipeName := c.Args().Get(1)
|
||||
err = tryUpgrade(cl, stackName, recipeName)
|
||||
err = tryUpgrade(cl, stackName, recipeName, conf)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
@ -136,7 +139,7 @@ update chaos deployments use the "--chaos" flag. Use it with care.
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
err = tryUpgrade(cl, stackName, recipeName)
|
||||
err = tryUpgrade(cl, stackName, recipeName, conf)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
@ -313,12 +316,12 @@ func getAvailableUpgrades(cl *dockerclient.Client, stackName string, recipeName
|
||||
|
||||
// processRecipeRepoVersion clones, pulls, checks out the version and lints the
|
||||
// recipe repository.
|
||||
func processRecipeRepoVersion(recipeName string, version string) error {
|
||||
if err := recipe.EnsureExists(recipeName); err != nil {
|
||||
func processRecipeRepoVersion(recipeName, version string, conf *runtime.Config) error {
|
||||
if err := recipe.EnsureExists(recipeName, conf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := recipe.EnsureUpToDate(recipeName); err != nil {
|
||||
if err := recipe.EnsureUpToDate(recipeName, conf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -326,7 +329,7 @@ func processRecipeRepoVersion(recipeName string, version string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if r, err := recipe.Get(recipeName); err != nil {
|
||||
if r, err := recipe.Get(recipeName, conf); err != nil {
|
||||
return err
|
||||
} else if err := lint.LintForErrors(r); err != nil {
|
||||
return err
|
||||
@ -383,7 +386,7 @@ func createDeployConfig(recipeName string, stackName string, env config.AppEnv)
|
||||
}
|
||||
|
||||
// tryUpgrade performs the upgrade if all the requirements are fulfilled.
|
||||
func tryUpgrade(cl *dockerclient.Client, stackName string, recipeName string) error {
|
||||
func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string, conf *runtime.Config) error {
|
||||
if recipeName == "" {
|
||||
logrus.Debugf("Don't update %s due to missing recipe name", stackName)
|
||||
return nil
|
||||
@ -419,13 +422,13 @@ func tryUpgrade(cl *dockerclient.Client, stackName string, recipeName string) er
|
||||
return nil
|
||||
}
|
||||
|
||||
err = upgrade(cl, stackName, recipeName, upgradeVersion)
|
||||
err = upgrade(cl, stackName, recipeName, upgradeVersion, conf)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// upgrade performs all necessary steps to upgrade an app.
|
||||
func upgrade(cl *dockerclient.Client, stackName string, recipeName string, upgradeVersion string) error {
|
||||
func upgrade(cl *dockerclient.Client, stackName, recipeName, upgradeVersion string, conf *runtime.Config) error {
|
||||
env, err := getEnv(cl, stackName)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -438,7 +441,7 @@ func upgrade(cl *dockerclient.Client, stackName string, recipeName string, upgra
|
||||
Env: env,
|
||||
}
|
||||
|
||||
if err = processRecipeRepoVersion(recipeName, upgradeVersion); err != nil {
|
||||
if err = processRecipeRepoVersion(recipeName, upgradeVersion, conf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
2
go.mod
2
go.mod
@ -12,7 +12,7 @@ require (
|
||||
github.com/docker/docker v20.10.23+incompatible
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/go-git/go-git/v5 v5.5.2
|
||||
github.com/hetznercloud/hcloud-go v1.39.0
|
||||
github.com/hetznercloud/hcloud-go v1.40.0
|
||||
github.com/moby/sys/signal v0.7.0
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
|
4
go.sum
4
go.sum
@ -605,8 +605,8 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn
|
||||
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
||||
github.com/hetznercloud/hcloud-go v1.39.0 h1:RUlzI458nGnPR6dlcZlrsGXYC1hQlFbKdm8tVtEQQB0=
|
||||
github.com/hetznercloud/hcloud-go v1.39.0/go.mod h1:mepQwR6va27S3UQthaEPGS86jtzSY9xWL1e9dyxXpgA=
|
||||
github.com/hetznercloud/hcloud-go v1.40.0 h1:rchS+LVd80GuAOQUM5pLs0Fn7tb4PflPCuxskLYc1JQ=
|
||||
github.com/hetznercloud/hcloud-go v1.40.0/go.mod h1:jcKomw7kDIjrJ9FI72S/RdRPK8X8arYzEnXjRM/uXT8=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// AppNameComplete copletes app names
|
||||
// AppNameComplete copletes app names.
|
||||
func AppNameComplete(c *cli.Context) {
|
||||
appNames, err := config.GetAppNames()
|
||||
if err != nil {
|
||||
@ -25,7 +25,7 @@ func AppNameComplete(c *cli.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// RecipeNameComplete completes recipe names
|
||||
// RecipeNameComplete completes recipe names.
|
||||
func RecipeNameComplete(c *cli.Context) {
|
||||
catl, err := recipe.ReadRecipeCatalogue()
|
||||
if err != nil {
|
||||
@ -41,7 +41,23 @@ func RecipeNameComplete(c *cli.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// SubcommandComplete completes subcommands.
|
||||
// ServerNameComplete completes server names.
|
||||
func ServerNameComplete(c *cli.Context) {
|
||||
files, err := config.LoadAppFiles("")
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if c.NArg() > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, appFile := range files {
|
||||
fmt.Println(appFile.Server)
|
||||
}
|
||||
}
|
||||
|
||||
// SubcommandComplete completes sub-commands.
|
||||
func SubcommandComplete(c *cli.Context) {
|
||||
if c.NArg() > 0 {
|
||||
return
|
||||
|
@ -26,7 +26,7 @@ func New(serverName string) (*client.Client, error) {
|
||||
if serverName != "default" {
|
||||
context, err := GetContext(serverName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("unknown server, run \"abra server add %s\"?", serverName)
|
||||
}
|
||||
|
||||
ctxEndpoint, err := contextPkg.GetContextEndpoint(context)
|
||||
|
@ -173,7 +173,7 @@ func newApp(env AppEnv, name string, appFile AppFile) (App, error) {
|
||||
if !exists {
|
||||
recipe, exists = env["TYPE"]
|
||||
if !exists {
|
||||
return App{}, fmt.Errorf("%s is missing the RECIPE env var", name)
|
||||
return App{}, fmt.Errorf("%s is missing the TYPE env var?", name)
|
||||
}
|
||||
}
|
||||
|
||||
@ -369,13 +369,6 @@ func GetAppStatuses(apps []App, MachineReadable bool) (map[string]map[string]str
|
||||
}
|
||||
}
|
||||
|
||||
for server := range servers {
|
||||
// validate that all server connections work
|
||||
if _, err := client.New(server); err != nil {
|
||||
return statuses, err
|
||||
}
|
||||
}
|
||||
|
||||
var bar *progressbar.ProgressBar
|
||||
if !MachineReadable {
|
||||
bar = formatter.CreateProgressbar(len(servers), "querying remote servers...")
|
||||
|
@ -1,6 +1,7 @@
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@ -70,3 +71,21 @@ func StripTagMeta(image string) string {
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
// ByteCountSI presents a human friendly representation of a byte count. See
|
||||
// https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format.
|
||||
func ByteCountSI(b uint64) string {
|
||||
const unit = 1000
|
||||
|
||||
if b < unit {
|
||||
return fmt.Sprintf("%d B", b)
|
||||
}
|
||||
|
||||
div, exp := uint64(unit), 0
|
||||
for n := b / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
gitPkg "coopcloud.tech/abra/pkg/git"
|
||||
"coopcloud.tech/abra/pkg/limit"
|
||||
"coopcloud.tech/abra/pkg/runtime"
|
||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
loader "coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"coopcloud.tech/abra/pkg/web"
|
||||
@ -206,8 +207,8 @@ func (r Recipe) Tags() ([]string, error) {
|
||||
}
|
||||
|
||||
// Get retrieves a recipe.
|
||||
func Get(recipeName string) (Recipe, error) {
|
||||
if err := EnsureExists(recipeName); err != nil {
|
||||
func Get(recipeName string, conf *runtime.Config) (Recipe, error) {
|
||||
if err := EnsureExists(recipeName, conf); err != nil {
|
||||
return Recipe{}, err
|
||||
}
|
||||
|
||||
@ -233,7 +234,7 @@ func Get(recipeName string) (Recipe, error) {
|
||||
return Recipe{}, err
|
||||
}
|
||||
|
||||
meta, err := GetRecipeMeta(recipeName)
|
||||
meta, err := GetRecipeMeta(recipeName, conf)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "does not exist") {
|
||||
meta = RecipeMeta{}
|
||||
@ -250,7 +251,11 @@ func Get(recipeName string) (Recipe, error) {
|
||||
}
|
||||
|
||||
// EnsureExists ensures that a recipe is locally cloned
|
||||
func EnsureExists(recipeName string) error {
|
||||
func EnsureExists(recipeName string, conf *runtime.Config) error {
|
||||
if !conf.EnsureRecipeExists {
|
||||
return nil
|
||||
}
|
||||
|
||||
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
|
||||
|
||||
if _, err := os.Stat(recipeDir); os.IsNotExist(err) {
|
||||
@ -333,7 +338,11 @@ func EnsureVersion(recipeName, version string) error {
|
||||
}
|
||||
|
||||
// EnsureLatest makes sure the latest commit is checked out for a local recipe repository
|
||||
func EnsureLatest(recipeName string) error {
|
||||
func EnsureLatest(recipeName string, conf *runtime.Config) error {
|
||||
if !conf.EnsureRecipeLatest {
|
||||
return nil
|
||||
}
|
||||
|
||||
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
|
||||
|
||||
isClean, err := gitPkg.IsClean(recipeDir)
|
||||
@ -361,7 +370,7 @@ func EnsureLatest(recipeName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
meta, err := GetRecipeMeta(recipeName)
|
||||
meta, err := GetRecipeMeta(recipeName, conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -578,7 +587,11 @@ func GetStringInBetween(recipeName, str, start, end string) (result string, err
|
||||
}
|
||||
|
||||
// EnsureUpToDate ensures that the local repo is synced to the remote
|
||||
func EnsureUpToDate(recipeName string) error {
|
||||
func EnsureUpToDate(recipeName string, conf *runtime.Config) error {
|
||||
if !conf.EnsureRecipeLatest {
|
||||
return nil
|
||||
}
|
||||
|
||||
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
|
||||
|
||||
isClean, err := gitPkg.IsClean(recipeDir)
|
||||
@ -790,7 +803,7 @@ func VersionsOfService(recipe, serviceName string) ([]string, error) {
|
||||
}
|
||||
|
||||
// GetRecipeMeta retrieves the recipe metadata from the recipe catalogue.
|
||||
func GetRecipeMeta(recipeName string) (RecipeMeta, error) {
|
||||
func GetRecipeMeta(recipeName string, conf *runtime.Config) (RecipeMeta, error) {
|
||||
catl, err := ReadRecipeCatalogue()
|
||||
if err != nil {
|
||||
return RecipeMeta{}, err
|
||||
@ -801,7 +814,7 @@ func GetRecipeMeta(recipeName string) (RecipeMeta, error) {
|
||||
return RecipeMeta{}, fmt.Errorf("recipe %s does not exist?", recipeName)
|
||||
}
|
||||
|
||||
if err := EnsureExists(recipeName); err != nil {
|
||||
if err := EnsureExists(recipeName, conf); err != nil {
|
||||
return RecipeMeta{}, err
|
||||
}
|
||||
|
||||
@ -923,9 +936,8 @@ func ReadReposMetadata() (RepoCatalogue, error) {
|
||||
}
|
||||
|
||||
// GetRecipeVersions retrieves all recipe versions.
|
||||
func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
|
||||
func GetRecipeVersions(recipeName string, conf *runtime.Config) (RecipeVersions, error) {
|
||||
versions := RecipeVersions{}
|
||||
|
||||
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
|
||||
|
||||
logrus.Debugf("attempting to open git repository in %s", recipeDir)
|
||||
@ -962,7 +974,7 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
|
||||
|
||||
logrus.Debugf("successfully checked out %s in %s", ref.Name(), recipeDir)
|
||||
|
||||
recipe, err := Get(recipeName)
|
||||
recipe, err := Get(recipeName, conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1027,7 +1039,7 @@ func GetRecipeCatalogueVersions(recipeName string, catl RecipeCatalogue) ([]stri
|
||||
}
|
||||
|
||||
// UpdateRepositories clones and updates all recipe repositories locally.
|
||||
func UpdateRepositories(repos RepoCatalogue, recipeName string) error {
|
||||
func UpdateRepositories(repos RepoCatalogue, recipeName string, conf *runtime.Config) error {
|
||||
var barLength int
|
||||
if recipeName != "" {
|
||||
barLength = 1
|
||||
@ -1061,7 +1073,7 @@ func UpdateRepositories(repos RepoCatalogue, recipeName string) error {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if err := EnsureUpToDate(rm.Name); err != nil {
|
||||
if err := EnsureUpToDate(rm.Name, conf); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
|
61
pkg/runtime/runtime.go
Normal file
61
pkg/runtime/runtime.go
Normal file
@ -0,0 +1,61 @@
|
||||
package runtime
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
// Config is an internal configuration modifier. It can be instantiated on a
|
||||
// command call and can be changed on the fly to help make decisions further
|
||||
// down in the internals, e.g. whether or not to clone the recipe locally or
|
||||
// not.
|
||||
type Config struct {
|
||||
EnsureRecipeExists bool // ensure that the recipe is cloned locally
|
||||
EnsureRecipeLatest bool // ensure the local recipe has latest changes
|
||||
}
|
||||
|
||||
// Option modified a Config. The convetion for passing Options to functions is
|
||||
// so far, only used in internal/validate.go. A Config is then constructed and
|
||||
// passed down further into the code. This may change in the future but this is
|
||||
// at least the abstraction so far.
|
||||
type Option func(c *Config)
|
||||
|
||||
// New instantiates a new Config.
|
||||
func New(opts ...Option) *Config {
|
||||
conf := &Config{
|
||||
EnsureRecipeExists: true,
|
||||
}
|
||||
|
||||
for _, optFunc := range opts {
|
||||
optFunc(conf)
|
||||
}
|
||||
|
||||
return conf
|
||||
}
|
||||
|
||||
// WithEnsureRecipeExists determines whether or not we should be cloning the
|
||||
// local recipe or not. This can be useful for being more adaptable to offline
|
||||
// scenarios.
|
||||
func WithEnsureRecipeExists(ensureRecipeExists bool) Option {
|
||||
return func(c *Config) {
|
||||
if ensureRecipeExists {
|
||||
logrus.Debugf("runtime config: EnsureRecipeExists = %v, ensuring recipes are cloned", ensureRecipeExists)
|
||||
} else {
|
||||
logrus.Debugf("runtime config: EnsureRecipeExists = %v, not cloning recipes", ensureRecipeExists)
|
||||
}
|
||||
|
||||
c.EnsureRecipeExists = ensureRecipeExists
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnsureRecipeLatest determines whether we should update the local recipes
|
||||
// remotely via Git. This can be useful when e.g. ensuring we have the latest
|
||||
// changes before making new ones.
|
||||
func WithEnsureRecipeLatest(ensureRecipeLatest bool) Option {
|
||||
return func(c *Config) {
|
||||
if ensureRecipeLatest {
|
||||
logrus.Debugf("runtime config: EnsureRecipeLatest = %v, ensuring recipes have latest changes", ensureRecipeLatest)
|
||||
} else {
|
||||
logrus.Debugf("runtime config: EnsureRecipeLatest = %v, leaving recipes alone", ensureRecipeLatest)
|
||||
}
|
||||
|
||||
c.EnsureRecipeLatest = ensureRecipeLatest
|
||||
}
|
||||
}
|
@ -77,6 +77,8 @@ func Fatal(hostname string, err error) error {
|
||||
return fmt.Errorf("connection timed out for %s", hostname)
|
||||
} else if strings.Contains(out, "Permission denied") {
|
||||
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
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ABRA_VERSION="0.6.0-beta"
|
||||
ABRA_VERSION="0.7.0-beta"
|
||||
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION"
|
||||
RC_VERSION="0.7.0-rc2-beta"
|
||||
RC_VERSION="0.7.0-beta"
|
||||
RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$RC_VERSION"
|
||||
|
||||
for arg in "$@"; do
|
||||
|
Reference in New Issue
Block a user