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 14, 2020
1 parent 736019f commit 28860fc
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 4 deletions.
159 changes: 157 additions & 2 deletions pkg/cmd/pulumi/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,165 @@ func newConfigCmd() *cobra.Command {
cmd.AddCommand(newConfigRmCmd(&stack))
cmd.AddCommand(newConfigSetCmd(&stack))
cmd.AddCommand(newConfigRefreshCmd(&stack))
cmd.AddCommand(newConfigCopyCmd(&stack))

return cmd
}

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

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

// Get current stack and ensure that it is a different stack to the destination stack
currentStack, err := requireStack(*stack, false, opts, true /*setCurrent*/)
if err != nil {
return err
}
if currentStack.Ref().Name().String() == destinationStackName {
return errors.New("config must be copied to a different destination stack than currently using")
}
currentProjectStack, err := loadProjectStack(currentStack)
if err != nil {
return err
}

// Get the destination stack
destinationStack, err := requireStack(destinationStackName, false, opts, false /*setCurrent*/)
if err != nil {
return err
}
destinationProjectStack, err := loadProjectStack(destinationStack)
if err != nil {
return err
}

// Do we need to copy a single value or the entire map
if len(args) > 0 {
// A single key was specified so we only need to copy that specific value
return copySingleConfigKey(args[0], path, currentStack, currentProjectStack, destinationStack,
destinationProjectStack)
}

return copyEntireConfigMap(currentStack, currentProjectStack, destinationStack, destinationProjectStack)
}),
}

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

return cpCommand
}

func copySingleConfigKey(configKey string, path bool, currentStack backend.Stack,
currentProjectStack *workspace.ProjectStack, destinationStack backend.Stack,
destinationProjectStack *workspace.ProjectStack) error {
var decrypter config.Decrypter
key, err := parseConfigKey(configKey)
if err != nil {
return errors.Wrap(err, "invalid configuration key")
}

v, ok, err := currentProjectStack.Config.Get(key, path)
if err != nil {
return err
}
if ok {
if v.Secure() {
var err error
if decrypter, err = getStackDecrypter(currentStack); err != nil {
return errors.Wrap(err, "could not create a decrypter")
}
} else {
decrypter = config.NewPanicCrypter()
}

raw, err := v.Value(decrypter)
if err != nil {
return errors.Wrap(err, "could not decrypt configuration value")
}

// now we start the work to copy it - if it was encrypted before then we need to do the same again
if v.Secure() {
// it was encrypted before, now we need to re-encrypt
c, cerr := getStackEncrypter(destinationStack)
if cerr != nil {
return cerr
}
enc, eerr := c.EncryptValue(raw)
if eerr != nil {
return eerr
}
v = config.NewSecureValue(enc)
} else {
v = config.NewValue(raw)
}

err = destinationProjectStack.Config.Set(key, v, path)
if err != nil {
return err
}

return saveProjectStack(destinationStack, destinationProjectStack)
}

return nil
}

func copyEntireConfigMap(currentStack backend.Stack,
currentProjectStack *workspace.ProjectStack, destinationStack backend.Stack,
destinationProjectStack *workspace.ProjectStack) error {

var decrypter config.Decrypter
currentConfig := currentProjectStack.Config
if currentConfig.HasSecureValue() {
dec, decerr := getStackDecrypter(currentStack)
if decerr != nil {
return decerr
}
decrypter = dec
} else {
decrypter = config.NewPanicCrypter()
}

encrypter, cerr := getStackEncrypter(destinationStack)
if cerr != nil {
return cerr
}

newProjectConfig, err := currentConfig.Copy(decrypter, encrypter)
if err != nil {
return err
}

for key, val := range newProjectConfig {
err = destinationProjectStack.Config.Set(key, val, false)
if err != nil {
return err
}

err = saveProjectStack(destinationStack, destinationProjectStack)
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 +586,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 +673,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
6 changes: 6 additions & 0 deletions pkg/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -323,12 +325,16 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
Expand Down
5 changes: 5 additions & 0 deletions scripts/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -80,10 +81,14 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
Expand Down
2 changes: 2 additions & 0 deletions sdk/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ require (
github.com/golang/protobuf v1.3.5
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645
github.com/hashicorp/go-multierror v1.0.0
github.com/kr/pretty v0.2.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.6 // indirect
github.com/mattn/go-runewidth v0.0.8 // indirect
github.com/mitchellh/go-ps v1.0.0
Expand Down
5 changes: 5 additions & 0 deletions sdk/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -104,10 +105,14 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
Expand Down
45 changes: 45 additions & 0 deletions sdk/go/common/resource/config/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,51 @@ func (m Map) Decrypt(decrypter Decrypter) (map[Key]string, error) {
return r, nil
}

func (m Map) Copy(decrypter Decrypter, encrypter Encrypter) (Map, error) {
newConfig := make(Map)
for k, c := range m {
var val Value
raw, err := c.Value(decrypter)
if err != nil {
return nil, err
}
if c.Secure() {
if c.Object() {
objVal, err := c.ToObject()
if err != nil {
return nil, err
}
encryptedObj, err := encryptObject(objVal, decrypter, encrypter)
if err != nil {
return nil, err
}
json, err := json.Marshal(encryptedObj)
if err != nil {
return nil, err
}

val = NewSecureObjectValue(string(json))
} else {
enc, eerr := encrypter.EncryptValue(raw)
if eerr != nil {
return nil, eerr
}
val = NewSecureValue(enc)
}
} else {
if c.Object() {
val = NewObjectValue(raw)
} else {
val = NewValue(raw)
}
}

newConfig[k] = val
}

return newConfig, nil
}

// HasSecureValue returns true if the config map contains a secure (encrypted) value.
func (m Map) HasSecureValue() bool {
for _, v := range m {
Expand Down
47 changes: 47 additions & 0 deletions sdk/go/common/resource/config/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,53 @@ func isSecureValue(v interface{}) (bool, string) {
return false, ""
}

func encryptObject(v interface{}, decrypter Decrypter, encrypter Encrypter) (interface{}, error) {
encryptIt := func(val interface{}) (interface{}, error) {
if isSecure, secureVal := isSecureValue(val); isSecure {
newVal := NewSecureValue(secureVal)
raw, err := newVal.Value(decrypter)
if err != nil {
return nil, err
}

encVal, err := encrypter.EncryptValue(raw)
if err != nil {
return nil, err
}

m := make(map[string]string)
m["secure"] = encVal

return m, nil
}
return encryptObject(val, decrypter, encrypter)
}

switch t := v.(type) {
case map[string]interface{}:
m := make(map[string]interface{})
for key, val := range t {
encrypted, err := encryptIt(val)
if err != nil {
return nil, err
}
m[key] = encrypted
}
return m, nil
case []interface{}:
a := make([]interface{}, len(t))
for i, val := range t {
encrypted, err := encryptIt(val)
if err != nil {
return nil, err
}
a[i] = encrypted
}
return a, nil
}
return v, nil
}

// decryptObject returns a new object with all secure values in the object converted to decrypted strings.
func decryptObject(v interface{}, decrypter Decrypter) (interface{}, error) {
decryptIt := func(val interface{}) (interface{}, error) {
Expand Down

0 comments on commit 28860fc

Please sign in to comment.