From 4e5a602e7af7891808306412f033c2c521f335b7 Mon Sep 17 00:00:00 2001 From: Venelin Date: Mon, 28 Apr 2025 16:18:08 +0300 Subject: [PATCH 01/11] repro --- .../schema_optional_input_with_default.go | 51 +++++++++++++ pkg/tfgen/generate_schema_test.go | 10 +++ .../testprovider/optionalinputwithdefault.go | 38 ++++++++++ .../required-input-with-default-schema.json | 72 +++++++++++++++++++ 4 files changed, 171 insertions(+) create mode 100644 internal/testprovider/schema_optional_input_with_default.go create mode 100644 pkg/tfgen/internal/testprovider/optionalinputwithdefault.go create mode 100644 pkg/tfgen/test_data/required-input-with-default-schema.json diff --git a/internal/testprovider/schema_optional_input_with_default.go b/internal/testprovider/schema_optional_input_with_default.go new file mode 100644 index 000000000..bd07e4544 --- /dev/null +++ b/internal/testprovider/schema_optional_input_with_default.go @@ -0,0 +1,51 @@ +// Copyright 2016-2022, 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 testprovider + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func ProviderRequiredInputWithDefaultFunc() *schema.Provider { + resourceRequiredInputWithDefaultFunc := func() *schema.Resource { + return &schema.Resource{ + Schema: resourceRequiredInputWithDefaultSchema(), + } + } + + return &schema.Provider{ + Schema: map[string]*schema.Schema{}, + ResourcesMap: map[string]*schema.Resource{ + "testprovider_res": resourceRequiredInputWithDefaultFunc(), + }, + } +} + +func resourceRequiredInputWithDefaultSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + DefaultFunc: func() (interface{}, error) { + return "default", nil + }, + }, + "other_name": { + Type: schema.TypeString, + Required: true, + Default: "default", + }, + } +} diff --git a/pkg/tfgen/generate_schema_test.go b/pkg/tfgen/generate_schema_test.go index 9ced81ae7..74acf62c7 100644 --- a/pkg/tfgen/generate_schema_test.go +++ b/pkg/tfgen/generate_schema_test.go @@ -438,6 +438,16 @@ func TestNestedDescriptions(t *testing.T) { bridgetesting.AssertEqualsJSONFile(t, "test_data/nested-descriptions-schema.json", schema) } +func TestRequiredInputWithDefault(t *testing.T) { + t.Parallel() + provider := testprovider.ProviderRequiredInputWithDefaultFunc() + schema, err := GenerateSchema(provider, diag.DefaultSink(io.Discard, io.Discard, diag.FormatOptions{ + Color: colors.Never, + })) + require.NoError(t, err) + bridgetesting.AssertEqualsJSONFile(t, "test_data/required-input-with-default-schema.json", schema) +} + func TestAppendExample_InsertMiddle(t *testing.T) { t.Parallel() descTmpl := `Description text diff --git a/pkg/tfgen/internal/testprovider/optionalinputwithdefault.go b/pkg/tfgen/internal/testprovider/optionalinputwithdefault.go new file mode 100644 index 000000000..87c51163e --- /dev/null +++ b/pkg/tfgen/internal/testprovider/optionalinputwithdefault.go @@ -0,0 +1,38 @@ +// Copyright 2016-2022, 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 testprovider + +import ( + testproviderdata "github.com/pulumi/pulumi-terraform-bridge/v3/internal/testprovider" + "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge" + shimv2 "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfshim/sdk-v2" +) + +func ProviderRequiredInputWithDefaultFunc() tfbridge.ProviderInfo { + return tfbridge.ProviderInfo{ + P: shimv2.NewProvider(testproviderdata.ProviderRequiredInputWithDefaultFunc()), + Name: "testprovider", + Description: "A Pulumi package to safely use testprovider in Pulumi programs.", + Keywords: []string{"pulumi", "testprovider"}, + License: "Apache-2.0", + Homepage: "https://pulumi.io", + Repository: "https://github.com/pulumi/pulumi-testprovider", + Resources: map[string]*tfbridge.ResourceInfo{ + "testprovider_res": { + Tok: tfbridge.MakeResource("testprovider", "index", "Res"), + }, + }, + } +} diff --git a/pkg/tfgen/test_data/required-input-with-default-schema.json b/pkg/tfgen/test_data/required-input-with-default-schema.json new file mode 100644 index 000000000..5ee4ee74e --- /dev/null +++ b/pkg/tfgen/test_data/required-input-with-default-schema.json @@ -0,0 +1,72 @@ +{ + "name": "testprovider", + "description": "A Pulumi package to safely use testprovider in Pulumi programs.", + "keywords": [ + "pulumi", + "testprovider" + ], + "homepage": "https://pulumi.io", + "license": "Apache-2.0", + "attribution": "This Pulumi package is based on the [`testprovider` Terraform Provider](https://github.com/terraform-providers/terraform-provider-testprovider).", + "repository": "https://github.com/pulumi/pulumi-testprovider", + "meta": { + "moduleFormat": "(.*)(?:/[^/]*)" + }, + "language": { + "nodejs": { + "packageDescription": "A Pulumi package to safely use testprovider in Pulumi programs.", + "readme": "\u003e This provider is a derived work of the [Terraform Provider](https://github.com/terraform-providers/terraform-provider-testprovider)\n\u003e distributed under [MPL 2.0](https://www.mozilla.org/en-US/MPL/2.0/). If you encounter a bug or missing feature,\n\u003e first check the [`pulumi-testprovider` repo](https://github.com/pulumi/pulumi-testprovider/issues); however, if that doesn't turn up anything,\n\u003e please consult the source [`terraform-provider-testprovider` repo](https://github.com/terraform-providers/terraform-provider-testprovider/issues).", + "compatibility": "tfbridge20", + "disableUnionOutputTypes": true + }, + "python": { + "readme": "\u003e This provider is a derived work of the [Terraform Provider](https://github.com/terraform-providers/terraform-provider-testprovider)\n\u003e distributed under [MPL 2.0](https://www.mozilla.org/en-US/MPL/2.0/). If you encounter a bug or missing feature,\n\u003e first check the [`pulumi-testprovider` repo](https://github.com/pulumi/pulumi-testprovider/issues); however, if that doesn't turn up anything,\n\u003e please consult the source [`terraform-provider-testprovider` repo](https://github.com/terraform-providers/terraform-provider-testprovider/issues).", + "compatibility": "tfbridge20", + "pyproject": {} + } + }, + "config": {}, + "provider": { + "description": "The provider type for the testprovider package. By default, resources use package-wide configuration\nsettings, however an explicit `Provider` instance may be created and passed during resource\nconstruction to achieve fine-grained programmatic control over provider settings. See the\n[documentation](https://www.pulumi.com/docs/reference/programming-model/#providers) for more information.\n" + }, + "resources": { + "testprovider:index/res:Res": { + "properties": { + "name": { + "type": "string" + }, + "otherName": { + "type": "string" + } + }, + "required": [ + "name", + "otherName" + ], + "inputProperties": { + "name": { + "type": "string" + }, + "otherName": { + "type": "string" + } + }, + "requiredInputs": [ + "name", + "otherName" + ], + "stateInputs": { + "description": "Input properties used for looking up and filtering Res resources.\n", + "properties": { + "name": { + "type": "string" + }, + "otherName": { + "type": "string" + } + }, + "type": "object" + } + } + } +} From 756b20373d87917d4a03745ef8dbbc47b252f239 Mon Sep 17 00:00:00 2001 From: Venelin Date: Mon, 28 Apr 2025 16:26:04 +0300 Subject: [PATCH 02/11] have tfgen check defaults when determining required inputs --- pkg/tfgen/generate_schema.go | 4 +++- pkg/tfgen/test_data/required-input-with-default-schema.json | 4 ---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/tfgen/generate_schema.go b/pkg/tfgen/generate_schema.go index 627550008..46340266b 100644 --- a/pkg/tfgen/generate_schema.go +++ b/pkg/tfgen/generate_schema.go @@ -865,7 +865,9 @@ func (g *schemaGenerator) genResourceType(mod tokens.Module, res *resourceType) } spec.InputProperties[prop.name] = g.genProperty(prop) - if !prop.optional() { + hasDefault := prop.schema.Default() != nil || prop.schema.DefaultFunc() != nil || prop.info.HasDefault() + + if !prop.optional() && !hasDefault { spec.RequiredInputs = append(spec.RequiredInputs, prop.name) } } diff --git a/pkg/tfgen/test_data/required-input-with-default-schema.json b/pkg/tfgen/test_data/required-input-with-default-schema.json index 5ee4ee74e..e8ccff2e0 100644 --- a/pkg/tfgen/test_data/required-input-with-default-schema.json +++ b/pkg/tfgen/test_data/required-input-with-default-schema.json @@ -51,10 +51,6 @@ "type": "string" } }, - "requiredInputs": [ - "name", - "otherName" - ], "stateInputs": { "description": "Input properties used for looking up and filtering Res resources.\n", "properties": { From 18351d6d29a47f506b9c9729c9daea2360e5ec2b Mon Sep 17 00:00:00 2001 From: Venelin Date: Tue, 29 Apr 2025 13:41:47 +0300 Subject: [PATCH 03/11] wip --- ... => schema_required_input_with_default.go} | 0 .../TestPFRequiredInputWithDefault.golden | 49 +++++++++++++++++++ pkg/pf/tfgen/tfgen_test.go | 40 +++++++++++++++ 3 files changed, 89 insertions(+) rename internal/testprovider/{schema_optional_input_with_default.go => schema_required_input_with_default.go} (100%) create mode 100644 pkg/pf/tfgen/testdata/TestPFRequiredInputWithDefault.golden diff --git a/internal/testprovider/schema_optional_input_with_default.go b/internal/testprovider/schema_required_input_with_default.go similarity index 100% rename from internal/testprovider/schema_optional_input_with_default.go rename to internal/testprovider/schema_required_input_with_default.go diff --git a/pkg/pf/tfgen/testdata/TestPFRequiredInputWithDefault.golden b/pkg/pf/tfgen/testdata/TestPFRequiredInputWithDefault.golden new file mode 100644 index 000000000..e150a5df1 --- /dev/null +++ b/pkg/pf/tfgen/testdata/TestPFRequiredInputWithDefault.golden @@ -0,0 +1,49 @@ +{ + "name": "testprovider", + "attribution": "This Pulumi package is based on the [`testprovider` Terraform Provider](https://github.com/terraform-providers/terraform-provider-testprovider).", + "meta": { + "moduleFormat": "(.*)(?:/[^/]*)" + }, + "language": { + "nodejs": { + "readme": "\u003e This provider is a derived work of the [Terraform Provider](https://github.com/terraform-providers/terraform-provider-testprovider)\n\u003e distributed under [MPL 2.0](https://www.mozilla.org/en-US/MPL/2.0/). If you encounter a bug or missing feature,\n\u003e please consult the source [`terraform-provider-testprovider` repo](https://github.com/terraform-providers/terraform-provider-testprovider/issues).", + "compatibility": "tfbridge20", + "disableUnionOutputTypes": true + }, + "python": { + "readme": "\u003e This provider is a derived work of the [Terraform Provider](https://github.com/terraform-providers/terraform-provider-testprovider)\n\u003e distributed under [MPL 2.0](https://www.mozilla.org/en-US/MPL/2.0/). If you encounter a bug or missing feature,\n\u003e please consult the source [`terraform-provider-testprovider` repo](https://github.com/terraform-providers/terraform-provider-testprovider/issues).", + "compatibility": "tfbridge20", + "pyproject": {} + } + }, + "config": {}, + "provider": { + "description": "The provider type for the testprovider package. By default, resources use package-wide configuration\nsettings, however an explicit `Provider` instance may be created and passed during resource\nconstruction to achieve fine-grained programmatic control over provider settings. See the\n[documentation](https://www.pulumi.com/docs/reference/programming-model/#providers) for more information.\n" + }, + "resources": { + "testprovider:index:Res": { + "properties": { + "a1": { + "type": "string" + } + }, + "required": [ + "a1" + ], + "inputProperties": { + "a1": { + "type": "string" + } + }, + "stateInputs": { + "description": "Input properties used for looking up and filtering Res resources.\n", + "properties": { + "a1": { + "type": "string" + } + }, + "type": "object" + } + } + } +} \ No newline at end of file diff --git a/pkg/pf/tfgen/tfgen_test.go b/pkg/pf/tfgen/tfgen_test.go index dd10f6c11..532781767 100644 --- a/pkg/pf/tfgen/tfgen_test.go +++ b/pkg/pf/tfgen/tfgen_test.go @@ -27,6 +27,7 @@ import ( pschema "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-framework/resource" rschema "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hexops/autogold/v2" @@ -521,3 +522,42 @@ func TestWriteOnlyOmit(t *testing.T) { require.NoError(t, json.Indent(&b, res.ProviderMetadata.PackageSchema, "", " ")) autogold.ExpectFile(t, autogold.Raw(b.String())) } + +func TestPFRequiredInputWithDefault(t *testing.T) { + t.Parallel() + + schema := rschema.Schema{ + Attributes: map[string]rschema.Attribute{ + "id": rschema.StringAttribute{Computed: true}, + "a1": rschema.StringAttribute{ + Required: true, + Default: stringdefault.StaticString("default"), + }, + }, + } + + info := &tfbridge.ResourceInfo{ + Tok: "testprovider:index:Res", + Docs: &tfbridge.DocInfo{Markdown: []byte{' '}}, + } + + res, err := GenerateSchema(context.Background(), GenerateSchemaOptions{ + ProviderInfo: tfbridge.ProviderInfo{ + Name: "testprovider", + UpstreamRepoPath: ".", // no invalid mappings warnings + // TODO: This uses a fake provider and does not actually test the right thing. + P: pftfbridge.ShimProvider(&schemaTestProvider{ + resources: map[string]rschema.Schema{ + "res": schema, + }, + }), + Resources: map[string]*tfbridge.ResourceInfo{ + "test_res": info, + }, + }, + }) + require.NoError(t, err) + var b bytes.Buffer + require.NoError(t, json.Indent(&b, res.ProviderMetadata.PackageSchema, "", " ")) + autogold.ExpectFile(t, autogold.Raw(b.String())) +} From 284dedc8774c757887e116d6fc2f76534f1134c8 Mon Sep 17 00:00:00 2001 From: Venelin Date: Wed, 30 Apr 2025 14:19:48 +0300 Subject: [PATCH 04/11] add HasDefault method --- pkg/pf/internal/pfutils/attr.go | 10 +- pkg/pf/internal/pfutils/default.go | 99 +++++++++++++++++++ pkg/pf/internal/schemashim/attr_schema.go | 6 ++ .../TestPFRequiredInputWithDefault.golden | 36 ++++++- pkg/pf/tfgen/tfgen_test.go | 1 - pkg/tfgen/generate_schema.go | 6 +- pkg/tfgen/generate_schema_test.go | 1 + .../required-input-with-default-schema.json | 36 ++++++- pkg/tfshim/sdk-v2/schema.go | 6 ++ pkg/tfshim/shim.go | 5 + 10 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 pkg/pf/internal/pfutils/default.go diff --git a/pkg/pf/internal/pfutils/attr.go b/pkg/pf/internal/pfutils/attr.go index 0b6899350..4952c5306 100644 --- a/pkg/pf/internal/pfutils/attr.go +++ b/pkg/pf/internal/pfutils/attr.go @@ -31,13 +31,14 @@ import ( // attrLike exposes these methods. // // GetAttributes method is special since it returns a NestedAttributes interface that is also internal and cannot be -// linked to. Instead, NestedAttriutes information is recorded in a dedicated new field. +// linked to. Instead, NestedAttributes information is recorded in a dedicated new field. type Attr interface { AttrLike IsNested() bool Nested() map[string]Attr NestingMode() NestingMode HasNestedObject() bool + HasDefault() bool } type AttrLike interface { @@ -66,9 +67,11 @@ func FromResourceAttribute(x rschema.Attribute) Attr { func FromAttrLike(attrLike AttrLike) Attr { nested, nestingMode := extractNestedAttributes(attrLike) + hasDefault := hasDefault(attrLike) return &attrAdapter{ nested: nested, nestingMode: nestingMode, + hasDefault: hasDefault, AttrLike: attrLike, } } @@ -76,6 +79,7 @@ func FromAttrLike(attrLike AttrLike) Attr { type attrAdapter struct { nested map[string]Attr nestingMode NestingMode + hasDefault bool AttrLike } @@ -102,6 +106,10 @@ func (a *attrAdapter) NestingMode() NestingMode { return a.nestingMode } +func (a *attrAdapter) HasDefault() bool { + return a.hasDefault +} + type NestingMode uint8 const ( diff --git a/pkg/pf/internal/pfutils/default.go b/pkg/pf/internal/pfutils/default.go new file mode 100644 index 000000000..328450c79 --- /dev/null +++ b/pkg/pf/internal/pfutils/default.go @@ -0,0 +1,99 @@ +package pfutils + +import ( + "github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults" +) + +// These interfaces are re-implemented here from "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" +// as we can not link to them directly. + +type attributeLikeWithBoolDefaultValue interface { + AttrLike + BoolDefaultValue() defaults.Bool +} + +type attributeLikeWithFloat32DefaultValue interface { + AttrLike + Float32DefaultValue() defaults.Float32 +} + +type attributeLikeWithFloat64DefaultValue interface { + AttrLike + Float64DefaultValue() defaults.Float64 +} + +type attributeLikeWithInt32DefaultValue interface { + AttrLike + Int32DefaultValue() defaults.Int32 +} + +type attributeLikeWithInt64DefaultValue interface { + AttrLike + Int64DefaultValue() defaults.Int64 +} + +type attributeLikeWithListDefaultValue interface { + AttrLike + ListDefaultValue() defaults.List +} + +type attributeLikeWithMapDefaultValue interface { + AttrLike + MapDefaultValue() defaults.Map +} + +type attributeLikeWithNumberDefaultValue interface { + AttrLike + NumberDefaultValue() defaults.Number +} + +type attributeLikeWithObjectDefaultValue interface { + AttrLike + ObjectDefaultValue() defaults.Object +} + +type attributeLikeWithSetDefaultValue interface { + AttrLike + SetDefaultValue() defaults.Set +} + +type attributeLikeWithStringDefaultValue interface { + AttrLike + StringDefaultValue() defaults.String +} + +type attributeLikeWithDynamicDefaultValue interface { + AttrLike + DynamicDefaultValue() defaults.Dynamic +} + +func hasDefault(attr AttrLike) bool { + switch attr.(type) { + case attributeLikeWithBoolDefaultValue: + return true + case attributeLikeWithFloat32DefaultValue: + return true + case attributeLikeWithFloat64DefaultValue: + return true + case attributeLikeWithInt32DefaultValue: + return true + case attributeLikeWithInt64DefaultValue: + return true + case attributeLikeWithListDefaultValue: + return true + case attributeLikeWithMapDefaultValue: + return true + case attributeLikeWithNumberDefaultValue: + return true + case attributeLikeWithObjectDefaultValue: + return true + case attributeLikeWithSetDefaultValue: + return true + case attributeLikeWithStringDefaultValue: + return true + case attributeLikeWithDynamicDefaultValue: + return true + default: + return false + } +} diff --git a/pkg/pf/internal/schemashim/attr_schema.go b/pkg/pf/internal/schemashim/attr_schema.go index 250af55ea..19088bfab 100644 --- a/pkg/pf/internal/schemashim/attr_schema.go +++ b/pkg/pf/internal/schemashim/attr_schema.go @@ -32,6 +32,8 @@ var _ shim.Schema = (*attrSchema)(nil) var _ shim.SchemaWithWriteOnly = (*attrSchema)(nil) +var _ shim.SchemaWithHasDefault = (*attrSchema)(nil) + func (s *attrSchema) Type() shim.ValueType { ty := s.attr.GetType() vt, err := convertType(ty) @@ -63,6 +65,10 @@ func (*attrSchema) DefaultValue() (interface{}, error) { return nil, bridge.ErrSchemaDefaultValue } +func (s *attrSchema) HasDefault() bool { + return s.attr.HasDefault() +} + func (s *attrSchema) Description() string { if desc := s.attr.GetMarkdownDescription(); desc != "" { return desc diff --git a/pkg/pf/tfgen/testdata/TestPFRequiredInputWithDefault.golden b/pkg/pf/tfgen/testdata/TestPFRequiredInputWithDefault.golden index e150a5df1..d7e3f286e 100644 --- a/pkg/pf/tfgen/testdata/TestPFRequiredInputWithDefault.golden +++ b/pkg/pf/tfgen/testdata/TestPFRequiredInputWithDefault.golden @@ -18,7 +18,10 @@ }, "config": {}, "provider": { - "description": "The provider type for the testprovider package. By default, resources use package-wide configuration\nsettings, however an explicit `Provider` instance may be created and passed during resource\nconstruction to achieve fine-grained programmatic control over provider settings. See the\n[documentation](https://www.pulumi.com/docs/reference/programming-model/#providers) for more information.\n" + "description": "The provider type for the testprovider package. By default, resources use package-wide configuration\nsettings, however an explicit `Provider` instance may be created and passed during resource\nconstruction to achieve fine-grained programmatic control over provider settings. See the\n[documentation](https://www.pulumi.com/docs/reference/programming-model/#providers) for more information.\n", + "methods": { + "terraformConfig": "pulumi:providers:testprovider/terraformConfig" + } }, "resources": { "testprovider:index:Res": { @@ -45,5 +48,36 @@ "type": "object" } } + }, + "functions": { + "pulumi:providers:testprovider/terraformConfig": { + "description": "This function returns a Terraform config object with terraform-namecased keys,to be used with the Terraform Module Provider.", + "inputs": { + "properties": { + "__self__": { + "type": "ref", + "$ref": "#/resources/pulumi:providers:testprovider" + } + }, + "type": "pulumi:providers:testprovider/terraformConfig", + "required": [ + "__self__" + ] + }, + "outputs": { + "properties": { + "result": { + "additionalProperties": { + "$ref": "pulumi.json#/Any" + }, + "type": "object" + } + }, + "required": [ + "result" + ], + "type": "object" + } + } } } \ No newline at end of file diff --git a/pkg/pf/tfgen/tfgen_test.go b/pkg/pf/tfgen/tfgen_test.go index 532781767..4d3b2da46 100644 --- a/pkg/pf/tfgen/tfgen_test.go +++ b/pkg/pf/tfgen/tfgen_test.go @@ -545,7 +545,6 @@ func TestPFRequiredInputWithDefault(t *testing.T) { ProviderInfo: tfbridge.ProviderInfo{ Name: "testprovider", UpstreamRepoPath: ".", // no invalid mappings warnings - // TODO: This uses a fake provider and does not actually test the right thing. P: pftfbridge.ShimProvider(&schemaTestProvider{ resources: map[string]rschema.Schema{ "res": schema, diff --git a/pkg/tfgen/generate_schema.go b/pkg/tfgen/generate_schema.go index 46340266b..752cdcf75 100644 --- a/pkg/tfgen/generate_schema.go +++ b/pkg/tfgen/generate_schema.go @@ -44,6 +44,7 @@ import ( "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfgen/internal/paths" + shim "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfshim" "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/x/muxer" "github.com/pulumi/pulumi-terraform-bridge/v3/unstable/metadata" ) @@ -865,7 +866,10 @@ func (g *schemaGenerator) genResourceType(mod tokens.Module, res *resourceType) } spec.InputProperties[prop.name] = g.genProperty(prop) - hasDefault := prop.schema.Default() != nil || prop.schema.DefaultFunc() != nil || prop.info.HasDefault() + hasDefault := false + if s, ok := prop.schema.(shim.SchemaWithHasDefault); ok { + hasDefault = s.HasDefault() + } if !prop.optional() && !hasDefault { spec.RequiredInputs = append(spec.RequiredInputs, prop.name) diff --git a/pkg/tfgen/generate_schema_test.go b/pkg/tfgen/generate_schema_test.go index 74acf62c7..ab108de17 100644 --- a/pkg/tfgen/generate_schema_test.go +++ b/pkg/tfgen/generate_schema_test.go @@ -445,6 +445,7 @@ func TestRequiredInputWithDefault(t *testing.T) { Color: colors.Never, })) require.NoError(t, err) + require.Empty(t, schema.Resources["testprovider:index:Res"].RequiredInputs) bridgetesting.AssertEqualsJSONFile(t, "test_data/required-input-with-default-schema.json", schema) } diff --git a/pkg/tfgen/test_data/required-input-with-default-schema.json b/pkg/tfgen/test_data/required-input-with-default-schema.json index e8ccff2e0..d0af0d509 100644 --- a/pkg/tfgen/test_data/required-input-with-default-schema.json +++ b/pkg/tfgen/test_data/required-input-with-default-schema.json @@ -27,7 +27,10 @@ }, "config": {}, "provider": { - "description": "The provider type for the testprovider package. By default, resources use package-wide configuration\nsettings, however an explicit `Provider` instance may be created and passed during resource\nconstruction to achieve fine-grained programmatic control over provider settings. See the\n[documentation](https://www.pulumi.com/docs/reference/programming-model/#providers) for more information.\n" + "description": "The provider type for the testprovider package. By default, resources use package-wide configuration\nsettings, however an explicit `Provider` instance may be created and passed during resource\nconstruction to achieve fine-grained programmatic control over provider settings. See the\n[documentation](https://www.pulumi.com/docs/reference/programming-model/#providers) for more information.\n", + "methods": { + "terraformConfig": "pulumi:providers:testprovider/terraformConfig" + } }, "resources": { "testprovider:index/res:Res": { @@ -64,5 +67,36 @@ "type": "object" } } + }, + "functions": { + "pulumi:providers:testprovider/terraformConfig": { + "description": "This function returns a Terraform config object with terraform-namecased keys,to be used with the Terraform Module Provider.", + "inputs": { + "properties": { + "__self__": { + "type": "ref", + "$ref": "#/resources/pulumi:providers:testprovider" + } + }, + "type": "pulumi:providers:testprovider/terraformConfig", + "required": [ + "__self__" + ] + }, + "outputs": { + "properties": { + "result": { + "additionalProperties": { + "$ref": "pulumi.json#/Any" + }, + "type": "object" + } + }, + "required": [ + "result" + ], + "type": "object" + } + } } } diff --git a/pkg/tfshim/sdk-v2/schema.go b/pkg/tfshim/sdk-v2/schema.go index 2b59f0275..d29d3042b 100644 --- a/pkg/tfshim/sdk-v2/schema.go +++ b/pkg/tfshim/sdk-v2/schema.go @@ -13,6 +13,7 @@ var ( _ = shim.SchemaMap(v2SchemaMap{}) _ = shim.SchemaWithWriteOnly(v2Schema{}) _ = shim.SchemaWithSetElementHash(v2Schema{}) + _ = shim.SchemaWithHasDefault(v2Schema{}) ) // UnknownVariableValue is the sentinal defined in github.com/hashicorp/terraform/configs/hcl2shim, @@ -70,6 +71,11 @@ func (s v2Schema) DefaultValue() (interface{}, error) { return withPatchedDefaults(s.tf).DefaultValue() } +func (s v2Schema) HasDefault() bool { + sch := withPatchedDefaults(s.tf) + return sch.Default != nil || sch.DefaultFunc != nil +} + func (s v2Schema) Description() string { return s.tf.Description } diff --git a/pkg/tfshim/shim.go b/pkg/tfshim/shim.go index 86ddde283..8791216f5 100644 --- a/pkg/tfshim/shim.go +++ b/pkg/tfshim/shim.go @@ -222,6 +222,11 @@ type SchemaWithSetElementHash interface { SetElementHash(v interface{}) (int, error) } +type SchemaWithHasDefault interface { + Schema + HasDefault() bool +} + type SchemaMap interface { Len() int Get(key string) Schema From 302c5ce3c4aa9d0aa58e8a45affe92443f9aeeb0 Mon Sep 17 00:00:00 2001 From: Venelin Date: Wed, 30 Apr 2025 20:07:38 +0300 Subject: [PATCH 05/11] fix pf, re-record tests --- .../schema_required_input_with_default.go | 4 +++ pkg/pf/internal/pfutils/default.go | 26 +++++++++---------- .../TestPFRequiredInputWithDefault.golden | 15 ++++++++++- pkg/pf/tfgen/tfgen_test.go | 3 +++ .../required-input-with-default-schema.json | 15 ++++++++++- 5 files changed, 48 insertions(+), 15 deletions(-) diff --git a/internal/testprovider/schema_required_input_with_default.go b/internal/testprovider/schema_required_input_with_default.go index bd07e4544..0788dacfb 100644 --- a/internal/testprovider/schema_required_input_with_default.go +++ b/internal/testprovider/schema_required_input_with_default.go @@ -47,5 +47,9 @@ func resourceRequiredInputWithDefaultSchema() map[string]*schema.Schema { Required: true, Default: "default", }, + "req": { + Type: schema.TypeString, + Required: true, + }, } } diff --git a/pkg/pf/internal/pfutils/default.go b/pkg/pf/internal/pfutils/default.go index 328450c79..82111b902 100644 --- a/pkg/pf/internal/pfutils/default.go +++ b/pkg/pf/internal/pfutils/default.go @@ -68,31 +68,31 @@ type attributeLikeWithDynamicDefaultValue interface { } func hasDefault(attr AttrLike) bool { - switch attr.(type) { + switch a := attr.(type) { case attributeLikeWithBoolDefaultValue: - return true + return a.BoolDefaultValue() != nil case attributeLikeWithFloat32DefaultValue: - return true + return a.Float32DefaultValue() != nil case attributeLikeWithFloat64DefaultValue: - return true + return a.Float64DefaultValue() != nil case attributeLikeWithInt32DefaultValue: - return true + return a.Int32DefaultValue() != nil case attributeLikeWithInt64DefaultValue: - return true + return a.Int64DefaultValue() != nil case attributeLikeWithListDefaultValue: - return true + return a.ListDefaultValue() != nil case attributeLikeWithMapDefaultValue: - return true + return a.MapDefaultValue() != nil case attributeLikeWithNumberDefaultValue: - return true + return a.NumberDefaultValue() != nil case attributeLikeWithObjectDefaultValue: - return true + return a.ObjectDefaultValue() != nil case attributeLikeWithSetDefaultValue: - return true + return a.SetDefaultValue() != nil case attributeLikeWithStringDefaultValue: - return true + return a.StringDefaultValue() != nil case attributeLikeWithDynamicDefaultValue: - return true + return a.DynamicDefaultValue() != nil default: return false } diff --git a/pkg/pf/tfgen/testdata/TestPFRequiredInputWithDefault.golden b/pkg/pf/tfgen/testdata/TestPFRequiredInputWithDefault.golden index d7e3f286e..5e7cd58f5 100644 --- a/pkg/pf/tfgen/testdata/TestPFRequiredInputWithDefault.golden +++ b/pkg/pf/tfgen/testdata/TestPFRequiredInputWithDefault.golden @@ -28,21 +28,34 @@ "properties": { "a1": { "type": "string" + }, + "a2": { + "type": "string" } }, "required": [ - "a1" + "a1", + "a2" ], "inputProperties": { "a1": { "type": "string" + }, + "a2": { + "type": "string" } }, + "requiredInputs": [ + "a2" + ], "stateInputs": { "description": "Input properties used for looking up and filtering Res resources.\n", "properties": { "a1": { "type": "string" + }, + "a2": { + "type": "string" } }, "type": "object" diff --git a/pkg/pf/tfgen/tfgen_test.go b/pkg/pf/tfgen/tfgen_test.go index 4d3b2da46..ea0560e4c 100644 --- a/pkg/pf/tfgen/tfgen_test.go +++ b/pkg/pf/tfgen/tfgen_test.go @@ -533,6 +533,9 @@ func TestPFRequiredInputWithDefault(t *testing.T) { Required: true, Default: stringdefault.StaticString("default"), }, + "a2": rschema.StringAttribute{ + Required: true, + }, }, } diff --git a/pkg/tfgen/test_data/required-input-with-default-schema.json b/pkg/tfgen/test_data/required-input-with-default-schema.json index d0af0d509..e28ac3d32 100644 --- a/pkg/tfgen/test_data/required-input-with-default-schema.json +++ b/pkg/tfgen/test_data/required-input-with-default-schema.json @@ -40,11 +40,15 @@ }, "otherName": { "type": "string" + }, + "req": { + "type": "string" } }, "required": [ "name", - "otherName" + "otherName", + "req" ], "inputProperties": { "name": { @@ -52,8 +56,14 @@ }, "otherName": { "type": "string" + }, + "req": { + "type": "string" } }, + "requiredInputs": [ + "req" + ], "stateInputs": { "description": "Input properties used for looking up and filtering Res resources.\n", "properties": { @@ -62,6 +72,9 @@ }, "otherName": { "type": "string" + }, + "req": { + "type": "string" } }, "type": "object" From 2d8f21d61f475d9abd2fab75fe6acfdc767f2136 Mon Sep 17 00:00:00 2001 From: Venelin Date: Thu, 1 May 2025 12:18:47 +0300 Subject: [PATCH 06/11] skip test on windows --- pkg/pf/tfgen/tfgen_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/pf/tfgen/tfgen_test.go b/pkg/pf/tfgen/tfgen_test.go index ea0560e4c..5a3960b25 100644 --- a/pkg/pf/tfgen/tfgen_test.go +++ b/pkg/pf/tfgen/tfgen_test.go @@ -526,6 +526,10 @@ func TestWriteOnlyOmit(t *testing.T) { func TestPFRequiredInputWithDefault(t *testing.T) { t.Parallel() + if runtime.GOOS == "windows" { + t.Skipf("Skipping on windows - tests cases need to be made robust to newline handling") + } + schema := rschema.Schema{ Attributes: map[string]rschema.Attribute{ "id": rschema.StringAttribute{Computed: true}, From 10b34b1e1bf074bd1688b3a1aec11b409a04ed70 Mon Sep 17 00:00:00 2001 From: Venelin Date: Thu, 1 May 2025 12:35:09 +0300 Subject: [PATCH 07/11] Fix nested fully computed properties showing as settable From cd8e947d6bff9a18610ea70a5435a8bda21b6ea7 Mon Sep 17 00:00:00 2001 From: Venelin Date: Thu, 1 May 2025 12:35:21 +0300 Subject: [PATCH 08/11] repros --- .../schema_nested_fully_computed.go | 55 ++++++++ .../testdata/TestPFNestedFullyComputed.golden | 100 ++++++++++++++ pkg/pf/tfgen/tfgen_test.go | 42 ++++++ pkg/tfgen/generate_schema_test.go | 10 ++ .../testprovider/nestedfullycomputed.go | 37 ++++++ .../nested-fully-computed-schema.json | 122 ++++++++++++++++++ 6 files changed, 366 insertions(+) create mode 100644 internal/testprovider/schema_nested_fully_computed.go create mode 100644 pkg/pf/tfgen/testdata/TestPFNestedFullyComputed.golden create mode 100644 pkg/tfgen/internal/testprovider/nestedfullycomputed.go create mode 100644 pkg/tfgen/test_data/nested-fully-computed-schema.json diff --git a/internal/testprovider/schema_nested_fully_computed.go b/internal/testprovider/schema_nested_fully_computed.go new file mode 100644 index 000000000..d5fa1e9e0 --- /dev/null +++ b/internal/testprovider/schema_nested_fully_computed.go @@ -0,0 +1,55 @@ +// Copyright 2016-2022, 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 testprovider + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func ProviderNestedFullyComputed() *schema.Provider { + resourceNestedFullyComputedFunc := func() *schema.Resource { + return &schema.Resource{ + Schema: resourceNestedFullyComputedSchema(), + } + } + + return &schema.Provider{ + Schema: map[string]*schema.Schema{}, + ResourcesMap: map[string]*schema.Resource{ + "testprovider_res": resourceNestedFullyComputedFunc(), + }, + } +} + +func resourceNestedFullyComputedSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "block": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "a1": { + Type: schema.TypeString, + Computed: true, + }, + "a2": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + } +} diff --git a/pkg/pf/tfgen/testdata/TestPFNestedFullyComputed.golden b/pkg/pf/tfgen/testdata/TestPFNestedFullyComputed.golden new file mode 100644 index 000000000..c112c3638 --- /dev/null +++ b/pkg/pf/tfgen/testdata/TestPFNestedFullyComputed.golden @@ -0,0 +1,100 @@ +{ + "name": "testprovider", + "attribution": "This Pulumi package is based on the [`testprovider` Terraform Provider](https://github.com/terraform-providers/terraform-provider-testprovider).", + "meta": { + "moduleFormat": "(.*)(?:/[^/]*)" + }, + "language": { + "nodejs": { + "readme": "\u003e This provider is a derived work of the [Terraform Provider](https://github.com/terraform-providers/terraform-provider-testprovider)\n\u003e distributed under [MPL 2.0](https://www.mozilla.org/en-US/MPL/2.0/). If you encounter a bug or missing feature,\n\u003e please consult the source [`terraform-provider-testprovider` repo](https://github.com/terraform-providers/terraform-provider-testprovider/issues).", + "compatibility": "tfbridge20", + "disableUnionOutputTypes": true + }, + "python": { + "readme": "\u003e This provider is a derived work of the [Terraform Provider](https://github.com/terraform-providers/terraform-provider-testprovider)\n\u003e distributed under [MPL 2.0](https://www.mozilla.org/en-US/MPL/2.0/). If you encounter a bug or missing feature,\n\u003e please consult the source [`terraform-provider-testprovider` repo](https://github.com/terraform-providers/terraform-provider-testprovider/issues).", + "compatibility": "tfbridge20", + "pyproject": {} + } + }, + "config": {}, + "types": { + "testprovider:index/ResB1:ResB1": { + "properties": { + "a1": { + "type": "string" + }, + "a2": { + "type": "string" + } + }, + "type": "object", + "language": { + "nodejs": { + "requiredOutputs": [ + "a1" + ] + } + } + } + }, + "provider": { + "description": "The provider type for the testprovider package. By default, resources use package-wide configuration\nsettings, however an explicit `Provider` instance may be created and passed during resource\nconstruction to achieve fine-grained programmatic control over provider settings. See the\n[documentation](https://www.pulumi.com/docs/reference/programming-model/#providers) for more information.\n", + "methods": { + "terraformConfig": "pulumi:providers:testprovider/terraformConfig" + } + }, + "resources": { + "testprovider:index:Res": { + "properties": { + "b1": { + "$ref": "#/types/testprovider:index/ResB1:ResB1" + } + }, + "inputProperties": { + "b1": { + "$ref": "#/types/testprovider:index/ResB1:ResB1" + } + }, + "stateInputs": { + "description": "Input properties used for looking up and filtering Res resources.\n", + "properties": { + "b1": { + "$ref": "#/types/testprovider:index/ResB1:ResB1" + } + }, + "type": "object" + } + } + }, + "functions": { + "pulumi:providers:testprovider/terraformConfig": { + "description": "This function returns a Terraform config object with terraform-namecased keys,to be used with the Terraform Module Provider.", + "inputs": { + "properties": { + "__self__": { + "type": "ref", + "$ref": "#/resources/pulumi:providers:testprovider" + } + }, + "type": "pulumi:providers:testprovider/terraformConfig", + "required": [ + "__self__" + ] + }, + "outputs": { + "properties": { + "result": { + "additionalProperties": { + "$ref": "pulumi.json#/Any" + }, + "type": "object" + } + }, + "required": [ + "result" + ], + "type": "object" + } + } + } +} \ No newline at end of file diff --git a/pkg/pf/tfgen/tfgen_test.go b/pkg/pf/tfgen/tfgen_test.go index 5a3960b25..55bc9215e 100644 --- a/pkg/pf/tfgen/tfgen_test.go +++ b/pkg/pf/tfgen/tfgen_test.go @@ -567,3 +567,45 @@ func TestPFRequiredInputWithDefault(t *testing.T) { require.NoError(t, json.Indent(&b, res.ProviderMetadata.PackageSchema, "", " ")) autogold.ExpectFile(t, autogold.Raw(b.String())) } + +func TestPFNestedFullyComputed(t *testing.T) { + t.Parallel() + + schema := rschema.Schema{ + Attributes: map[string]rschema.Attribute{ + "id": rschema.StringAttribute{Computed: true}, + }, + Blocks: map[string]rschema.Block{ + "b1": rschema.SingleNestedBlock{ + Attributes: map[string]rschema.Attribute{ + "a1": rschema.StringAttribute{Computed: true}, + "a2": rschema.StringAttribute{Optional: true}, + }, + }, + }, + } + + info := &tfbridge.ResourceInfo{ + Tok: "testprovider:index:Res", + Docs: &tfbridge.DocInfo{Markdown: []byte{' '}}, + } + + res, err := GenerateSchema(context.Background(), GenerateSchemaOptions{ + ProviderInfo: tfbridge.ProviderInfo{ + Name: "testprovider", + UpstreamRepoPath: ".", // no invalid mappings warnings + P: pftfbridge.ShimProvider(&schemaTestProvider{ + resources: map[string]rschema.Schema{ + "res": schema, + }, + }), + Resources: map[string]*tfbridge.ResourceInfo{ + "test_res": info, + }, + }, + }) + require.NoError(t, err) + var b bytes.Buffer + require.NoError(t, json.Indent(&b, res.ProviderMetadata.PackageSchema, "", " ")) + autogold.ExpectFile(t, autogold.Raw(b.String())) +} diff --git a/pkg/tfgen/generate_schema_test.go b/pkg/tfgen/generate_schema_test.go index ab108de17..7237101aa 100644 --- a/pkg/tfgen/generate_schema_test.go +++ b/pkg/tfgen/generate_schema_test.go @@ -449,6 +449,16 @@ func TestRequiredInputWithDefault(t *testing.T) { bridgetesting.AssertEqualsJSONFile(t, "test_data/required-input-with-default-schema.json", schema) } +func TestNestedFullyComputed(t *testing.T) { + t.Parallel() + provider := testprovider.ProviderNestedFullyComputed() + schema, err := GenerateSchema(provider, diag.DefaultSink(io.Discard, io.Discard, diag.FormatOptions{ + Color: colors.Never, + })) + require.NoError(t, err) + bridgetesting.AssertEqualsJSONFile(t, "test_data/nested-fully-computed-schema.json", schema) +} + func TestAppendExample_InsertMiddle(t *testing.T) { t.Parallel() descTmpl := `Description text diff --git a/pkg/tfgen/internal/testprovider/nestedfullycomputed.go b/pkg/tfgen/internal/testprovider/nestedfullycomputed.go new file mode 100644 index 000000000..898fdaebe --- /dev/null +++ b/pkg/tfgen/internal/testprovider/nestedfullycomputed.go @@ -0,0 +1,37 @@ +// Copyright 2016-2022, 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 testprovider + +import ( + testproviderdata "github.com/pulumi/pulumi-terraform-bridge/v3/internal/testprovider" + "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge" + shimv2 "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfshim/sdk-v2" +) + +func ProviderNestedFullyComputed() tfbridge.ProviderInfo { + return tfbridge.ProviderInfo{ + P: shimv2.NewProvider(testproviderdata.ProviderNestedFullyComputed()), + Name: "testprovider", + Keywords: []string{"pulumi", "testprovider"}, + License: "Apache-2.0", + Homepage: "https://pulumi.io", + Repository: "https://github.com/pulumi/pulumi-testprovider", + Resources: map[string]*tfbridge.ResourceInfo{ + "testprovider_res": { + Tok: tfbridge.MakeResource("testprovider", "index", "Res"), + }, + }, + } +} diff --git a/pkg/tfgen/test_data/nested-fully-computed-schema.json b/pkg/tfgen/test_data/nested-fully-computed-schema.json new file mode 100644 index 000000000..664c60e16 --- /dev/null +++ b/pkg/tfgen/test_data/nested-fully-computed-schema.json @@ -0,0 +1,122 @@ +{ + "name": "testprovider", + "keywords": [ + "pulumi", + "testprovider" + ], + "homepage": "https://pulumi.io", + "license": "Apache-2.0", + "attribution": "This Pulumi package is based on the [`testprovider` Terraform Provider](https://github.com/terraform-providers/terraform-provider-testprovider).", + "repository": "https://github.com/pulumi/pulumi-testprovider", + "meta": { + "moduleFormat": "(.*)(?:/[^/]*)" + }, + "language": { + "nodejs": { + "readme": "\u003e This provider is a derived work of the [Terraform Provider](https://github.com/terraform-providers/terraform-provider-testprovider)\n\u003e distributed under [MPL 2.0](https://www.mozilla.org/en-US/MPL/2.0/). If you encounter a bug or missing feature,\n\u003e first check the [`pulumi-testprovider` repo](https://github.com/pulumi/pulumi-testprovider/issues); however, if that doesn't turn up anything,\n\u003e please consult the source [`terraform-provider-testprovider` repo](https://github.com/terraform-providers/terraform-provider-testprovider/issues).", + "compatibility": "tfbridge20", + "disableUnionOutputTypes": true + }, + "python": { + "readme": "\u003e This provider is a derived work of the [Terraform Provider](https://github.com/terraform-providers/terraform-provider-testprovider)\n\u003e distributed under [MPL 2.0](https://www.mozilla.org/en-US/MPL/2.0/). If you encounter a bug or missing feature,\n\u003e first check the [`pulumi-testprovider` repo](https://github.com/pulumi/pulumi-testprovider/issues); however, if that doesn't turn up anything,\n\u003e please consult the source [`terraform-provider-testprovider` repo](https://github.com/terraform-providers/terraform-provider-testprovider/issues).", + "compatibility": "tfbridge20", + "pyproject": {} + } + }, + "config": {}, + "types": { + "testprovider:index/ResBlock:ResBlock": { + "properties": { + "a1": { + "type": "string" + }, + "a2": { + "type": "string" + } + }, + "type": "object", + "language": { + "nodejs": { + "requiredOutputs": [ + "a1" + ] + } + } + } + }, + "provider": { + "description": "The provider type for the testprovider package. By default, resources use package-wide configuration\nsettings, however an explicit `Provider` instance may be created and passed during resource\nconstruction to achieve fine-grained programmatic control over provider settings. See the\n[documentation](https://www.pulumi.com/docs/reference/programming-model/#providers) for more information.\n", + "methods": { + "terraformConfig": "pulumi:providers:testprovider/terraformConfig" + } + }, + "resources": { + "testprovider:index/res:Res": { + "properties": { + "blocks": { + "type": "array", + "items": { + "$ref": "#/types/testprovider:index/ResBlock:ResBlock" + } + } + }, + "required": [ + "blocks" + ], + "inputProperties": { + "blocks": { + "type": "array", + "items": { + "$ref": "#/types/testprovider:index/ResBlock:ResBlock" + } + } + }, + "requiredInputs": [ + "blocks" + ], + "stateInputs": { + "description": "Input properties used for looking up and filtering Res resources.\n", + "properties": { + "blocks": { + "type": "array", + "items": { + "$ref": "#/types/testprovider:index/ResBlock:ResBlock" + } + } + }, + "type": "object" + } + } + }, + "functions": { + "pulumi:providers:testprovider/terraformConfig": { + "description": "This function returns a Terraform config object with terraform-namecased keys,to be used with the Terraform Module Provider.", + "inputs": { + "properties": { + "__self__": { + "type": "ref", + "$ref": "#/resources/pulumi:providers:testprovider" + } + }, + "type": "pulumi:providers:testprovider/terraformConfig", + "required": [ + "__self__" + ] + }, + "outputs": { + "properties": { + "result": { + "additionalProperties": { + "$ref": "pulumi.json#/Any" + }, + "type": "object" + } + }, + "required": [ + "result" + ], + "type": "object" + } + } + } +} From c8fb1b67852981f446c8954c8aad585c2a8c50de Mon Sep 17 00:00:00 2001 From: Venelin Date: Thu, 1 May 2025 13:40:30 +0300 Subject: [PATCH 09/11] test arrays and blocks --- .../schema_nested_fully_computed.go | 21 ++++++++++ .../nested-fully-computed-schema.json | 40 ++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/internal/testprovider/schema_nested_fully_computed.go b/internal/testprovider/schema_nested_fully_computed.go index d5fa1e9e0..7eced9a4f 100644 --- a/internal/testprovider/schema_nested_fully_computed.go +++ b/internal/testprovider/schema_nested_fully_computed.go @@ -48,6 +48,27 @@ func resourceNestedFullyComputedSchema() map[string]*schema.Schema { Type: schema.TypeString, Optional: true, }, + "a3": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "other_block": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "b1": { + Type: schema.TypeString, + Computed: true, + }, + "b2": { + Type: schema.TypeString, + Required: true, + }, }, }, }, diff --git a/pkg/tfgen/test_data/nested-fully-computed-schema.json b/pkg/tfgen/test_data/nested-fully-computed-schema.json index 664c60e16..23438be38 100644 --- a/pkg/tfgen/test_data/nested-fully-computed-schema.json +++ b/pkg/tfgen/test_data/nested-fully-computed-schema.json @@ -32,13 +32,42 @@ }, "a2": { "type": "string" + }, + "a3": { + "type": "string" + } + }, + "type": "object", + "required": [ + "a3" + ], + "language": { + "nodejs": { + "requiredOutputs": [ + "a1", + "a3" + ] + } + } + }, + "testprovider:index/ResOtherBlock:ResOtherBlock": { + "properties": { + "b1": { + "type": "string" + }, + "b2": { + "type": "string" } }, "type": "object", + "required": [ + "b2" + ], "language": { "nodejs": { "requiredOutputs": [ - "a1" + "b1", + "b2" ] } } @@ -58,6 +87,9 @@ "items": { "$ref": "#/types/testprovider:index/ResBlock:ResBlock" } + }, + "otherBlock": { + "$ref": "#/types/testprovider:index/ResOtherBlock:ResOtherBlock" } }, "required": [ @@ -69,6 +101,9 @@ "items": { "$ref": "#/types/testprovider:index/ResBlock:ResBlock" } + }, + "otherBlock": { + "$ref": "#/types/testprovider:index/ResOtherBlock:ResOtherBlock" } }, "requiredInputs": [ @@ -82,6 +117,9 @@ "items": { "$ref": "#/types/testprovider:index/ResBlock:ResBlock" } + }, + "otherBlock": { + "$ref": "#/types/testprovider:index/ResOtherBlock:ResOtherBlock" } }, "type": "object" From a97b811eb9465451e9d278be1172608bf7cd1a87 Mon Sep 17 00:00:00 2001 From: Venelin Date: Thu, 1 May 2025 13:51:27 +0300 Subject: [PATCH 10/11] wip separate input and output types --- .../testdata/TestPFNestedFullyComputed.golden | 20 +++++-- pkg/tfgen/generate_schema.go | 7 +++ .../nested-fully-computed-schema.json | 57 +++++++++++++++---- 3 files changed, 70 insertions(+), 14 deletions(-) diff --git a/pkg/pf/tfgen/testdata/TestPFNestedFullyComputed.golden b/pkg/pf/tfgen/testdata/TestPFNestedFullyComputed.golden index c112c3638..ebb250d51 100644 --- a/pkg/pf/tfgen/testdata/TestPFNestedFullyComputed.golden +++ b/pkg/pf/tfgen/testdata/TestPFNestedFullyComputed.golden @@ -19,6 +19,17 @@ "config": {}, "types": { "testprovider:index/ResB1:ResB1": { + "properties": { + "a1": { + "type": "string" + }, + "a2": { + "type": "string" + } + }, + "type": "object" + }, + "testprovider:index/ResB1Output:ResB1Output": { "properties": { "a1": { "type": "string" @@ -28,11 +39,12 @@ } }, "type": "object", + "required": [ + "a1" + ], "language": { "nodejs": { - "requiredOutputs": [ - "a1" - ] + "requiredInputs": [] } } } @@ -47,7 +59,7 @@ "testprovider:index:Res": { "properties": { "b1": { - "$ref": "#/types/testprovider:index/ResB1:ResB1" + "$ref": "#/types/testprovider:index/ResB1Output:ResB1OutputOutput" } }, "inputProperties": { diff --git a/pkg/tfgen/generate_schema.go b/pkg/tfgen/generate_schema.go index 752cdcf75..f1010b7cb 100644 --- a/pkg/tfgen/generate_schema.go +++ b/pkg/tfgen/generate_schema.go @@ -136,6 +136,10 @@ func (nt *schemaNestedTypes) declareType(typePath paths.TypePath, declarer decla typeName = typ.nestedType.Name().String() } + if !isInput { + typeName += "Output" + } + typ.name = typeName required := codegen.StringSet{} @@ -1097,6 +1101,9 @@ func (g *schemaGenerator) schemaType(path paths.TypePath, typ *propertyType, out case kindObject: mod := modulePlacementForType(g.pkg, path) ref := fmt.Sprintf("#/types/%s/%s:%s", mod.String(), typ.name, typ.name) + if out { + ref += "Output" + } return pschema.TypeSpec{Ref: ref} default: contract.Failf("Unrecognized type kind: %v", typ.kind) diff --git a/pkg/tfgen/test_data/nested-fully-computed-schema.json b/pkg/tfgen/test_data/nested-fully-computed-schema.json index 23438be38..48005371f 100644 --- a/pkg/tfgen/test_data/nested-fully-computed-schema.json +++ b/pkg/tfgen/test_data/nested-fully-computed-schema.json @@ -43,10 +43,30 @@ ], "language": { "nodejs": { - "requiredOutputs": [ - "a1", - "a3" - ] + "requiredOutputs": [] + } + } + }, + "testprovider:index/ResBlockOutput:ResBlockOutput": { + "properties": { + "a1": { + "type": "string" + }, + "a2": { + "type": "string" + }, + "a3": { + "type": "string" + } + }, + "type": "object", + "required": [ + "a1", + "a3" + ], + "language": { + "nodejs": { + "requiredInputs": [] } } }, @@ -65,10 +85,27 @@ ], "language": { "nodejs": { - "requiredOutputs": [ - "b1", - "b2" - ] + "requiredOutputs": [] + } + } + }, + "testprovider:index/ResOtherBlockOutput:ResOtherBlockOutput": { + "properties": { + "b1": { + "type": "string" + }, + "b2": { + "type": "string" + } + }, + "type": "object", + "required": [ + "b1", + "b2" + ], + "language": { + "nodejs": { + "requiredInputs": [] } } } @@ -85,11 +122,11 @@ "blocks": { "type": "array", "items": { - "$ref": "#/types/testprovider:index/ResBlock:ResBlock" + "$ref": "#/types/testprovider:index/ResBlockOutput:ResBlockOutputOutput" } }, "otherBlock": { - "$ref": "#/types/testprovider:index/ResOtherBlock:ResOtherBlock" + "$ref": "#/types/testprovider:index/ResOtherBlockOutput:ResOtherBlockOutputOutput" } }, "required": [ From 3d24dfcb6e7008a2c63b8b0cc38be8f57c7d7c48 Mon Sep 17 00:00:00 2001 From: Venelin Date: Tue, 6 May 2025 16:39:12 +0300 Subject: [PATCH 11/11] update tests and wip --- .../schema_nested_fully_computed.go | 76 --------------- pkg/tests/schema_test.go | 94 +++++++++++++++++++ .../testdata/TestNestedFullyComputed.golden | 79 ++++++++++++++++ pkg/tfgen/generate.go | 4 + pkg/tfgen/generate_schema.go | 6 -- pkg/tfgen/generate_schema_test.go | 10 -- .../testprovider/nestedfullycomputed.go | 37 -------- 7 files changed, 177 insertions(+), 129 deletions(-) delete mode 100644 internal/testprovider/schema_nested_fully_computed.go create mode 100644 pkg/tests/schema_test.go create mode 100644 pkg/tests/testdata/TestNestedFullyComputed.golden delete mode 100644 pkg/tfgen/internal/testprovider/nestedfullycomputed.go diff --git a/internal/testprovider/schema_nested_fully_computed.go b/internal/testprovider/schema_nested_fully_computed.go deleted file mode 100644 index 7eced9a4f..000000000 --- a/internal/testprovider/schema_nested_fully_computed.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2016-2022, 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 testprovider - -import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func ProviderNestedFullyComputed() *schema.Provider { - resourceNestedFullyComputedFunc := func() *schema.Resource { - return &schema.Resource{ - Schema: resourceNestedFullyComputedSchema(), - } - } - - return &schema.Provider{ - Schema: map[string]*schema.Schema{}, - ResourcesMap: map[string]*schema.Resource{ - "testprovider_res": resourceNestedFullyComputedFunc(), - }, - } -} - -func resourceNestedFullyComputedSchema() map[string]*schema.Schema { - return map[string]*schema.Schema{ - "block": { - Type: schema.TypeList, - Required: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "a1": { - Type: schema.TypeString, - Computed: true, - }, - "a2": { - Type: schema.TypeString, - Optional: true, - }, - "a3": { - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, - "other_block": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "b1": { - Type: schema.TypeString, - Computed: true, - }, - "b2": { - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, - } -} diff --git a/pkg/tests/schema_test.go b/pkg/tests/schema_test.go new file mode 100644 index 000000000..cb20ee63a --- /dev/null +++ b/pkg/tests/schema_test.go @@ -0,0 +1,94 @@ +package tests + +import ( + "bytes" + "encoding/json" + "io" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hexops/autogold/v2" + "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/internal/tests/pulcheck" + "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfgen" + pschema "github.com/pulumi/pulumi/pkg/v3/codegen/schema" + "github.com/pulumi/pulumi/sdk/v3/go/common/diag" + "github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNestedFullyComputed(t *testing.T) { + t.Parallel() + p := &schema.Provider{ + Schema: map[string]*schema.Schema{}, + ResourcesMap: map[string]*schema.Resource{ + "testprovider_res": { + Schema: map[string]*schema.Schema{ + "list_block": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "a1": { + Type: schema.TypeString, + Computed: true, + }, + "a2": { + Type: schema.TypeString, + Optional: true, + }, + "a3": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "object_block": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "b1": { + Type: schema.TypeString, + Computed: true, + }, + "b2": { + Type: schema.TypeString, + Optional: true, + }, + "b3": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, + } + + info := pulcheck.BridgedProvider(t, "testprovider", p) + + schema, err := tfgen.GenerateSchema(info, diag.DefaultSink(io.Discard, io.Discard, diag.FormatOptions{ + Color: colors.Never, + })) + require.NoError(t, err) + + marshal := func(s pschema.PackageSpec, w io.Writer) error { + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + return enc.Encode(s) + } + + toString := func(s pschema.PackageSpec) string { + buf := bytes.Buffer{} + err := marshal(s, &buf) + assert.NoError(t, err) + return buf.String() + } + + autogold.ExpectFile(t, autogold.Raw(toString(schema))) +} diff --git a/pkg/tests/testdata/TestNestedFullyComputed.golden b/pkg/tests/testdata/TestNestedFullyComputed.golden new file mode 100644 index 000000000..00f0d7281 --- /dev/null +++ b/pkg/tests/testdata/TestNestedFullyComputed.golden @@ -0,0 +1,79 @@ +{ + "name": "testprovider", + "version": "0.0.1", + "attribution": "This Pulumi package is based on the [`testprovider` Terraform Provider](https://github.com/terraform-providers/terraform-provider-testprovider).", + "meta": { + "moduleFormat": "(.*)(?:/[^/]*)" + }, + "language": { + "nodejs": { + "readme": "\u003e This provider is a derived work of the [Terraform Provider](https://github.com/terraform-providers/terraform-provider-testprovider)\n\u003e distributed under [MPL 2.0](https://www.mozilla.org/en-US/MPL/2.0/). If you encounter a bug or missing feature,\n\u003e please consult the source [`terraform-provider-testprovider` repo](https://github.com/terraform-providers/terraform-provider-testprovider/issues).", + "compatibility": "tfbridge20", + "disableUnionOutputTypes": true + }, + "python": { + "readme": "\u003e This provider is a derived work of the [Terraform Provider](https://github.com/terraform-providers/terraform-provider-testprovider)\n\u003e distributed under [MPL 2.0](https://www.mozilla.org/en-US/MPL/2.0/). If you encounter a bug or missing feature,\n\u003e please consult the source [`terraform-provider-testprovider` repo](https://github.com/terraform-providers/terraform-provider-testprovider/issues).", + "compatibility": "tfbridge20", + "pyproject": {} + } + }, + "config": {}, + "types": { + "testprovider:index/ResListBlock:ResListBlock": { + "properties": { + "a2": { + "type": "string" + } + }, + "type": "object", + "language": { + "nodejs": { + "requiredOutputs": [ + "a1" + ] + } + } + } + }, + "provider": { + "description": "The provider type for the testprovider package. By default, resources use package-wide configuration\nsettings, however an explicit `Provider` instance may be created and passed during resource\nconstruction to achieve fine-grained programmatic control over provider settings. See the\n[documentation](https://www.pulumi.com/docs/reference/programming-model/#providers) for more information.\n" + }, + "resources": { + "testprovider:index/res:Res": { + "properties": { + "listBlocks": { + "type": "array", + "items": { + "$ref": "#/types/testprovider:index/ResListBlock:ResListBlock" + } + } + }, + "required": [ + "listBlocks" + ], + "inputProperties": { + "listBlocks": { + "type": "array", + "items": { + "$ref": "#/types/testprovider:index/ResListBlock:ResListBlock" + } + } + }, + "requiredInputs": [ + "listBlocks" + ], + "stateInputs": { + "description": "Input properties used for looking up and filtering Res resources.\n", + "properties": { + "listBlocks": { + "type": "array", + "items": { + "$ref": "#/types/testprovider:index/ResListBlock:ResListBlock" + } + } + }, + "type": "object" + } + } + } +} diff --git a/pkg/tfgen/generate.go b/pkg/tfgen/generate.go index 5dedee44a..c609b1e87 100644 --- a/pkg/tfgen/generate.go +++ b/pkg/tfgen/generate.go @@ -1798,6 +1798,10 @@ func (g *Generator) propertyVariable(parentPath paths.TypePath, key string, varInfo = info[key] } + if !input(shimSchema, varInfo) && !out { + return nil, nil + } + // If a variable is marked as omitted, omit it. // // Because the recursive traversal into the fields used by this type are diff --git a/pkg/tfgen/generate_schema.go b/pkg/tfgen/generate_schema.go index f1010b7cb..a10dbc94c 100644 --- a/pkg/tfgen/generate_schema.go +++ b/pkg/tfgen/generate_schema.go @@ -136,9 +136,6 @@ func (nt *schemaNestedTypes) declareType(typePath paths.TypePath, declarer decla typeName = typ.nestedType.Name().String() } - if !isInput { - typeName += "Output" - } typ.name = typeName @@ -1101,9 +1098,6 @@ func (g *schemaGenerator) schemaType(path paths.TypePath, typ *propertyType, out case kindObject: mod := modulePlacementForType(g.pkg, path) ref := fmt.Sprintf("#/types/%s/%s:%s", mod.String(), typ.name, typ.name) - if out { - ref += "Output" - } return pschema.TypeSpec{Ref: ref} default: contract.Failf("Unrecognized type kind: %v", typ.kind) diff --git a/pkg/tfgen/generate_schema_test.go b/pkg/tfgen/generate_schema_test.go index 7237101aa..ab108de17 100644 --- a/pkg/tfgen/generate_schema_test.go +++ b/pkg/tfgen/generate_schema_test.go @@ -449,16 +449,6 @@ func TestRequiredInputWithDefault(t *testing.T) { bridgetesting.AssertEqualsJSONFile(t, "test_data/required-input-with-default-schema.json", schema) } -func TestNestedFullyComputed(t *testing.T) { - t.Parallel() - provider := testprovider.ProviderNestedFullyComputed() - schema, err := GenerateSchema(provider, diag.DefaultSink(io.Discard, io.Discard, diag.FormatOptions{ - Color: colors.Never, - })) - require.NoError(t, err) - bridgetesting.AssertEqualsJSONFile(t, "test_data/nested-fully-computed-schema.json", schema) -} - func TestAppendExample_InsertMiddle(t *testing.T) { t.Parallel() descTmpl := `Description text diff --git a/pkg/tfgen/internal/testprovider/nestedfullycomputed.go b/pkg/tfgen/internal/testprovider/nestedfullycomputed.go deleted file mode 100644 index 898fdaebe..000000000 --- a/pkg/tfgen/internal/testprovider/nestedfullycomputed.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2016-2022, 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 testprovider - -import ( - testproviderdata "github.com/pulumi/pulumi-terraform-bridge/v3/internal/testprovider" - "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfbridge" - shimv2 "github.com/pulumi/pulumi-terraform-bridge/v3/pkg/tfshim/sdk-v2" -) - -func ProviderNestedFullyComputed() tfbridge.ProviderInfo { - return tfbridge.ProviderInfo{ - P: shimv2.NewProvider(testproviderdata.ProviderNestedFullyComputed()), - Name: "testprovider", - Keywords: []string{"pulumi", "testprovider"}, - License: "Apache-2.0", - Homepage: "https://pulumi.io", - Repository: "https://github.com/pulumi/pulumi-testprovider", - Resources: map[string]*tfbridge.ResourceInfo{ - "testprovider_res": { - Tok: tfbridge.MakeResource("testprovider", "index", "Res"), - }, - }, - } -}