Skip to content

Commit

Permalink
Merge pull request #262 from DopplerHQ/secrets-set-stdin
Browse files Browse the repository at this point in the history
Support setting secrets via stdin
  • Loading branch information
Piccirello authored Oct 1, 2021
2 parents d0ed838 + a8a7a9a commit 043e36f
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 11 deletions.
1 change: 1 addition & 0 deletions pkg/cmd/enclave_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ func init() {
enclaveSecretsSetCmd.Flags().StringP("project", "p", "", "enclave project (e.g. backend)")
enclaveSecretsSetCmd.Flags().StringP("config", "c", "", "enclave config (e.g. dev)")
enclaveSecretsSetCmd.Flags().Bool("raw", false, "print the raw secret value without processing variables")
enclaveSecretsSetCmd.Flags().Bool("no-interactive", false, "do not allow entering secret value via interactive mode")
enclaveSecretsCmd.AddCommand(enclaveSecretsSetCmd)

enclaveSecretsDeleteCmd.Flags().StringP("project", "p", "", "enclave project (e.g. backend)")
Expand Down
13 changes: 12 additions & 1 deletion pkg/cmd/enclave_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ limitations under the License.
package cmd

import (
"github.com/DopplerHQ/cli/pkg/utils"
"github.com/spf13/cobra"
)

Expand All @@ -32,7 +33,17 @@ var enclaveSetupCmd = &cobra.Command{
func init() {
enclaveSetupCmd.Flags().StringP("project", "p", "", "enclave project (e.g. backend)")
enclaveSetupCmd.Flags().StringP("config", "c", "", "enclave config (e.g. dev)")
enclaveSetupCmd.Flags().Bool("no-prompt", false, "do not prompt for information. if the project or config is not specified, an error will be thrown.")
enclaveSetupCmd.Flags().Bool("no-interactive", false, "do not prompt for information. if the project or config is not specified, an error will be thrown.")
enclaveSetupCmd.Flags().Bool("no-save-token", false, "do not save the token to the config when passed via flag or environment variable.")

// deprecated
enclaveSetupCmd.Flags().Bool("no-prompt", false, "do not prompt for information. if the project or config is not specified, an error will be thrown.")
if err := enclaveSetupCmd.Flags().MarkDeprecated("no-prompt", "please use --no-interactive instead"); err != nil {
utils.HandleError(err)
}
if err := enclaveSetupCmd.Flags().MarkHidden("no-prompt"); err != nil {
utils.HandleError(err)
}

enclaveCmd.AddCommand(enclaveSetupCmd)
}
4 changes: 2 additions & 2 deletions pkg/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ var rootCmd = &cobra.Command{
}

plain := utils.GetBoolFlagIfChanged(cmd, "plain", false)
canPrompt := !utils.GetBoolFlagIfChanged(cmd, "no-prompt", false)
canPrompt := !utils.GetBoolFlagIfChanged(cmd, "no-prompt", false) && !utils.GetBoolFlagIfChanged(cmd, "no-interactive", false)
// tty is required to accept user input, otherwise the update can't be accepted/declined
isTTY := isatty.IsTerminal(os.Stdout.Fd())

// only run version check if we can print the results
// --plain doesn't normally affect logging output, but due to legacy reasons it does here
// also don't want to display updates if user doesn't want to be prompted (--no-prompt)
// also don't want to display updates if user doesn't want to be prompted (--no-prompt/--no-interactive)
if isTTY && utils.CanLogInfo() && !plain && canPrompt {
checkVersion(cmd.CommandPath())
}
Expand Down
92 changes: 89 additions & 3 deletions pkg/cmd/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ limitations under the License.
package cmd

import (
"bufio"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -64,8 +65,23 @@ var secretsSetCmd = &cobra.Command{
Short: "Set the value of one or more secrets",
Long: `Set the value of one or more secrets.
Ex: set the secrets "API_KEY" and "CRYPTO_KEY":
doppler secrets set API_KEY=123 CRYPTO_KEY=456`,
There are several methods for setting secrets:
1) stdin (recommended)
$ echo -e 'multiline\nvalue' | doppler secrets set CERT
2) interactive stdin (recommended)
$ doppler secrets set CERT
multiline
value
.
3) one secret
$ doppler secrets set API_KEY '123'
4) multiple secrets
$ doppler secrets set API_KEY='123' DATABASE_URL='postgres:[email protected]:5432'`,
Args: cobra.MinimumNArgs(1),
Run: setSecrets,
}
Expand Down Expand Up @@ -206,14 +222,83 @@ func getSecrets(cmd *cobra.Command, args []string) {
func setSecrets(cmd *cobra.Command, args []string) {
jsonFlag := utils.OutputJSON
raw := utils.GetBoolFlag(cmd, "raw")
canPromptUser := !utils.GetBoolFlag(cmd, "no-interactive")
localConfig := configuration.LocalConfig(cmd)

utils.RequireValue("token", localConfig.Token.Value)

secrets := map[string]interface{}{}
var keys []string

if len(args) == 2 && !strings.Contains(args[0], "=") {
// if only one arg, read from stdin
if len(args) == 1 && !strings.Contains(args[0], "=") {
// format: 'echo "value" | doppler secrets set KEY'
// OR
// format: 'doppler secrets set KEY' (interactive)

// check for existing data on stdin
stat, e := os.Stdin.Stat()
if e != nil {
utils.HandleError(e, "Unable to stat stdin")
}

dataOnStdin := (stat.Mode() & os.ModeCharDevice) == 0
interactiveMode := !dataOnStdin
if interactiveMode {
if !canPromptUser {
utils.HandleError(errors.New("Secret value must be provided when using --no-interactive"))
}

utils.Log("Enter your secret value")
utils.Log("When finished, type a newline followed by a period")
utils.Log("Run 'doppler secrets set --help' for more information")
utils.Log("———————————————————— START INPUT ————————————————————")
}

isNewline := false
isPreviousNewline := false
var input []string
scanner := bufio.NewScanner(os.Stdin)
// read input from stdin
for {
if ok := scanner.Scan(); !ok {
if e := scanner.Err(); e != nil {
utils.HandleError(e, "Unable to read input from stdin")
}

break
}

s := scanner.Text()

if interactiveMode {
isPreviousNewline = isNewline
isNewline = false

if isPreviousNewline && s == "." {
utils.Log("————————————————————— END INPUT —————————————————————")
break
}

if len(s) == 0 {
isNewline = true
}
}

input = append(input, s)
}

if interactiveMode {
// remove final newline
input = input[:len(input)-1]
}

key := args[0]
value := strings.Join(input, "\n")

keys = append(keys, key)
secrets[key] = value
} else if len(args) == 2 && !strings.Contains(args[0], "=") {
// format: 'doppler secrets set KEY value'
key := args[0]
value := args[1]
Expand Down Expand Up @@ -520,6 +605,7 @@ func init() {
secretsSetCmd.Flags().StringP("project", "p", "", "project (e.g. backend)")
secretsSetCmd.Flags().StringP("config", "c", "", "config (e.g. dev)")
secretsSetCmd.Flags().Bool("raw", false, "print the raw secret value without processing variables")
secretsSetCmd.Flags().Bool("no-interactive", false, "do not allow entering secret value via interactive mode")
secretsCmd.AddCommand(secretsSetCmd)

secretsUploadCmd.Flags().StringP("project", "p", "", "project (e.g. backend)")
Expand Down
20 changes: 15 additions & 5 deletions pkg/cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ var setupCmd = &cobra.Command{
}

func setup(cmd *cobra.Command, args []string) {
canPromptUser := !utils.GetBoolFlag(cmd, "no-prompt")
canPromptUser := !utils.GetBoolFlag(cmd, "no-prompt") && !utils.GetBoolFlag(cmd, "no-interactive")
canSaveToken := !utils.GetBoolFlag(cmd, "no-save-token")
localConfig := configuration.LocalConfig(cmd)
scopedConfig := configuration.Get(configuration.Scope)
Expand Down Expand Up @@ -69,7 +69,7 @@ func setup(cmd *cobra.Command, args []string) {
// ignore when project and config are already specified
(localConfig.EnclaveProject.Source == models.FlagSource.String() && localConfig.EnclaveConfig.Source == models.FlagSource.String())

// default to true so repo config is used on --no-prompt
// default to true so repo config is used on --no-interactive
useRepoConfig := true
if !ignoreRepoConfig && canPromptUser {
useRepoConfig = utils.ConfirmationPrompt("Use settings from repo config file (doppler.yaml)?", true)
Expand Down Expand Up @@ -190,7 +190,7 @@ func selectProject(projects []models.ProjectInfo, prevConfiguredProject string,
}

if !canPromptUser {
utils.HandleError(errors.New("project must be specified via --project flag, DOPPLER_PROJECT environment variable, or repo config file when using --no-prompt"))
utils.HandleError(errors.New("project must be specified via --project flag, DOPPLER_PROJECT environment variable, or repo config file when using --no-interactive"))
}

selectedProject := utils.SelectPrompt("Select a project:", options, defaultOption)
Expand Down Expand Up @@ -226,7 +226,7 @@ func selectConfig(configs []models.ConfigInfo, selectedConfiguredProject bool, p
}

if !canPromptUser {
utils.HandleError(errors.New("config must be specified via --config flag, DOPPLER_CONFIG environment variable, or repo config file when using --no-prompt"))
utils.HandleError(errors.New("config must be specified via --config flag, DOPPLER_CONFIG environment variable, or repo config file when using --no-interactive"))
}

selectedConfig := utils.SelectPrompt("Select a config:", options, defaultOption)
Expand All @@ -240,7 +240,17 @@ func valueFromEnvironmentNotice(name string) string {
func init() {
setupCmd.Flags().StringP("project", "p", "", "project (e.g. backend)")
setupCmd.Flags().StringP("config", "c", "", "config (e.g. dev)")
setupCmd.Flags().Bool("no-prompt", false, "do not prompt for information. if the project or config is not specified, an error will be thrown.")
setupCmd.Flags().Bool("no-interactive", false, "do not prompt for information. if the project or config is not specified, an error will be thrown.")
setupCmd.Flags().Bool("no-save-token", false, "do not save the token to the config when passed via flag or environment variable.")

// deprecated
setupCmd.Flags().Bool("no-prompt", false, "do not prompt for information. if the project or config is not specified, an error will be thrown.")
if err := setupCmd.Flags().MarkDeprecated("no-prompt", "please use --no-interactive instead"); err != nil {
utils.HandleError(err)
}
if err := setupCmd.Flags().MarkHidden("no-prompt"); err != nil {
utils.HandleError(err)
}

rootCmd.AddCommand(setupCmd)
}

0 comments on commit 043e36f

Please sign in to comment.