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/recipe" "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 allSecretsFlag = &cli.BoolFlag{ Name: "all, a", Destination: &allSecrets, Usage: "Generate all secrets", } ) var ( rmAllSecrets bool 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, internal.MachineReadableFlag, internal.OfflineFlag, internal.ChaosFlag, }, Before: internal.SubCommandBefore, BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { app := internal.ValidateApp(c) if err := recipe.EnsureExists(app.Recipe); err != nil { logrus.Fatal(err) } if !internal.Chaos { if err := recipe.EnsureIsClean(app.Recipe); err != nil { logrus.Fatal(err) } if !internal.Offline { if err := recipe.EnsureUpToDate(app.Recipe); err != nil { logrus.Fatal(err) } } if err := recipe.EnsureLatest(app.Recipe); err != nil { logrus.Fatal(err) } } 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) } composeFiles, err := config.GetComposeFiles(app.Recipe, app.Env) if err != nil { logrus.Fatal(err) } secrets, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.StackName()) if err != nil { logrus.Fatal(err) } if !allSecrets { secretName := c.Args().Get(1) secretVersion := c.Args().Get(2) s, ok := secrets[secretName] if !ok { logrus.Fatalf("%s doesn't exist in the env config?", secretName) } s.Version = secretVersion secrets = map[string]secret.Secret{ secretName: s, } } cl, err := client.New(app.Server) if err != nil { logrus.Fatal(err) } secretVals, err := secret.GenerateSecrets(cl, secrets, 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}) } if internal.MachineReadable { table.JSONRender() } else { 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?")) } cl, err := client.New(app.Server) if err != nil { logrus.Fatal(err) } 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(cl, 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, internal.OfflineFlag, internal.ChaosFlag, }, 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) if err := recipe.EnsureExists(app.Recipe); err != nil { logrus.Fatal(err) } if !internal.Chaos { if err := recipe.EnsureIsClean(app.Recipe); err != nil { logrus.Fatal(err) } if !internal.Offline { if err := recipe.EnsureUpToDate(app.Recipe); err != nil { logrus.Fatal(err) } } if err := recipe.EnsureLatest(app.Recipe); err != nil { logrus.Fatal(err) } } composeFiles, err := config.GetComposeFiles(app.Recipe, app.Env) if err != nil { logrus.Fatal(err) } secrets, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.StackName()) if err != nil { logrus.Fatal(err) } 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 secretName, val := range secrets { secretRemoteName := fmt.Sprintf("%s_%s_%s", app.StackName(), secretName, val.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, internal.OfflineFlag, internal.ChaosFlag, internal.MachineReadableFlag, }, Before: internal.SubCommandBefore, Usage: "List all secrets", BashComplete: autocomplete.AppNameComplete, Action: func(c *cli.Context) error { app := internal.ValidateApp(c) if err := recipe.EnsureExists(app.Recipe); err != nil { logrus.Fatal(err) } if !internal.Chaos { if err := recipe.EnsureIsClean(app.Recipe); err != nil { logrus.Fatal(err) } if !internal.Offline { if err := recipe.EnsureUpToDate(app.Recipe); err != nil { logrus.Fatal(err) } } if err := recipe.EnsureLatest(app.Recipe); err != nil { logrus.Fatal(err) } } cl, err := client.New(app.Server) if err != nil { logrus.Fatal(err) } tableCol := []string{"Name", "Version", "Generated Name", "Created On Server"} table := formatter.CreateTable(tableCol) secStats, err := secret.PollSecretsStatus(cl, app) if err != nil { logrus.Fatal(err) } for _, secStat := range secStats { tableRow := []string{ secStat.LocalName, secStat.Version, secStat.RemoteName, strconv.FormatBool(secStat.CreatedOnRemote), } table.Append(tableRow) } if table.NumLines() > 0 { if internal.MachineReadable { table.JSONRender() } else { table.Render() } } else { logrus.Warnf("no secrets stored for %s", app.Name) } return nil }, } var appSecretCommand = cli.Command{ Name: "secret", Aliases: []string{"s"}, Usage: "Manage app secrets", ArgsUsage: "", Subcommands: []cli.Command{ appSecretGenerateCommand, appSecretInsertCommand, appSecretRmCommand, appSecretLsCommand, }, }