Skip to content

Commit 323ab9d

Browse files
committed
Update backend formatting and add --format flag
1 parent f6f9ff1 commit 323ab9d

6 files changed

Lines changed: 201 additions & 195 deletions

File tree

‎pkg/cmd/run.go‎

Lines changed: 82 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -107,48 +107,22 @@ doppler run --mount secrets.json -- cat secrets.json`,
107107
}
108108
}
109109

110-
const format = models.JSON
111-
fallbackPath := ""
112-
legacyFallbackPath := ""
113-
metadataPath := ""
114-
if enableFallback {
115-
fallbackPath, legacyFallbackPath = initFallbackDir(cmd, localConfig, format, nameTransformer, secretsToInclude, exitOnWriteFailure)
116-
}
117-
if enableCache {
118-
metadataPath = controllers.MetadataFilePath(localConfig.Token.Value, localConfig.EnclaveProject.Value, localConfig.EnclaveConfig.Value, format, nameTransformer, secretsToInclude)
119-
}
120-
121110
passphrase := getPassphrase(cmd, "passphrase", localConfig)
122111
if passphrase == "" {
123112
utils.HandleError(errors.New("invalid passphrase"))
124113
}
125114

126-
if !enableFallback {
127-
flags := []string{"fallback", "fallback-only", "offline", "fallback-readonly", "no-exit-on-write-failure", "passphrase"}
128-
for _, flag := range flags {
129-
if cmd.Flags().Changed(flag) {
130-
utils.LogWarning(fmt.Sprintf("--%s has no effect when the fallback file is disabled", flag))
131-
}
132-
}
133-
}
134-
135-
fallbackOpts := controllers.FallbackOptions{
136-
Enable: enableFallback,
137-
Path: fallbackPath,
138-
LegacyPath: legacyFallbackPath,
139-
Readonly: fallbackReadonly,
140-
Exclusive: fallbackOnly,
141-
ExclusiveFlag: fallbackFlag,
142-
ExitOnWriteFailure: exitOnWriteFailure,
143-
Passphrase: passphrase,
144-
}
145-
146115
mountPath := cmd.Flag("mount").Value.String()
147-
mountFormatString := cmd.Flag("mount-format").Value.String()
116+
// --format is the primary flag, --mount-format is a deprecated alias
117+
mountFormatString := cmd.Flag("format").Value.String()
118+
if cmd.Flags().Changed("mount-format") && !cmd.Flags().Changed("format") {
119+
// Use --mount-format value if specified and --format was not
120+
mountFormatString = cmd.Flag("mount-format").Value.String()
121+
}
148122
mountTemplate := cmd.Flag("mount-template").Value.String()
149123
maxReads := utils.GetIntFlag(cmd, "mount-max-reads", 32)
150124
// only auto-detect the format if it hasn't been explicitly specified
151-
shouldAutoDetectFormat := !cmd.Flags().Changed("mount-format")
125+
shouldAutoDetectFormat := !cmd.Flags().Changed("format") && !cmd.Flags().Changed("mount-format")
152126
shouldMountFile := mountPath != ""
153127
shouldMountTemplate := mountTemplate != ""
154128

@@ -205,6 +179,51 @@ doppler run --mount secrets.json -- cat secrets.json`,
205179
}
206180
}
207181

