Skip to content

Commit c12aae1

Browse files
Refactor CustomizeSchema for ResourceProvider to skip unfeasible customizations (#3481)
* update * update * update * update
1 parent ec030f2 commit c12aae1

File tree

9 files changed

+263
-132
lines changed

9 files changed

+263
-132
lines changed

clusters/resource_cluster.go

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -123,62 +123,62 @@ type ClusterSpec struct {
123123
Libraries []compute.Library `json:"libraries,omitempty" tf:"slice_set,alias:library"`
124124
}
125125

126-
func (ClusterSpec) CustomizeSchema(s map[string]*schema.Schema) map[string]*schema.Schema {
127-
common.CustomizeSchemaPath(s, "cluster_source").SetReadOnly()
128-
common.CustomizeSchemaPath(s, "enable_elastic_disk").SetComputed()
129-
common.CustomizeSchemaPath(s, "enable_local_disk_encryption").SetComputed()
130-
common.CustomizeSchemaPath(s, "node_type_id").SetComputed().SetConflictsWith([]string{"driver_instance_pool_id", "instance_pool_id"})
131-
common.CustomizeSchemaPath(s, "driver_node_type_id").SetComputed().SetConflictsWith([]string{"driver_instance_pool_id", "instance_pool_id"})
132-
common.CustomizeSchemaPath(s, "driver_instance_pool_id").SetComputed().SetConflictsWith([]string{"driver_node_type_id", "node_type_id"})
133-
common.CustomizeSchemaPath(s, "ssh_public_keys").SetMaxItems(10)
134-
common.CustomizeSchemaPath(s, "init_scripts").SetMaxItems(10)
135-
common.CustomizeSchemaPath(s, "init_scripts", "dbfs").SetDeprecated(DbfsDeprecationWarning)
136-
common.CustomizeSchemaPath(s, "init_scripts", "dbfs", "destination").SetRequired()
137-
common.CustomizeSchemaPath(s, "init_scripts", "s3", "destination").SetRequired()
138-
common.CustomizeSchemaPath(s, "init_scripts", "volumes", "destination").SetRequired()
139-
common.CustomizeSchemaPath(s, "init_scripts", "workspace", "destination").SetRequired()
140-
common.CustomizeSchemaPath(s, "workload_type", "clients").SetRequired()
141-
common.CustomizeSchemaPath(s, "workload_type", "clients", "notebooks").SetDefault(true)
142-
common.CustomizeSchemaPath(s, "workload_type", "clients", "jobs").SetDefault(true)
143-
s["library"].Set = func(i any) int {
126+
func (ClusterSpec) CustomizeSchema(s *common.CustomizableSchema) *common.CustomizableSchema {
127+
s.SchemaPath("cluster_source").SetReadOnly()
128+
s.SchemaPath("enable_elastic_disk").SetComputed()
129+
s.SchemaPath("enable_local_disk_encryption").SetComputed()
130+
s.SchemaPath("node_type_id").SetComputed().SetConflictsWith([]string{"driver_instance_pool_id", "instance_pool_id"})
131+
s.SchemaPath("driver_node_type_id").SetComputed().SetConflictsWith([]string{"driver_instance_pool_id", "instance_pool_id"})
132+
s.SchemaPath("driver_instance_pool_id").SetComputed().SetConflictsWith([]string{"driver_node_type_id", "node_type_id"})
133+
s.SchemaPath("ssh_public_keys").SetMaxItems(10)
134+
s.SchemaPath("init_scripts").SetMaxItems(10)
135+
s.SchemaPath("init_scripts", "dbfs").SetDeprecated(DbfsDeprecationWarning)
136+
s.SchemaPath("init_scripts", "dbfs", "destination").SetRequired()
137+
s.SchemaPath("init_scripts", "s3", "destination").SetRequired()
138+
s.SchemaPath("init_scripts", "volumes", "destination").SetRequired()
139+
s.SchemaPath("init_scripts", "workspace", "destination").SetRequired()
140+
s.SchemaPath("workload_type", "clients").SetRequired()
141+
s.SchemaPath("workload_type", "clients", "notebooks").SetDefault(true)
142+
s.SchemaPath("workload_type", "clients", "jobs").SetDefault(true)
143+
s.SchemaPath("library").Schema.Set = func(i any) int {
144144
lib := libraries.NewLibraryFromInstanceState(i)
145145
return schema.HashString(lib.String())
146146
}
147-
common.CustomizeSchemaPath(s).AddNewField("idempotency_token", &schema.Schema{
147+
s.AddNewField("idempotency_token", &schema.Schema{
148148
Type: schema.TypeString,
149149
Optional: true,
150150
ForceNew: true,
151151
})
152-
common.CustomizeSchemaPath(s, "data_security_mode").SetSuppressDiff()
153-
common.CustomizeSchemaPath(s, "docker_image", "url").SetRequired()
154-
common.CustomizeSchemaPath(s, "docker_image", "basic_auth", "password").SetRequired().SetSensitive()
155-
common.CustomizeSchemaPath(s, "docker_image", "basic_auth", "username").SetRequired()
156-
common.CustomizeSchemaPath(s, "spark_conf").SetCustomSuppressDiff(SparkConfDiffSuppressFunc)
157-
common.CustomizeSchemaPath(s, "aws_attributes").SetSuppressDiff().SetConflictsWith([]string{"azure_attributes", "gcp_attributes"})
158-
common.CustomizeSchemaPath(s, "aws_attributes", "zone_id").SetCustomSuppressDiff(ZoneDiffSuppress)
159-
common.CustomizeSchemaPath(s, "azure_attributes").SetSuppressDiff().SetConflictsWith([]string{"aws_attributes", "gcp_attributes"})
160-
common.CustomizeSchemaPath(s, "gcp_attributes").SetSuppressDiff().SetConflictsWith([]string{"aws_attributes", "azure_attributes"})
161-
common.CustomizeSchemaPath(s, "autotermination_minutes").SetDefault(60)
162-
common.CustomizeSchemaPath(s, "autoscale", "max_workers").SetOptional()
163-
common.CustomizeSchemaPath(s, "autoscale", "min_workers").SetOptional()
164-
common.CustomizeSchemaPath(s, "cluster_log_conf", "dbfs", "destination").SetRequired()
165-
common.CustomizeSchemaPath(s, "cluster_log_conf", "s3", "destination").SetRequired()
166-
common.CustomizeSchemaPath(s, "spark_version").SetRequired()
167-
common.CustomizeSchemaPath(s).AddNewField("cluster_id", &schema.Schema{
152+
s.SchemaPath("data_security_mode").SetSuppressDiff()
153+
s.SchemaPath("docker_image", "url").SetRequired()
154+
s.SchemaPath("docker_image", "basic_auth", "password").SetRequired().SetSensitive()
155+
s.SchemaPath("docker_image", "basic_auth", "username").SetRequired()
156+
s.SchemaPath("spark_conf").SetCustomSuppressDiff(SparkConfDiffSuppressFunc)
157+
s.SchemaPath("aws_attributes").SetSuppressDiff().SetConflictsWith([]string{"azure_attributes", "gcp_attributes"})
158+
s.SchemaPath("aws_attributes", "zone_id").SetCustomSuppressDiff(ZoneDiffSuppress)
159+
s.SchemaPath("azure_attributes").SetSuppressDiff().SetConflictsWith([]string{"aws_attributes", "gcp_attributes"})
160+
s.SchemaPath("gcp_attributes").SetSuppressDiff().SetConflictsWith([]string{"aws_attributes", "azure_attributes"})
161+
s.SchemaPath("autotermination_minutes").SetDefault(60)
162+
s.SchemaPath("autoscale", "max_workers").SetOptional()
163+
s.SchemaPath("autoscale", "min_workers").SetOptional()
164+
s.SchemaPath("cluster_log_conf", "dbfs", "destination").SetRequired()
165+
s.SchemaPath("cluster_log_conf", "s3", "destination").SetRequired()
166+
s.SchemaPath("spark_version").SetRequired()
167+
s.AddNewField("cluster_id", &schema.Schema{
168168
Type: schema.TypeString,
169169
Computed: true,
170170
})
171-
common.CustomizeSchemaPath(s).AddNewField("default_tags", &schema.Schema{
171+
s.AddNewField("default_tags", &schema.Schema{
172172
Type: schema.TypeMap,
173173
Computed: true,
174174
})
175-
common.CustomizeSchemaPath(s).AddNewField("state", &schema.Schema{
175+
s.AddNewField("state", &schema.Schema{
176176
Type: schema.TypeString,
177177
Computed: true,
178178
})
179-
common.CustomizeSchemaPath(s, "instance_pool_id").SetConflictsWith([]string{"driver_node_type_id", "node_type_id"})
180-
common.CustomizeSchemaPath(s, "runtime_engine").SetValidateFunc(validation.StringInSlice([]string{"PHOTON", "STANDARD"}, false))
181-
common.CustomizeSchemaPath(s).AddNewField("is_pinned", &schema.Schema{
179+
s.SchemaPath("instance_pool_id").SetConflictsWith([]string{"driver_node_type_id", "node_type_id"})
180+
s.SchemaPath("runtime_engine").SetValidateFunc(validation.StringInSlice([]string{"PHOTON", "STANDARD"}, false))
181+
s.AddNewField("is_pinned", &schema.Schema{
182182
Type: schema.TypeBool,
183183
Optional: true,
184184
Default: false,
@@ -189,12 +189,12 @@ func (ClusterSpec) CustomizeSchema(s map[string]*schema.Schema) map[string]*sche
189189
return old == new
190190
},
191191
})
192-
common.CustomizeSchemaPath(s).AddNewField("url", &schema.Schema{
192+
s.AddNewField("url", &schema.Schema{
193193
Type: schema.TypeString,
194194
Computed: true,
195195
})
196-
common.CustomizeSchemaPath(s, "num_workers").SetDefault(0).SetValidateDiagFunc(validation.ToDiagFunc(validation.IntAtLeast(0)))
197-
common.CustomizeSchemaPath(s).AddNewField("cluster_mount_info", &schema.Schema{
196+
s.SchemaPath("num_workers").SetDefault(0).SetValidateDiagFunc(validation.ToDiagFunc(validation.IntAtLeast(0)))
197+
s.AddNewField("cluster_mount_info", &schema.Schema{
198198
Type: schema.TypeList,
199199
Optional: true,
200200
Elem: &schema.Resource{

clusters/resource_library.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ type LibraryResource struct {
1616
compute.Library
1717
}
1818

19-
func (LibraryResource) CustomizeSchema(s map[string]*schema.Schema) map[string]*schema.Schema {
20-
common.CustomizeSchemaPath(s).AddNewField("cluster_id", &schema.Schema{
19+
func (LibraryResource) CustomizeSchema(s *common.CustomizableSchema) *common.CustomizableSchema {
20+
s.AddNewField("cluster_id", &schema.Schema{
2121
Type: schema.TypeString,
2222
Required: true,
2323
})

common/customizable_schema.go

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package common
22

33
import (
44
"fmt"
5+
"log"
56
"slices"
7+
"strings"
68

79
"github.com/hashicorp/go-cty/cty"
810
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
@@ -13,6 +15,32 @@ type CustomizableSchema struct {
1315
Schema *schema.Schema
1416
path []string
1517
isSuppressDiff bool
18+
context schemaPathContext
19+
}
20+
21+
func (s *CustomizableSchema) pathContainsMultipleItemsList() bool {
22+
schemaPath := s.context.schemaPath
23+
for _, scm := range schemaPath {
24+
if scm.Type == schema.TypeList && scm.MaxItems != 1 {
25+
return true
26+
}
27+
}
28+
return false
29+
}
30+
31+
// Used to get the prefix path for functions like ConflictsWith, by joining `path` in SchemaPathContext.
32+
func getPrefixedValue(path []string, value []string) []string {
33+
var prefix string
34+
if len(path) != 0 {
35+
prefix = strings.Join(path, ".") + "."
36+
} else {
37+
prefix = ""
38+
}
39+
prefixedPaths := make([]string, len(value))
40+
for i, item := range value {
41+
prefixedPaths[i] = prefix + item
42+
}
43+
return prefixedPaths
1644
}
1745

1846
func CustomizeSchemaPath(s map[string]*schema.Schema, path ...string) *CustomizableSchema {
@@ -30,6 +58,22 @@ func CustomizeSchemaPath(s map[string]*schema.Schema, path ...string) *Customiza
3058
return &CustomizableSchema{Schema: sch, path: path}
3159
}
3260

61+
func (s *CustomizableSchema) SchemaPath(path ...string) *CustomizableSchema {
62+
sch := MustSchemaPath(s.GetSchemaMap(), path...)
63+
return &CustomizableSchema{Schema: sch, path: path, context: s.context}
64+
}
65+
66+
func (s *CustomizableSchema) GetSchemaMap() map[string]*schema.Schema {
67+
if s.Schema.Elem == nil {
68+
panic("Elem of Schema field for CustomizableSchema is nil.")
69+
}
70+
schemaResource, ok := s.Schema.Elem.(*schema.Resource)
71+
if !ok {
72+
panic("Elem of Schema field for CustomizableSchema is not a *schema.Resource.")
73+
}
74+
return schemaResource.Schema
75+
}
76+
3377
func (s *CustomizableSchema) SetOptional() *CustomizableSchema {
3478
s.Schema.Optional = true
3579
s.Schema.Required = false
@@ -149,31 +193,47 @@ func (s *CustomizableSchema) SetConflictsWith(value []string) *CustomizableSchem
149193
if len(value) == 0 {
150194
panic("SetConflictsWith cannot take in an empty list")
151195
}
152-
s.Schema.ConflictsWith = value
196+
if s.pathContainsMultipleItemsList() {
197+
log.Printf("[DEBUG] ConflictsWith skipped for %v, path contains TypeList block with MaxItems not equal to 1", strings.Join(s.path, "."))
198+
return s
199+
}
200+
s.Schema.ConflictsWith = getPrefixedValue(s.context.path, value)
153201
return s
154202
}
155203

156204
func (s *CustomizableSchema) SetExactlyOneOf(value []string) *CustomizableSchema {
157205
if len(value) == 0 {
158206
panic("SetExactlyOneOf cannot take in an empty list")
159207
}
160-
s.Schema.ExactlyOneOf = value
208+
if s.pathContainsMultipleItemsList() {
209+
log.Printf("[DEBUG] ExactlyOneOf skipped for %v, path contains TypeList block with MaxItems not equal to 1", strings.Join(s.path, "."))
210+
return s
211+
}
212+
s.Schema.ExactlyOneOf = getPrefixedValue(s.context.path, value)
161213
return s
162214
}
163215

164216
func (s *CustomizableSchema) SetAtLeastOneOf(value []string) *CustomizableSchema {
165217
if len(value) == 0 {
166218
panic("SetAtLeastOneOf cannot take in an empty list")
167219
}
168-
s.Schema.AtLeastOneOf = value
220+
if s.pathContainsMultipleItemsList() {
221+
log.Printf("[DEBUG] AtLeastOneOf skipped for %v, path contains TypeList block with MaxItems not equal to 1", strings.Join(s.path, "."))
222+
return s
223+
}
224+
s.Schema.AtLeastOneOf = getPrefixedValue(s.context.path, value)
169225
return s
170226
}
171227

172228
func (s *CustomizableSchema) SetRequiredWith(value []string) *CustomizableSchema {
173229
if len(value) == 0 {
174230
panic("SetRequiredWith cannot take in an empty list")
175231
}
176-
s.Schema.RequiredWith = value
232+
if s.pathContainsMultipleItemsList() {
233+
log.Printf("[DEBUG] SetRequiredWith skipped for %v, path contains TypeList block with MaxItems not equal to 1", strings.Join(s.path, "."))
234+
return s
235+
}
236+
s.Schema.RequiredWith = getPrefixedValue(s.context.path, value)
177237
return s
178238
}
179239

common/customizable_schema_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,34 @@ func TestCustomizableSchemaSetConflictsWith(t *testing.T) {
166166
assert.Truef(t, len(testCustomizableSchemaScm["non_optional"].ConflictsWith) == 1, "conflictsWith should be set in field: non_optional")
167167
}
168168

169+
func TestCustomizableSchemaSetConflictsWith_PathInContext(t *testing.T) {
170+
fakeContextWithPath := schemaPathContext{
171+
path: []string{"a", "0", "b"},
172+
schemaPath: []*schema.Schema{},
173+
}
174+
cs := CustomizeSchemaPath(testCustomizableSchemaScm, "float")
175+
cs.context = fakeContextWithPath
176+
cs.SetConflictsWith([]string{"abc"})
177+
assert.Truef(t, len(testCustomizableSchemaScm["float"].ConflictsWith) == 1, "conflictsWith should be set in field: float")
178+
assert.Truef(t, cs.Schema.ConflictsWith[0] == "a.0.b.abc", "conflictsWith should be set with the correct prefix")
179+
}
180+
181+
func TestCustomizableSchemaSetConflictsWith_MultiItemList(t *testing.T) {
182+
fakeContextWithPath := schemaPathContext{
183+
path: []string{"a", "0", "b"},
184+
schemaPath: []*schema.Schema{
185+
{
186+
Type: schema.TypeList,
187+
MaxItems: 10,
188+
},
189+
},
190+
}
191+
cs := CustomizeSchemaPath(testCustomizableSchemaScm, "bool")
192+
cs.context = fakeContextWithPath
193+
cs.SetConflictsWith([]string{"abc"})
194+
assert.Truef(t, len(testCustomizableSchemaScm["bool"].ConflictsWith) == 0, "conflictsWith should not be set when there's multi item list in the path")
195+
}
196+
169197
func TestCustomizableSchemaSetExactlyOneOf(t *testing.T) {
170198
CustomizeSchemaPath(testCustomizableSchemaScm, "non_optional").SetExactlyOneOf([]string{"abc"})
171199
assert.Truef(t, len(testCustomizableSchemaScm["non_optional"].ExactlyOneOf) == 1, "ExactlyOneOf should be set in field: non_optional")

common/recursion_tracking.go

Lines changed: 0 additions & 56 deletions
This file was deleted.

0 commit comments

Comments
 (0)