diff --git a/cli/app/new.go b/cli/app/new.go index 5bdd6bb3..3dcdef39 100644 --- a/cli/app/new.go +++ b/cli/app/new.go @@ -16,10 +16,9 @@ import ( "github.com/urfave/cli/v2" ) -var appNewCommand = &cli.Command{ - Name: "new", - Usage: "Create a new app", - Description: ` +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. @@ -37,7 +36,12 @@ 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, @@ -46,108 +50,161 @@ on your $PATH. internal.SecretsFlag, }, ArgsUsage: "", - Action: func(c *cli.Context) error { - appType := c.Args().First() - if appType == "" { - internal.ShowSubcommandHelpAndError(c, errors.New("no app type provided")) - return nil - } - - config.EnsureAbraDirExists() - - appFiles, err := config.LoadAppFiles(internal.Server) - if err != nil { - logrus.Fatal(err) - } - - catl, err := catalogue.ReadAppsCatalogue() - if err != nil { - logrus.Fatal(err) - } - - app := catl[appType] - app.EnsureExists() - - latestVersion := app.LatestVersion() - if err := app.EnsureVersion(latestVersion); err != nil { - logrus.Fatal(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 { - logrus.Fatal(err) - } - } - - if internal.Domain == "" { - prompt := &survey.Input{ - Message: "Specify app domain", - } - if err := survey.AskOne(prompt, &internal.Domain); err != nil { - logrus.Fatal(err) - } - } - - if internal.AppName == "" { - prompt := &survey.Input{ - Message: "Specify app name:", - Default: strings.ReplaceAll(internal.Domain, ".", "_"), - } - if err := survey.AskOne(prompt, &internal.AppName); err != nil { - logrus.Fatal(err) - } - } - - sanitisedAppName := strings.ReplaceAll(internal.AppName, ".", "_") - if len(sanitisedAppName) > 45 { - logrus.Fatal(fmt.Errorf("'%s' cannot be longer than 45 characters", sanitisedAppName)) - } - - if err := config.CopyAppEnvSample(appType, internal.AppName, internal.Server); err != nil { - logrus.Fatal(err) - } - - secrets := make(map[string]string) - if internal.Secrets { - appEnvPath := path.Join(config.ABRA_DIR, "servers", internal.Server, fmt.Sprintf("%s.env", sanitisedAppName)) - appEnv, err := config.ReadEnv(appEnvPath) - if err != nil { - logrus.Fatal(err) - } - secretEnvVars := secret.ReadSecretEnvVars(appEnv) - secrets, err = secret.GenerateSecrets(secretEnvVars, sanitisedAppName, internal.Server) - if err != nil { - logrus.Fatal(err) - } - if internal.Pass { - for secretName := range secrets { - secretValue := secrets[secretName] - if err := secret.PassInsertSecret(secretValue, secretName, sanitisedAppName, internal.Server); err != nil { - logrus.Fatal(err) - } - } - } - } - - tableCol := []string{"Name", "Domain", "Type", "Server"} - table := abraFormatter.CreateTable(tableCol) - table.Append([]string{sanitisedAppName, internal.Domain, appType, internal.Server}) - table.Render() - - if internal.Secrets { - secretCols := []string{"Name", "Value"} - secretTable := abraFormatter.CreateTable(secretCols) - for secret := range secrets { - secretTable.Append([]string{secret, secrets[secret]}) - } - secretTable.Render() - } - - return nil - }, + Action: action, +} + +func sanitiseAppName(name string) string { + return strings.ReplaceAll(name, ".", "_") +} + +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: 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 := 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 + }