feat(secrets): Reading from stdin and reproducible secret list(#614)
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #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 (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"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
|
||||
|
||||
# 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),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
@ -191,12 +197,9 @@ environment. Typically, you can let Abra generate them for you on app creation
|
||||
|
||||
name := args[1]
|
||||
version := args[2]
|
||||
data := ""
|
||||
|
||||
if len(args) > 3 {
|
||||
data = args[3]
|
||||
} else if internal.NoInput {
|
||||
log.Fatal(i18n.G("must provide <data> argument if --no-input is passed"))
|
||||
data, err := readSecretData(args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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 {
|
||||
raw, err := os.ReadFile(data)
|
||||
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.
|
||||
func secretRm(cl *dockerClient.Client, app appPkg.App, secretName, parsed string) error {
|
||||
if err := cl.SecretRemove(context.Background(), secretName); err != nil {
|
||||
@ -432,6 +467,10 @@ var AppSecretLsCommand = &cobra.Command{
|
||||
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
|
||||
for _, secStat := range secStats {
|
||||
row := []string{
|
||||
|
@ -206,10 +206,10 @@ teardown(){
|
||||
run $ABRA app secret insert "$TEST_APP_DOMAIN" bar
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -251,6 +251,20 @@ teardown(){
|
||||
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" {
|
||||
run $ABRA app secret rm
|
||||
assert_failure
|
||||
@ -343,6 +357,12 @@ teardown(){
|
||||
| jq -r ".[] | select(.name==\"test_pass_two\") | .version"'
|
||||
assert_success
|
||||
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" {
|
||||
|
Reference in New Issue
Block a user