From ca5ea216d6e0e30e3af15460c91c2ea09963b415 Mon Sep 17 00:00:00 2001 From: Tanmay Rustagi <88379306+tanmay-db@users.noreply.github.com> Date: Mon, 9 Sep 2024 15:42:42 +0200 Subject: [PATCH] [Internal] Add support for `computed` tag in TfSDK Structs (#4005) ## Changes Added support for `computed` tag in TfSDK Structs making code easier to maintain and read, minimising the lines of code required in customizing the schema ## Tests Unit test - [x] `make test` run locally - [ ] relevant change in `docs/` folder - [ ] covered with integration tests in `internal/acceptance` - [ ] relevant acceptance tests are passing - [ ] using Go SDK --- .../pluginfw/tfschema/struct_to_schema.go | 111 +++++++++++++++--- .../tfschema/struct_to_schema_test.go | 20 ++++ 2 files changed, 116 insertions(+), 15 deletions(-) diff --git a/internal/providers/pluginfw/tfschema/struct_to_schema.go b/internal/providers/pluginfw/tfschema/struct_to_schema.go index 471479676e..2ac1303e19 100644 --- a/internal/providers/pluginfw/tfschema/struct_to_schema.go +++ b/internal/providers/pluginfw/tfschema/struct_to_schema.go @@ -30,6 +30,7 @@ func typeToSchema(v reflect.Value) map[string]AttributeBuilder { continue } isOptional := fieldIsOptional(typeField) + isComputed := fieldIsComputed(typeField) kind := typeField.Type.Kind() value := field.Value typeFieldType := typeField.Type @@ -48,17 +49,44 @@ func typeToSchema(v reflect.Value) map[string]AttributeBuilder { } switch elemType { case reflect.TypeOf(types.Bool{}): - scm[fieldName] = ListAttributeBuilder{ElementType: types.BoolType, Optional: isOptional, Required: !isOptional} + scm[fieldName] = ListAttributeBuilder{ + ElementType: types.BoolType, + Optional: isOptional, + Required: !isOptional, + Computed: isComputed, + } case reflect.TypeOf(types.Int64{}): - scm[fieldName] = ListAttributeBuilder{ElementType: types.Int64Type, Optional: isOptional, Required: !isOptional} + scm[fieldName] = ListAttributeBuilder{ + ElementType: types.Int64Type, + Optional: isOptional, + Required: !isOptional, + Computed: isComputed, + } case reflect.TypeOf(types.Float64{}): - scm[fieldName] = ListAttributeBuilder{ElementType: types.Float64Type, Optional: isOptional, Required: !isOptional} + scm[fieldName] = ListAttributeBuilder{ + ElementType: types.Float64Type, + Optional: isOptional, + Required: !isOptional, + Computed: isComputed, + } case reflect.TypeOf(types.String{}): - scm[fieldName] = ListAttributeBuilder{ElementType: types.StringType, Optional: isOptional, Required: !isOptional} + scm[fieldName] = ListAttributeBuilder{ + ElementType: types.StringType, + Optional: isOptional, + Required: !isOptional, + Computed: isComputed, + } default: // Nested struct nestedScm := typeToSchema(reflect.New(elemType).Elem()) - scm[fieldName] = ListNestedAttributeBuilder{NestedObject: NestedAttributeObject{Attributes: nestedScm}, Optional: isOptional, Required: !isOptional} + scm[fieldName] = ListNestedAttributeBuilder{ + NestedObject: NestedAttributeObject{ + Attributes: nestedScm, + }, + Optional: isOptional, + Required: !isOptional, + Computed: isComputed, + } } } else if kind == reflect.Map { elemType := typeFieldType.Elem() @@ -70,28 +98,71 @@ func typeToSchema(v reflect.Value) map[string]AttributeBuilder { } switch elemType { case reflect.TypeOf(types.Bool{}): - scm[fieldName] = MapAttributeBuilder{ElementType: types.BoolType, Optional: isOptional, Required: !isOptional} + scm[fieldName] = MapAttributeBuilder{ + ElementType: types.BoolType, + Optional: isOptional, + Required: !isOptional, + Computed: isComputed, + } case reflect.TypeOf(types.Int64{}): - scm[fieldName] = MapAttributeBuilder{ElementType: types.Int64Type, Optional: isOptional, Required: !isOptional} + scm[fieldName] = MapAttributeBuilder{ + ElementType: types.Int64Type, + Optional: isOptional, + Required: !isOptional, + Computed: isComputed, + } case reflect.TypeOf(types.Float64{}): - scm[fieldName] = MapAttributeBuilder{ElementType: types.Float64Type, Optional: isOptional, Required: !isOptional} + scm[fieldName] = MapAttributeBuilder{ + ElementType: types.Float64Type, + Optional: isOptional, + Required: !isOptional, + Computed: isComputed, + } case reflect.TypeOf(types.String{}): - scm[fieldName] = MapAttributeBuilder{ElementType: types.StringType, Optional: isOptional, Required: !isOptional} + scm[fieldName] = MapAttributeBuilder{ + ElementType: types.StringType, + Optional: isOptional, + Required: !isOptional, + Computed: isComputed, + } default: // Nested struct nestedScm := typeToSchema(reflect.New(elemType).Elem()) - scm[fieldName] = MapNestedAttributeBuilder{NestedObject: NestedAttributeObject{Attributes: nestedScm}, Optional: isOptional, Required: !isOptional} + scm[fieldName] = MapNestedAttributeBuilder{ + NestedObject: NestedAttributeObject{ + Attributes: nestedScm, + }, + Optional: isOptional, + Required: !isOptional, + Computed: isComputed, + } } } else if kind == reflect.Struct { switch value.Interface().(type) { case types.Bool: - scm[fieldName] = BoolAttributeBuilder{Optional: isOptional, Required: !isOptional} + scm[fieldName] = BoolAttributeBuilder{ + Optional: isOptional, + Required: !isOptional, + Computed: isComputed, + } case types.Int64: - scm[fieldName] = Int64AttributeBuilder{Optional: isOptional, Required: !isOptional} + scm[fieldName] = Int64AttributeBuilder{ + Optional: isOptional, + Required: !isOptional, + Computed: isComputed, + } case types.Float64: - scm[fieldName] = Float64AttributeBuilder{Optional: isOptional, Required: !isOptional} + scm[fieldName] = Float64AttributeBuilder{ + Optional: isOptional, + Required: !isOptional, + Computed: isComputed, + } case types.String: - scm[fieldName] = StringAttributeBuilder{Optional: isOptional, Required: !isOptional} + scm[fieldName] = StringAttributeBuilder{ + Optional: isOptional, + Required: !isOptional, + Computed: isComputed, + } case types.List: panic(fmt.Errorf("types.List should never be used in tfsdk structs. %s", common.TerraformBugErrorMessage)) case types.Map: @@ -101,7 +172,12 @@ func typeToSchema(v reflect.Value) map[string]AttributeBuilder { elem := typeFieldType sv := reflect.New(elem) nestedScm := typeToSchema(sv) - scm[fieldName] = SingleNestedAttributeBuilder{Attributes: nestedScm, Optional: isOptional, Required: !isOptional} + scm[fieldName] = SingleNestedAttributeBuilder{ + Attributes: nestedScm, + Optional: isOptional, + Required: !isOptional, + Computed: isComputed, + } } } else { panic(fmt.Errorf("unknown type for field: %s. %s", typeField.Name, common.TerraformBugErrorMessage)) @@ -110,6 +186,11 @@ func typeToSchema(v reflect.Value) map[string]AttributeBuilder { return scm } +func fieldIsComputed(field reflect.StructField) bool { + tagValue := field.Tag.Get("tf") + return strings.Contains(tagValue, "computed") +} + func fieldIsOptional(field reflect.StructField) bool { tagValue := field.Tag.Get("tf") return strings.Contains(tagValue, "optional") diff --git a/internal/providers/pluginfw/tfschema/struct_to_schema_test.go b/internal/providers/pluginfw/tfschema/struct_to_schema_test.go index 7eca181b17..f566c5feab 100644 --- a/internal/providers/pluginfw/tfschema/struct_to_schema_test.go +++ b/internal/providers/pluginfw/tfschema/struct_to_schema_test.go @@ -24,6 +24,12 @@ type TestIntTfSdk struct { Workers types.Int64 `tfsdk:"workers" tf:"optional"` } +type TestComputedTfSdk struct { + ComputedTag types.String `tfsdk:"computedtag" tf:"computed"` + MultipleTags types.String `tfsdk:"multipletags" tf:"computed,optional"` + NonComputed types.String `tfsdk:"noncomputed" tf:"optional"` +} + type TestFloatTfSdk struct { Float types.Float64 `tfsdk:"float" tf:"optional"` } @@ -248,3 +254,17 @@ func TestStructToSchemaExpectedError(t *testing.T) { t.Run(test.name, func(t *testing.T) { testStructToSchemaPanics(t, test.testStruct, test.expectedError) }) } } + +func TestComputedField(t *testing.T) { + // Test that ComputedTag field is computed and required + scm := ResourceStructToSchema(TestComputedTfSdk{}, nil) + assert.True(t, scm.Attributes["computedtag"].IsComputed()) + assert.True(t, scm.Attributes["computedtag"].IsRequired()) + + // Test that MultipleTags field is computed and optional + assert.True(t, scm.Attributes["multipletags"].IsComputed()) + assert.True(t, scm.Attributes["multipletags"].IsOptional()) + + // Test that NonComputed field is not computed + assert.True(t, !scm.Attributes["noncomputed"].IsComputed()) +}