All checks were successful
continuous-integration/drone/push Build is passing
See toolshed/organising#652
530 lines
11 KiB
Go
530 lines
11 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"coopcloud.tech/abra/cli/internal"
|
|
appPkg "coopcloud.tech/abra/pkg/app"
|
|
"coopcloud.tech/abra/pkg/autocomplete"
|
|
"coopcloud.tech/abra/pkg/client"
|
|
"coopcloud.tech/abra/pkg/formatter"
|
|
"coopcloud.tech/abra/pkg/log"
|
|
"coopcloud.tech/abra/pkg/secret"
|
|
"github.com/docker/docker/api/types"
|
|
dockerClient "github.com/docker/docker/client"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var AppSecretGenerateCommand = &cobra.Command{
|
|
Use: "generate <app> [[secret] [version] | --all] [flags]",
|
|
Aliases: []string{"g"},
|
|
Short: "Generate secrets",
|
|
Args: cobra.RangeArgs(1, 3),
|
|
ValidArgsFunction: func(
|
|
cmd *cobra.Command,
|
|
args []string,
|
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
switch l := len(args); l {
|
|
case 0:
|
|
return autocomplete.AppNameComplete()
|
|
case 1:
|
|
app, err := appPkg.Get(args[0])
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
|
|
return []string{errMsg}, cobra.ShellCompDirectiveError
|
|
}
|
|
return autocomplete.SecretComplete(app.Recipe.Name)
|
|
default:
|
|
return nil, cobra.ShellCompDirectiveDefault
|
|
}
|
|
},
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
app := internal.ValidateApp(args)
|
|
|
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if len(args) == 1 && !generateAllSecrets {
|
|
log.Fatal("missing arguments [secret]/[version] or '--all'")
|
|
}
|
|
|
|
if len(args) > 1 && generateAllSecrets {
|
|
log.Fatal("cannot use '[secret] [version]' and '--all' together")
|
|
}
|
|
|
|
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
secrets, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.StackName())
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if !generateAllSecrets {
|
|
secretName := args[1]
|
|
secretVersion := args[2]
|
|
s, ok := secrets[secretName]
|
|
if !ok {
|
|
log.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 {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
secretVals, err := secret.GenerateSecrets(cl, secrets, app.Server)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if storeInPass {
|
|
for name, data := range secretVals {
|
|
if err := secret.PassInsertSecret(data, name, app.Name, app.Server); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(secretVals) == 0 {
|
|
log.Warn("no secrets generated")
|
|
os.Exit(1)
|
|
}
|
|
|
|
headers := []string{"NAME", "VALUE"}
|
|
table, err := formatter.CreateTable()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
table.Headers(headers...)
|
|
|
|
var rows [][]string
|
|
for name, val := range secretVals {
|
|
row := []string{name, val}
|
|
rows = append(rows, row)
|
|
table.Row(row...)
|
|
}
|
|
|
|
if internal.MachineReadable {
|
|
out, err := formatter.ToJSON(headers, rows)
|
|
if err != nil {
|
|
log.Fatal("unable to render to JSON: %s", err)
|
|
}
|
|
fmt.Println(out)
|
|
return
|
|
}
|
|
|
|
if err := formatter.PrintTable(table); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
log.Warnf(
|
|
"generated secrets %s shown again, please take note of them %s",
|
|
formatter.BoldStyle.Render("NOT"),
|
|
formatter.BoldStyle.Render("NOW"),
|
|
)
|
|
},
|
|
}
|
|
|
|
var AppSecretInsertCommand = &cobra.Command{
|
|
Use: "insert <app> <secret> <version> <data> [flags]",
|
|
Aliases: []string{"i"},
|
|
Short: "Insert secret",
|
|
Long: `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/-S" for more).`,
|
|
Args: cobra.MinimumNArgs(4),
|
|
ValidArgsFunction: func(
|
|
cmd *cobra.Command,
|
|
args []string,
|
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
switch l := len(args); l {
|
|
case 0:
|
|
return autocomplete.AppNameComplete()
|
|
case 1:
|
|
app, err := appPkg.Get(args[0])
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
|
|
return []string{errMsg}, cobra.ShellCompDirectiveError
|
|
}
|
|
return autocomplete.SecretComplete(app.Recipe.Name)
|
|
default:
|
|
return nil, cobra.ShellCompDirectiveDefault
|
|
}
|
|
},
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
app := internal.ValidateApp(args)
|
|
|
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
cl, err := client.New(app.Server)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
name := args[1]
|
|
version := args[2]
|
|
data := args[3]
|
|
|
|
if insertFromFile {
|
|
raw, err := os.ReadFile(data)
|
|
if err != nil {
|
|
log.Fatalf("reading secret from file: %s", err)
|
|
}
|
|
data = string(raw)
|
|
}
|
|
|
|
if trimInput {
|
|
data = strings.TrimSpace(data)
|
|
}
|
|
|
|
secretName := fmt.Sprintf("%s_%s_%s", app.StackName(), name, version)
|
|
if err := client.StoreSecret(cl, secretName, data, app.Server); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
log.Infof("%s successfully stored on server", secretName)
|
|
|
|
if storeInPass {
|
|
if err := secret.PassInsertSecret(data, name, app.Name, app.Server); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
},
|
|
}
|
|
|
|
// secretRm removes a secret.
|
|
func secretRm(cl *dockerClient.Client, app appPkg.App, secretName, parsed string) error {
|
|
if err := cl.SecretRemove(context.Background(), secretName); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("deleted %s successfully from server", secretName)
|
|
|
|
if removeFromPass {
|
|
if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("deleted %s successfully from local pass store", secretName)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var AppSecretRmCommand = &cobra.Command{
|
|
Use: "remove <app> [[secret] | --all] [flags]",
|
|
Aliases: []string{"rm"},
|
|
Short: "Remove a secret",
|
|
Args: cobra.RangeArgs(1, 2),
|
|
ValidArgsFunction: func(
|
|
cmd *cobra.Command,
|
|
args []string,
|
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
switch l := len(args); l {
|
|
case 0:
|
|
return autocomplete.AppNameComplete()
|
|
case 1:
|
|
if !rmAllSecrets {
|
|
app, err := appPkg.Get(args[0])
|
|
if err != nil {
|
|
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
|
|
return []string{errMsg}, cobra.ShellCompDirectiveError
|
|
}
|
|
return autocomplete.SecretComplete(app.Recipe.Name)
|
|
}
|
|
return nil, cobra.ShellCompDirectiveDefault
|
|
default:
|
|
return nil, cobra.ShellCompDirectiveError
|
|
}
|
|
},
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
app := internal.ValidateApp(args)
|
|
|
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
secrets, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.StackName())
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if len(args) == 2 && rmAllSecrets {
|
|
log.Fatal("cannot use [secret] and --all/-a together")
|
|
}
|
|
|
|
if len(args) != 2 && !rmAllSecrets {
|
|
log.Fatal("no secret(s) specified?")
|
|
}
|
|
|
|
cl, err := client.New(app.Server)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
filters, err := app.Filters(false, false)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
secretList, err := cl.SecretList(context.Background(), types.SecretListOptions{Filters: filters})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
remoteSecretNames := make(map[string]bool)
|
|
for _, cont := range secretList {
|
|
remoteSecretNames[cont.Spec.Annotations.Name] = true
|
|
}
|
|
|
|
var secretToRm string
|
|
if len(args) == 2 {
|
|
secretToRm = args[1]
|
|
}
|
|
|
|
match := false
|
|
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 {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
return
|
|
}
|
|
} else {
|
|
match = true
|
|
|
|
if err := secretRm(cl, app, secretRemoteName, secretName); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if !match && secretToRm != "" {
|
|
log.Fatalf("%s doesn't exist on server?", secretToRm)
|
|
}
|
|
|
|
if !match {
|
|
log.Fatal("no secrets to remove?")
|
|
}
|
|
},
|
|
}
|
|
|
|
var AppSecretLsCommand = &cobra.Command{
|
|
Use: "list <app>",
|
|
Aliases: []string{"ls"},
|
|
Short: "List all secrets",
|
|
Args: cobra.MinimumNArgs(1),
|
|
ValidArgsFunction: func(
|
|
cmd *cobra.Command,
|
|
args []string,
|
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
return autocomplete.AppNameComplete()
|
|
},
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
app := internal.ValidateApp(args)
|
|
|
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
cl, err := client.New(app.Server)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
headers := []string{"NAME", "VERSION", "GENERATED NAME", "CREATED ON SERVER"}
|
|
table, err := formatter.CreateTable()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
table.Headers(headers...)
|
|
|
|
secStats, err := secret.PollSecretsStatus(cl, app)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
var rows [][]string
|
|
for _, secStat := range secStats {
|
|
row := []string{
|
|
secStat.LocalName,
|
|
secStat.Version,
|
|
secStat.RemoteName,
|
|
strconv.FormatBool(secStat.CreatedOnRemote),
|
|
}
|
|
|
|
rows = append(rows, row)
|
|
table.Row(row...)
|
|
}
|
|
|
|
if len(rows) > 0 {
|
|
if internal.MachineReadable {
|
|
out, err := formatter.ToJSON(headers, rows)
|
|
if err != nil {
|
|
log.Fatal("unable to render to JSON: %s", err)
|
|
}
|
|
fmt.Println(out)
|
|
return
|
|
}
|
|
|
|
if err := formatter.PrintTable(table); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
log.Warnf("no secrets stored for %s", app.Name)
|
|
},
|
|
}
|
|
|
|
var AppSecretCommand = &cobra.Command{
|
|
Use: "secret [cmd] [args] [flags]",
|
|
Aliases: []string{"s"},
|
|
Short: "Manage app secrets",
|
|
}
|
|
|
|
var (
|
|
storeInPass bool
|
|
insertFromFile bool
|
|
trimInput bool
|
|
rmAllSecrets bool
|
|
generateAllSecrets bool
|
|
removeFromPass bool
|
|
)
|
|
|
|
func init() {
|
|
AppSecretGenerateCommand.Flags().BoolVarP(
|
|
&internal.MachineReadable,
|
|
"machine",
|
|
"m",
|
|
false,
|
|
"print machine-readable output",
|
|
)
|
|
|
|
AppSecretGenerateCommand.Flags().BoolVarP(
|
|
&storeInPass,
|
|
"pass",
|
|
"p",
|
|
false,
|
|
"store generated secrets in a local pass store",
|
|
)
|
|
|
|
AppSecretGenerateCommand.Flags().BoolVarP(
|
|
&internal.Chaos,
|
|
"chaos",
|
|
"C",
|
|
false,
|
|
"ignore uncommitted recipes changes",
|
|
)
|
|
|
|
AppSecretGenerateCommand.Flags().BoolVarP(
|
|
&generateAllSecrets,
|
|
"all",
|
|
"a",
|
|
false,
|
|
"generate all secrets",
|
|
)
|
|
|
|
AppSecretInsertCommand.Flags().BoolVarP(
|
|
&storeInPass,
|
|
"pass",
|
|
"p",
|
|
false,
|
|
"store generated secrets in a local pass store",
|
|
)
|
|
|
|
AppSecretInsertCommand.Flags().BoolVarP(
|
|
&insertFromFile,
|
|
"file",
|
|
"f",
|
|
false,
|
|
"treat input as a file",
|
|
)
|
|
|
|
AppSecretInsertCommand.Flags().BoolVarP(
|
|
&trimInput,
|
|
"trim",
|
|
"t",
|
|
false,
|
|
"trim input",
|
|
)
|
|
|
|
AppSecretInsertCommand.Flags().BoolVarP(
|
|
&internal.Chaos,
|
|
"chaos",
|
|
"C",
|
|
false,
|
|
"ignore uncommitted recipes changes",
|
|
)
|
|
|
|
AppSecretRmCommand.Flags().BoolVarP(
|
|
&rmAllSecrets,
|
|
"all",
|
|
"a",
|
|
false,
|
|
"remove all secrets",
|
|
)
|
|
|
|
AppSecretRmCommand.Flags().BoolVarP(
|
|
&removeFromPass,
|
|
"pass",
|
|
"p",
|
|
false,
|
|
"remove generated secrets from a local pass store",
|
|
)
|
|
|
|
AppSecretRmCommand.Flags().BoolVarP(
|
|
&internal.Chaos,
|
|
"chaos",
|
|
"C",
|
|
false,
|
|
"ignore uncommitted recipes changes",
|
|
)
|
|
|
|
AppSecretLsCommand.Flags().BoolVarP(
|
|
&internal.Chaos,
|
|
"chaos",
|
|
"C",
|
|
false,
|
|
"ignore uncommitted recipes changes",
|
|
)
|
|
|
|
AppSecretLsCommand.Flags().BoolVarP(
|
|
&internal.MachineReadable,
|
|
"machine",
|
|
"m",
|
|
false,
|
|
"print machine-readable output",
|
|
)
|
|
}
|