From 92fa71c6061d56bafa0a7f0a32d8d529102c385e Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Wed, 26 Feb 2025 12:38:38 -0800 Subject: [PATCH 1/3] teach env set to take a file as input --- cmd/esc/cli/env_set.go | 50 +++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/cmd/esc/cli/env_set.go b/cmd/esc/cli/env_set.go index 626eb3b5..893bb36a 100644 --- a/cmd/esc/cli/env_set.go +++ b/cmd/esc/cli/env_set.go @@ -5,21 +5,22 @@ package cli import ( "context" "fmt" - "regexp" - "strconv" - "github.com/ccojocar/zxcvbn-go" - "github.com/spf13/cobra" - "gopkg.in/yaml.v3" - "github.com/pulumi/esc/syntax/encoding" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" + "io" + "os" + "regexp" + "strconv" ) func newEnvSetCmd(env *envCommand) *cobra.Command { var secret bool var plaintext bool + var filename string cmd := &cobra.Command{ Use: "set [/][/] ", @@ -45,7 +46,12 @@ func newEnvSetCmd(env *envCommand) *cobra.Command { if ref.version != "" { return fmt.Errorf("the set command does not accept versions") } - if len(args) < 2 { + reqArgs := 2 + if filename != "" { + // if filename is provided, we only need a path + reqArgs = 1 + } + if len(args) < reqArgs { return fmt.Errorf("expected a path and a value") } @@ -57,8 +63,18 @@ func newEnvSetCmd(env *envCommand) *cobra.Command { return fmt.Errorf("path must contain at least one element") } + var value []byte + if filename == "" { + value = []byte(args[1]) + } else { + value, err = readFileInput(filename) + if err != nil { + return err + } + } + var yamlValue yaml.Node - if err := yaml.Unmarshal([]byte(args[1]), &yamlValue); err != nil { + if err := yaml.Unmarshal(value, &yamlValue); err != nil { return fmt.Errorf("invalid value: %w", err) } if len(yamlValue.Content) == 0 { @@ -147,10 +163,28 @@ func newEnvSetCmd(env *envCommand) *cobra.Command { cmd.Flags().BoolVar( &plaintext, "plaintext", false, "true to leave the value in plaintext") + cmd.Flags().StringVar( + &filename, "file", "", "read value from file. use `-` to read from stdin.") return cmd } +// readFileInput reads the full content of filename, or stdin if filename is `-` +func readFileInput(filename string) (input []byte, err error) { + if filename == "-" { + input, err = io.ReadAll(os.Stdin) + if err != nil { + return nil, fmt.Errorf("reading stdin: %w", err) + } + } else { + input, err = os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("reading file: %w", err) + } + } + return input, err +} + // keyPattern is the regular expression a configuration key must match before we check (and error) if we think // it is a password var keyPattern = regexp.MustCompile("(?i)passwd|pass|password|pwd|secret|token") From db51cc65f82f9d058e7c8d13c701c39562b854f7 Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Wed, 26 Feb 2025 12:50:15 -0800 Subject: [PATCH 2/3] add flag to opt out of interpreting input value as yaml --- cmd/esc/cli/env_set.go | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/cmd/esc/cli/env_set.go b/cmd/esc/cli/env_set.go index 893bb36a..f3552dac 100644 --- a/cmd/esc/cli/env_set.go +++ b/cmd/esc/cli/env_set.go @@ -21,6 +21,7 @@ func newEnvSetCmd(env *envCommand) *cobra.Command { var secret bool var plaintext bool var filename string + var inputYaml bool cmd := &cobra.Command{ Use: "set [/][/] ", @@ -74,16 +75,21 @@ func newEnvSetCmd(env *envCommand) *cobra.Command { } var yamlValue yaml.Node - if err := yaml.Unmarshal(value, &yamlValue); err != nil { - return fmt.Errorf("invalid value: %w", err) - } - if len(yamlValue.Content) == 0 { - // This can happen when the value is empty (e.g. when "" is present on the command line). Treat this - // as the empty string. - err = yaml.Unmarshal([]byte(`""`), &yamlValue) - contract.IgnoreError(err) + if inputYaml { + if err := yaml.Unmarshal(value, &yamlValue); err != nil { + return fmt.Errorf("invalid value: %w", err) + } + if len(yamlValue.Content) == 0 { + // This can happen when the value is empty (e.g. when "" is present on the command line). Treat this + // as the empty string. + err = yaml.Unmarshal([]byte(`""`), &yamlValue) + contract.IgnoreError(err) + } + yamlValue = *yamlValue.Content[0] + } else { + // treat input as a raw string + yamlValue.SetString(string(value)) } - yamlValue = *yamlValue.Content[0] if looksLikeSecret(path, yamlValue) && !secret && !plaintext { return fmt.Errorf("value looks like a secret; rerun with --secret to mark it as such, or --plaintext if you meant to leave it as plaintext") @@ -165,6 +171,8 @@ func newEnvSetCmd(env *envCommand) *cobra.Command { "true to leave the value in plaintext") cmd.Flags().StringVar( &filename, "file", "", "read value from file. use `-` to read from stdin.") + cmd.Flags().BoolVar( + &inputYaml, "yaml", true, "treat value as a structured yaml node.") return cmd } From d3c8be4d5f08075e9cfe243ee3a584731cff30e2 Mon Sep 17 00:00:00 2001 From: Claire Gaestel <213631+nyobe@users.noreply.github.com> Date: Wed, 26 Feb 2025 12:50:45 -0800 Subject: [PATCH 3/3] preserve yaml node style so that block literals are encoded correctly --- syntax/encoding/yaml.go | 1 + 1 file changed, 1 insertion(+) diff --git a/syntax/encoding/yaml.go b/syntax/encoding/yaml.go index abc0cb65..ef43b2fa 100644 --- a/syntax/encoding/yaml.go +++ b/syntax/encoding/yaml.go @@ -136,6 +136,7 @@ func (s YAMLSyntax) Set(prefix, path resource.PropertyPath, new yaml.Node) (*yam s.Kind = new.Kind s.Tag = new.Tag s.Value = new.Value + s.Style = new.Style return s.Node, nil }