Add the ability to copy configs between stacks (#4971)
This commit is contained in:
parent
418e2291a2
commit
6d09fe32df
|
@ -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)
|
||||
|
||||
- Add logic to parce pulumi venv on github action
|
||||
[#4994](https://github.com/pulumi/pulumi/pull/4994)
|
||||
|
|
|
@ -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")
|
||||
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 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()
|
||||
}
|
||||
|
||||
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 errors.Errorf(
|
||||
"configuration key '%s' not found for stack '%s'", prettyKey(key), currentStack.Ref())
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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=
|
||||
|
@ -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=
|
||||
|
|
|
@ -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=
|
||||
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=
|
||||
|
@ -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=
|
||||
|
|
|
@ -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, for use in tests.
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1170,6 +1170,82 @@ func TestSetFail(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCopyMap(t *testing.T) {
|
||||
tests := []struct {
|
||||
Config Map
|
||||
Expected Map
|
||||
}{
|
||||
{
|
||||
Config: Map{
|
||||
MustMakeKey("my", "testKey"): NewValue("testValue"),
|
||||
},
|
||||
Expected: Map{
|
||||
MustMakeKey("my", "testKey"): NewValue("testValue"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Config: Map{
|
||||
MustMakeKey("my", "testKey"): NewSecureValue("stackAsecurevalue"),
|
||||
},
|
||||
Expected: Map{
|
||||
MustMakeKey("my", "testKey"): NewSecureValue("stackBsecurevalue"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Config: Map{
|
||||
MustMakeKey("my", "testKey"): NewObjectValue(`{"inner":"value"}`),
|
||||
},
|
||||
Expected: Map{
|
||||
MustMakeKey("my", "testKey"): NewObjectValue(`{"inner":"value"}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
Config: Map{
|
||||
MustMakeKey("my", "testKey"): NewSecureObjectValue(`{"inner":{"secure":"stackAsecurevalue"}}`),
|
||||
},
|
||||
Expected: Map{
|
||||
MustMakeKey("my", "testKey"): NewSecureObjectValue(`{"inner":{"secure":"stackBsecurevalue"}}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
Config: Map{
|
||||
//nolint:lll
|
||||
MustMakeKey("my", "testKey"): NewSecureObjectValue(`[{"inner":{"secure":"stackAsecurevalue"}},{"secure":"stackAsecurevalue2"}]`),
|
||||
},
|
||||
Expected: Map{
|
||||
//nolint:lll
|
||||
MustMakeKey("my", "testKey"): NewSecureObjectValue(`[{"inner":{"secure":"stackBsecurevalue"}},{"secure":"stackBsecurevalue2"}]`),
|
||||
},
|
||||
},
|
||||
{
|
||||
Config: Map{
|
||||
MustMakeKey("my", "test.Key"): NewValue("testValue"),
|
||||
},
|
||||
Expected: Map{
|
||||
MustMakeKey("my", "test.Key"): NewValue("testValue"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Config: Map{
|
||||
MustMakeKey("my", "name"): NewObjectValue(`[["value"]]`),
|
||||
},
|
||||
Expected: Map{
|
||||
MustMakeKey("my", "name"): NewObjectValue(`[["value"]]`),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("%v", test), func(t *testing.T) {
|
||||
newConfig, err := test.Config.Copy(newPrefixCrypter("stackA"), newPrefixCrypter("stackB"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.Expected, newConfig)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func roundtripMapYAML(m Map) (Map, error) {
|
||||
return roundtripMap(m, yaml.Marshal, yaml.Unmarshal)
|
||||
}
|
||||
|
|
|
@ -72,6 +72,46 @@ func (c Value) Value(decrypter Decrypter) (string, error) {
|
|||
return decrypter.DecryptValue(c.value)
|
||||
}
|
||||
|
||||
func (c Value) Copy(decrypter Decrypter, encrypter Encrypter) (Value, error) {
|
||||
var val Value
|
||||
raw, err := c.Value(decrypter)
|
||||
if err != nil {
|
||||
return Value{}, err
|
||||
}
|
||||
if c.Secure() {
|
||||
if c.Object() {
|
||||
objVal, err := c.ToObject()
|
||||
if err != nil {
|
||||
return Value{}, err
|
||||
}
|
||||
encryptedObj, err := reencryptObject(objVal, decrypter, encrypter)
|
||||
if err != nil {
|
||||
return Value{}, err
|
||||
}
|
||||
json, err := json.Marshal(encryptedObj)
|
||||
if err != nil {
|
||||
return Value{}, err
|
||||
}
|
||||
|
||||
val = NewSecureObjectValue(string(json))
|
||||
} else {
|
||||
enc, eerr := encrypter.EncryptValue(raw)
|
||||
if eerr != nil {
|
||||
return Value{}, eerr
|
||||
}
|
||||
val = NewSecureValue(enc)
|
||||
}
|
||||
} else {
|
||||
if c.Object() {
|
||||
val = NewObjectValue(raw)
|
||||
} else {
|
||||
val = NewValue(raw)
|
||||
}
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (c Value) SecureValues(decrypter Decrypter) ([]string, error) {
|
||||
d := NewTrackingDecrypter(decrypter)
|
||||
if _, err := c.Value(d); err != nil {
|
||||
|
@ -240,6 +280,53 @@ func isSecureValue(v interface{}) (bool, string) {
|
|||
return false, ""
|
||||
}
|
||||
|
||||
func reencryptObject(v interface{}, decrypter Decrypter, encrypter Encrypter) (interface{}, error) {
|
||||
reencryptIt := 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 reencryptObject(val, decrypter, encrypter)
|
||||
}
|
||||
|
||||
switch t := v.(type) {
|
||||
case map[string]interface{}:
|
||||
m := make(map[string]interface{})
|
||||
for key, val := range t {
|
||||
encrypted, err := reencryptIt(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 := reencryptIt(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) {
|
||||
|
|
|
@ -245,6 +245,43 @@ func TestSecureValues(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCopyValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
Val Value
|
||||
Expected Value
|
||||
}{
|
||||
{
|
||||
Val: NewValue("value"),
|
||||
Expected: NewValue("value"),
|
||||
},
|
||||
{
|
||||
Val: NewObjectValue(`{"foo":"bar"}`),
|
||||
Expected: NewObjectValue(`{"foo":"bar"}`),
|
||||
},
|
||||
{
|
||||
Val: NewSecureObjectValue(`{"foo":{"secure":"stackAsecurevalue"}}`),
|
||||
Expected: NewSecureObjectValue(`{"foo":{"secure":"stackBsecurevalue"}}`),
|
||||
},
|
||||
{
|
||||
Val: NewSecureValue("stackAsecurevalue"),
|
||||
Expected: NewSecureValue("stackBsecurevalue"),
|
||||
},
|
||||
{
|
||||
Val: NewSecureObjectValue(`["a",{"secure":"stackAalpha"},{"test":{"secure":"stackAbeta"}}]`),
|
||||
Expected: NewSecureObjectValue(`["a",{"secure":"stackBalpha"},{"test":{"secure":"stackBbeta"}}]`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("%v", test), func(t *testing.T) {
|
||||
newConfig, err := test.Val.Copy(newPrefixCrypter("stackA"), newPrefixCrypter("stackB"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.Expected, newConfig)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func roundtripValueYAML(v Value) (Value, error) {
|
||||
return roundtripValue(v, yaml.Marshal, yaml.Unmarshal)
|
||||
}
|
||||
|
|
|
@ -130,6 +130,8 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
|
|||
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=
|
||||
|
@ -314,12 +316,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/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
|
|
Loading…
Reference in a new issue