182+
// Determine the API format to use
183+
// When mounting: use the requested mount format (template requires JSON for client-side rendering)
184+
// When not mounting: always use JSON for env injection
185+
var format models.SecretsFormat
186+
if shouldMountFile && mountFormat != models.TemplateMountFormat {
187+
for _, val := range models.SecretsFormatList {
188+
if val.String() == mountFormat {
189+
format = val
190+
break
191+
}
192+
}
193+
} else {
194+
format = models.JSON
195+
}
196+
197+
fallbackPath := ""
198+
legacyFallbackPath := ""
199+
metadataPath := ""
200+
if enableFallback {
201+
fallbackPath, legacyFallbackPath = initFallbackDir(cmd, localConfig, format, nameTransformer, secretsToInclude, exitOnWriteFailure)
202+
}
203+
if enableCache {
204+
metadataPath = controllers.MetadataFilePath(localConfig.Token.Value, localConfig.EnclaveProject.Value, localConfig.EnclaveConfig.Value, format, nameTransformer, secretsToInclude)
205+
}
206+
207+
if !enableFallback {
208+
flags := []string{"fallback", "fallback-only", "offline", "fallback-readonly", "no-exit-on-write-failure", "passphrase"}
209+
for _, flag := range flags {
210+
if cmd.Flags().Changed(flag) {
211+
utils.LogWarning(fmt.Sprintf("--%s has no effect when the fallback file is disabled", flag))
212+
}
213+
}
214+
}
215+
216+
fallbackOpts := controllers.FallbackOptions{
217+
Enable: enableFallback,
218+
Path: fallbackPath,
219+
LegacyPath: legacyFallbackPath,
220+
Readonly: fallbackReadonly,
221+
Exclusive: fallbackOnly,
222+
ExclusiveFlag: fallbackFlag,
223+
ExitOnWriteFailure: exitOnWriteFailure,
224+
Passphrase: passphrase,
225+
}
226+
208227
mountOptions := controllers.MountOptions{
209228
Enable: shouldMountFile,
210229
Format: mountFormat,
@@ -252,8 +271,9 @@ doppler run --mount secrets.json -- cat secrets.json`,
252271
}
253272

254273
startProcess := func() {
255-
// ensure we can fetch the new secrets before restarting the process
256-
secrets, fromCache := controllers.FetchSecrets(localConfig, enableCache, fallbackOpts, metadataPath, nameTransformer, dynamicSecretsTTL, format, secretsToInclude)
274+
// Fetch secrets (returns raw bytes, supports caching/fallback for all formats)
275+
secretsBytes, fromCache := controllers.FetchSecrets(localConfig, enableCache, fallbackOpts, metadataPath, nameTransformer, dynamicSecretsTTL, format, secretsToInclude)
276+
257277
secretsFetchedAt := time.Now()
258278
if secretsFetchedAt.After(lastSecretsFetch) {
259279
lastSecretsFetch = secretsFetchedAt
@@ -262,7 +282,24 @@ doppler run --mount secrets.json -- cat secrets.json`,
262282
watchedValuesMayBeStale = false
263283
}
264284

265-
controllers.ValidateSecrets(secrets, secretsToInclude, exitOnMissingIncludedSecrets, mountOptions)
285+
// Parse secrets to map when needed:
286+
// - For env injection (not mounting)
287+
// - For validation
288+
// - For template rendering
289+
var secrets map[string]string
290+
needsParsedSecrets := !shouldMountFile || mountFormat == models.TemplateMountFormat
291+
if needsParsedSecrets || len(secretsToInclude) > 0 {
292+
// Template format and env injection require JSON, so we need to parse
293+
var parseErr error
294+
secrets, parseErr = controllers.ParseSecrets(secretsBytes)
295+
if parseErr != nil {
296+
utils.HandleError(parseErr, "Unable to parse secrets")
297+
}
298+
}
299+
300+
if needsParsedSecrets || len(secretsToInclude) > 0 {
301+
controllers.ValidateSecrets(secrets, secretsToInclude, exitOnMissingIncludedSecrets, mountOptions)
302+
}
266303

