forked from hashicorp/terraform-plugin-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
internal/fwschemadata: Migrate tfsdk Set and SetAttribute logic (hash…
…icorp#454) Reference: hashicorp#366 This change continues the migration of `tfsdk.Config`/`tfsdk.Plan`/`tfsdk.State` type logic into the `internal/fwschemadata` package `Data` type, so the implementation is shared and to potentially introduce other implementations.
- Loading branch information
Showing
11 changed files
with
3,049 additions
and
5,860 deletions.
There are no files selected for viewing
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,35 @@ | ||
package fwschemadata | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/diag" | ||
"github.com/hashicorp/terraform-plugin-framework/internal/reflect" | ||
"github.com/hashicorp/terraform-plugin-framework/path" | ||
) | ||
|
||
// Set replaces the entire value. The value should be a struct whose fields | ||
// have one of the attr.Value types. Each field must have the tfsdk field tag. | ||
func (d *Data) Set(ctx context.Context, val any) diag.Diagnostics { | ||
attrValue, diags := reflect.FromValue(ctx, d.Schema.Type(), val, path.Empty()) | ||
|
||
if diags.HasError() { | ||
return diags | ||
} | ||
|
||
tfValue, err := attrValue.ToTerraformValue(ctx) | ||
|
||
if err != nil { | ||
diags.AddError( | ||
"Data Write Error", | ||
"An unexpected error was encountered trying to write the data. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ | ||
fmt.Sprintf("Error: Unable to run ToTerraformValue on new value: %s", err), | ||
) | ||
return diags | ||
} | ||
|
||
d.TerraformValue = tfValue | ||
|
||
return diags | ||
} |
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,199 @@ | ||
package fwschemadata | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/attr/xattr" | ||
"github.com/hashicorp/terraform-plugin-framework/diag" | ||
"github.com/hashicorp/terraform-plugin-framework/internal/logging" | ||
"github.com/hashicorp/terraform-plugin-framework/internal/reflect" | ||
"github.com/hashicorp/terraform-plugin-framework/internal/totftypes" | ||
"github.com/hashicorp/terraform-plugin-framework/path" | ||
"github.com/hashicorp/terraform-plugin-go/tftypes" | ||
) | ||
|
||
// SetAtPath sets the attribute at `path` using the supplied Go value. | ||
// | ||
// The attribute path and value must be valid with the current schema. If the | ||
// attribute path already has a value, it will be overwritten. If the attribute | ||
// path does not have a value, it will be added, including any parent attribute | ||
// paths as necessary. | ||
// | ||
// Lists can only have the next element added according to the current length. | ||
func (d *Data) SetAtPath(ctx context.Context, path path.Path, val interface{}) diag.Diagnostics { | ||
var diags diag.Diagnostics | ||
|
||
ctx = logging.FrameworkWithAttributePath(ctx, path.String()) | ||
|
||
tftypesPath, tftypesPathDiags := totftypes.AttributePath(ctx, path) | ||
|
||
diags.Append(tftypesPathDiags...) | ||
|
||
if diags.HasError() { | ||
return diags | ||
} | ||
|
||
attrType, err := d.Schema.TypeAtTerraformPath(ctx, tftypesPath) | ||
|
||
if err != nil { | ||
diags.AddAttributeError( | ||
path, | ||
"Data Write Error", | ||
"An unexpected error was encountered trying to retrieve type information at a given path. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ | ||
"Error: "+err.Error(), | ||
) | ||
return diags | ||
} | ||
|
||
newVal, newValDiags := reflect.FromValue(ctx, attrType, val, path) | ||
diags.Append(newValDiags...) | ||
|
||
if diags.HasError() { | ||
return diags | ||
} | ||
|
||
tfVal, err := newVal.ToTerraformValue(ctx) | ||
|
||
if err != nil { | ||
diags.AddAttributeError( | ||
path, | ||
"Data Write Error", | ||
"An unexpected error was encountered trying to write an attribute to the data. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ | ||
"Error: Cannot run ToTerraformValue on new data value: "+err.Error(), | ||
) | ||
return diags | ||
} | ||
|
||
if attrTypeWithValidate, ok := attrType.(xattr.TypeWithValidate); ok { | ||
logging.FrameworkTrace(ctx, "Type implements TypeWithValidate") | ||
logging.FrameworkDebug(ctx, "Calling provider defined Type Validate") | ||
diags.Append(attrTypeWithValidate.Validate(ctx, tfVal, path)...) | ||
logging.FrameworkDebug(ctx, "Called provider defined Type Validate") | ||
|
||
if diags.HasError() { | ||
return diags | ||
} | ||
} | ||
|
||
transformFunc, transformFuncDiags := d.SetAtPathTransformFunc(ctx, path, tfVal, nil) | ||
diags.Append(transformFuncDiags...) | ||
|
||
if diags.HasError() { | ||
return diags | ||
} | ||
|
||
d.TerraformValue, err = tftypes.Transform(d.TerraformValue, transformFunc) | ||
|
||
if err != nil { | ||
diags.AddAttributeError( | ||
path, | ||
"Data Write Error", | ||
"An unexpected error was encountered trying to write an attribute to the data. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ | ||
"Error: Cannot transform data: "+err.Error(), | ||
) | ||
return diags | ||
} | ||
|
||
return diags | ||
} | ||
|
||
// SetAttributeTransformFunc recursively creates a value based on the current | ||
// Plan values along the path. If the value at the path does not yet exist, | ||
// this will perform recursion to add the child value to a parent value, | ||
// creating the parent value if necessary. | ||
func (d Data) SetAtPathTransformFunc(ctx context.Context, path path.Path, tfVal tftypes.Value, diags diag.Diagnostics) (func(*tftypes.AttributePath, tftypes.Value) (tftypes.Value, error), diag.Diagnostics) { | ||
exists, pathExistsDiags := d.PathExists(ctx, path) | ||
diags.Append(pathExistsDiags...) | ||
|
||
if diags.HasError() { | ||
return nil, diags | ||
} | ||
|
||
tftypesPath, tftypesPathDiags := totftypes.AttributePath(ctx, path) | ||
|
||
diags.Append(tftypesPathDiags...) | ||
|
||
if diags.HasError() { | ||
return nil, diags | ||
} | ||
|
||
if exists { | ||
// Overwrite existing value | ||
return func(p *tftypes.AttributePath, v tftypes.Value) (tftypes.Value, error) { | ||
if p.Equal(tftypesPath) { | ||
return tfVal, nil | ||
} | ||
return v, nil | ||
}, diags | ||
} | ||
|
||
parentPath := path.ParentPath() | ||
parentTftypesPath := tftypesPath.WithoutLastStep() | ||
parentAttrType, err := d.Schema.TypeAtTerraformPath(ctx, parentTftypesPath) | ||
|
||
if err != nil { | ||
err = fmt.Errorf("error getting parent attribute type in schema: %w", err) | ||
diags.AddAttributeError( | ||
parentPath, | ||
"Plan Write Error", | ||
"An unexpected error was encountered trying to write an attribute to the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), | ||
) | ||
return nil, diags | ||
} | ||
|
||
parentValue, err := d.TerraformValueAtTerraformPath(ctx, parentTftypesPath) | ||
|
||
if err != nil && !errors.Is(err, tftypes.ErrInvalidStep) { | ||
diags.AddAttributeError( | ||
parentPath, | ||
"Plan Read Error", | ||
"An unexpected error was encountered trying to read an attribute from the plan. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), | ||
) | ||
return nil, diags | ||
} | ||
|
||
if parentValue.IsNull() || !parentValue.IsKnown() { | ||
// TODO: This will break when DynamicPsuedoType is introduced. | ||
// tftypes.Type should implement AttributePathStepper, but it currently does not. | ||
// When it does, we should use: tftypes.WalkAttributePath(p.Raw.Type(), parentPath) | ||
// Reference: https://github.com/hashicorp/terraform-plugin-go/issues/110 | ||
parentType := parentAttrType.TerraformType(ctx) | ||
var childValue interface{} | ||
|
||
if !parentValue.IsKnown() { | ||
childValue = tftypes.UnknownValue | ||
} | ||
|
||
var parentValueDiags diag.Diagnostics | ||
parentValue, parentValueDiags = CreateParentTerraformValue(ctx, parentPath, parentType, childValue) | ||
diags.Append(parentValueDiags...) | ||
|
||
if diags.HasError() { | ||
return nil, diags | ||
} | ||
} | ||
|
||
var childValueDiags diag.Diagnostics | ||
childStep, _ := path.Steps().LastStep() | ||
parentValue, childValueDiags = UpsertChildTerraformValue(ctx, parentPath, parentValue, childStep, tfVal) | ||
diags.Append(childValueDiags...) | ||
|
||
if diags.HasError() { | ||
return nil, diags | ||
} | ||
|
||
if attrTypeWithValidate, ok := parentAttrType.(xattr.TypeWithValidate); ok { | ||
logging.FrameworkTrace(ctx, "Type implements TypeWithValidate") | ||
logging.FrameworkDebug(ctx, "Calling provider defined Type Validate") | ||
diags.Append(attrTypeWithValidate.Validate(ctx, parentValue, parentPath)...) | ||
logging.FrameworkDebug(ctx, "Called provider defined Type Validate") | ||
|
||
if diags.HasError() { | ||
return nil, diags | ||
} | ||
} | ||
|
||
return d.SetAtPathTransformFunc(ctx, parentPath, parentValue, diags) | ||
} |
Oops, something went wrong.