diff --git a/pkg/internal/tests/cross-tests/tfwrite.go b/pkg/internal/tests/cross-tests/tfwrite.go index cc6a74f3a..155ea2194 100644 --- a/pkg/internal/tests/cross-tests/tfwrite.go +++ b/pkg/internal/tests/cross-tests/tfwrite.go @@ -21,7 +21,6 @@ import ( "sort" "github.com/hashicorp/hcl/v2/hclwrite" - pfproviderschema "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" "github.com/zclconf/go-cty/cty" @@ -61,95 +60,6 @@ func (w SDKv2Writer) Resource( return err } -type PFWriter struct{ out io.Writer } - -func WritePF(out io.Writer) PFWriter { return PFWriter{out} } - -func (w PFWriter) Provider(sch pfproviderschema.Schema, providerName string, config map[string]cty.Value) error { - if !cty.ObjectVal(config).IsWhollyKnown() { - return fmt.Errorf("WriteHCL cannot yet write unknowns") - } - f := hclwrite.NewEmptyFile() - block := f.Body().AppendNewBlock("provider", []string{providerName}) - writePfProvider(block.Body(), sch, config) - _, err := f.WriteTo(w.out) - return err -} - -func writePfProvider(body *hclwrite.Body, schemas pfproviderschema.Schema, values map[string]cty.Value) { - writePfObject(body, pfproviderschema.NestedBlockObject{ - Attributes: schemas.Attributes, - Blocks: schemas.Blocks, - }, values) -} - -// writePfBlock writes the values for a single schema block to parentBody. -// -// Because blocks can be repeated (ListNestedBlock and SetNestedBlock), writePfBlock -// can write an arbitrary number of blocks. -// -// For example, writing a list would add two blocks to parentBody: -// -// writePfBlock("key", parentBody, ListNestedBlock{count: int}, cty.Value([{count: 1}, {count: 2}])) -// -// key { -// count = 1 -// } -// key { -// count = 2 -// } -// -// This is why writePfBlock is called with parentBody, instead of with the block body -// already created (as with [writeBlock]). -func writePfBlock(key string, parentBody *hclwrite.Body, schemas pfproviderschema.Block, value cty.Value) { - switch schemas := schemas.(type) { - case pfproviderschema.ListNestedBlock: - for _, v := range value.AsValueSlice() { - b := parentBody.AppendNewBlock(key, nil).Body() - writePfObject(b, schemas.NestedObject, v.AsValueMap()) - } - case pfproviderschema.SetNestedBlock: - values := value.AsValueSet().Values() - for _, v := range values { - b := parentBody.AppendNewBlock(key, nil).Body() - writePfObject(b, schemas.NestedObject, v.AsValueMap()) - } - case pfproviderschema.SingleNestedBlock: - body := parentBody.AppendNewBlock(key, nil).Body() - - if value.IsNull() { - return - } - - writePfObject(body, pfproviderschema.NestedBlockObject{ - Attributes: schemas.Attributes, - Blocks: schemas.Blocks, - }, value.AsValueMap()) - default: - contract.Failf("Unknown block type: %T", schemas) - } -} - -func writePfObject(body *hclwrite.Body, schemas pfproviderschema.NestedBlockObject, values map[string]cty.Value) { - keys := make([]string, 0, len(values)) - for k := range values { - keys = append(keys, k) - } - sort.Strings(keys) - - for _, key := range keys { - if _, ok := schemas.Attributes[key]; ok { - body.SetAttributeValue(key, values[key]) - continue - } - if block, ok := schemas.Blocks[key]; ok { - writePfBlock(key, body, block, values[key]) - continue - } - contract.Failf("Could not find a attr or block for value key %q", key) - } -} - func writeBlock(body *hclwrite.Body, schemas map[string]*schema.Schema, values map[string]cty.Value) { internalMap := schema.InternalMap(schemas) coreConfigSchema := internalMap.CoreConfigSchema() diff --git a/pkg/pf/tests/internal/cross-tests/configure.go b/pkg/pf/tests/internal/cross-tests/configure.go index 9cdff8914..47d979a33 100644 --- a/pkg/pf/tests/internal/cross-tests/configure.go +++ b/pkg/pf/tests/internal/cross-tests/configure.go @@ -112,7 +112,7 @@ func Configure(t *testing.T, schema schema.Schema, tfConfig map[string]cty.Value t.Run("tf", func(t *testing.T) { defer propagateSkip(topLevelT, t) var hcl bytes.Buffer - err := crosstests.WritePF(&hcl).Provider(schema, providerName, tfConfig) + err := WritePF(&hcl).Provider(schema, providerName, tfConfig) require.NoError(t, err) // TF does not configure providers unless they are involved with creating // a resource or datasource, so we create "res" to give the TF provider a diff --git a/pkg/pf/tests/internal/cross-tests/tfwrite.go b/pkg/pf/tests/internal/cross-tests/tfwrite.go new file mode 100644 index 000000000..65abdad21 --- /dev/null +++ b/pkg/pf/tests/internal/cross-tests/tfwrite.go @@ -0,0 +1,169 @@ +package crosstests + +import ( + "fmt" + "io" + "sort" + + "github.com/hashicorp/hcl/v2/hclwrite" + pschema "github.com/hashicorp/terraform-plugin-framework/provider/schema" + rschema "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" + "github.com/zclconf/go-cty/cty" +) + +type PFWriter struct{ out io.Writer } + +func WritePF(out io.Writer) PFWriter { return PFWriter{out} } + +func (w PFWriter) Provider(sch pschema.Schema, providerName string, config map[string]cty.Value) error { + if !cty.ObjectVal(config).IsWhollyKnown() { + return fmt.Errorf("WriteHCL cannot yet write unknowns") + } + f := hclwrite.NewEmptyFile() + block := f.Body().AppendNewBlock("provider", []string{providerName}) + writePfProvider(block.Body(), sch, config) + _, err := f.WriteTo(w.out) + return err +} + +func (w PFWriter) Resource(sch rschema.Schema, resourceType, resourceName string, config map[string]cty.Value) error { + if !cty.ObjectVal(config).IsWhollyKnown() { + return fmt.Errorf("WriteHCL cannot yet write unknowns") + } + f := hclwrite.NewEmptyFile() + block := f.Body().AppendNewBlock("resource", []string{resourceType, resourceName}) + writePfResource(block.Body(), sch, config) + _, err := f.WriteTo(w.out) + return err +} + +func writePfProvider(body *hclwrite.Body, schemas pschema.Schema, values map[string]cty.Value) { + writePfObjectProvider(body, pschema.NestedBlockObject{ + Attributes: schemas.Attributes, + Blocks: schemas.Blocks, + }, values) +} + +func writePfResource(body *hclwrite.Body, schemas rschema.Schema, values map[string]cty.Value) { + writePfObjectResource(body, rschema.NestedBlockObject{ + Attributes: schemas.Attributes, + Blocks: schemas.Blocks, + }, values) +} + +// writePfBlockProvider writes the values for a single schema block to parentBody. +// +// Because blocks can be repeated (ListNestedBlock and SetNestedBlock), writePfBlockProvider +// can write an arbitrary number of blocks. +// +// For example, writing a list would add two blocks to parentBody: +// +// writePfBlockProvider("key", parentBody, ListNestedBlock{count: int}, cty.Value([{count: 1}, {count: 2}])) +// +// key { +// count = 1 +// } +// key { +// count = 2 +// } +// +// This is why writePfBlockProvider is called with parentBody, instead of with the block body +// already created (as with [writeBlock]). +func writePfBlockProvider(key string, parentBody *hclwrite.Body, schemas pschema.Block, value cty.Value) { + switch schemas := schemas.(type) { + case pschema.ListNestedBlock: + for _, v := range value.AsValueSlice() { + b := parentBody.AppendNewBlock(key, nil).Body() + writePfObjectProvider(b, schemas.NestedObject, v.AsValueMap()) + } + case pschema.SetNestedBlock: + values := value.AsValueSet().Values() + for _, v := range values { + b := parentBody.AppendNewBlock(key, nil).Body() + writePfObjectProvider(b, schemas.NestedObject, v.AsValueMap()) + } + case pschema.SingleNestedBlock: + body := parentBody.AppendNewBlock(key, nil).Body() + + if value.IsNull() { + return + } + + writePfObjectProvider(body, pschema.NestedBlockObject{ + Attributes: schemas.Attributes, + Blocks: schemas.Blocks, + }, value.AsValueMap()) + default: + contract.Failf("Unknown block type: %T", schemas) + } +} + +func writePfBlockResource(key string, parentBody *hclwrite.Body, schemas rschema.Block, value cty.Value) { + switch schemas := schemas.(type) { + case rschema.ListNestedBlock: + for _, v := range value.AsValueSlice() { + b := parentBody.AppendNewBlock(key, nil).Body() + writePfObjectResource(b, schemas.NestedObject, v.AsValueMap()) + } + case rschema.SetNestedBlock: + values := value.AsValueSet().Values() + for _, v := range values { + b := parentBody.AppendNewBlock(key, nil).Body() + writePfObjectResource(b, schemas.NestedObject, v.AsValueMap()) + } + case rschema.SingleNestedBlock: + body := parentBody.AppendNewBlock(key, nil).Body() + + if value.IsNull() { + return + } + + writePfObjectResource(body, rschema.NestedBlockObject{ + Attributes: schemas.Attributes, + Blocks: schemas.Blocks, + }, value.AsValueMap()) + default: + contract.Failf("Unknown block type: %T", schemas) + } +} + +func writePfObjectProvider(body *hclwrite.Body, schemas pschema.NestedBlockObject, values map[string]cty.Value) { + keys := make([]string, 0, len(values)) + for k := range values { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, key := range keys { + if _, ok := schemas.Attributes[key]; ok { + body.SetAttributeValue(key, values[key]) + continue + } + if block, ok := schemas.Blocks[key]; ok { + writePfBlockProvider(key, body, block, values[key]) + continue + } + contract.Failf("Could not find a attr or block for value key %q", key) + } +} + +func writePfObjectResource(body *hclwrite.Body, schemas rschema.NestedBlockObject, values map[string]cty.Value) { + keys := make([]string, 0, len(values)) + for k := range values { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, key := range keys { + if _, ok := schemas.Attributes[key]; ok { + body.SetAttributeValue(key, values[key]) + continue + } + if block, ok := schemas.Blocks[key]; ok { + writePfBlockResource(key, body, block, values[key]) + continue + } + contract.Failf("Could not find a attr or block for value key %q", key) + } +}