fix: improved offline support
continuous-integration/drone/push Build is passing Details

Closes coop-cloud/organising#471.
This commit is contained in:
decentral1se 2023-07-26 08:16:07 +02:00
parent ab64eb2e8d
commit 3dc5662821
Signed by: decentral1se
GPG Key ID: 03789458B3D0C410
49 changed files with 455 additions and 375 deletions

View File

@ -41,6 +41,7 @@ var appBackupCommand = cli.Command{
ArgsUsage: "<domain> [<service>]", ArgsUsage: "<domain> [<service>]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
@ -71,8 +72,8 @@ This file is a compressed archive which contains all backup paths. To see paths,
This single file can be used to restore your app. See "abra app restore" for more. This single file can be used to restore your app. See "abra app restore" for more.
`, `,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
conf := runtime.New() app := internal.ValidateApp(c, conf)
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {

View File

@ -8,6 +8,7 @@ import (
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/runtime"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -19,10 +20,12 @@ var appCheckCommand = cli.Command{
ArgsUsage: "<domain>", ArgsUsage: "<domain>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
envSamplePath := path.Join(config.RECIPES_DIR, app.Recipe, ".env.sample") envSamplePath := path.Join(config.RECIPES_DIR, app.Recipe, ".env.sample")
if _, err := os.Stat(envSamplePath); err != nil { if _, err := os.Stat(envSamplePath); err != nil {

View File

@ -12,6 +12,7 @@ import (
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/runtime"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -38,11 +39,13 @@ Example:
internal.LocalCmdFlag, internal.LocalCmdFlag,
internal.RemoteUserFlag, internal.RemoteUserFlag,
internal.TtyFlag, internal.TtyFlag,
internal.OfflineFlag,
}, },
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {

View File

@ -20,6 +20,7 @@ var appConfigCommand = cli.Command{
ArgsUsage: "<domain>", ArgsUsage: "<domain>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {

View File

@ -12,6 +12,7 @@ import (
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/container" "coopcloud.tech/abra/pkg/container"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/runtime"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
@ -27,6 +28,7 @@ var appCpCommand = cli.Command{
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.NoInputFlag, internal.NoInputFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "Copy files to/from a running app service", Usage: "Copy files to/from a running app service",
@ -42,7 +44,8 @@ And if you want to copy that file back to your current working directory locally
abra app cp <domain> app:/myfile.txt . abra app cp <domain> app:/myfile.txt .
`, `,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {

View File

@ -10,6 +10,7 @@ import (
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/runtime"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
@ -18,7 +19,6 @@ import (
"coopcloud.tech/abra/pkg/git" "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/lint" "coopcloud.tech/abra/pkg/lint"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/runtime"
"coopcloud.tech/abra/pkg/upstream/stack" "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
@ -38,6 +38,7 @@ var appDeployCommand = cli.Command{
internal.ChaosFlag, internal.ChaosFlag,
internal.NoDomainChecksFlag, internal.NoDomainChecksFlag,
internal.DontWaitConvergeFlag, internal.DontWaitConvergeFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `
@ -53,9 +54,9 @@ recipes.
`, `,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
stackName := app.StackName() stackName := app.StackName()
conf := runtime.New()
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {
@ -94,7 +95,7 @@ recipes.
version := deployedVersion version := deployedVersion
if version == "unknown" && !internal.Chaos { if version == "unknown" && !internal.Chaos {
catl, err := recipe.ReadRecipeCatalogue() catl, err := recipe.ReadRecipeCatalogue(conf)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -51,12 +51,13 @@ the logs.
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.WatchFlag, internal.WatchFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
conf := runtime.New() app := internal.ValidateApp(c, conf)
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {

View File

@ -11,6 +11,7 @@ import (
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/runtime"
"coopcloud.tech/tagcmp" "coopcloud.tech/tagcmp"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -79,9 +80,12 @@ can take some time.
statusFlag, statusFlag,
listAppServerFlag, listAppServerFlag,
recipeFlag, recipeFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
conf := runtime.New(runtime.WithOffline(internal.Offline))
appFiles, err := config.LoadAppFiles(listAppServer) appFiles, err := config.LoadAppFiles(listAppServer)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
@ -109,7 +113,7 @@ can take some time.
logrus.Fatal(err) logrus.Fatal(err)
} }
catl, err = recipe.ReadRecipeCatalogue() catl, err = recipe.ReadRecipeCatalogue(conf)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -79,11 +79,13 @@ var appLogsCommand = cli.Command{
internal.StdErrOnlyFlag, internal.StdErrOnlyFlag,
internal.SinceLogsFlag, internal.SinceLogsFlag,
internal.DebugFlag, internal.DebugFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c, runtime.WithEnsureRecipeExists(false)) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {

View File

@ -1,8 +1,23 @@
package app package app
import ( import (
"fmt"
"path"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"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/runtime"
"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"
) )
@ -38,9 +53,199 @@ var appNewCommand = cli.Command{
internal.DomainFlag, internal.DomainFlag,
internal.PassFlag, internal.PassFlag,
internal.SecretsFlag, internal.SecretsFlag,
internal.OfflineFlag,
},
Before: internal.SubCommandBefore,
ArgsUsage: "[<recipe>]",
Action: func(c *cli.Context) error {
conf := runtime.New(runtime.WithOffline(internal.Offline))
recipe := internal.ValidateRecipeWithPrompt(c, conf)
if err := recipePkg.EnsureUpToDate(recipe.Name, conf); err != nil {
logrus.Fatal(err)
}
if err := ensureServerFlag(); err != nil {
logrus.Fatal(err)
}
if err := ensureDomainFlag(recipe, internal.NewAppServer); err != nil {
logrus.Fatal(err)
}
sanitisedAppName := config.SanitiseAppName(internal.Domain)
logrus.Debugf("%s sanitised as %s for new app", internal.Domain, sanitisedAppName)
if err := config.TemplateAppEnvSample(
recipe.Name,
internal.Domain,
internal.NewAppServer,
internal.Domain,
); err != nil {
logrus.Fatal(err)
}
if err := promptForSecrets(internal.Domain); err != nil {
logrus.Fatal(err)
}
cl, err := client.New(internal.NewAppServer)
if err != nil {
logrus.Fatal(err)
}
var secrets AppSecrets
var secretTable *jsontable.JSONTable
if internal.Secrets {
secrets, err := createSecrets(cl, sanitisedAppName)
if err != nil {
logrus.Fatal(err)
}
secretCols := []string{"Name", "Value"}
secretTable = formatter.CreateTable(secretCols)
for secret := range secrets {
secretTable.Append([]string{secret, secrets[secret]})
}
}
if internal.NewAppServer == "default" {
internal.NewAppServer = "local"
}
tableCol := []string{"server", "recipe", "domain"}
table := formatter.CreateTable(tableCol)
table.Append([]string{internal.NewAppServer, recipe.Name, internal.Domain})
fmt.Println("")
fmt.Println(fmt.Sprintf("A new %s app has been created! Here is an overview:", recipe.Name))
fmt.Println("")
table.Render()
fmt.Println("")
fmt.Println("You can configure this app by running the following:")
fmt.Println(fmt.Sprintf("\n abra app config %s", internal.Domain))
fmt.Println("")
fmt.Println("You can deploy this app by running the following:")
fmt.Println(fmt.Sprintf("\n abra app deploy %s", internal.Domain))
fmt.Println("")
if len(secrets) > 0 {
fmt.Println("Here are your generated secrets:")
fmt.Println("")
secretTable.Render()
fmt.Println("")
logrus.Warn("generated secrets are not shown again, please take note of them *now*")
}
return nil
}, },
Before: internal.SubCommandBefore,
ArgsUsage: "[<recipe>]",
Action: internal.NewAction,
BashComplete: autocomplete.RecipeNameComplete, BashComplete: autocomplete.RecipeNameComplete,
} }
// AppSecrets represents all app secrest
type AppSecrets map[string]string
// createSecrets creates all secrets for a new app.
func createSecrets(cl *dockerClient.Client, sanitisedAppName string) (AppSecrets, error) {
appEnvPath := path.Join(
config.ABRA_DIR,
"servers",
internal.NewAppServer,
fmt.Sprintf("%s.env", internal.Domain),
)
appEnv, err := config.ReadEnv(appEnvPath)
if err != nil {
return nil, err
}
secretEnvVars := secret.ReadSecretEnvVars(appEnv)
secrets, err := secret.GenerateSecrets(cl, secretEnvVars, sanitisedAppName, internal.NewAppServer)
if err != nil {
return nil, err
}
if internal.Pass {
for secretName := range secrets {
secretValue := secrets[secretName]
if err := secret.PassInsertSecret(
secretValue,
secretName,
internal.Domain,
internal.NewAppServer,
); err != nil {
return nil, err
}
}
}
return secrets, nil
}
// ensureDomainFlag checks if the domain flag was used. if not, asks the user for it/
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", recipe.Name, server),
}
if err := survey.AskOne(prompt, &internal.Domain); err != nil {
return err
}
}
if internal.Domain == "" {
return fmt.Errorf("no domain provided")
}
return nil
}
// promptForSecrets asks if we should generate secrets for a new app.
func promptForSecrets(appName string) error {
app, err := app.Get(appName)
if err != nil {
return err
}
secretEnvVars := secret.ReadSecretEnvVars(app.Env)
if len(secretEnvVars) == 0 {
logrus.Debugf("%s has no secrets to generate, skipping...", app.Recipe)
return nil
}
if !internal.Secrets && !internal.NoInput {
prompt := &survey.Confirm{
Message: "Generate app secrets?",
}
if err := survey.AskOne(prompt, &internal.Secrets); err != nil {
return err
}
}
return nil
}
// ensureServerFlag checks if the server flag was used. if not, asks the user for it.
func ensureServerFlag() error {
servers, err := config.GetServers()
if err != nil {
return err
}
if internal.NewAppServer == "" && !internal.NoInput {
prompt := &survey.Select{
Message: "Select app server:",
Options: servers,
}
if err := survey.AskOne(prompt, &internal.NewAppServer); err != nil {
return err
}
}
if internal.NewAppServer == "" {
return fmt.Errorf("no server provided")
}
return nil
}

View File

@ -10,6 +10,7 @@ import (
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/runtime"
"coopcloud.tech/abra/pkg/service" "coopcloud.tech/abra/pkg/service"
stack "coopcloud.tech/abra/pkg/upstream/stack" stack "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/buger/goterm" "github.com/buger/goterm"
@ -29,11 +30,13 @@ var appPsCommand = cli.Command{
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.WatchFlag, internal.WatchFlag,
internal.DebugFlag, internal.DebugFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {

View File

@ -8,6 +8,7 @@ import (
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/runtime"
stack "coopcloud.tech/abra/pkg/upstream/stack" stack "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -43,11 +44,13 @@ flag.
internal.ForceFlag, internal.ForceFlag,
internal.DebugFlag, internal.DebugFlag,
internal.NoInputFlag, internal.NoInputFlag,
internal.OfflineFlag,
}, },
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
if !internal.Force && !internal.NoInput { if !internal.Force && !internal.NoInput {
response := false response := false

View File

@ -8,6 +8,7 @@ import (
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/runtime"
upstream "coopcloud.tech/abra/pkg/upstream/service" upstream "coopcloud.tech/abra/pkg/upstream/service"
stack "coopcloud.tech/abra/pkg/upstream/stack" stack "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -21,12 +22,14 @@ var appRestartCommand = cli.Command{
ArgsUsage: "<domain>", ArgsUsage: "<domain>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: `This command restarts a service within a deployed app.`, Description: `This command restarts a service within a deployed app.`,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
serviceNameShort := c.Args().Get(1) serviceNameShort := c.Args().Get(1)
if serviceNameShort == "" { if serviceNameShort == "" {

View File

@ -35,6 +35,7 @@ var appRestoreCommand = cli.Command{
ArgsUsage: "<domain> <service> <file>", ArgsUsage: "<domain> <service> <file>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
@ -55,8 +56,8 @@ Example:
abra app restore example.com app ~/.abra/backups/example_com_app_609341138.tar.gz abra app restore example.com app ~/.abra/backups/example_com_app_609341138.tar.gz
`, `,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
conf := runtime.New() app := internal.ValidateApp(c, conf)
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {

View File

@ -31,6 +31,7 @@ var appRollbackCommand = cli.Command{
internal.ChaosFlag, internal.ChaosFlag,
internal.NoDomainChecksFlag, internal.NoDomainChecksFlag,
internal.DontWaitConvergeFlag, internal.DontWaitConvergeFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `
@ -48,9 +49,9 @@ recipes.
`, `,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
stackName := app.StackName() stackName := app.StackName()
conf := runtime.New()
if !internal.Chaos { if !internal.Chaos {
if err := recipe.EnsureUpToDate(app.Recipe, conf); err != nil { if err := recipe.EnsureUpToDate(app.Recipe, conf); err != nil {
@ -83,7 +84,7 @@ recipes.
logrus.Fatalf("%s is not deployed?", app.Name) logrus.Fatalf("%s is not deployed?", app.Name)
} }
catl, err := recipe.ReadRecipeCatalogue() catl, err := recipe.ReadRecipeCatalogue(conf)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -9,6 +9,7 @@ import (
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
containerPkg "coopcloud.tech/abra/pkg/container" containerPkg "coopcloud.tech/abra/pkg/container"
"coopcloud.tech/abra/pkg/runtime"
"coopcloud.tech/abra/pkg/upstream/container" "coopcloud.tech/abra/pkg/upstream/container"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -37,13 +38,15 @@ var appRunCommand = cli.Command{
internal.DebugFlag, internal.DebugFlag,
noTTYFlag, noTTYFlag,
userFlag, userFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
ArgsUsage: "<domain> <service> <args>...", ArgsUsage: "<domain> <service> <args>...",
Usage: "Run a command in a service container", Usage: "Run a command in a service container",
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
if len(c.Args()) < 2 { if len(c.Args()) < 2 {
internal.ShowSubcommandHelpAndError(c, errors.New("no <service> provided?")) internal.ShowSubcommandHelpAndError(c, errors.New("no <service> provided?"))

View File

@ -12,6 +12,7 @@ import (
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/runtime"
"coopcloud.tech/abra/pkg/secret" "coopcloud.tech/abra/pkg/secret"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
@ -42,11 +43,13 @@ var appSecretGenerateCommand = cli.Command{
internal.DebugFlag, internal.DebugFlag,
allSecretsFlag, allSecretsFlag,
internal.PassFlag, internal.PassFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {
@ -121,6 +124,7 @@ var appSecretInsertCommand = cli.Command{
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.PassFlag, internal.PassFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
ArgsUsage: "<domain> <secret-name> <version> <data>", ArgsUsage: "<domain> <secret-name> <version> <data>",
@ -138,7 +142,8 @@ Example:
`, `,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {
@ -198,6 +203,7 @@ var appSecretRmCommand = cli.Command{
internal.NoInputFlag, internal.NoInputFlag,
rmAllSecretsFlag, rmAllSecretsFlag,
internal.PassRemoveFlag, internal.PassRemoveFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
ArgsUsage: "<domain> [<secret-name>]", ArgsUsage: "<domain> [<secret-name>]",
@ -210,7 +216,8 @@ Example:
abra app secret remove myapp db_pass abra app secret remove myapp db_pass
`, `,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
secrets := secret.ReadSecretEnvVars(app.Env) secrets := secret.ReadSecretEnvVars(app.Env)
if c.Args().Get(1) != "" && rmAllSecrets { if c.Args().Get(1) != "" && rmAllSecrets {
@ -288,11 +295,13 @@ var appSecretLsCommand = cli.Command{
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "List all secrets", Usage: "List all secrets",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
secrets := secret.ReadSecretEnvVars(app.Env) secrets := secret.ReadSecretEnvVars(app.Env)
tableCol := []string{"Name", "Version", "Generated Name", "Created On Server"} tableCol := []string{"Name", "Version", "Generated Name", "Created On Server"}

View File

@ -9,6 +9,7 @@ import (
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/runtime"
"coopcloud.tech/abra/pkg/service" "coopcloud.tech/abra/pkg/service"
stack "coopcloud.tech/abra/pkg/upstream/stack" stack "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -23,11 +24,13 @@ var appServicesCommand = cli.Command{
ArgsUsage: "<domain>", ArgsUsage: "<domain>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {

View File

@ -10,6 +10,7 @@ import (
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/runtime"
stack "coopcloud.tech/abra/pkg/upstream/stack" stack "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
@ -86,6 +87,7 @@ var appUndeployCommand = cli.Command{
internal.DebugFlag, internal.DebugFlag,
internal.NoInputFlag, internal.NoInputFlag,
pruneFlag, pruneFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "Undeploy an app", Usage: "Undeploy an app",
@ -99,8 +101,10 @@ any previously attached volumes as eligible for pruning once undeployed.
Passing "-p/--prune" does not remove those volumes. Passing "-p/--prune" does not remove those volumes.
`, `,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
stackName := app.StackName() stackName := app.StackName()
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)

View File

@ -30,6 +30,7 @@ var appUpgradeCommand = cli.Command{
internal.ChaosFlag, internal.ChaosFlag,
internal.NoDomainChecksFlag, internal.NoDomainChecksFlag,
internal.DontWaitConvergeFlag, internal.DontWaitConvergeFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `
@ -51,9 +52,9 @@ including unstaged changes and can be useful for live hacking and testing new
recipes. recipes.
`, `,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
stackName := app.StackName() stackName := app.StackName()
conf := runtime.New()
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {
@ -86,7 +87,7 @@ recipes.
logrus.Fatalf("%s is not deployed?", app.Name) logrus.Fatalf("%s is not deployed?", app.Name)
} }
catl, err := recipe.ReadRecipeCatalogue() catl, err := recipe.ReadRecipeCatalogue(conf)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -38,6 +38,7 @@ var appVersionCommand = cli.Command{
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.NoInputFlag, internal.NoInputFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "Show app versions", Usage: "Show app versions",
@ -47,9 +48,9 @@ the individual image names, tags and digests. But also the Co-op Cloud recipe
version. version.
`, `,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
stackName := app.StackName() stackName := app.StackName()
conf := runtime.New()
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {

View File

@ -7,6 +7,7 @@ import (
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/runtime"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -19,12 +20,14 @@ var appVolumeListCommand = cli.Command{
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.NoInputFlag, internal.NoInputFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "List volumes associated with an app", Usage: "List volumes associated with an app",
BashComplete: autocomplete.AppNameComplete, BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {
@ -80,10 +83,12 @@ Passing "--force/-f" will select all volumes for removal. Be careful.
internal.DebugFlag, internal.DebugFlag,
internal.NoInputFlag, internal.NoInputFlag,
internal.ForceFlag, internal.ForceFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
app := internal.ValidateApp(c, conf)
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {

View File

@ -29,6 +29,7 @@ var catalogueGenerateCommand = cli.Command{
internal.PublishFlag, internal.PublishFlag,
internal.DryFlag, internal.DryFlag,
internal.SkipUpdatesFlag, internal.SkipUpdatesFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `
@ -54,18 +55,18 @@ keys configured on your account.
`, `,
ArgsUsage: "[<recipe>]", ArgsUsage: "[<recipe>]",
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
conf := runtime.New(runtime.WithOffline(internal.Offline))
recipeName := c.Args().First() recipeName := c.Args().First()
conf := runtime.New()
if recipeName != "" { if recipeName != "" {
internal.ValidateRecipe(c) internal.ValidateRecipe(c, conf)
} }
if err := catalogue.EnsureUpToDate(); err != nil { if err := catalogue.EnsureUpToDate(conf); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
repos, err := recipe.ReadReposMetadata() repos, err := recipe.ReadReposMetadata(conf)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -136,7 +137,7 @@ keys configured on your account.
logrus.Fatal(err) logrus.Fatal(err)
} }
} else { } else {
catlFS, err := recipe.ReadRecipeCatalogue() catlFS, err := recipe.ReadRecipeCatalogue(conf)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -253,6 +253,16 @@ var DebugFlag = &cli.BoolFlag{
Usage: "Show DEBUG messages", Usage: "Show DEBUG messages",
} }
// Offline stores the variable from OfflineFlag.
var Offline bool
// DebugFlag turns on/off offline mode.
var OfflineFlag = &cli.BoolFlag{
Name: "offline, o",
Destination: &Offline,
Usage: "Prefer offline & filesystem access when possible",
}
// MachineReadable stores the variable from MachineReadableFlag // MachineReadable stores the variable from MachineReadableFlag
var MachineReadable bool var MachineReadable bool
@ -270,7 +280,7 @@ var RC bool
var RCFlag = &cli.BoolFlag{ var RCFlag = &cli.BoolFlag{
Name: "rc, r", Name: "rc, r",
Destination: &RC, Destination: &RC,
Usage: "Insatll the latest release candidate", Usage: "Install the latest release candidate",
} }
var Major bool var Major bool

View File

@ -1,199 +0,0 @@
package internal
import (
"fmt"
"path"
"coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/client"
"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/runtime"
"coopcloud.tech/abra/pkg/secret"
"github.com/AlecAivazis/survey/v2"
dockerClient "github.com/docker/docker/client"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
// AppSecrets represents all app secrest
type AppSecrets map[string]string
// RecipeName is used for configuring recipe name programmatically
var RecipeName string
// createSecrets creates all secrets for a new app.
func createSecrets(cl *dockerClient.Client, sanitisedAppName string) (AppSecrets, error) {
appEnvPath := path.Join(config.ABRA_DIR, "servers", NewAppServer, fmt.Sprintf("%s.env", Domain))
appEnv, err := config.ReadEnv(appEnvPath)
if err != nil {
return nil, err
}
secretEnvVars := secret.ReadSecretEnvVars(appEnv)
secrets, err := secret.GenerateSecrets(cl, secretEnvVars, sanitisedAppName, NewAppServer)
if err != nil {
return nil, err
}
if Pass {
for secretName := range secrets {
secretValue := secrets[secretName]
if err := secret.PassInsertSecret(secretValue, secretName, Domain, NewAppServer); err != nil {
return nil, err
}
}
}
return secrets, nil
}
// ensureDomainFlag checks if the domain flag was used. if not, asks the user for it/
func ensureDomainFlag(recipe recipe.Recipe, server string) error {
if Domain == "" && !NoInput {
prompt := &survey.Input{
Message: "Specify app domain",
Default: fmt.Sprintf("%s.%s", recipe.Name, server),
}
if err := survey.AskOne(prompt, &Domain); err != nil {
return err
}
}
if Domain == "" {
return fmt.Errorf("no domain provided")
}
return nil
}
// promptForSecrets asks if we should generate secrets for a new app.
func promptForSecrets(appName string) error {
app, err := app.Get(appName)
if err != nil {
return err
}
secretEnvVars := secret.ReadSecretEnvVars(app.Env)
if len(secretEnvVars) == 0 {
logrus.Debugf("%s has no secrets to generate, skipping...", app.Recipe)
return nil
}
if !Secrets && !NoInput {
prompt := &survey.Confirm{
Message: "Generate app secrets?",
}
if err := survey.AskOne(prompt, &Secrets); err != nil {
return err
}
}
return nil
}
// ensureServerFlag checks if the server flag was used. if not, asks the user for it.
func ensureServerFlag() error {
servers, err := config.GetServers()
if err != nil {
return err
}
if NewAppServer == "" && !NoInput {
prompt := &survey.Select{
Message: "Select app server:",
Options: servers,
}
if err := survey.AskOne(prompt, &NewAppServer); err != nil {
return err
}
}
if NewAppServer == "" {
return fmt.Errorf("no server provided")
}
return nil
}
// NewAction is the new app creation logic
func NewAction(c *cli.Context) error {
recipe := ValidateRecipeWithPrompt(c)
conf := runtime.New()
if err := recipePkg.EnsureUpToDate(recipe.Name, conf); err != nil {
logrus.Fatal(err)
}
if err := ensureServerFlag(); err != nil {
logrus.Fatal(err)
}
if err := ensureDomainFlag(recipe, NewAppServer); err != nil {
logrus.Fatal(err)
}
sanitisedAppName := config.SanitiseAppName(Domain)
logrus.Debugf("%s sanitised as %s for new app", Domain, sanitisedAppName)
if err := config.TemplateAppEnvSample(recipe.Name, Domain, NewAppServer, Domain); err != nil {
logrus.Fatal(err)
}
if err := promptForSecrets(Domain); err != nil {
logrus.Fatal(err)
}
cl, err := client.New(NewAppServer)
if err != nil {
logrus.Fatal(err)
}
var secrets AppSecrets
var secretTable *jsontable.JSONTable
if Secrets {
secrets, err := createSecrets(cl, sanitisedAppName)
if err != nil {
logrus.Fatal(err)
}
secretCols := []string{"Name", "Value"}
secretTable = formatter.CreateTable(secretCols)
for secret := range secrets {
secretTable.Append([]string{secret, secrets[secret]})
}
}
if NewAppServer == "default" {
NewAppServer = "local"
}
tableCol := []string{"server", "recipe", "domain"}
table := formatter.CreateTable(tableCol)
table.Append([]string{NewAppServer, recipe.Name, Domain})
fmt.Println("")
fmt.Println(fmt.Sprintf("A new %s app has been created! Here is an overview:", recipe.Name))
fmt.Println("")
table.Render()
fmt.Println("")
fmt.Println("You can configure this app by running the following:")
fmt.Println(fmt.Sprintf("\n abra app config %s", Domain))
fmt.Println("")
fmt.Println("You can deploy this app by running the following:")
fmt.Println(fmt.Sprintf("\n abra app deploy %s", Domain))
fmt.Println("")
if len(secrets) > 0 {
fmt.Println("Here are your generated secrets:")
fmt.Println("")
secretTable.Render()
fmt.Println("")
logrus.Warn("generated secrets are not shown again, please take note of them *now*")
}
return nil
}

View File

@ -15,13 +15,9 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
) )
// AppName is used for configuring app name programmatically
var AppName string
// ValidateRecipe ensures the recipe arg is valid. // ValidateRecipe ensures the recipe arg is valid.
func ValidateRecipe(c *cli.Context, opts ...runtime.Option) recipe.Recipe { func ValidateRecipe(c *cli.Context, conf *runtime.Config) recipe.Recipe {
recipeName := c.Args().First() recipeName := c.Args().First()
conf := runtime.New(opts...)
if recipeName == "" { if recipeName == "" {
ShowSubcommandHelpAndError(c, errors.New("no recipe name provided")) ShowSubcommandHelpAndError(c, errors.New("no recipe name provided"))
@ -53,14 +49,13 @@ func ValidateRecipe(c *cli.Context, opts ...runtime.Option) recipe.Recipe {
// ValidateRecipeWithPrompt ensures a recipe argument is present before // ValidateRecipeWithPrompt ensures a recipe argument is present before
// validating, asking for input if required. // validating, asking for input if required.
func ValidateRecipeWithPrompt(c *cli.Context, opts ...runtime.Option) recipe.Recipe { func ValidateRecipeWithPrompt(c *cli.Context, conf *runtime.Config) recipe.Recipe {
recipeName := c.Args().First() recipeName := c.Args().First()
conf := runtime.New(opts...)
if recipeName == "" && !NoInput { if recipeName == "" && !NoInput {
var recipes []string var recipes []string
catl, err := recipe.ReadRecipeCatalogue() catl, err := recipe.ReadRecipeCatalogue(conf)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -94,11 +89,6 @@ func ValidateRecipeWithPrompt(c *cli.Context, opts ...runtime.Option) recipe.Rec
} }
} }
if RecipeName != "" {
recipeName = RecipeName
logrus.Debugf("programmatically setting recipe name to %s", recipeName)
}
if recipeName == "" { if recipeName == "" {
ShowSubcommandHelpAndError(c, errors.New("no recipe name provided")) ShowSubcommandHelpAndError(c, errors.New("no recipe name provided"))
} }
@ -118,14 +108,8 @@ func ValidateRecipeWithPrompt(c *cli.Context, opts ...runtime.Option) recipe.Rec
} }
// ValidateApp ensures the app name arg is valid. // ValidateApp ensures the app name arg is valid.
func ValidateApp(c *cli.Context, opts ...runtime.Option) config.App { func ValidateApp(c *cli.Context, conf *runtime.Config) config.App {
appName := c.Args().First() appName := c.Args().First()
conf := runtime.New(opts...)
if AppName != "" {
appName = AppName
logrus.Debugf("programmatically setting app name to %s", appName)
}
if appName == "" { if appName == "" {
ShowSubcommandHelpAndError(c, errors.New("no app provided")) ShowSubcommandHelpAndError(c, errors.New("no app provided"))

View File

@ -17,19 +17,20 @@ var recipeFetchCommand = cli.Command{
Description: "Fetchs all recipes without arguments.", Description: "Fetchs all recipes without arguments.",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete, BashComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
conf := runtime.New(runtime.WithOffline(internal.Offline))
recipeName := c.Args().First() recipeName := c.Args().First()
conf := runtime.New()
if recipeName != "" { if recipeName != "" {
internal.ValidateRecipe(c) internal.ValidateRecipe(c, conf)
return nil // ValidateRecipe ensures latest checkout return nil // ValidateRecipe ensures latest checkout
} }
repos, err := recipe.ReadReposMetadata() repos, err := recipe.ReadReposMetadata(conf)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -21,12 +21,13 @@ var recipeLintCommand = cli.Command{
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.OnlyErrorFlag, internal.OnlyErrorFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete, BashComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipe(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
conf := runtime.New() recipe := internal.ValidateRecipe(c, conf)
if err := recipePkg.EnsureUpToDate(recipe.Name, conf); err != nil { if err := recipePkg.EnsureUpToDate(recipe.Name, conf); err != nil {
logrus.Fatal(err) logrus.Fatal(err)

View File

@ -9,6 +9,7 @@ import (
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/runtime"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -29,10 +30,13 @@ var recipeListCommand = cli.Command{
internal.DebugFlag, internal.DebugFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,
patternFlag, patternFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
catl, err := recipe.ReadRecipeCatalogue() conf := runtime.New(runtime.WithOffline(internal.Offline))
catl, err := recipe.ReadRecipeCatalogue(conf)
if err != nil { if err != nil {
logrus.Fatal(err.Error()) logrus.Fatal(err.Error())
} }

View File

@ -36,6 +36,7 @@ var recipeNewCommand = cli.Command{
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.NoInputFlag, internal.NoInputFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "Create a new recipe", Usage: "Create a new recipe",

View File

@ -13,6 +13,7 @@ import (
gitPkg "coopcloud.tech/abra/pkg/git" gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
recipePkg "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/runtime"
"coopcloud.tech/tagcmp" "coopcloud.tech/tagcmp"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
@ -54,11 +55,13 @@ your SSH keys configured on your account.
internal.MinorFlag, internal.MinorFlag,
internal.PatchFlag, internal.PatchFlag,
internal.PublishFlag, internal.PublishFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete, BashComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipeWithPrompt(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
recipe := internal.ValidateRecipeWithPrompt(c, conf)
imagesTmp, err := getImageVersions(recipe) imagesTmp, err := getImageVersions(recipe)
if err != nil { if err != nil {

View File

@ -8,6 +8,7 @@ import (
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/runtime"
"coopcloud.tech/tagcmp" "coopcloud.tech/tagcmp"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
@ -28,6 +29,7 @@ var recipeSyncCommand = cli.Command{
internal.MajorFlag, internal.MajorFlag,
internal.MinorFlag, internal.MinorFlag,
internal.PatchFlag, internal.PatchFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `
@ -41,7 +43,8 @@ auto-generate it for you. The <recipe> configuration will be updated on the
local file system. local file system.
`, `,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipeWithPrompt(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
recipe := internal.ValidateRecipeWithPrompt(c, conf)
mainApp, err := internal.GetMainAppImage(recipe) mainApp, err := internal.GetMainAppImage(recipe)
if err != nil { if err != nil {

View File

@ -68,11 +68,12 @@ You may invoke this command in "wizard" mode and be prompted for input:
internal.MajorFlag, internal.MajorFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,
internal.AllTagsFlag, internal.AllTagsFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipeWithPrompt(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
conf := runtime.New() recipe := internal.ValidateRecipeWithPrompt(c, conf)
if err := recipePkg.EnsureUpToDate(recipe.Name, conf); err != nil { if err := recipePkg.EnsureUpToDate(recipe.Name, conf); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
@ -184,7 +185,7 @@ You may invoke this command in "wizard" mode and be prompted for input:
continue // skip on to the next tag and don't update any compose files continue // skip on to the next tag and don't update any compose files
} }
catlVersions, err := recipePkg.VersionsOfService(recipe.Name, service.Name) catlVersions, err := recipePkg.VersionsOfService(recipe.Name, service.Name, conf)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -5,6 +5,7 @@ import (
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
recipePkg "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/runtime"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -16,13 +17,15 @@ var recipeVersionCommand = cli.Command{
ArgsUsage: "<recipe>", ArgsUsage: "<recipe>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete, BashComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
recipe := internal.ValidateRecipe(c) conf := runtime.New(runtime.WithOffline(internal.Offline))
recipe := internal.ValidateRecipe(c, conf)
catalogue, err := recipePkg.ReadRecipeCatalogue() catalogue, err := recipePkg.ReadRecipeCatalogue(conf)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -22,6 +22,7 @@ var RecordListCommand = cli.Command{
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
internal.DNSProviderFlag, internal.DNSProviderFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `

View File

@ -30,6 +30,7 @@ var RecordNewCommand = cli.Command{
internal.DNSValueFlag, internal.DNSValueFlag,
internal.DNSTTLFlag, internal.DNSTTLFlag,
internal.DNSPriorityFlag, internal.DNSPriorityFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `

View File

@ -27,6 +27,7 @@ var RecordRemoveCommand = cli.Command{
internal.DNSProviderFlag, internal.DNSProviderFlag,
internal.DNSTypeFlag, internal.DNSTypeFlag,
internal.DNSNameFlag, internal.DNSNameFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `

View File

@ -118,6 +118,7 @@ developer machine.
internal.DebugFlag, internal.DebugFlag,
internal.NoInputFlag, internal.NoInputFlag,
localFlag, localFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
ArgsUsage: "<domain>", ArgsUsage: "<domain>",

View File

@ -28,6 +28,7 @@ var serverListCommand = cli.Command{
problemsFilterFlag, problemsFilterFlag,
internal.DebugFlag, internal.DebugFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {

View File

@ -222,6 +222,7 @@ API tokens are read from the environment if specified, e.g.
internal.DebugFlag, internal.DebugFlag,
internal.NoInputFlag, internal.NoInputFlag,
internal.ServerProviderFlag, internal.ServerProviderFlag,
internal.OfflineFlag,
// Capsul // Capsul
internal.CapsulInstanceURLFlag, internal.CapsulInstanceURLFlag,

View File

@ -43,6 +43,7 @@ also be removed. This can result in unwanted data loss if not used carefully.
allFilterFlag, allFilterFlag,
volumesFilterFlag, volumesFilterFlag,
internal.DebugFlag, internal.DebugFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.ServerNameComplete, BashComplete: autocomplete.ServerNameComplete,

View File

@ -120,6 +120,7 @@ like tears in rain.
internal.NoInputFlag, internal.NoInputFlag,
rmServerFlag, rmServerFlag,
internal.ServerProviderFlag, internal.ServerProviderFlag,
internal.OfflineFlag,
// Hetzner // Hetzner
internal.HetznerCloudNameFlag, internal.HetznerCloudNameFlag,

View File

@ -49,6 +49,7 @@ var Notify = cli.Command{
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag, internal.DebugFlag,
majorFlag, majorFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `
@ -57,6 +58,8 @@ catalogue. If a new patch/minor version is available, a notification is
printed. To include major versions use the --major flag. printed. To include major versions use the --major flag.
`, `,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
conf := runtime.New(runtime.WithOffline(internal.Offline))
cl, err := client.New("default") cl, err := client.New("default")
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
@ -75,7 +78,7 @@ printed. To include major versions use the --major flag.
} }
if recipeName != "" { if recipeName != "" {
_, err = getLatestUpgrade(cl, stackName, recipeName) _, err = getLatestUpgrade(cl, stackName, recipeName, conf)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -97,6 +100,7 @@ var UpgradeApp = cli.Command{
internal.ChaosFlag, internal.ChaosFlag,
majorFlag, majorFlag,
allFlag, allFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `
@ -109,13 +113,13 @@ break things. Only apps that are not deployed with "--chaos" are upgraded, to
update chaos deployments use the "--chaos" flag. Use it with care. update chaos deployments use the "--chaos" flag. Use it with care.
`, `,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
conf := runtime.New(runtime.WithOffline(internal.Offline))
cl, err := client.New("default") cl, err := client.New("default")
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
conf := runtime.New()
if !updateAll { if !updateAll {
stackName := c.Args().Get(0) stackName := c.Args().Get(0)
recipeName := c.Args().Get(1) recipeName := c.Args().Get(1)
@ -223,13 +227,14 @@ func getEnv(cl *dockerclient.Client, stackName string) (config.AppEnv, error) {
// getLatestUpgrade returns the latest available version for an app respecting // getLatestUpgrade returns the latest available version for an app respecting
// the "--major" flag if it is newer than the currently deployed version. // the "--major" flag if it is newer than the currently deployed version.
func getLatestUpgrade(cl *dockerclient.Client, stackName string, recipeName string) (string, error) { func getLatestUpgrade(cl *dockerclient.Client, stackName string,
recipeName string, conf *runtime.Config) (string, error) {
deployedVersion, err := getDeployedVersion(cl, stackName, recipeName) deployedVersion, err := getDeployedVersion(cl, stackName, recipeName)
if err != nil { if err != nil {
return "", err return "", err
} }
availableUpgrades, err := getAvailableUpgrades(cl, stackName, recipeName, deployedVersion) availableUpgrades, err := getAvailableUpgrades(cl, stackName, recipeName, deployedVersion, conf)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -272,8 +277,8 @@ func getDeployedVersion(cl *dockerclient.Client, stackName string, recipeName st
// than the deployed version. It only includes major upgrades if the "--major" // than the deployed version. It only includes major upgrades if the "--major"
// flag is set. // flag is set.
func getAvailableUpgrades(cl *dockerclient.Client, stackName string, recipeName string, func getAvailableUpgrades(cl *dockerclient.Client, stackName string, recipeName string,
deployedVersion string) ([]string, error) { deployedVersion string, conf *runtime.Config) ([]string, error) {
catl, err := recipe.ReadRecipeCatalogue() catl, err := recipe.ReadRecipeCatalogue(conf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -389,7 +394,7 @@ func createDeployConfig(recipeName string, stackName string, env config.AppEnv)
// tryUpgrade performs the upgrade if all the requirements are fulfilled. // tryUpgrade performs the upgrade if all the requirements are fulfilled.
func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string, conf *runtime.Config) error { func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string, conf *runtime.Config) error {
if recipeName == "" { if recipeName == "" {
logrus.Debugf("Don't update %s due to missing recipe name", stackName) logrus.Debugf("don't update %s due to missing recipe name", stackName)
return nil return nil
} }
@ -399,7 +404,7 @@ func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string, conf *run
} }
if chaos && !internal.Chaos { if chaos && !internal.Chaos {
logrus.Debugf("Don't update %s due to chaos deployment.", stackName) logrus.Debugf("don't update %s due to chaos deployment", stackName)
return nil return nil
} }
@ -409,17 +414,17 @@ func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string, conf *run
} }
if !updatesEnabled { if !updatesEnabled {
logrus.Debugf("Don't update %s due to disabled auto updates or missing ENABLE_AUTO_UPDATE env.", stackName) logrus.Debugf("don't update %s due to disabled auto updates or missing ENABLE_AUTO_UPDATE env", stackName)
return nil return nil
} }
upgradeVersion, err := getLatestUpgrade(cl, stackName, recipeName) upgradeVersion, err := getLatestUpgrade(cl, stackName, recipeName, conf)
if err != nil { if err != nil {
return err return err
} }
if upgradeVersion == "" { if upgradeVersion == "" {
logrus.Debugf("Don't update %s due to no new version.", stackName) logrus.Debugf("don't update %s due to no new version", stackName)
return nil return nil
} }
@ -429,7 +434,8 @@ func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string, conf *run
} }
// upgrade performs all necessary steps to upgrade an app. // upgrade performs all necessary steps to upgrade an app.
func upgrade(cl *dockerclient.Client, stackName, recipeName, upgradeVersion string, conf *runtime.Config) error { func upgrade(cl *dockerclient.Client, stackName, recipeName,
upgradeVersion string, conf *runtime.Config) error {
env, err := getEnv(cl, stackName) env, err := getEnv(cl, stackName)
if err != nil { if err != nil {
return err return err
@ -455,7 +461,7 @@ func upgrade(cl *dockerclient.Client, stackName, recipeName, upgradeVersion stri
return err return err
} }
logrus.Infof("Upgrade %s (%s) to version %s", stackName, recipeName, upgradeVersion) logrus.Infof("upgrade %s (%s) to version %s", stackName, recipeName, upgradeVersion)
err = stack.RunDeploy(cl, deployOpts, compose, stackName, true) err = stack.RunDeploy(cl, deployOpts, compose, stackName, true)

View File

@ -5,6 +5,7 @@ import (
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/runtime"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@ -27,7 +28,12 @@ func AppNameComplete(c *cli.Context) {
// RecipeNameComplete completes recipe names. // RecipeNameComplete completes recipe names.
func RecipeNameComplete(c *cli.Context) { func RecipeNameComplete(c *cli.Context) {
catl, err := recipe.ReadRecipeCatalogue() // defaults since we can't take arguments here... this means auto-completion
// of recipe names always access the network if e.g. the catalogue needs
// cloning / updating
conf := runtime.New()
catl, err := recipe.ReadRecipeCatalogue(conf)
if err != nil { if err != nil {
logrus.Warn(err) logrus.Warn(err)
} }

View File

@ -8,6 +8,7 @@ import (
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
gitPkg "coopcloud.tech/abra/pkg/git" gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/runtime"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -52,9 +53,13 @@ var CatalogueSkipList = map[string]bool{
} }
// EnsureCatalogue ensures that the catalogue is cloned locally & present. // EnsureCatalogue ensures that the catalogue is cloned locally & present.
func EnsureCatalogue() error { func EnsureCatalogue(conf *runtime.Config) error {
catalogueDir := path.Join(config.ABRA_DIR, "catalogue") catalogueDir := path.Join(config.ABRA_DIR, "catalogue")
if _, err := os.Stat(catalogueDir); err != nil && os.IsNotExist(err) { if _, err := os.Stat(catalogueDir); err != nil && os.IsNotExist(err) {
if conf.Offline {
return fmt.Errorf("no local copy of the catalogue available, network access required")
}
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, config.CATALOGUE_JSON_REPO_NAME) url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, config.CATALOGUE_JSON_REPO_NAME)
if err := gitPkg.Clone(catalogueDir, url); err != nil { if err := gitPkg.Clone(catalogueDir, url); err != nil {
return err return err
@ -68,7 +73,7 @@ func EnsureCatalogue() error {
// EnsureUpToDate ensures that the local catalogue has no unstaged changes as // EnsureUpToDate ensures that the local catalogue has no unstaged changes as
// is up to date. This is useful to run before doing catalogue generation. // is up to date. This is useful to run before doing catalogue generation.
func EnsureUpToDate() error { func EnsureUpToDate(conf *runtime.Config) error {
isClean, err := gitPkg.IsClean(config.CATALOGUE_DIR) isClean, err := gitPkg.IsClean(config.CATALOGUE_DIR)
if err != nil { if err != nil {
return err return err
@ -79,6 +84,11 @@ func EnsureUpToDate() error {
return fmt.Errorf(msg, config.CATALOGUE_DIR) return fmt.Errorf(msg, config.CATALOGUE_DIR)
} }
if conf.Offline {
logrus.Debug("attempting to use local catalogue without access network (\"--offline\")")
return nil
}
repo, err := git.PlainOpen(config.CATALOGUE_DIR) repo, err := git.PlainOpen(config.CATALOGUE_DIR)
if err != nil { if err != nil {
return err return err

View File

@ -9,6 +9,7 @@ import (
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
recipePkg "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/runtime"
"coopcloud.tech/tagcmp" "coopcloud.tech/tagcmp"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
@ -333,7 +334,11 @@ func LintImagePresent(recipe recipe.Recipe) (bool, error) {
} }
func LintHasPublishedVersion(recipe recipe.Recipe) (bool, error) { func LintHasPublishedVersion(recipe recipe.Recipe) (bool, error) {
catl, err := recipePkg.ReadRecipeCatalogue() // defaults since we can't take arguments here... this means this lint rule
// always access the network if e.g. the catalogue needs cloning / updating
conf := runtime.New()
catl, err := recipePkg.ReadRecipeCatalogue(conf)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }

View File

@ -252,13 +252,13 @@ func Get(recipeName string, conf *runtime.Config) (Recipe, error) {
// EnsureExists ensures that a recipe is locally cloned // EnsureExists ensures that a recipe is locally cloned
func EnsureExists(recipeName string, conf *runtime.Config) error { func EnsureExists(recipeName string, conf *runtime.Config) error {
if !conf.EnsureRecipeExists {
return nil
}
recipeDir := path.Join(config.RECIPES_DIR, recipeName) recipeDir := path.Join(config.RECIPES_DIR, recipeName)
if _, err := os.Stat(recipeDir); os.IsNotExist(err) { if _, err := os.Stat(recipeDir); os.IsNotExist(err) {
if conf.Offline {
return fmt.Errorf("no local copy of %s available, network access required", recipeName)
}
logrus.Debugf("%s does not exist, attemmpting to clone", recipeDir) logrus.Debugf("%s does not exist, attemmpting to clone", recipeDir)
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, recipeName) url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, recipeName)
if err := gitPkg.Clone(recipeDir, url); err != nil { if err := gitPkg.Clone(recipeDir, url); err != nil {
@ -339,10 +339,6 @@ func EnsureVersion(recipeName, version string) error {
// EnsureLatest makes sure the latest commit is checked out for a local recipe repository // EnsureLatest makes sure the latest commit is checked out for a local recipe repository
func EnsureLatest(recipeName string, conf *runtime.Config) error { func EnsureLatest(recipeName string, conf *runtime.Config) error {
if !conf.EnsureRecipeLatest {
return nil
}
recipeDir := path.Join(config.RECIPES_DIR, recipeName) recipeDir := path.Join(config.RECIPES_DIR, recipeName)
isClean, err := gitPkg.IsClean(recipeDir) isClean, err := gitPkg.IsClean(recipeDir)
@ -588,10 +584,6 @@ func GetStringInBetween(recipeName, str, start, end string) (result string, err
// EnsureUpToDate ensures that the local repo is synced to the remote // EnsureUpToDate ensures that the local repo is synced to the remote
func EnsureUpToDate(recipeName string, conf *runtime.Config) error { func EnsureUpToDate(recipeName string, conf *runtime.Config) error {
if !conf.EnsureRecipeLatest {
return nil
}
recipeDir := path.Join(config.RECIPES_DIR, recipeName) recipeDir := path.Join(config.RECIPES_DIR, recipeName)
isClean, err := gitPkg.IsClean(recipeDir) isClean, err := gitPkg.IsClean(recipeDir)
@ -604,6 +596,11 @@ func EnsureUpToDate(recipeName string, conf *runtime.Config) error {
return fmt.Errorf(msg, recipeName, recipeDir) return fmt.Errorf(msg, recipeName, recipeDir)
} }
if conf.Offline {
logrus.Debug("attempting to use local recipe without access network (\"--offline\")")
return nil
}
repo, err := git.PlainOpen(recipeDir) repo, err := git.PlainOpen(recipeDir)
if err != nil { if err != nil {
return fmt.Errorf("unable to open %s: %s", recipeDir, err) return fmt.Errorf("unable to open %s: %s", recipeDir, err)
@ -659,14 +656,14 @@ func EnsureUpToDate(recipeName string, conf *runtime.Config) error {
} }
// ReadRecipeCatalogue reads the recipe catalogue. // ReadRecipeCatalogue reads the recipe catalogue.
func ReadRecipeCatalogue() (RecipeCatalogue, error) { func ReadRecipeCatalogue(conf *runtime.Config) (RecipeCatalogue, error) {
recipes := make(RecipeCatalogue) recipes := make(RecipeCatalogue)
if err := catalogue.EnsureCatalogue(); err != nil { if err := catalogue.EnsureCatalogue(conf); err != nil {
return nil, err return nil, err
} }
if err := catalogue.EnsureUpToDate(); err != nil { if err := catalogue.EnsureUpToDate(conf); err != nil {
return nil, err return nil, err
} }
@ -694,10 +691,10 @@ func readRecipeCatalogueFS(target interface{}) error {
} }
// VersionsOfService lists the version of a service. // VersionsOfService lists the version of a service.
func VersionsOfService(recipe, serviceName string) ([]string, error) { func VersionsOfService(recipe, serviceName string, conf *runtime.Config) ([]string, error) {
var versions []string var versions []string
catalogue, err := ReadRecipeCatalogue() catalogue, err := ReadRecipeCatalogue(conf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -724,7 +721,7 @@ func VersionsOfService(recipe, serviceName string) ([]string, error) {
// GetRecipeMeta retrieves the recipe metadata from the recipe catalogue. // GetRecipeMeta retrieves the recipe metadata from the recipe catalogue.
func GetRecipeMeta(recipeName string, conf *runtime.Config) (RecipeMeta, error) { func GetRecipeMeta(recipeName string, conf *runtime.Config) (RecipeMeta, error) {
catl, err := ReadRecipeCatalogue() catl, err := ReadRecipeCatalogue(conf)
if err != nil { if err != nil {
return RecipeMeta{}, err return RecipeMeta{}, err
} }
@ -821,7 +818,11 @@ type InternalTracker struct {
type RepoCatalogue map[string]RepoMeta type RepoCatalogue map[string]RepoMeta
// ReadReposMetadata retrieves coop-cloud/... repo metadata from Gitea. // ReadReposMetadata retrieves coop-cloud/... repo metadata from Gitea.
func ReadReposMetadata() (RepoCatalogue, error) { func ReadReposMetadata(conf *runtime.Config) (RepoCatalogue, error) {
if conf.Offline {
return nil, fmt.Errorf("network access required to query recipes metadata")
}
reposMeta := make(RepoCatalogue) reposMeta := make(RepoCatalogue)
pageIdx := 1 pageIdx := 1
@ -1002,6 +1003,10 @@ func GetRecipeCatalogueVersions(recipeName string, catl RecipeCatalogue) ([]stri
// UpdateRepositories clones and updates all recipe repositories locally. // UpdateRepositories clones and updates all recipe repositories locally.
func UpdateRepositories(repos RepoCatalogue, recipeName string, conf *runtime.Config) error { func UpdateRepositories(repos RepoCatalogue, recipeName string, conf *runtime.Config) error {
if conf.Offline {
return fmt.Errorf("network access required to update recipes")
}
var barLength int var barLength int
if recipeName != "" { if recipeName != "" {
barLength = 1 barLength = 1

28
pkg/runtime/config.go Normal file
View File

@ -0,0 +1,28 @@
package runtime
import "github.com/sirupsen/logrus"
type Config struct {
Offline bool
}
type Option func(c *Config)
func New(opts ...Option) *Config {
conf := &Config{Offline: false}
for _, optFunc := range opts {
optFunc(conf)
}
return conf
}
func WithOffline(offline bool) Option {
return func(c *Config) {
if offline {
logrus.Debugf("runtime config: attempting to run in offline mode")
}
c.Offline = offline
}
}

View File

@ -1,62 +0,0 @@
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 modifies a Config. The convention for passing an Option to a function
// 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 Config.
func New(opts ...Option) *Config {
conf := &Config{
EnsureRecipeExists: true,
EnsureRecipeLatest: 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
}
}