Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to copy configs between stacks #4971

Merged
merged 4 commits into from
Jul 17, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ CHANGELOG

- Add support for streamInvoke during update
[#4990](https://github.com/pulumi/pulumi/pull/4990)

- Add ability to copy configuration values between stacks
[#4971](https://github.com/pulumi/pulumi/pull/4971)

## 2.6.1 (2020-07-09)

Expand Down
156 changes: 154 additions & 2 deletions pkg/cmd/pulumi/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,162 @@ 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("current stack and destination stack are the same")
}
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")
stack72 marked this conversation as resolved.
Show resolved Hide resolved
cpCommand.PersistentFlags().StringVarP(
&destinationStackName, "dest", "d", "",
"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 errors.Errorf(
"configuration key '%s' not found for stack '%s'", prettyKey(key), currentStack.Ref())
}
if ok {
stack72 marked this conversation as resolved.
Show resolved Hide resolved
stack72 marked this conversation as resolved.
Show resolved Hide resolved
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()
}

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

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

err = destinationProjectStack.Config.Set(key, val, 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
}

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

// The use of `requiresSaving` here ensures that there was actually some config
// that needed saved, otherwise it's an unnecessary save call
if requiresSaving {
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 +583,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 +670,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
18 changes: 18 additions & 0 deletions sdk/go/common/resource/config/crypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,21 @@ func decryptAES256GCM(ciphertext []byte, key []byte, nonce []byte) (string, erro

return string(msg), err
}

// Crypter that just adds a prefix to the plaintext string when encrypting,
// and removes the prefix from the ciphertext when decrypting.
stack72 marked this conversation as resolved.
Show resolved Hide resolved
type prefixCrypter struct {
prefix string
}

func newPrefixCrypter(prefix string) Crypter {
return prefixCrypter{prefix: prefix}
}

func (c prefixCrypter) DecryptValue(ciphertext string) (string, error) {
return strings.TrimPrefix(ciphertext, c.prefix), nil
}

func (c prefixCrypter) EncryptValue(plaintext string) (string, error) {
return c.prefix + plaintext, nil
}
14 changes: 14 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,20 @@ 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 {
val, err := c.Copy(decrypter, encrypter)
if err != nil {
return nil, err
}

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