Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 58 additions & 16 deletions cmd/esc/cli/env_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,23 @@ 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
var inputYaml bool

cmd := &cobra.Command{
Use: "set [<org-name>/][<project-name>/]<environment-name> <path> <value>",
Expand All @@ -45,7 +47,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")
}

Expand All @@ -57,17 +64,32 @@ func newEnvSetCmd(env *envCommand) *cobra.Command {
return fmt.Errorf("path must contain at least one element")
}

var yamlValue yaml.Node
if err := yaml.Unmarshal([]byte(args[1]), &yamlValue); err != nil {
return fmt.Errorf("invalid value: %w", err)
var value []byte
if filename == "" {
value = []byte(args[1])
} else {
value, err = readFileInput(filename)
if err != nil {
return 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)

var yamlValue yaml.Node
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")
Expand Down Expand Up @@ -147,10 +169,30 @@ 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.")
cmd.Flags().BoolVar(
&inputYaml, "yaml", true, "treat value as a structured yaml node.")

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")
Expand Down
1 change: 1 addition & 0 deletions syntax/encoding/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
Loading