feat(secrets): Reading from stdin and reproducible secret list(#614)
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:
2025-08-27 15:37:43 +00:00
committed by p4u1
parent b86cd2e85f
commit 7c31e4dc45
2 changed files with 85 additions and 26 deletions

View File

@ -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{