Skip to content

Commit

Permalink
Permit computed default values to consult PriorState of the resource (#…
Browse files Browse the repository at this point in the history
…1318)

With these changes, ComputedDefault machinery is now able to consult PriorState. This is a building block to enable AutoNaming to work in the Plugin Framework context where __defaults are not being tracked, and therefore AutoName machinery is called repeatedly, causing any entropy in the randomized name generation to rebuild the name for a resource during a routine update, which needs to be avoided as the names need to persist through updates to avoid needless replace plans. With PriorState exposed, the machinery can be modified to consult it to avoid re-running random generators. This is coming in a subsequent PR.

Tests added to cover pkg/v3 and pf versions of providers.
  • Loading branch information
t0yv0 authored Aug 1, 2023
1 parent 7565808 commit 5d215c8
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 23 deletions.
63 changes: 54 additions & 9 deletions pf/tests/provider_check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ func TestCheck(t *testing.T) {
schema schema.Schema
replay string
replayMulti string
callback tfbridge0.PreCheckCallback

callback tfbridge0.PreCheckCallback

customizeResource func(*tfbridge0.ResourceInfo)
}

testCases := []testCase{
Expand Down Expand Up @@ -257,6 +260,44 @@ func TestCheck(t *testing.T) {
return config, nil
},
},
{
name: "default application can consult prior state",
schema: schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{Computed: true},
"s": schema.StringAttribute{Optional: true},
},
},
customizeResource: func(info *tfbridge0.ResourceInfo) {
info.Fields["s"] = &tfbridge0.SchemaInfo{
Default: &tfbridge0.DefaultInfo{
ComputeDefault: func(
_ context.Context,
opts tfbridge0.ComputeDefaultOptions,
) (any, error) {
return opts.PriorState["s"].StringValue(), nil
},
},
}
},
replay: `
{
"method": "/pulumirpc.ResourceProvider/Check",
"request": {
"urn": "urn:pulumi:st::pg::testprovider:index/res:Res::r",
"olds": {
"s": "oldString"
},
"news": {},
"randomSeed": "wqZZaHWVfsS1ozo3bdauTfZmjslvWcZpUjn7BzpS79c="
},
"response": {
"inputs": {
"s": "oldString"
}
}
}`,
},
}

for _, tc := range testCases {
Expand All @@ -278,23 +319,27 @@ func TestCheck(t *testing.T) {
ResourceSchema: tc.schema,
}},
}
res := tfbridge0.ResourceInfo{
Tok: "testprovider:index/res:Res",
Docs: &tfbridge0.DocInfo{
Markdown: []byte("OK"),
},
PreCheckCallback: tc.callback,
Fields: map[string]*tfbridge0.SchemaInfo{},
}
if tc.customizeResource != nil {
tc.customizeResource(&res)
}
info := tfbridge0.ProviderInfo{
Name: "testprovider",
P: tfbridge.ShimProvider(testProvider),
Version: "0.0.1",
MetadataInfo: &tfbridge0.MetadataInfo{},
Resources: map[string]*tfbridge0.ResourceInfo{
"testprovider_res": {
Tok: "testprovider:index/res:Res",
Docs: &tfbridge0.DocInfo{
Markdown: []byte("OK"),
},
PreCheckCallback: tc.callback,
},
"testprovider_res": &res,
},
}
s := newProviderServer(t, info)

