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 (
|
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