Hi,
I’m struggling with semantics equal mechanism…
It looks like code implemented as Int64SemanticEquals() is called and the function returns true, but for some reason terraform still returns “inconsistent result after apply” when planned value for capacity_bytes is different than the one returned from real volume after creation.
I intentionally put warning to confirm that the call is done. If warning is removed, it does not change anything. I tried to return true in every function which could be called from these implemented below - nothing has changed. Do you maybe have an idea what is missing? Implementation follows (IMO) example for string in official documentation.
2024-08-16T11:49:07.956+0200 [TRACE] statemgr.Filesystem: state has changed since last snapshot, so incrementing serial to 47
2024-08-16T11:49:07.956+0200 [TRACE] statemgr.Filesystem: writing snapshot at terraform.tfstate
╷
│ Warning: Int64SemanticsEquals
│
│ with irmc-redfish_storage_volume.volume["batman"],
│ on resource.tf line 1, in resource "irmc-redfish_storage_volume" "volume":
│ 1: resource "irmc-redfish_storage_volume" "volume" {
│
│ Difference is ok!
╵
╷
│ Error: Provider produced inconsistent result after apply
│
│ When applying changes to irmc-redfish_storage_volume.volume["batman"], provider "provider[\"hashicorp/fujitsu/irmc-redfish\"]" produced an unexpected new value: .capacity_bytes: was cty.NumberIntVal(1e+08), but now cty.NumberIntVal(9.961472e+07).
│
│ This is a bug in the provider, which should be reported in the provider's own issue tracker.
╵
2024-08-16T11:49:07.957+0200 [TRACE] statemgr.Filesystem: removing lock metadata file .terraform.tfstate.lock.info
2024-08-16T11:49:07.957+0200 [TRACE] statemgr.Filesystem: unlocking terraform.tfstate using fcntl flock
2024-08-16T11:49:07.958+0200 [DEBUG] provider.stdio: received EOF, stopping recv loop: err="rpc error: code = Unavailable desc = error reading from server: EOF"
2024-08-16T11:49:07.962+0200 [INFO] provider: plugin process exited: plugin=/home/andrzej/go/bin/terraform-provider-irmc-redfish id=1308882
2024-08-16T11:49:07.962+0200 [DEBUG] provider: plugin exited```
How the custom type and semantics logic are implemented.
package models
import (
“context”
“fmt”
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)
type CapacityByteValue struct {
basetypes.Int64Value
}
var _ basetypes.Int64Valuable = CapacityByteValue{}
var _ basetypes.Int64ValuableWithSemanticEquals = CapacityByteValue{}
var _ basetypes.Int64Typable = CapacityByteType{}
type CapacityByteType struct {
basetypes.Int64Type
}
func (t CapacityByteType) Equal(o attr.Type) bool {
return true
}
func (t CapacityByteType) String() string {
return “CapacityByteType”
}
func (t CapacityByteType) ValueFromInt64(ctx context.Context, in basetypes.Int64Value) (basetypes.Int64Valuable, diag.Diagnostics) {
value := CapacityByteValue{
Int64Value: in,
}
return value, nil
}
func (t CapacityByteType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
attrValue, err := t.Int64Type.ValueFromTerraform(ctx, in)
if err != nil {
return nil, err
}
stringValue, ok := attrValue.(basetypes.Int64Value)
if !ok {
return nil, fmt.Errorf("unexpected value type of %T", attrValue)
}
stringValuable, diags := t.ValueFromInt64(ctx, stringValue)
if diags.HasError() {
return nil, fmt.Errorf("unexpected error converting Int64Value to IntValuable: %v", diags)
}
return stringValuable, nil
}
func (v CapacityByteType) ValueType(ctx context.Context) attr.Value {
return CapacityByteValue{}
}
func (v CapacityByteValue) Int64SemanticEquals(_ context.Context, newValueable basetypes.Int64Valuable) (bool, diag.Diagnostics) {
var diags diag.Diagnostics
newValue, ok := newValueable.(CapacityByteValue)
if !ok {
diags.AddError(“Semantics equality check error”, “”)
return false, diags
}
diff := v.Int64Value.ValueInt64() - newValue.ValueInt64()
if (diff < 50000000) {
diags.AddWarning("Int64SemanticsEquals", "Difference is ok!")
return true, diags
}
diags.AddError("Int64SemanticsEquals", "Difference too big")
return false, diags
}
func (v CapacityByteValue) Equal(o attr.Value) bool {
newValue, ok := o.(CapacityByteValue)
if !ok {
return false
}
diff := v.Int64Value.ValueInt64() - newValue.ValueInt64()
if (diff < 50000000) {
return true
}
return false
}
func (v CapacityByteValue) Type(ctx context.Context) attr.Type {
return CapacityByteType{}
}
// VirtualMediaResourceModel describes the resource data model.
type StorageVolumeResourceModel struct {
Id types.String tfsdk:"id"
StorageId types.String tfsdk:"storage_controller_id"
RedfishServer RedfishServer tfsdk:"server"
RaidType types.String `tfsdk:"raid_type"`
CapacityBytes CapacityByteValue `tfsdk:"capacity_bytes"`
VolumeName types.String `tfsdk:"name"`
InitMode types.String `tfsdk:"init_mode"`
PhysicalDrives types.List `tfsdk:"physical_drives"`
OptimumIOSizeBytes types.Int64 `tfsdk:"optimum_io_size_bytes"`
ReadMode types.String `tfsdk:"read_mode"`
WriteMode types.String `tfsdk:"write_mode"`
// CacheMode types.String tfsdk:"cache_mode"
DriveCacheMode types.String tfsdk:"drive_cache_mode"
}
part of schema for the resource
"capacity_bytes": schema.Int64Attribute{
CustomType: models.CapacityByteType{},
Description: "Volume capacity in bytes.",
MarkdownDescription: "Volume capacity in bytes. If not specified during creation, volume will have maximum size calculated from chosen disks.",
Optional: true,
Computed: true,
},