package app import ( "errors" "fmt" "path" "coopcloud.tech/abra/catalogue" abraFormatter "coopcloud.tech/abra/cli/formatter" "coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/config" "coopcloud.tech/abra/secret" "github.com/AlecAivazis/survey/v2" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" ) type Secrets map[string]string var appNewDescription = ` This command takes an app recipe and uses it to create a new app. This new app configuration is stored in your ~/.abra directory under the appropriate server. This command does not deploy your app for you. You will need to run "abra app deploy " to do so. You can see what apps can be created (i.e. values for the argument) by running "abra recipe ls". Passing the "--secrets/-S" flag will automatically generate secrets for your app and store them encrypted at rest on the chosen target server. These generated secrets are only visible at generation time, so please take care to store them somewhere safe. You can use the "--pass/-P" to store these generated passwords locally in a pass store (see passwordstore.org for more). The pass command must be available on your $PATH. ` var appNewCommand = &cli.Command{ Name: "new", Usage: "Create a new app", Description: appNewDescription, Flags: []cli.Flag{ internal.ServerFlag, internal.DomainFlag, internal.AppNameFlag, internal.PassFlag, internal.SecretsFlag, }, ArgsUsage: "", Action: action, } func appLookup(appType string) (catalogue.App, error) { catl, err := catalogue.ReadAppsCatalogue() if err != nil { return catalogue.App{}, err } app, ok := catl[appType] if !ok { return catalogue.App{}, fmt.Errorf("app type does not exist: %s", appType) } if err := app.EnsureExists(); err != nil { return catalogue.App{}, err } return app, nil } // ensureDomainFlag checks if the domain flag was used. if not, asks the user for it func ensureDomainFlag() error { if internal.Domain == "" { prompt := &survey.Input{ Message: "Specify app domain", } if err := survey.AskOne(prompt, &internal.Domain); err != nil { return err } } return nil } // ensureServerFlag checks if the server flag was used. if not, asks the user for it func ensureServerFlag() error { appFiles, err := config.LoadAppFiles(internal.Server) if err != nil { return err } servers := appFiles.GetServers() if internal.Server == "" { prompt := &survey.Select{ Message: "Select app server:", Options: servers, } if err := survey.AskOne(prompt, &internal.Server); err != nil { return err } } return nil } // ensureServerFlag checks if the AppName flag was used. if not, asks the user for it func ensureAppNameFlag() error { if internal.AppName == "" { prompt := &survey.Input{ Message: "Specify app name:", Default: config.SanitiseAppName(internal.Domain), } if err := survey.AskOne(prompt, &internal.AppName); err != nil { return err } } return nil } func createSecrets(sanitisedAppName string) (Secrets, error) { appEnvPath := path.Join(config.ABRA_DIR, "servers", internal.Server, fmt.Sprintf("%s.env", sanitisedAppName)) appEnv, err := config.ReadEnv(appEnvPath) if err != nil { return nil, err } secretEnvVars := secret.ReadSecretEnvVars(appEnv) secrets, err := secret.GenerateSecrets(secretEnvVars, sanitisedAppName, internal.Server) if err != nil { return nil, err } if internal.Pass { for secretName := range secrets { secretValue := secrets[secretName] if err := secret.PassInsertSecret(secretValue, secretName, sanitisedAppName, internal.Server); err != nil { return nil, err } } } return secrets, nil } func action(c *cli.Context) error { appType := c.Args().First() if appType == "" { internal.ShowSubcommandHelpAndError(c, errors.New("no app type provided")) } if err := config.EnsureAbraDirExists(); err != nil { logrus.Fatal(err) } app, err := appLookup(appType) if err != nil { logrus.Fatal(err) } latestVersion := app.LatestVersion() if err := app.EnsureVersion(latestVersion); err != nil { logrus.Fatal(err) } // These use the flag from internal.x to check and edit so no need to return anything if err := ensureServerFlag(); err != nil { logrus.Fatal(err) } if err := ensureDomainFlag(); err != nil { logrus.Fatal(err) } if err := ensureAppNameFlag(); err != nil { logrus.Fatal(err) } sanitisedAppName := config.SanitiseAppName(internal.AppName) if len(sanitisedAppName) > 45 { logrus.Fatalf("'%s' cannot be longer than 45 characters", sanitisedAppName) } if err := config.CopyAppEnvSample(appType, internal.AppName, internal.Server); err != nil { logrus.Fatal(err) } if internal.Secrets { secrets, err := createSecrets(sanitisedAppName) if err != nil { logrus.Fatal(err) } secretCols := []string{"Name", "Value"} secretTable := abraFormatter.CreateTable(secretCols) for secret := range secrets { secretTable.Append([]string{secret, secrets[secret]}) } // Defer secret table first so it is last no matter what defer secretTable.Render() } tableCol := []string{"Name", "Domain", "Type", "Server"} table := abraFormatter.CreateTable(tableCol) table.Append([]string{sanitisedAppName, internal.Domain, appType, internal.Server}) defer table.Render() return nil }