Skip to content

Commit

Permalink
Add the ability to copy configs between stacks
Browse files Browse the repository at this point in the history
Fixes: #1583
  • Loading branch information
stack72 committed Jul 6, 2020
1 parent 45d2fa9 commit 9793932
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 4 deletions.
182 changes: 180 additions & 2 deletions pkg/cmd/pulumi/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,188 @@ func newConfigCmd() *cobra.Command {
cmd.AddCommand(newConfigRmCmd(&stack))
cmd.AddCommand(newConfigSetCmd(&stack))
cmd.AddCommand(newConfigRefreshCmd(&stack))
cmd.AddCommand(newConfigCopyCmd(&stack))

return cmd
}

type stackConfigKeyValues struct {
name string
configNamespace string
value string
encrypted bool
}

func newConfigCopyCmd(stack *string) *cobra.Command {
var path bool
var newStackName string

cpCommand := &cobra.Command{
Use: "cp <key>",
Short: "Copy config to another stack",
Long: "Copies the config from the current stack to the newly specified stack. If a key is omitted,\n" +
"then all of the config, of the current stack, will copied to the new stack.",
Args: cmdutil.MaximumNArgs(1),
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
opts := display.Options{
Color: cmdutil.GetGlobalColorization(),
}

s, err := requireStack(*stack, false, opts, true /*setCurrent*/)
if err != nil {
return err
}

if s.Ref().Name().String() == newStackName {
return errors.New("config must be copied to a different stack than currently using")
}

currentStack, err := loadProjectStack(s)
if err != nil {
return err
}

configToCopy, err := buildConfigKeysToCopy(args, path, s, currentStack)
if err != nil {
return err
}

ns, err := requireStack(newStackName, false, opts, false /*setCurrent*/)
if err != nil {
return err
}
newStack, err := loadProjectStack(ns)
if err != nil {
return err
}

var projectNeedsSaved bool
for _, key := range configToCopy {
err := buildAndSaveConfig(ns, newStack, key)
if err != nil {
return err
}
projectNeedsSaved = true
}

if projectNeedsSaved {
return saveProjectStack(ns, newStack)
}

return nil
}),
}

cpCommand.PersistentFlags().BoolVar(
&path, "path", false,
"The key contains a path to a property in a map or list to set")
cpCommand.PersistentFlags().StringVarP(
&newStackName, "new-stack", "", "",
"The name of the new stack to copy the config to")

return cpCommand
}

func buildConfigKeysToCopy(args []string, path bool, stack backend.Stack,
projectStack *workspace.ProjectStack) ([]stackConfigKeyValues, error) {
var decrypter config.Decrypter
var keysToCopy []stackConfigKeyValues
cfg := projectStack.Config
if len(args) > 0 {
key, err := parseConfigKey(args[0])
if err != nil {
return nil, errors.Wrap(err, "invalid configuration key")
}

v, ok, err := cfg.Get(key, path)
if err != nil {
return nil, err
}
if ok {
configToCopy := stackConfigKeyValues{
name: key.Name(),
configNamespace: key.Namespace(),
encrypted: v.Secure(),
}
if v.Secure() {
var err error
if decrypter, err = getStackDecrypter(stack); err != nil {
return nil, errors.Wrap(err, "could not create a decrypter")
}
} else {
decrypter = config.NewPanicCrypter()
}
raw, err := v.Value(decrypter)
if err != nil {
return nil, errors.Wrap(err, "could not decrypt configuration value")
}
configToCopy.value = raw

keysToCopy = append(keysToCopy, configToCopy)
}
} else {
if cfg.HasSecureValue() {
dec, decerr := getStackDecrypter(stack)
if decerr != nil {
return nil, decerr
}
decrypter = dec
}
for configKey, configValue := range cfg {
configToCopy := stackConfigKeyValues{
configNamespace: configKey.Namespace(),
name: configKey.Name(),
}
if configValue.Secure() {
decryptedVal, err := cfg[configKey].Value(decrypter)
if err != nil {
return nil, errors.Wrap(err, "could not decrypt configuration value")
}
configToCopy.encrypted = true
configToCopy.value = decryptedVal
} else {
val, err := cfg[configKey].Value(config.NewBlindingDecrypter())
if err != nil {
return nil, errors.Wrap(err, "could not decrypt configuration value")
}
configToCopy.value = val
}

keysToCopy = append(keysToCopy, configToCopy)
}
}
return nil, nil
}

func buildAndSaveConfig(stack backend.Stack, project *workspace.ProjectStack, key stackConfigKeyValues) error {
var v config.Value
if key.encrypted {
c, cerr := getStackEncrypter(stack)
if cerr != nil {
return cerr
}
enc, eerr := c.EncryptValue(key.value)
if eerr != nil {
return eerr
}
v = config.NewSecureValue(enc)
} else {
v = config.NewValue(key.value)
}

configKey, err := config.ParseKey(fmt.Sprintf("%s:%s", key.configNamespace, key.name))
if err != nil {
return errors.Wrap(err, "invalid configuration key")
}

err = project.Config.Set(configKey, v, false)
if err != nil {
return err
}

return nil
}

func newConfigGetCmd(stack *string) *cobra.Command {
var jsonOut bool
var path bool
Expand Down Expand Up @@ -431,7 +609,7 @@ func listConfig(stack backend.Stack, showSecrets bool, jsonOut bool) error {
// By default, we will use a blinding decrypter to show "[secret]". If requested, display secrets in plaintext.
decrypter := config.NewBlindingDecrypter()
if cfg.HasSecureValue() && showSecrets {
dec, decerr := getStackDencrypter(stack)
dec, decerr := getStackDecrypter(stack)
if decerr != nil {
return decerr
}
Expand Down Expand Up @@ -518,7 +696,7 @@ func getConfig(stack backend.Stack, key config.Key, path, jsonOut bool) error {
var d config.Decrypter
if v.Secure() {
var err error
if d, err = getStackDencrypter(stack); err != nil {
if d, err = getStackDecrypter(stack); err != nil {
return errors.Wrap(err, "could not create a decrypter")
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/pulumi/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func getStackEncrypter(s backend.Stack) (config.Encrypter, error) {
return sm.Encrypter()
}

func getStackDencrypter(s backend.Stack) (config.Decrypter, error) {
func getStackDecrypter(s backend.Stack) (config.Decrypter, error) {
sm, err := getStackSecretsManager(s)
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/pulumi/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ This command lists data about previous updates for a stack.`,
}
var decrypter config.Decrypter
if showSecrets {
crypter, err := getStackDencrypter(s)
crypter, err := getStackDecrypter(s)
if err != nil {
return errors.Wrap(err, "decrypting secrets")
}
Expand Down

0 comments on commit 9793932

Please sign in to comment.