267304
isRestart := c != nil
268305
// terminate the old process
@@ -316,7 +353,7 @@ doppler run --mount secrets.json -- cat secrets.json`,
316353
terminatedByWatch = false
317354

318355
var env []string
319-
env, cleanupMount = controllers.PrepareSecrets(secrets, os.Environ(), preserveEnv, mountOptions)
356+
env, cleanupMount = controllers.PrepareSecrets(secrets, secretsBytes, os.Environ(), preserveEnv, mountOptions)
320357

321358
global.WaitGroup.Add(1)
322359

@@ -656,13 +693,18 @@ func init() {
656693
runCmd.Flags().Bool("no-liveness-ping", false, "disable the periodic liveness ping")
657694
// secrets mount flags
658695
runCmd.Flags().String("mount", "", "write secrets to an ephemeral file, accessible at DOPPLER_CLI_SECRETS_PATH. when enabled, secrets are NOT injected into the environment")
659-
runCmd.Flags().String("mount-format", "json", fmt.Sprintf("file format to use. if not specified, will be auto-detected from mount name. one of %v", models.SecretsMountFormats))
660-
err = runCmd.RegisterFlagCompletionFunc("mount-format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
661-
return []string{projectTemplateFileName}, cobra.ShellCompDirectiveDefault
696+
runCmd.Flags().String("format", "json", fmt.Sprintf("file format to use. if not specified, will be auto-detected from mount name. one of %v", models.SecretsMountFormats))
697+
err = runCmd.RegisterFlagCompletionFunc("format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
698+
return models.SecretsMountFormats, cobra.ShellCompDirectiveDefault
662699
})
663700
if err != nil {
664701
utils.HandleError(err)
665702
}
703+
// --mount-format is a deprecated alias for --format
704+
runCmd.Flags().String("mount-format", "json", fmt.Sprintf("file format to use. one of %v", models.SecretsMountFormats))
705+
if err := runCmd.Flags().MarkDeprecated("mount-format", "use --format instead"); err != nil {
706+
utils.HandleError(err)
707+
}
666708
runCmd.Flags().String("mount-template", "", "template file to use. secrets will be rendered into this template before mount. see 'doppler secrets substitute' for more info.")
667709
runCmd.Flags().Int("mount-max-reads", 0, "maximum number of times the mounted secrets file can be read (0 for unlimited)")
668710
runCmd.Flags().StringSliceVar(&secretsToInclude, "only-secrets", []string{}, "only include the specified secrets")

‎pkg/cmd/secrets.go‎

Lines changed: 21 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ package cmd
1717

1818
import (
1919
"bufio"
20-
"encoding/json"
2120
"errors"
2221
"fmt"
2322
"io/ioutil"
@@ -508,53 +507,30 @@ func downloadSecrets(cmd *cobra.Command, args []string) {
508507
utils.HandleError(errors.New("invalid fallback file passphrase"))
509508
}
510509

511-
var body []byte
512-
if format == models.JSON {
513-
fallbackPath := ""
514-
legacyFallbackPath := ""
515-
metadataPath := ""
516-
if enableFallback {
517-
fallbackPath, legacyFallbackPath = initFallbackDir(cmd, localConfig, format, nameTransformer, nil, exitOnWriteFailure)
518-
}
519-
if enableCache {
520-
metadataPath = controllers.MetadataFilePath(localConfig.Token.Value, localConfig.EnclaveProject.Value, localConfig.EnclaveConfig.Value, format, nameTransformer, nil)
521-
}
522-
523-
fallbackOpts := controllers.FallbackOptions{
524-
Enable: enableFallback,
525-
Path: fallbackPath,
526-
LegacyPath: legacyFallbackPath,
527-
Readonly: fallbackReadonly,
528-
Exclusive: fallbackOnly,
529-
ExclusiveFlag: fallbackFlag,
530-
ExitOnWriteFailure: exitOnWriteFailure,
531-
Passphrase: fallbackPassphrase,
532-
}
533-
secrets, _ := controllers.FetchSecrets(localConfig, enableCache, fallbackOpts, metadataPath, nameTransformer, dynamicSecretsTTL, format, nil)
534-
535-
var err error
536-
body, err = json.Marshal(secrets)
537-
if err != nil {
538-
utils.HandleError(err, "Unable to parse JSON secrets")
539-
}
540-
} else {
541-
// fallback file is not supported when fetching env/yaml format
542-
enableFallback = false
543-
enableCache = false
544-
flags := []string{"fallback", "fallback-only", "offline", "fallback-readonly", "no-exit-on-write-failure"}
545-
for _, flag := range flags {
546-
if cmd.Flags().Changed(flag) {
547-
utils.LogWarning(fmt.Sprintf("--%s has no effect when format is %s", flag, format))
548-
}
549-
}
510+
fallbackPath := ""
511+
legacyFallbackPath := ""
512+
metadataPath := ""
513+
if enableFallback {
514+
fallbackPath, legacyFallbackPath = initFallbackDir(cmd, localConfig, format, nameTransformer, nil, exitOnWriteFailure)
515+
}
516+
if enableCache {
517+
metadataPath = controllers.MetadataFilePath(localConfig.Token.Value, localConfig.EnclaveProject.Value, localConfig.EnclaveConfig.Value, format, nameTransformer, nil)
518+
}
550519

551-
var apiError http.Error
552-
_, _, body, apiError = http.DownloadSecrets(localConfig.APIHost.Value, utils.GetBool(localConfig.VerifyTLS.Value, true), localConfig.Token.Value, localConfig.EnclaveProject.Value, localConfig.EnclaveConfig.Value, format, nameTransformer, "", dynamicSecretsTTL, nil)
553-
if !apiError.IsNil() {
554-
utils.HandleError(apiError.Unwrap(), apiError.Message)
555-
}
520+
fallbackOpts := controllers.FallbackOptions{
521+
Enable: enableFallback,
522+
Path: fallbackPath,
523+
LegacyPath: legacyFallbackPath,
524+
Readonly: fallbackReadonly,
525+
Exclusive: fallbackOnly,
526+
ExclusiveFlag: fallbackFlag,
527+
ExitOnWriteFailure: exitOnWriteFailure,
528+
Passphrase: fallbackPassphrase,
556529
}
557530

531+
// FetchSecrets returns raw bytes and supports caching/fallback for all formats
532+
body, _ := controllers.FetchSecrets(localConfig, enableCache, fallbackOpts, metadataPath, nameTransformer, dynamicSecretsTTL, format, nil)
533+
558534
if !saveFile {
559535
utils.Print(string(body))
560536
return

‎pkg/controllers/fallback.go‎

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,24 @@ func WriteMetadataFile(path string, etag string, hash string) Error {
119119
return Error{}
120120
}
121121

122-
// SecretsCacheFile reads the contents of the cache file
122+
// SecretsCacheFile reads the contents of the cache file and parses as JSON
123123
func SecretsCacheFile(path string, passphrase string) (map[string]string, Error) {
124+
bytes, err := SecretsCacheFileBytes(path, passphrase)
125+
if !err.IsNil() {
126+
return nil, err
127+
}
128+
129+
secrets := map[string]string{}
130+
parseErr := json.Unmarshal(bytes, &secrets)
131+
if parseErr != nil {
132+
return nil, Error{Err: parseErr, Message: "Unable to parse cache file"}
133+
}
134+
135+
return secrets, Error{}
136+
}
137+
138+
// SecretsCacheFileBytes reads the raw contents of the cache file
139+
func SecretsCacheFileBytes(path string, passphrase string) ([]byte, Error) {
124140
utils.LogDebug(fmt.Sprintf("Using fallback file for cache %s", path))
125141

126142
if _, err := os.Stat(path); err != nil {
@@ -138,11 +154,5 @@ func SecretsCacheFile(path string, passphrase string) (map[string]string, Error)
138154
return nil, Error{Err: err, Message: "Unable to decrypt cache file"}
139155
}
140156

141-
secrets := map[string]string{}
142-
err = json.Unmarshal([]byte(decryptedSecrets), &secrets)
143-
if err != nil {
144-
return nil, Error{Err: err, Message: "Unable to parse cache file"}
145-
}
146-
147-
return secrets, Error{}
157+
return []byte(decryptedSecrets), Error{}
148158
}

0 commit comments

Comments
 (0)