-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor SDKv2 and PF HCL utilities to expose a shared interface (#2589)
This change refactors the HCL writing utilities to expose a shared interface on which we can implement the writing capabilities. I've opted to make a new HCL shim layer because: - We shouldn't complicate the runtime behaviour with test code - The HCL writing is simpler than the runtime behaviour so we can get away with a much simpler interface which only cares about Attributes, Blocks and nesting We did discuss that this is not necessary in #2577 but I then hit the need to implement TF lifecycle methods for PF which is already done for SDKv2, so this makes it possible to implement such shared concerns once.
- Loading branch information
1 parent
e38d894
commit e6ffc06
Showing
8 changed files
with
408 additions
and
247 deletions.
There are no files selected for viewing
167 changes: 167 additions & 0 deletions
167
pkg/internal/tests/cross-tests/impl/hclwrite/hclwrite.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
// hclwrite is a shared interface for writing HCL files for cross-tests. | ||
// Both the Terraform Plugin SDK bridge and the Pulumi Framework bridge implement this interface. | ||
package hclwrite | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"sort" | ||
|
||
"github.com/hashicorp/hcl/v2/hclwrite" | ||
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" | ||
"github.com/zclconf/go-cty/cty" | ||
) | ||
|
||
// Nesting is the nesting mode of a block. | ||
type Nesting string | ||
|
||
const ( | ||
NestingInvalid Nesting = "NestingInvalid" | ||
NestingSingle Nesting = "NestingSingle" | ||
NestingList Nesting = "NestingList" | ||
NestingSet Nesting = "NestingSet" | ||
) | ||
|
||
// ShimHCLAttribute is an attribute used to write a value to an HCL file. | ||
type ShimHCLAttribute struct{} | ||
|
||
// ShimHCLBlock is a block used to write a value to an HCL file. | ||
type ShimHCLBlock interface { | ||
GetNestingMode() Nesting | ||
GetAttributes() map[string]ShimHCLAttribute | ||
GetBlocks() map[string]ShimHCLBlock | ||
} | ||
|
||
// ShimHCLSchema is the schema used to write a value to an HCL file. | ||
type ShimHCLSchema interface { | ||
GetAttributes() map[string]ShimHCLAttribute | ||
GetBlocks() map[string]ShimHCLBlock | ||
} | ||
|
||
// WriteProvider writes a provider declaration to an HCL file. | ||
// | ||
// Note that unknowns are not yet supported in cty.Value, it will error out if found. | ||
func WriteProvider(w io.Writer, schema ShimHCLSchema, providerType string, config map[string]cty.Value) error { | ||
if !cty.ObjectVal(config).IsWhollyKnown() { | ||
return fmt.Errorf("WriteProvider cannot yet write unknowns") | ||
} | ||
f := hclwrite.NewEmptyFile() | ||
block := f.Body().AppendNewBlock("provider", []string{providerType}) | ||
writeBlock(block.Body(), schema, config) | ||
_, err := f.WriteTo(w) | ||
return err | ||
} | ||
|
||
type lifecycleArgs struct { | ||
CreateBeforeDestroy bool | ||
} | ||
|
||
type writeResourceOptions struct { | ||
lifecycleArgs lifecycleArgs | ||
} | ||
|
||
// WriteResourceOption is an option for WriteResource. | ||
type WriteResourceOption func(*writeResourceOptions) | ||
|
||
// WithCreateBeforeDestroy is an option to set the create_before_destroy attribute on a resource. | ||
func WithCreateBeforeDestroy(createBeforeDestroy bool) WriteResourceOption { | ||
return func(o *writeResourceOptions) { | ||
o.lifecycleArgs.CreateBeforeDestroy = createBeforeDestroy | ||
} | ||
} | ||
|
||
// WriteResource writes a resource declaration to an HCL file. | ||
// | ||
// Note that unknowns are not yet supported in cty.Value, it will error out if found. | ||
func WriteResource( | ||
w io.Writer, schema ShimHCLSchema, resourceType, resourceName string, config map[string]cty.Value, | ||
opts ...WriteResourceOption, | ||
) error { | ||
if !cty.ObjectVal(config).IsWhollyKnown() { | ||
return fmt.Errorf("WriteResource cannot yet write unknowns") | ||
} | ||
o := &writeResourceOptions{} | ||
for _, opt := range opts { | ||
opt(o) | ||
} | ||
|
||
if config == nil { | ||
config = map[string]cty.Value{} | ||
} | ||
|
||
f := hclwrite.NewEmptyFile() | ||
block := f.Body().AppendNewBlock("resource", []string{resourceType, resourceName}) | ||
writeBlock(block.Body(), schema, config) | ||
|
||
// lifecycle block | ||
contract.Assertf(config["lifecycle"].IsNull(), "lifecycle block should be specified with a lifecycle option") | ||
lifecycle := map[string]cty.Value{} | ||
if o.lifecycleArgs.CreateBeforeDestroy { | ||
lifecycle["create_before_destroy"] = cty.True | ||
} | ||
if len(lifecycle) > 0 { | ||
newBlock := block.Body().AppendNewBlock("lifecycle", nil) | ||
writeBlock(newBlock.Body(), &lifecycleBlock{}, lifecycle) | ||
} | ||
_, err := f.WriteTo(w) | ||
return err | ||
} | ||
|
||
type lifecycleBlock struct{} | ||
|
||
var _ ShimHCLBlock = &lifecycleBlock{} | ||
|
||
func (b *lifecycleBlock) GetNestingMode() Nesting { | ||
return NestingSingle | ||
} | ||
|
||
func (b *lifecycleBlock) GetAttributes() map[string]ShimHCLAttribute { | ||
return map[string]ShimHCLAttribute{ | ||
"create_before_destroy": {}, | ||
} | ||
} | ||
|
||
func (b *lifecycleBlock) GetBlocks() map[string]ShimHCLBlock { | ||
return map[string]ShimHCLBlock{} | ||
} | ||
|
||
func writeBlock(body *hclwrite.Body, schema ShimHCLSchema, config map[string]cty.Value) { | ||
attributeList := make([]string, 0, len(schema.GetAttributes())) | ||
for key := range schema.GetAttributes() { | ||
attributeList = append(attributeList, key) | ||
} | ||
sort.Strings(attributeList) | ||
for _, key := range attributeList { | ||
v, ok := config[key] | ||
if !ok { | ||
continue | ||
} | ||
body.SetAttributeValue(key, v) | ||
} | ||
|
||
blockList := make([]string, 0, len(schema.GetBlocks())) | ||
for key := range schema.GetBlocks() { | ||
blockList = append(blockList, key) | ||
} | ||
sort.Strings(blockList) | ||
|
||
for _, key := range blockList { | ||
block := schema.GetBlocks()[key] | ||
if v, ok := config[key]; !ok || v.IsNull() { | ||
continue | ||
} | ||
|
||
switch block.GetNestingMode() { | ||
case NestingSingle: | ||
newBlock := body.AppendNewBlock(key, nil) | ||
writeBlock(newBlock.Body(), block, config[key].AsValueMap()) | ||
case NestingList, NestingSet: | ||
for _, elem := range config[key].AsValueSlice() { | ||
newBlock := body.AppendNewBlock(key, nil) | ||
writeBlock(newBlock.Body(), block, elem.AsValueMap()) | ||
} | ||
default: | ||
contract.Failf("unexpected nesting mode %v", block.GetNestingMode()) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.