Skip to content

Commit

Permalink
Passing tests + cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
blampe committed Dec 22, 2023
1 parent 7ec8c57 commit d22212f
Show file tree
Hide file tree
Showing 6 changed files with 454 additions and 69 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ examples/**/pulumi-resource-*

/.vscode

**/testdata/rapid/**
**/testdata/rapid/**
70 changes: 17 additions & 53 deletions internal/configencoding/configencoding.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2023, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package configencoding

import (
Expand Down Expand Up @@ -79,11 +93,11 @@ func (enc *configEncoding) convertStringToPropertyValue(s string, prop schema.Pr

func (*configEncoding) zeroValue(typ string) resource.PropertyValue {
switch typ {
case "bool":
case "boolean":
return resource.NewPropertyValue(false)
case "int", "float":
case "integer", "number":
return resource.NewPropertyValue(0)
case "list", "set":
case "array":
return resource.NewPropertyValue([]interface{}{})
default:
return resource.NewPropertyValue(map[string]interface{}{})
Expand Down Expand Up @@ -190,53 +204,3 @@ func (enc *configEncoding) UnmarshalProperties(props *structpb.Struct) (resource

return result, nil
}

// Inverse of UnmarshalProperties, with additional support for secrets. Since the encoding cannot represent nested
// secrets, any nested secrets will be approximated by making the entire top-level property secret.
// func (enc *ConfigEncoding) MarshalProperties(props resource.PropertyMap) (*structpb.Struct, error) {
// opts := plugin.MarshalOptions{
// Label: "config",
// KeepUnknowns: true,
// SkipNulls: true,
// RejectAssets: true,
// KeepSecrets: true,
// }

// copy := make(resource.PropertyMap)
// for k, v := range props {
// var err error
// copy[k], err = enc.jsonEncodePropertyValue(k, v)
// if err != nil {
// return nil, err
// }
// }
// return plugin.MarshalProperties(copy, opts)
// }

// func (enc *ConfigEncoding) jsonEncodePropertyValue(k resource.PropertyKey,
// v resource.PropertyValue,
// ) (resource.PropertyValue, error) {
// if v.ContainsUnknowns() {
// return resource.NewStringProperty(plugin.UnknownStringValue), nil
// }
// if v.ContainsSecrets() {
// encoded, err := enc.jsonEncodePropertyValue(k, propertyvalue.RemoveSecrets(v))
// if err != nil {
// return v, err
// }
// return resource.MakeSecret(encoded), err
// }
// _, knownKey := enc.schema.Variables[string(k)]
// switch {
// case knownKey && v.IsNull():
// return resource.NewStringProperty(""), nil
// case knownKey && !v.IsNull() && !v.IsString():
// encoded, err := json.Marshal(v.Mappable())
// if err != nil {
// return v, fmt.Errorf("JSON encoding error while marshalling property %q: %w", k, err)
// }
// return resource.NewStringProperty(string(encoded)), nil
// default:
// return v, nil
// }
// }
285 changes: 285 additions & 0 deletions internal/configencoding/configencoding_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
// Copyright 2023, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package configencoding

import (
"fmt"
"testing"

structpb "google.golang.org/protobuf/types/known/structpb"

"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
"github.com/stretchr/testify/assert"
)

func TestConfigEncoding(t *testing.T) {
type testCase struct {
ty schema.TypeSpec
v *structpb.Value
pv resource.PropertyValue
}

knownKey := "mykey"

makeEnc := func(typ schema.TypeSpec) *configEncoding {
return New(
schema.ConfigSpec{
Variables: map[string]schema.PropertySpec{
knownKey: {
TypeSpec: typ,
},
},
},
)
}

makeValue := func(x any) *structpb.Value {
vv, err := structpb.NewValue(x)
assert.NoErrorf(t, err, "structpb.NewValue failed")
return vv
}

checkUnmarshal := func(t *testing.T, tc testCase) {
enc := makeEnc(tc.ty)
pv, err := enc.unmarshalPropertyValue(resource.PropertyKey(knownKey), tc.v)
assert.NoError(t, err)
assert.NotNil(t, pv)
assert.Equal(t, tc.pv, *pv)
}

turnaroundTestCases := []testCase{
{
schema.TypeSpec{Type: "boolean"},
makeValue(`true`),
resource.NewBoolProperty(true),
},
{
schema.TypeSpec{Type: "boolean"},
makeValue(`false`),
resource.NewBoolProperty(false),
},
{
schema.TypeSpec{Type: "integer"},
makeValue(`0`),
resource.NewNumberProperty(0),
},
{
schema.TypeSpec{Type: "integer"},
makeValue(`42`),
resource.NewNumberProperty(42),
},
{
schema.TypeSpec{Type: "number"},
makeValue(`0`),
resource.NewNumberProperty(0.0),
},
{
schema.TypeSpec{Type: "number"},
makeValue(`42.5`),
resource.NewNumberProperty(42.5),
},
{
schema.TypeSpec{Type: "string"},
structpb.NewStringValue(""),
resource.NewStringProperty(""),
},
{
schema.TypeSpec{Type: "string"},
structpb.NewStringValue("hello"),
resource.NewStringProperty("hello"),
},
{
schema.TypeSpec{Type: "array"},
makeValue(`[]`),
resource.NewArrayProperty([]resource.PropertyValue{}),
},
{
schema.TypeSpec{Type: "array"},
makeValue(`["hello","there"]`),
resource.NewArrayProperty([]resource.PropertyValue{
resource.NewStringProperty("hello"),
resource.NewStringProperty("there"),
}),
},
{
schema.TypeSpec{Type: "object"},
makeValue(`{}`),
resource.NewObjectProperty(resource.PropertyMap{}),
},
{
schema.TypeSpec{Type: "object"},
makeValue(`{"key":"value"}`),
resource.NewObjectProperty(resource.PropertyMap{
"key": resource.NewStringProperty("value"),
}),
},
}

t.Run("turnaround", func(t *testing.T) {
for i, tc := range turnaroundTestCases {
tc := tc

t.Run(fmt.Sprintf("UnmarshalPropertyValue/%d", i), func(t *testing.T) {
checkUnmarshal(t, tc)
})
}
})

t.Run("zero_values", func(t *testing.T) {
// Historically the encoding was able to convert empty strings into type-appropriate zero values.
cases := []testCase{
{
schema.TypeSpec{Type: "boolean"},
makeValue(""),
resource.NewBoolProperty(false),
},
{
schema.TypeSpec{Type: "number"},
makeValue(""),
resource.NewNumberProperty(0.),
},
{
schema.TypeSpec{Type: "integer"},
makeValue(""),
resource.NewNumberProperty(0),
},
{
schema.TypeSpec{Type: "string"},
makeValue(""),
resource.NewStringProperty(""),
},
{
schema.TypeSpec{Type: "object"},
makeValue(""),
resource.NewObjectProperty(make(resource.PropertyMap)),
},
{
schema.TypeSpec{Type: "array"},
makeValue(""),
resource.NewArrayProperty([]resource.PropertyValue{}),
},
}
for _, tc := range cases {
t.Run(fmt.Sprintf("%v", tc.ty), func(t *testing.T) {
checkUnmarshal(t, tc)
})
}
})

t.Run("computed", func(t *testing.T) {
unk := makeValue(plugin.UnknownStringValue)

for i, tc := range turnaroundTestCases {
tc := tc

t.Run(fmt.Sprintf("UnmarshalPropertyValue/%d", i), func(t *testing.T) {
// Unknown sentinel unmarshals to a Computed with a type-appropriate zero value.
checkUnmarshal(t, testCase{
tc.ty,
unk,
resource.MakeComputed(makeEnc(tc.ty).zeroValue(tc.ty.Type)),
})
})
}
})

t.Run("secret", func(t *testing.T) {
// Unmarshalling happens with KeepSecrets=false, replacing them with the underlying values. This case
// does not need to be tested.
//
// Marhalling however supports sending secrets back to the engine, intending to mark values as secret
// that happen on paths that are declared as secret in the schema. Due to the limitation of the
// JSON-in-proto-encoding, secrets are communicated imprecisely as an approximation: if any nested
// element of a property is secret, the entire property is marshalled as secret.

var secretCases []testCase

pbSecret := func(v *structpb.Value) *structpb.Value {
return structpb.NewStructValue(&structpb.Struct{Fields: map[string]*structpb.Value{
"4dabf18193072939515e22adb298388d": makeValue("1b47061264138c4ac30d75fd1eb44270"),
"value": v,
}})
}

for _, tc := range turnaroundTestCases {
secretCases = append(secretCases, testCase{
tc.ty,
pbSecret(tc.v),
resource.MakeSecret(tc.pv),
})
}

for i, tc := range secretCases {
tc := tc

t.Run(fmt.Sprintf("secret/UnmarshalPropertyValue/%d", i), func(t *testing.T) {
// Unmarshallin will remove secrts, so the expected value needs to be modified.
tc.pv = tc.pv.SecretValue().Element
checkUnmarshal(t, tc)
})
}

t.Run("tolerate secrets in Configure", func(t *testing.T) {
// This is a bit of a histirocal quirk: the engine may send secrets to Configure before
// receiving the response from Configure indicating that the provider does not want to receive
// secrets. These are simply ignored. The engine does not currently send secrets to CheckConfig.
// The engine does take care of making sure the secrets are stored as such in the statefile.
//
// Check here that unmarshalilng such values removes the secrets.
checkUnmarshal(t, testCase{
schema.TypeSpec{Type: "object"},
pbSecret(makeValue(`{"key":"val"}`)),
resource.NewObjectProperty(resource.PropertyMap{
"key": resource.NewStringProperty("val"),
}),
})
})
})

regressUnmarshalTestCases := []testCase{
{
schema.TypeSpec{Type: "array"},
makeValue(`
[
{
"address": "somewhere.org",
"password": {
"4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270",
"value": "some-password"
},
"username": "some-user"
}
]`),
resource.NewArrayProperty([]resource.PropertyValue{
resource.NewObjectProperty(resource.PropertyMap{
"address": resource.NewStringProperty("somewhere.org"),
"password": resource.NewStringProperty("some-password"),
"username": resource.NewStringProperty("some-user"),
}),
}),
},
}

t.Run("regress-unmarshal", func(t *testing.T) {
for i, tc := range regressUnmarshalTestCases {
tc := tc
t.Run(fmt.Sprintf("UnmarshalPropertyValue/%d", i), func(t *testing.T) {
checkUnmarshal(t, tc)
})
}
})
}
1 change: 0 additions & 1 deletion tests/grpc/config/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
pulumi-resource-config

14 changes: 0 additions & 14 deletions tests/grpc/config/consumer/ts/run.sh

This file was deleted.

Loading

0 comments on commit d22212f

Please sign in to comment.