package app import ( "context" "errors" "fmt" "os" "strconv" "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" "coopcloud.tech/abra/pkg/secret" "github.com/docker/docker/api/types" dockerClient "github.com/docker/docker/client" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) var allSecrets bool var allSecretsFlag = &cli.BoolFlag{ Name: "all, a", Destination: &allSecrets, Usage: "Generate all secrets", } var rmAllSecrets bool var rmAllSecretsFlag = &cli.BoolFlag{ Name: "all, a", Destination: &rmAllSecrets, Usage: "Remove all secrets", } var appSecretGenerateCommand = cli.Command{ Name: "generate", Aliases: []string{"g"}, Usage: "Generate secrets", ArgsUsage: " ", Flags: []cli.Flag{ internal.DebugFlag, allSecretsFlag, internal.PassFlag, }, Before: internal.SubCommandBefore, BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { app := internal.ValidateApp(c) if len(c.Args()) == 1 && !allSecrets { err := errors.New("missing arguments / or '--all'") internal.ShowSubcommandHelpAndError(c, err) } if c.Args().Get(1) != "" && allSecrets { err := errors.New("cannot use ' ' and '--all' together") internal.ShowSubcommandHelpAndError(c, err) } secretsToCreate := make(map[string]string) secretEnvVars := secret.ReadSecretEnvVars(app.Env) if allSecrets { secretsToCreate = secretEnvVars } else { secretName := c.Args().Get(1) secretVersion := c.Args().Get(2) matches := false for sec := range secretEnvVars { parsed := secret.ParseSecretEnvVarName(sec) if secretName == parsed { secretsToCreate[sec] = secretVersion matches = true } } if !matches { logrus.Fatalf("%s doesn't exist in the env config?", secretName) } } secretVals, err := secret.GenerateSecrets(secretsToCreate, app.StackName(), app.Server) if err != nil { logrus.Fatal(err) } if internal.Pass { for name, data := range secretVals { if err := secret.PassInsertSecret(data, name, app.Name, app.Server); err != nil { logrus.Fatal(err) } } } if len(secretVals) == 0 { logrus.Warn("no secrets generated") os.Exit(1) } tableCol := []string{"name", "value"} table := formatter.CreateTable(tableCol) for name, val := range secretVals { table.Append([]string{name, val}) } table.Render() logrus.Warn("generated secrets are not shown again, please take note of them *now*") return nil }, } var appSecretInsertCommand = cli.Command{ Name: "insert", Aliases: []string{"i"}, Usage: "Insert secret", Flags: []cli.Flag{ internal.DebugFlag, internal.PassFlag, }, Before: internal.SubCommandBefore, ArgsUsage: " ", BashComplete: autocomplete.AppNameComplete, Description: ` This command inserts a secret into an app environment. This can be useful when you want to manually generate secrets for an app environment. Typically, you can let Abra generate them for you on app creation (see "abra app new --secrets" for more). Example: abra app secret insert myapp db_pass v1 mySecretPassword `, Action: func(c *cli.Context) error { app := internal.ValidateApp(c) if len(c.Args()) != 4 { internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments?")) } name := c.Args().Get(1) version := c.Args().Get(2) data := c.Args().Get(3) secretName := fmt.Sprintf("%s_%s_%s", app.StackName(), name, version) if err := client.StoreSecret(secretName, data, app.Server); err != nil { logrus.Fatal(err) } logrus.Infof("%s successfully stored on server", secretName) if internal.Pass { if err := secret.PassInsertSecret(data, name, app.Name, app.Server); err != nil { logrus.Fatal(err) } } return nil }, } // secretRm removes a secret. func secretRm(cl *dockerClient.Client, app config.App, secretName, parsed string) error { if err := cl.SecretRemove(context.Background(), secretName); err != nil { return err } logrus.Infof("deleted %s successfully from server", secretName) if internal.PassRemove { if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil { return err } logrus.Infof("deleted %s successfully from local pass store", secretName) } return nil } var appSecretRmCommand = cli.Command{ Name: "remove", Aliases: []string{"rm"}, Usage: "Remove a secret", Flags: []cli.Flag{ internal.DebugFlag, internal.NoInputFlag, rmAllSecretsFlag, internal.PassRemoveFlag, }, Before: internal.SubCommandBefore, ArgsUsage: " []", BashComplete: autocomplete.AppNameComplete, Description: ` This command removes app secrets. Example: abra app secret remove myapp db_pass `, Action: func(c *cli.Context) error { app := internal.ValidateApp(c) secrets := secret.ReadSecretEnvVars(app.Env) if c.Args().Get(1) != "" && rmAllSecrets { internal.ShowSubcommandHelpAndError(c, errors.New("cannot use '' and '--all' together")) } if c.Args().Get(1) == "" && !rmAllSecrets { internal.ShowSubcommandHelpAndError(c, errors.New("no secret(s) specified?")) } cl, err := client.New(app.Server) if err != nil { logrus.Fatal(err) } filters, err := app.Filters(false, false) if err != nil { logrus.Fatal(err) } secretList, err := cl.SecretList(context.Background(), types.SecretListOptions{Filters: filters}) if err != nil { logrus.Fatal(err) } remoteSecretNames := make(map[string]bool) for _, cont := range secretList { remoteSecretNames[cont.Spec.Annotations.Name] = true } match := false secretToRm := c.Args().Get(1) for sec := range secrets { secretName := secret.ParseSecretEnvVarName(sec) secVal, err := secret.ParseSecretEnvVarValue(secrets[sec]) if err != nil { logrus.Fatal(err) } secretRemoteName := fmt.Sprintf("%s_%s_%s", app.StackName(), secretName, secVal.Version) if _, ok := remoteSecretNames[secretRemoteName]; ok { if secretToRm != "" { if secretName == secretToRm { if err := secretRm(cl, app, secretRemoteName, secretName); err != nil { logrus.Fatal(err) } return nil } } else { match = true if err := secretRm(cl, app, secretRemoteName, secretName); err != nil { logrus.Fatal(err) } } } } if !match && secretToRm != "" { logrus.Fatalf("%s doesn't exist on server?", secretToRm) } if !match { logrus.Fatal("no secrets to remove?") } return nil }, } var appSecretLsCommand = cli.Command{ Name: "list", Aliases: []string{"ls"}, Flags: []cli.Flag{ internal.DebugFlag, }, Before: internal.SubCommandBefore, Usage: "List all secrets", Action: func(c *cli.Context) error { app := internal.ValidateApp(c) secrets := secret.ReadSecretEnvVars(app.Env) tableCol := []string{"Name", "Version", "Generated Name", "Created On Server"} table := formatter.CreateTable(tableCol) cl, err := client.New(app.Server) if err != nil { logrus.Fatal(err) } filters, err := app.Filters(false, false) if err != nil { logrus.Fatal(err) } secretList, err := cl.SecretList(context.Background(), types.SecretListOptions{Filters: filters}) if err != nil { logrus.Fatal(err) } remoteSecretNames := make(map[string]bool) for _, cont := range secretList { remoteSecretNames[cont.Spec.Annotations.Name] = true } for sec := range secrets { createdRemote := false secretName := secret.ParseSecretEnvVarName(sec) secVal, err := secret.ParseSecretEnvVarValue(secrets[sec]) if err != nil { logrus.Fatal(err) } secretRemoteName := fmt.Sprintf("%s_%s_%s", app.StackName(), secretName, secVal.Version) if _, ok := remoteSecretNames[secretRemoteName]; ok { createdRemote = true } tableRow := []string{secretName, secVal.Version, secretRemoteName, strconv.FormatBool(createdRemote)} table.Append(tableRow) } if table.NumLines() > 0 { table.Render() } else { logrus.Warnf("no secrets stored for %s", app.Name) } return nil }, BashComplete: autocomplete.AppNameComplete, } var appSecretCommand = cli.Command{ Name: "secret", Aliases: []string{"s"}, Usage: "Manage app secrets", ArgsUsage: "", Subcommands: []cli.Command{ appSecretGenerateCommand, appSecretInsertCommand, appSecretRmCommand, appSecretLsCommand, }, }