Skip to content

Commit

Permalink
[Internal] Add support for computed tag in TfSDK Structs (#4005)
Browse files Browse the repository at this point in the history
## Changes
<!-- Summary of your changes that are easy to understand -->
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
<!-- 
How is this tested? Please see the checklist below and also describe any
other relevant 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
  • Loading branch information
tanmay-db authored Sep 9, 2024
1 parent afc4764 commit ca5ea21
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 15 deletions.
111 changes: 96 additions & 15 deletions internal/providers/pluginfw/tfschema/struct_to_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -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:
Expand All @@ -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))
Expand All @@ -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")
Expand Down
20 changes: 20 additions & 0 deletions internal/providers/pluginfw/tfschema/struct_to_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
Expand Down Expand Up @@ -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())
}

0 comments on commit ca5ea21

Please sign in to comment.