forked from toolshed/abra
		
	feat(secrets): Reading from stdin and reproducible secret list(#614)
Reviewed-on: toolshed/abra#614 Reviewed-by: decentral1se <decentral1se@noreply.git.coopcloud.tech> Co-authored-by: p4u1 <p4u1_f4u1@riseup.net> Co-committed-by: p4u1 <p4u1_f4u1@riseup.net>
This commit is contained in:
		| @ -2,8 +2,11 @@ package app | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"io" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"sort" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| @ -157,7 +160,10 @@ environment. Typically, you can let Abra generate them for you on app creation | |||||||
|   abra app secret insert 1312.net my_secret v1 mySuperSecret |   abra app secret insert 1312.net my_secret v1 mySuperSecret | ||||||
|  |  | ||||||
|   # insert secret as file |   # insert secret as file | ||||||
|   abra app secret insert 1312.net my_secret v1 secret.txt -f`), |   abra app secret insert 1312.net my_secret v1 secret.txt -f | ||||||
|  |  | ||||||
|  |   # insert secret from stdin | ||||||
|  |   echo "mmySuperSecret" | abra app secret insert 1312.net my_secret v1`), | ||||||
| 	Args: cobra.MinimumNArgs(3), | 	Args: cobra.MinimumNArgs(3), | ||||||
| 	ValidArgsFunction: func( | 	ValidArgsFunction: func( | ||||||
| 		cmd *cobra.Command, | 		cmd *cobra.Command, | ||||||
| @ -191,12 +197,9 @@ environment. Typically, you can let Abra generate them for you on app creation | |||||||
|  |  | ||||||
| 		name := args[1] | 		name := args[1] | ||||||
| 		version := args[2] | 		version := args[2] | ||||||
| 		data := "" | 		data, err := readSecretData(args) | ||||||
|  | 		if err != nil { | ||||||
| 		if len(args) > 3 { | 			log.Fatal(err) | ||||||
| 			data = args[3] |  | ||||||
| 		} else if internal.NoInput { |  | ||||||
| 			log.Fatal(i18n.G("must provide <data> argument if --no-input is passed")) |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		composeFiles, err := app.Recipe.GetComposeFiles(app.Env) | 		composeFiles, err := app.Recipe.GetComposeFiles(app.Env) | ||||||
| @ -219,23 +222,6 @@ environment. Typically, you can let Abra generate them for you on app creation | |||||||
| 			log.Fatal(i18n.G("no secret %s available for recipe %s?", name, app.Recipe.Name)) | 			log.Fatal(i18n.G("no secret %s available for recipe %s?", name, app.Recipe.Name)) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if data == "" && !internal.NoInput { |  | ||||||
| 			log.Debug(i18n.G("secret data not provided on command-line, prompting")) |  | ||||||
| 			var prompt survey.Prompt |  | ||||||
| 			if !insertFromFile { |  | ||||||
| 				prompt = &survey.Password{ |  | ||||||
| 					Message: i18n.G("specify secret value"), |  | ||||||
| 				} |  | ||||||
| 			} else { |  | ||||||
| 				prompt = &survey.Input{ |  | ||||||
| 					Message: i18n.G("specify secret file"), |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			if err := survey.AskOne(prompt, &data); err != nil { |  | ||||||
| 				log.Fatal(err) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if insertFromFile { | 		if insertFromFile { | ||||||
| 			raw, err := os.ReadFile(data) | 			raw, err := os.ReadFile(data) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @ -263,6 +249,55 @@ environment. Typically, you can let Abra generate them for you on app creation | |||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func readSecretData(args []string) (string, error) { | ||||||
|  | 	if len(args) == 4 { | ||||||
|  | 		return args[3], nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(args) != 3 { | ||||||
|  | 		return "", errors.New(i18n.G("need 3 or 4 arguments")) | ||||||
|  | 	} | ||||||
|  | 	// First check if data is provided by stdin | ||||||
|  | 	fi, err := os.Stdin.Stat() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	if fi.Mode()&os.ModeNamedPipe != 0 { | ||||||
|  | 		// Can't insert from stdin and read from file | ||||||
|  | 		if insertFromFile { | ||||||
|  | 			return "", errors.New(i18n.G("can not insert from file and read from stdin")) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		log.Debug(i18n.G("reading secret data from stdin")) | ||||||
|  | 		bytes, err := io.ReadAll(os.Stdin) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return "", errors.New(i18n.G("reading data from stdin: %s", err)) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return string(bytes), nil | ||||||
|  | 	} | ||||||
|  | 	if internal.NoInput { | ||||||
|  | 		return "", errors.New(i18n.G("must provide <data> argument if --no-input is passed")) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	log.Debug(i18n.G("secret data not provided on command-line or stdin, prompting")) | ||||||
|  | 	var prompt survey.Prompt | ||||||
|  | 	if !insertFromFile { | ||||||
|  | 		prompt = &survey.Password{ | ||||||
|  | 			Message: i18n.G("specify secret value"), | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		prompt = &survey.Input{ | ||||||
|  | 			Message: i18n.G("specify secret file"), | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	var data string | ||||||
|  | 	if err := survey.AskOne(prompt, &data); err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return data, nil | ||||||
|  | } | ||||||
|  |  | ||||||
| // secretRm removes a secret. | // secretRm removes a secret. | ||||||
| func secretRm(cl *dockerClient.Client, app appPkg.App, secretName, parsed string) error { | func secretRm(cl *dockerClient.Client, app appPkg.App, secretName, parsed string) error { | ||||||
| 	if err := cl.SecretRemove(context.Background(), secretName); err != nil { | 	if err := cl.SecretRemove(context.Background(), secretName); err != nil { | ||||||
| @ -432,6 +467,10 @@ var AppSecretLsCommand = &cobra.Command{ | |||||||
| 			log.Fatal(err) | 			log.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// Sort secrets to ensure reproducible output | ||||||
|  | 		sort.Slice(secStats, func(i, j int) bool { | ||||||
|  | 			return secStats[i].LocalName < secStats[j].LocalName | ||||||
|  | 		}) | ||||||
| 		var rows [][]string | 		var rows [][]string | ||||||
| 		for _, secStat := range secStats { | 		for _, secStat := range secStats { | ||||||
| 			row := []string{ | 			row := []string{ | ||||||
|  | |||||||
| @ -206,10 +206,10 @@ teardown(){ | |||||||
|   run $ABRA app secret insert "$TEST_APP_DOMAIN" bar |   run $ABRA app secret insert "$TEST_APP_DOMAIN" bar | ||||||
|   assert_failure |   assert_failure | ||||||
|  |  | ||||||
|   run $ABRA app secret insert "$TEST_APP_DOMAIN" bar baz |   run $ABRA app secret insert "$TEST_APP_DOMAIN" bar baz --no-input | ||||||
|   assert_failure |   assert_failure | ||||||
|  |  | ||||||
|   run $ABRA app secret insert "$TEST_APP_DOMAIN" test_pass_one v1 -n |   run bash -c "echo foo | $ABRA app secret insert $TEST_APP_DOMAIN bar baz -f" | ||||||
|   assert_failure |   assert_failure | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -251,6 +251,20 @@ teardown(){ | |||||||
|   assert_output --partial 'true' |   assert_output --partial 'true' | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @test "insert: create secret from stdin" { | ||||||
|  |   run $ABRA app secret ls "$TEST_APP_DOMAIN" | ||||||
|  |   assert_success | ||||||
|  |   assert_output --partial 'false' | ||||||
|  |  | ||||||
|  |   run bash -c "echo foo | $ABRA app secret insert $TEST_APP_DOMAIN test_pass_one v1" | ||||||
|  |   assert_success | ||||||
|  |   assert_output --partial 'successfully stored on server' | ||||||
|  |  | ||||||
|  |   run $ABRA app secret ls "$TEST_APP_DOMAIN" | ||||||
|  |   assert_success | ||||||
|  |   assert_output --partial 'true' | ||||||
|  | } | ||||||
|  |  | ||||||
| @test "rm: validate arguments" { | @test "rm: validate arguments" { | ||||||
|   run $ABRA app secret rm |   run $ABRA app secret rm | ||||||
|   assert_failure |   assert_failure | ||||||
| @ -343,6 +357,12 @@ teardown(){ | |||||||
|     | jq -r ".[] | select(.name==\"test_pass_two\") | .version"' |     | jq -r ".[] | select(.name==\"test_pass_two\") | .version"' | ||||||
|   assert_success |   assert_success | ||||||
|   assert_output --partial 'v1' |   assert_output --partial 'v1' | ||||||
|  |  | ||||||
|  |   # Can always expect the secret at this position | ||||||
|  |   run bash -c '$ABRA app secret ls "$TEST_APP_DOMAIN" --machine \ | ||||||
|  |     | jq -r ".[1] | .name"' | ||||||
|  |   assert_success | ||||||
|  |   assert_output --partial 'test_pass_two' | ||||||
| } | } | ||||||
|  |  | ||||||
| @test "ls: bail if unstaged changes and no --chaos" { | @test "ls: bail if unstaged changes and no --chaos" { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user