diff --git a/pkg/internal/tests/cross-tests/adapt.go b/pkg/internal/tests/cross-tests/adapt.go index cbe4717be..9711a1e14 100644 --- a/pkg/internal/tests/cross-tests/adapt.go +++ b/pkg/internal/tests/cross-tests/adapt.go @@ -17,120 +17,13 @@ package crosstests import ( - "context" - "fmt" "math/big" "github.com/hashicorp/terraform-plugin-go/tftypes" - "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" - "github.com/stretchr/testify/require" "github.com/zclconf/go-cty/cty" - - "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/convert" - "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge/info" - shim "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfshim" - "github.com/pulumi/pulumi-terraform-bridge/v3/unstable/logging" ) -// InferPulumiValue generates a Pulumi value that is semantically equivalent to v. -// -// InferPulumiValue takes into account schema information. -func InferPulumiValue(t T, schema shim.SchemaMap, infos map[string]*info.Schema, v cty.Value) resource.PropertyMap { - if v.IsNull() { - return nil - } - decoder, err := convert.NewObjectDecoder(convert.ObjectSchema{ - SchemaMap: schema, - SchemaInfos: infos, - }) - require.NoError(t, err) - - ctx := logging.InitLogging(context.Background(), logging.LogOptions{}) - // There is not yet a way to opt out of marking schema secrets, so the resulting map might have secrets marked. - pm, err := convert.DecodePropertyMap(ctx, decoder, ctyToTftypes(v)) - require.NoError(t, err) - return pm -} - -func ctyToTftypes(v cty.Value) tftypes.Value { - typ := v.Type() - if !v.IsKnown() { - return tftypes.NewValue(ctyTypeToTfType(typ), tftypes.UnknownValue) - } - if v.IsNull() { - return tftypes.NewValue(ctyTypeToTfType(typ), nil) - } - switch { - case typ.Equals(cty.String): - return tftypes.NewValue(ctyTypeToTfType(typ), v.AsString()) - case typ.Equals(cty.Bool): - return tftypes.NewValue(ctyTypeToTfType(typ), v.True()) - case typ.Equals(cty.Number): - return tftypes.NewValue(ctyTypeToTfType(typ), v.AsBigFloat()) - - case typ.IsListType(): - src := v.AsValueSlice() - dst := make([]tftypes.Value, len(src)) - for i, v := range src { - dst[i] = ctyToTftypes(v) - } - return tftypes.NewValue(ctyTypeToTfType(typ), dst) - case typ.IsSetType(): - src := v.AsValueSet().Values() - dst := make([]tftypes.Value, len(src)) - for i, v := range src { - dst[i] = ctyToTftypes(v) - } - return tftypes.NewValue(ctyTypeToTfType(typ), dst) - case typ.IsMapType(): - src := v.AsValueMap() - dst := make(map[string]tftypes.Value, len(src)) - for k, v := range src { - dst[k] = ctyToTftypes(v) - } - return tftypes.NewValue(ctyTypeToTfType(typ), dst) - case typ.IsObjectType(): - src := v.AsValueMap() - dst := make(map[string]tftypes.Value, len(src)) - for k, v := range src { - dst[k] = ctyToTftypes(v) - } - return tftypes.NewValue(ctyTypeToTfType(typ), dst) - default: - panic(fmt.Sprintf("unknown type %s", typ.GoString())) - } -} - -func ctyTypeToTfType(typ cty.Type) tftypes.Type { - switch { - case typ.Equals(cty.String): - return tftypes.String - case typ.Equals(cty.Bool): - return tftypes.Bool - case typ.Equals(cty.Number): - return tftypes.Number - case typ == cty.DynamicPseudoType: - return tftypes.DynamicPseudoType - - case typ.IsListType(): - return tftypes.List{ElementType: ctyTypeToTfType(typ.ElementType())} - case typ.IsSetType(): - return tftypes.Set{ElementType: ctyTypeToTfType(typ.ElementType())} - case typ.IsMapType(): - return tftypes.Map{ElementType: ctyTypeToTfType(typ.ElementType())} - case typ.IsObjectType(): - src := typ.AttributeTypes() - dst := make(map[string]tftypes.Type, len(src)) - for k, v := range src { - dst[k] = ctyTypeToTfType(v) - } - return tftypes.Object{AttributeTypes: dst} - default: - panic(fmt.Sprintf("unknown type %s", typ.GoString())) - } -} - type typeAdapter struct { typ tftypes.Type } diff --git a/pkg/internal/tests/cross-tests/configure.go b/pkg/internal/tests/cross-tests/configure.go index 656544717..84c8a0453 100644 --- a/pkg/internal/tests/cross-tests/configure.go +++ b/pkg/internal/tests/cross-tests/configure.go @@ -25,6 +25,7 @@ import ( "github.com/zclconf/go-cty/cty" "gopkg.in/yaml.v3" + crosstestsimpl "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/internal/tests/cross-tests/impl" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tests/pulcheck" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tests/tfcheck" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge/info" @@ -61,7 +62,7 @@ func Configure( if opts.puConfig != nil { puConfig = *opts.puConfig } else { - puConfig = InferPulumiValue(t, + puConfig = crosstestsimpl.InferPulumiValue(t, shimv2.NewSchemaMap(provider), opts.resourceInfo.GetFields(), tfConfig, diff --git a/pkg/internal/tests/cross-tests/create.go b/pkg/internal/tests/cross-tests/create.go index 541ca731b..4d218bc7e 100644 --- a/pkg/internal/tests/cross-tests/create.go +++ b/pkg/internal/tests/cross-tests/create.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/require" "github.com/zclconf/go-cty/cty" + crosstestsimpl "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/internal/tests/cross-tests/impl" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tests/pulcheck" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge/info" shimv2 "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfshim/sdk-v2" @@ -63,7 +64,7 @@ func Create( if opts.puConfig != nil { puConfig = *opts.puConfig } else { - puConfig = InferPulumiValue(t, + puConfig = crosstestsimpl.InferPulumiValue(t, shimv2.NewSchemaMap(resourceSchema), opts.resourceInfo.GetFields(), tfConfig, diff --git a/pkg/internal/tests/cross-tests/diff_check.go b/pkg/internal/tests/cross-tests/diff_check.go index c2e260946..0258e3e9a 100644 --- a/pkg/internal/tests/cross-tests/diff_check.go +++ b/pkg/internal/tests/cross-tests/diff_check.go @@ -29,6 +29,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + crosstestsimpl "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/internal/tests/cross-tests/impl" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tests/pulcheck" ) @@ -85,12 +86,12 @@ func runDiffCheck(t T, tc diffTestCase) diffResult { tfResourceName: defRtype, } - yamlProgram := pd.generateYAML(t, InferPulumiValue(t, + yamlProgram := pd.generateYAML(t, crosstestsimpl.InferPulumiValue(t, bridgedProvider.P.ResourcesMap().Get(defRtype).Schema(), nil, tfConfig1)) pt := pulcheck.PulCheck(t, bridgedProvider, string(yamlProgram)) pt.Up(t) - yamlProgram = pd.generateYAML(t, InferPulumiValue(t, + yamlProgram = pd.generateYAML(t, crosstestsimpl.InferPulumiValue(t, bridgedProvider.P.ResourcesMap().Get(defRtype).Schema(), nil, tfConfig2)) err := os.WriteFile(filepath.Join(pt.CurrentStack().Workspace().WorkDir(), "Pulumi.yaml"), yamlProgram, 0o600) require.NoErrorf(t, err, "writing Pulumi.yaml") diff --git a/pkg/internal/tests/cross-tests/impl/inferPulumiValue.go b/pkg/internal/tests/cross-tests/impl/inferPulumiValue.go new file mode 100644 index 000000000..8d5ecbb51 --- /dev/null +++ b/pkg/internal/tests/cross-tests/impl/inferPulumiValue.go @@ -0,0 +1,131 @@ +// Copyright 2016-2024, 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 crosstestsimpl (cross-tests implementation) contains code meant to be shared +// across cross-test implementations (SDKv2, PF) but not used by people writing tests +// themselves. +package crosstestsimpl + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/pulumi/pulumi/sdk/v3/go/common/resource" + "github.com/stretchr/testify/require" + "github.com/zclconf/go-cty/cty" + + "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/convert" + "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge/info" + shim "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfshim" + "github.com/pulumi/pulumi-terraform-bridge/v3/unstable/logging" +) + +// InferPulumiValue generates a Pulumi value that is semantically equivalent to v. +// +// InferPulumiValue takes into account schema information. +func InferPulumiValue(t T, schema shim.SchemaMap, infos map[string]*info.Schema, v cty.Value) resource.PropertyMap { + if v.IsNull() { + return nil + } + decoder, err := convert.NewObjectDecoder(convert.ObjectSchema{ + SchemaMap: schema, + SchemaInfos: infos, + }) + require.NoError(t, err) + + ctx := logging.InitLogging(context.Background(), logging.LogOptions{}) + // There is not yet a way to opt out of marking schema secrets, so the resulting map might have secrets marked. + pm, err := convert.DecodePropertyMap(ctx, decoder, ctyToTftypes(v)) + require.NoError(t, err) + return pm +} + +func ctyToTftypes(v cty.Value) tftypes.Value { + typ := v.Type() + if !v.IsKnown() { + return tftypes.NewValue(ctyTypeToTfType(typ), tftypes.UnknownValue) + } + if v.IsNull() { + return tftypes.NewValue(ctyTypeToTfType(typ), nil) + } + switch { + case typ.Equals(cty.String): + return tftypes.NewValue(ctyTypeToTfType(typ), v.AsString()) + case typ.Equals(cty.Bool): + return tftypes.NewValue(ctyTypeToTfType(typ), v.True()) + case typ.Equals(cty.Number): + return tftypes.NewValue(ctyTypeToTfType(typ), v.AsBigFloat()) + + case typ.IsListType(): + src := v.AsValueSlice() + dst := make([]tftypes.Value, len(src)) + for i, v := range src { + dst[i] = ctyToTftypes(v) + } + return tftypes.NewValue(ctyTypeToTfType(typ), dst) + case typ.IsSetType(): + src := v.AsValueSet().Values() + dst := make([]tftypes.Value, len(src)) + for i, v := range src { + dst[i] = ctyToTftypes(v) + } + return tftypes.NewValue(ctyTypeToTfType(typ), dst) + case typ.IsMapType(): + src := v.AsValueMap() + dst := make(map[string]tftypes.Value, len(src)) + for k, v := range src { + dst[k] = ctyToTftypes(v) + } + return tftypes.NewValue(ctyTypeToTfType(typ), dst) + case typ.IsObjectType(): + src := v.AsValueMap() + dst := make(map[string]tftypes.Value, len(src)) + for k, v := range src { + dst[k] = ctyToTftypes(v) + } + return tftypes.NewValue(ctyTypeToTfType(typ), dst) + default: + panic(fmt.Sprintf("unknown type %s", typ.GoString())) + } +} + +func ctyTypeToTfType(typ cty.Type) tftypes.Type { + switch { + case typ.Equals(cty.String): + return tftypes.String + case typ.Equals(cty.Bool): + return tftypes.Bool + case typ.Equals(cty.Number): + return tftypes.Number + case typ == cty.DynamicPseudoType: + return tftypes.DynamicPseudoType + + case typ.IsListType(): + return tftypes.List{ElementType: ctyTypeToTfType(typ.ElementType())} + case typ.IsSetType(): + return tftypes.Set{ElementType: ctyTypeToTfType(typ.ElementType())} + case typ.IsMapType(): + return tftypes.Map{ElementType: ctyTypeToTfType(typ.ElementType())} + case typ.IsObjectType(): + src := typ.AttributeTypes() + dst := make(map[string]tftypes.Type, len(src)) + for k, v := range src { + dst[k] = ctyTypeToTfType(v) + } + return tftypes.Object{AttributeTypes: dst} + default: + panic(fmt.Sprintf("unknown type %s", typ.GoString())) + } +} diff --git a/pkg/internal/tests/cross-tests/impl/t.go b/pkg/internal/tests/cross-tests/impl/t.go new file mode 100644 index 000000000..fdee2d76a --- /dev/null +++ b/pkg/internal/tests/cross-tests/impl/t.go @@ -0,0 +1,31 @@ +// Copyright 2016-2024, 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 crosstestsimpl + +import ( + "github.com/pulumi/providertest/pulumitest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Abstractions to allow tests to work against both *[testing.T] and [rapid.TB]. +type T interface { + Logf(string, ...any) + TempDir() string + Skip(...any) + require.TestingT + assert.TestingT + pulumitest.PT +} diff --git a/pkg/internal/tests/cross-tests/puwrite_test.go b/pkg/internal/tests/cross-tests/puwrite_test.go index 10b051f16..dc0959c9a 100644 --- a/pkg/internal/tests/cross-tests/puwrite_test.go +++ b/pkg/internal/tests/cross-tests/puwrite_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" + crosstestsimpl "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/internal/tests/cross-tests/impl" shimv2 "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfshim/sdk-v2" ) @@ -124,7 +125,7 @@ runtime: yaml )) schema := shimProvider.ResourcesMap().Get(rtype).Schema() out, err := generateYaml(t, rtoken, - InferPulumiValue(t, schema, nil, coalesceInputs(t, tc.schema, tc.tfConfig))) + crosstestsimpl.InferPulumiValue(t, schema, nil, coalesceInputs(t, tc.schema, tc.tfConfig))) require.NoError(t, err) b, err := yaml.Marshal(out) require.NoError(t, err) diff --git a/pkg/internal/tests/cross-tests/t.go b/pkg/internal/tests/cross-tests/t.go index 56d31217d..57cfc868a 100644 --- a/pkg/internal/tests/cross-tests/t.go +++ b/pkg/internal/tests/cross-tests/t.go @@ -12,20 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Abstractions to allow tests to work against both *testing.T and rapid.TB. package crosstests import ( - "github.com/pulumi/providertest/pulumitest" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + crosstestsimpl "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/internal/tests/cross-tests/impl" ) -type T interface { - Logf(string, ...any) - TempDir() string - Skip(...any) - require.TestingT - assert.TestingT - pulumitest.PT -} +type T = crosstestsimpl.T diff --git a/pkg/internal/tests/cross-tests/upgrade_state_check.go b/pkg/internal/tests/cross-tests/upgrade_state_check.go index e0ed59e6a..de7c59a6f 100644 --- a/pkg/internal/tests/cross-tests/upgrade_state_check.go +++ b/pkg/internal/tests/cross-tests/upgrade_state_check.go @@ -29,6 +29,7 @@ import ( "github.com/zclconf/go-cty/cty" "gotest.tools/v3/assert" + crosstestsimpl "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/internal/tests/cross-tests/impl" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tests/pulcheck" ) @@ -87,14 +88,14 @@ func runPulumiUpgrade(t T, res1, res2 *schema.Resource, config1, config2 cty.Val tfResourceName: defRtype, } - yamlProgram := pd.generateYAML(t, InferPulumiValue(t, + yamlProgram := pd.generateYAML(t, crosstestsimpl.InferPulumiValue(t, prov1.P.ResourcesMap().Get(pd.tfResourceName).Schema(), nil, config1)) pt := pulcheck.PulCheck(t, prov1, string(yamlProgram)) pt.Up(t) stack := pt.ExportStack(t) schemaVersion1 := getVersionInState(t, stack) - yamlProgram = pd.generateYAML(t, InferPulumiValue(t, + yamlProgram = pd.generateYAML(t, crosstestsimpl.InferPulumiValue(t, prov1.P.ResourcesMap().Get(pd.tfResourceName).Schema(), nil, config2)) p := filepath.Join(pt.CurrentStack().Workspace().WorkDir(), "Pulumi.yaml") err := os.WriteFile(p, yamlProgram, 0o600) diff --git a/pkg/pf/tests/internal/cross-tests/configure.go b/pkg/pf/tests/internal/cross-tests/configure.go index 319bfde05..a8f67261a 100644 --- a/pkg/pf/tests/internal/cross-tests/configure.go +++ b/pkg/pf/tests/internal/cross-tests/configure.go @@ -28,6 +28,7 @@ import ( "github.com/pulumi/providertest/pulumitest" "github.com/pulumi/providertest/pulumitest/opttest" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/internal/tests/cross-tests" + crosstestsimpl "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/internal/tests/cross-tests/impl" pb "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/pf/tests/internal/providerbuilder" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/pf/tfbridge" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/pf/tfgen" @@ -138,7 +139,7 @@ resource "` + providerName + `_res" "res" {} if opts.puConfig != nil { puConfig = *opts.puConfig } else { - puConfig = crosstests.InferPulumiValue(t, + puConfig = crosstestsimpl.InferPulumiValue(t, tfbridge.ShimProvider(prov(nil)).Schema(), opts.resourceInfo, cty.ObjectVal(tfConfig),