Skip to content

Commit

Permalink
[sdk/go] Ensure stackref getters localize secret-ness to the key retr…
Browse files Browse the repository at this point in the history
…ieved
  • Loading branch information
AaronFriel committed Jun 14, 2022
1 parent 818c81a commit 5910584
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 12 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG_PENDING.md
Expand Up @@ -13,3 +13,6 @@

- [sdk/go] Mark StackReference keys that don't exist as unknown. Error when converting unknown keys to strings.
[#9855](https://github.com/pulumi/pulumi/pull/9855)

- [sdk/go] Precisely mark values obtained via stack reference `Get...Output(key)` methods as secret or not.
[#9842](https://github.com/pulumi/pulumi/pull/9842)
35 changes: 23 additions & 12 deletions sdk/go/pulumi/stack_reference.go
Expand Up @@ -3,6 +3,8 @@ package pulumi
import (
"fmt"
"reflect"

"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
)

// StackReference manages a reference to a Pulumi stack.
Expand All @@ -22,21 +24,30 @@ type StackReference struct {
// GetOutput returns a stack output keyed by the given name as an AnyOutput
// If the given name is not present in the StackReference, Output<nil> is returned.
func (s *StackReference) GetOutput(name StringInput) AnyOutput {
return All(name, s.Outputs).
ApplyT(func(args []interface{}) interface{} {
n, outs := args[0].(string), args[1].(map[string]interface{})
v, ok := outs[n]
if ok {
return v
return All(name, s.rawOutputs).
ApplyT(func(args []interface{}) (interface{}, error) {
n, stack := args[0].(string), args[1].(resource.PropertyMap)
if !stack["outputs"].IsObject() {
return Any(nil), fmt.Errorf("failed to convert %T to object", stack)
}
if s.ctx.DryRun() {
// It is a dry run, so it is safe to return an unknown output.
return UnsafeUnknownOutput([]Resource{s})
outs := stack["outputs"].ObjectValue()
v, ok := outs[resource.PropertyKey(n)]
if !ok {
if s.ctx.DryRun() {
// It is a dry run, so it is safe to return an unknown output.
return UnsafeUnknownOutput([]Resource{s}), nil
}

// We don't return an error to remain consistent with other SDKs regarding missing keys.
return nil, nil
}

// We don't return an error to remain consistent with other SDKs regarding
// missing keys.
return nil
ret, secret, _ := unmarshalPropertyValue(s.ctx, v)

if secret {
ret = ToSecret(ret)
}
return ret, nil
}).(AnyOutput)
}

Expand Down
92 changes: 92 additions & 0 deletions sdk/go/pulumi/stack_reference_test.go
Expand Up @@ -2,6 +2,7 @@ package pulumi

import (
"fmt"
"strings"
"testing"

"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
Expand Down Expand Up @@ -56,6 +57,9 @@ func TestStackReference(t *testing.T) {
zed1, _, _, _, err := await(ref1.GetOutput(String("zed")))
assert.NoError(t, err)
assert.Equal(t, outputs["zed"], zed1)
nonexistant, _, _, _, err := await(ref0.GetOutput(String("nonexistant")))
assert.NoError(t, err)
assert.Equal(t, nil, nonexistant)
numf, _, _, _, err := await(ref1.GetFloat64Output(String("numf")))
assert.NoError(t, err)
assert.Equal(t, outputs["numf"], numf)
Expand Down Expand Up @@ -91,3 +95,91 @@ func TestStackReference(t *testing.T) {
}))
assert.NoError(t, err)
}

func TestStackReferenceSecrets(t *testing.T) {
t.Parallel()
var resName string

expected := map[string]interface{}{
"foo": "bar",
"baz": []interface{}{"qux"},
"zed": map[string]interface{}{
"alpha": "beta",
},
"numf": 123.4,
"numi": 567.0,

"secret-foo": "bar",
"secret-baz": []interface{}{"qux"},
"secret-zed": map[string]interface{}{
"alpha": "beta",
},
"secret-numf": 123.4,
"secret-numi": 567.0,
}

properties := resource.PropertyMap{}
for k, v := range expected {
v := resource.NewPropertyValue(v)
if strings.HasPrefix(k, "secret-") {
v = resource.MakeSecret(v)
}
properties[resource.PropertyKey(k)] = v
}

outputs := resource.NewObjectProperty(properties)

mocks := &testMonitor{
NewResourceF: func(args MockResourceArgs) (string, resource.PropertyMap, error) {
assert.Equal(t, "pulumi:pulumi:StackReference", args.TypeToken)
assert.Equal(t, resName, args.Name)
assert.True(t, args.Inputs.DeepEquals(resource.NewPropertyMapFromMap(map[string]interface{}{
"name": "stack",
})))
assert.Equal(t, "", args.Provider)
assert.Equal(t, args.Inputs["name"].StringValue(), args.ID)
return args.Inputs["name"].StringValue(), resource.PropertyMap{
"name": resource.NewStringProperty("stack"),
"outputs": outputs,
}, nil
},
}
err := RunErr(func(ctx *Context) error {
resName = "stack"
ref0, err := NewStackReference(ctx, resName, nil)
assert.NoError(t, err)
_, _, _, _, err = await(ref0.ID())
assert.NoError(t, err)
resName = "stack2"
ref1, err := NewStackReference(ctx, resName, &StackReferenceArgs{Name: String("stack")})
assert.NoError(t, err)

outs0, _, _, _, err := await(ref0.Outputs)
assert.NoError(t, err)
assert.Equal(t, expected, outs0)
outs1, _, _, _, err := await(ref1.Outputs)
assert.NoError(t, err)
assert.Equal(t, expected, outs1)

for _, ref := range []*StackReference{ref0, ref1} {
for k, v := range expected {
shouldSecret := strings.HasPrefix(k, "secret-")

outputV, known, secret, _, err := await(ref.GetOutput(String(k)))
assert.NoError(t, err)
assert.True(t, known)
assert.Equal(t, shouldSecret, secret)
assert.Equal(t, v, outputV)
}

outputV, known, secret, _, err := await(ref.GetOutput(String("nonexistant-key")))
assert.NoError(t, err)
assert.True(t, known)
assert.Equal(t, false, secret)
assert.Equal(t, nil, outputV)
}

return nil
}, WithMocks("project", "stack", mocks))
assert.NoError(t, err)
}

0 comments on commit 5910584

Please sign in to comment.