if tc.replay != "" {
testutils.Replay(t, s, tc.replay)
}
Expand Down
1 change: 1 addition & 0 deletions pf/tfbridge/provider_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func (p *provider) CheckWithContext(
URN: urn,
Properties: checkedInputs,
Seed: randomSeed,
PriorState: priorState,
},
PropertyMap: checkedInputs,
ProviderConfig: p.lastKnownProviderConfig,
Expand Down
3 changes: 3 additions & 0 deletions pkg/tfbridge/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,9 @@ type ComputeDefaultOptions struct {
// Property map before computing the defaults.
Properties resource.PropertyMap

// Property map representing prior state, only set for non-Create Resource operations.
PriorState resource.PropertyMap

// The engine provides a stable seed useful for generating random values consistently. This guarantees, for
// example, that random values generated across "pulumi preview" and "pulumi up" in the same deployment are
// consistent. This currently is only available for resource changes.
Expand Down
76 changes: 76 additions & 0 deletions pkg/tfbridge/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,82 @@ func TestProviderReadNestedSecretV2(t *testing.T) {
testProviderReadNestedSecret(t, provider, "NestedSecretResource")
}

func TestCheck(t *testing.T) {
t.Run("Default application can consult prior state in Check", func(t *testing.T) {
provider := &Provider{
tf: shimv2.NewProvider(testTFProviderV2),
config: shimv2.NewSchemaMap(testTFProviderV2.Schema),
}
computeStringDefault := func(_ context.Context, opts ComputeDefaultOptions) (interface{}, error) {
if v, ok := opts.PriorState["stringPropertyValue"]; ok {
return v.StringValue() + "!", nil
}
return nil, nil
}
provider.resources = map[tokens.Type]Resource{
"ExampleResource": {
TF: shimv2.NewResource(testTFProviderV2.ResourcesMap["example_resource"]),
TFName: "example_resource",
Schema: &ResourceInfo{
Tok: "ExampleResource",
Fields: map[string]*SchemaInfo{
"string_property_value": {
Default: &DefaultInfo{
ComputeDefault: computeStringDefault,
},
},
},
},
},
}
testutils.Replay(t, provider, `
{
"method": "/pulumirpc.ResourceProvider/Check",
"request": {
"urn": "urn:pulumi:dev::teststack::ExampleResource::exres",
"randomSeed": "ZCiVOcvG/CT5jx4XriguWgj2iMpQEb8P3ZLqU/AS2yg=",
"olds": {
"__defaults": [],
"stringPropertyValue": "oldString"
},
"news": {
"arrayPropertyValues": []
}
},
"response": {
"inputs": {
"__defaults": ["stringPropertyValue"],
"arrayPropertyValues": [],
"stringPropertyValue": "oldString!"
}
}
}
`)
// If old value is missing it is ignored.
testutils.Replay(t, provider, `
{
"method": "/pulumirpc.ResourceProvider/Check",
"request": {
"urn": "urn:pulumi:dev::teststack::ExampleResource::exres",
"randomSeed": "ZCiVOcvG/CT5jx4XriguWgj2iMpQEb8P3ZLqU/AS2yg=",
"olds": {
"__defaults": []
},
"news": {
"arrayPropertyValues": []
}
},
"response": {
"inputs": {
"__defaults": [],
"arrayPropertyValues": []
}
}
}
`)
})
}

func TestCheckConfig(t *testing.T) {
t.Run("minimal", func(t *testing.T) {
// Ensure the method is minimally implemented. Pulumi will be passing a provider version. Make sure it
Expand Down
39 changes: 25 additions & 14 deletions pkg/tfbridge/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,20 +282,30 @@ func elemSchemas(sch shim.Schema, ps *SchemaInfo) (shim.Schema, *SchemaInfo) {
}

type conversionContext struct {
Instance *PulumiResource
ProviderConfig resource.PropertyMap
ApplyDefaults bool
Assets AssetTable
ComputeDefaultOptions ComputeDefaultOptions
ProviderConfig resource.PropertyMap
ApplyDefaults bool
Assets AssetTable
}

func MakeTerraformInputs(instance *PulumiResource, config resource.PropertyMap, olds, news resource.PropertyMap,
tfs shim.SchemaMap, ps map[string]*SchemaInfo) (map[string]interface{}, AssetTable, error) {

cdOptions := ComputeDefaultOptions{}
if instance != nil {
cdOptions = ComputeDefaultOptions{
PriorState: olds,
Properties: instance.Properties,
Seed: instance.Seed,
URN: instance.URN,
}
}

ctx := &conversionContext{
Instance: instance,
ProviderConfig: config,
ApplyDefaults: true,
Assets: AssetTable{},
ComputeDefaultOptions: cdOptions,
ProviderConfig: config,
ApplyDefaults: true,
Assets: AssetTable{},
}
inputs, err := ctx.MakeTerraformInputs(olds, news, tfs, ps, false)
if err != nil {
Expand Down Expand Up @@ -689,17 +699,18 @@ func (ctx *conversionContext) applyDefaults(result map[string]interface{}, olds,
// Getting the correct context needs to refactor public methods such as
// MakeTerraformInput to MakeTerraformInputWithContext.
context.TODO(),
ComputeDefaultOptions{
URN: ctx.Instance.URN,
Properties: ctx.Instance.Properties,
Seed: ctx.Instance.Seed,
})
ctx.ComputeDefaultOptions,
)
if err != nil {
return err
}
defaultValue, source = v, "func"
} else if from := info.Default.From; from != nil {
v, err := from(ctx.Instance)
v, err := from(&PulumiResource{
URN: ctx.ComputeDefaultOptions.URN,
Properties: ctx.ComputeDefaultOptions.Properties,
Seed: ctx.ComputeDefaultOptions.Seed,
})
if err != nil {
return err
}
Expand Down

0 comments on commit 5d215c8

Please sign in to comment.