Skip to content

Commit

Permalink
flow difference calculation (1)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickcping committed Feb 15, 2024
1 parent 8a7f797 commit 8d5f3d2
Show file tree
Hide file tree
Showing 5 changed files with 293 additions and 83 deletions.
1 change: 0 additions & 1 deletion docs/resources/flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ resource "davinci_flow" "my_awesome_main_flow" {
### Read-Only

- `flow_configuration_json` (String) The parsed configuration of the DaVinci Flow import JSON. Drift is calculated based on this attribute.
- `flow_export_json` (String, Sensitive) The DaVinci Flow export in raw JSON format following successful import, including target environment metadata.
- `flow_variables` (Attributes Set) Returned list of Flow Context variables. These are variable resources that are created and managed by the Flow resource via `flow_json`. (see [below for nested schema](#nestedatt--flow_variables))
- `id` (String) The ID of this resource.

Expand Down
159 changes: 159 additions & 0 deletions internal/framework/customtypes/davinciexporttype/parsed_type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package davinciexporttype

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/attr/xattr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tftypes"
"github.com/samir-gandhi/davinci-client-go/davinci"
)

// Ensure the implementation satisfies the expected interfaces
var _ basetypes.StringTypable = ParsedType{}
var _ xattr.TypeWithValidate = ParsedType{}

type ParsedType struct {
basetypes.StringType
ImportFile bool
// ... potentially other fields ...
}

func (t ParsedType) Equal(o attr.Type) bool {
other, ok := o.(ParsedType)

if !ok {
return false
}

return t.StringType.Equal(other.StringType)
}

func (t ParsedType) String() string {
return "davinciexporttype.ParsedType"
}

func (t ParsedType) ValueFromString(ctx context.Context, in basetypes.StringValue) (basetypes.StringValuable, diag.Diagnostics) {
// ParsedValue defined in the value type section
value := ParsedValue{
StringValue: in,
ImportFile: t.ImportFile,
}

return value, nil
}

func (t ParsedType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
attrValue, err := t.StringType.ValueFromTerraform(ctx, in)

if err != nil {
return nil, err
}

stringValue, ok := attrValue.(basetypes.StringValue)

if !ok {
return nil, fmt.Errorf("unexpected value type of %T", attrValue)
}

stringValuable, diags := t.ValueFromString(ctx, stringValue)

if diags.HasError() {
return nil, fmt.Errorf("unexpected error converting StringValue to StringValuable: %v", diags)
}

return stringValuable, nil
}

func (t ParsedType) ValueType(ctx context.Context) attr.Value {
// ParsedValue defined in the value type section
return ParsedValue{}
}

func (t ParsedType) Validate(ctx context.Context, in tftypes.Value, path path.Path) diag.Diagnostics {
var diags diag.Diagnostics

if in.Type() == nil {
return diags
}

if !in.Type().Is(tftypes.String) {
err := fmt.Errorf("expected String value, received %T with value: %v", in, in)
diags.AddAttributeError(
path,
"DaVinci Export Parsed Type Validation Error",
"An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. "+
"Please report the following to the provider developer:\n\n"+err.Error(),
)
return diags
}

if !in.IsKnown() || in.IsNull() {
return diags
}

var valueString string

if err := in.As(&valueString); err != nil {
diags.AddAttributeError(
path,
"DaVinci Export Parsed Type Validation Error",
"An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. "+
"Please report the following to the provider developer:\n\n"+err.Error(),
)

return diags
}

if ok := davinci.ValidFlowsInfoJSON([]byte(valueString), davinci.ExportCmpOpts{}); ok {

Check failure on line 112 in internal/framework/customtypes/davinciexporttype/parsed_type.go

View workflow job for this annotation

GitHub Actions / Analyze (go)

undefined: davinci.ValidFlowsInfoJSON

Check failure on line 112 in internal/framework/customtypes/davinciexporttype/parsed_type.go

View workflow job for this annotation

GitHub Actions / Analyze (go)

undefined: davinci.ExportCmpOpts
diags.AddAttributeError(
path,
"Invalid DaVinci Flow Export String Value",
"A string value was provided that is not valid DaVinci Export JSON string format. The export should not including subflows as these should be managed separately, as their own independent flows.\n\n"+
"Please re-export the DaVinci flow without subflows included.\n",
)

return diags
}

// Validate just the config of the export
if ok := davinci.ValidFlowJSON([]byte(valueString), davinci.ExportCmpOpts{

Check failure on line 124 in internal/framework/customtypes/davinciexporttype/parsed_type.go

View workflow job for this annotation

GitHub Actions / Analyze (go)

undefined: davinci.ValidFlowJSON

Check failure on line 124 in internal/framework/customtypes/davinciexporttype/parsed_type.go

View workflow job for this annotation

GitHub Actions / Analyze (go)

undefined: davinci.ExportCmpOpts
IgnoreConfig: false,
IgnoreDesignerCues: true,
IgnoreEnvironmentMetadata: true,
IgnoreUnmappedProperties: true,
IgnoreVersionMetadata: true,
IgnoreFlowMetadata: true,
}); !ok {
diags.AddAttributeError(
path,
"Invalid DaVinci Flow Export String Value",
"A string value was provided that is not valid DaVinci Export JSON string format.\n\n"+
"Please re-export the DaVinci flow. If the flow JSON has been correctly exported from the DaVinci environment (and can be re-imported), please report this error to the provider maintainers.\n",
)

return diags
}

// Warn in case there are AdditionalProperties in the import file (since these aren't cleanly handled in the SDK, while they are preserved on import, there may be unpredictable results in diff calculation)
if ok := davinci.ValidFlowJSON([]byte(valueString), davinci.ExportCmpOpts{

Check failure on line 143 in internal/framework/customtypes/davinciexporttype/parsed_type.go

View workflow job for this annotation

GitHub Actions / Analyze (go)

undefined: davinci.ValidFlowJSON

Check failure on line 143 in internal/framework/customtypes/davinciexporttype/parsed_type.go

View workflow job for this annotation

GitHub Actions / Analyze (go)

undefined: davinci.ExportCmpOpts
IgnoreConfig: true,
IgnoreDesignerCues: true,
IgnoreEnvironmentMetadata: true,
IgnoreUnmappedProperties: false,
IgnoreVersionMetadata: true,
IgnoreFlowMetadata: true,
}); !ok {
diags.AddAttributeWarning(
path,
"DaVinci Export JSON contains unknown properties",
"The DaVinci Flow Export contains properties that cannot be validated. These parameters will be preserved on import to the DaVinci service, but there may be unpredictable results in difference calculation.\n",
)
}

return diags
}
86 changes: 86 additions & 0 deletions internal/framework/customtypes/davinciexporttype/parsed_value.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package davinciexporttype

import (
"context"
"encoding/json"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/samir-gandhi/davinci-client-go/davinci"
)

// Ensure the implementation satisfies the expected interfaces
var _ basetypes.StringValuable = ParsedValue{}
var _ basetypes.StringValuableWithSemanticEquals = ParsedValue{}

type ParsedValue struct {
basetypes.StringValue
// ... potentially other fields ...
ImportFile bool
}

func (v ParsedValue) Equal(o attr.Value) bool {
other, ok := o.(ParsedValue)

if !ok {
return false
}

return v.StringValue.Equal(other.StringValue)
}

func (v ParsedValue) Type(ctx context.Context) attr.Type {
// ParsedType defined in the schema type section
return ParsedType{}
}

func (v ParsedValue) StringSemanticEquals(ctx context.Context, newValuable basetypes.StringValuable) (bool, diag.Diagnostics) {
var diags diag.Diagnostics

// The framework should always pass the correct value type, but always check
newValue, ok := newValuable.(ParsedValue)

if !ok {
diags.AddError(
"DaVinci Export Parsed Type Semantic Equality Check Error",
"An unexpected value type was received while performing semantic equality checks. "+
"Please report this to the provider developers.\n\n"+
"Expected Value Type: "+fmt.Sprintf("%T", v)+"\n"+
"Got Value Type: "+fmt.Sprintf("%T", newValuable),
)

return false, diags
}

var priorFlow, newFlow davinci.Flow

if err := json.Unmarshal([]byte(v.StringValue.ValueString()), &priorFlow); err != nil {
diags.AddError(
"DaVinci Export Parsed Type Semantic Equality Check Error",
"An unexpected error was encountered trying to parse the prior attribute value to perform semantic equals evaluation. This is always an error in the provider. "+
"Please report the following to the provider developer:\n\n"+err.Error(),
)
return false, diags
}

if err := json.Unmarshal([]byte(newValue.ValueString()), &newFlow); err != nil {
diags.AddError(
"DaVinci Export Parsed Type Semantic Equality Check Error",
"An unexpected error was encountered trying to parse the new attribute value to perform semantic equals evaluation. This is always an error in the provider. "+
"Please report the following to the provider developer:\n\n"+err.Error(),
)
return false, diags
}

// Check whether the flows are equal, ignoring environment metadata and designer UI cues. Just the flow configuration
return davinci.FlowEqual(priorFlow, newFlow, davinci.ExportCmpOpts{

Check failure on line 78 in internal/framework/customtypes/davinciexporttype/parsed_value.go

View workflow job for this annotation

GitHub Actions / Analyze (go)

undefined: davinci.FlowEqual

Check failure on line 78 in internal/framework/customtypes/davinciexporttype/parsed_value.go

View workflow job for this annotation

GitHub Actions / Analyze (go)

undefined: davinci.ExportCmpOpts
IgnoreConfig: false,
IgnoreDesignerCues: true,
IgnoreEnvironmentMetadata: !v.ImportFile,
IgnoreUnmappedProperties: true,
IgnoreVersionMetadata: true,
IgnoreFlowMetadata: true,
}), diags
}
Loading

0 comments on commit 8d5f3d2

Please sign in to comment.