Skip to content

Commit 1bf97ef

Browse files
Only suppress diff for run_if when it is ALL_SUCCESS (#3454)
* Do not suppress diff for run_if if it's not the default value * add support for for_each_task as well * abstract default suppress diff into a common interface * add test cases * Update common/customizable_schema.go --------- Co-authored-by: Miles Yucht <[email protected]>
1 parent 3c21b25 commit 1bf97ef

File tree

4 files changed

+309
-2
lines changed

4 files changed

+309
-2
lines changed

common/customizable_schema.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package common
22

33
import (
4+
"fmt"
5+
"slices"
6+
47
"github.com/hashicorp/go-cty/cty"
58
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
69
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -87,6 +90,36 @@ func (s *CustomizableSchema) SetSuppressDiff() *CustomizableSchema {
8790
return s
8891
}
8992

93+
// SetSuppressDiffWithDefault suppresses the diff if the
94+
// new value (ie value from HCL config) is not set and
95+
// the old value (ie value from state / platform) is equal to the default value.
96+
//
97+
// Often Databricks HTTP APIs will return values for fields that were not set by
98+
// the author in their terraform configuration. This function allows us to suppress
99+
// the diff in these cases.
100+
func (s *CustomizableSchema) SetSuppressDiffWithDefault(dv any) *CustomizableSchema {
101+
primitiveTypes := []schema.ValueType{schema.TypeBool, schema.TypeString, schema.TypeInt, schema.TypeFloat}
102+
if !slices.Contains(primitiveTypes, s.Schema.Type) {
103+
panic(fmt.Errorf("expected primitive type, got: %s", s.Schema.Type))
104+
}
105+
106+
// Get zero value for the schema type
107+
zero := fmt.Sprintf("%v", s.Schema.Type.Zero())
108+
109+
// Get string representation of the default value
110+
sv := fmt.Sprintf("%v", dv)
111+
112+
// Suppress diff if the new value (ie value from HCL config) is not set and
113+
// the old value (ie value from state / platform) is equal to the default value.
114+
s.Schema.DiffSuppressFunc = func(k, old, new string, d *schema.ResourceData) bool {
115+
if new == zero && old == sv {
116+
return true
117+
}
118+
return false
119+
}
120+
return s
121+
}
122+
90123
func (s *CustomizableSchema) SetCustomSuppressDiff(suppressor func(k, old, new string, d *schema.ResourceData) bool) *CustomizableSchema {
91124
s.Schema.DiffSuppressFunc = suppressor
92125
return s

common/customizable_schema_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,99 @@ func TestCustomizableSchemaSetSuppressDiff(t *testing.T) {
4343
assert.Truef(t, testCustomizableSchemaScm["non_optional"].DiffSuppressFunc != nil, "DiffSuppressfunc should be set in field: non_optional")
4444
}
4545

46+
func TestCustomizableSchemaSetCustomSuppressDiffWithDefault(t *testing.T) {
47+
tc := []struct {
48+
name string
49+
fieldName string
50+
dv any
51+
old string
52+
new string
53+
result bool
54+
}{
55+
{
56+
name: "default string",
57+
fieldName: "string",
58+
dv: "foobar",
59+
old: "foobar",
60+
new: "",
61+
result: true,
62+
},
63+
{
64+
name: "default int",
65+
fieldName: "integer",
66+
dv: 123,
67+
old: "123",
68+
new: "0",
69+
result: true,
70+
},
71+
{
72+
name: "default bool",
73+
fieldName: "bool",
74+
dv: true,
75+
old: "true",
76+
new: "false",
77+
result: true,
78+
},
79+
{
80+
name: "default float",
81+
fieldName: "float",
82+
dv: 123.456,
83+
old: "123.456",
84+
new: "0",
85+
result: true,
86+
},
87+
{
88+
name: "non default string",
89+
fieldName: "string",
90+
dv: "foobar",
91+
old: "non-default-val",
92+
new: "",
93+
result: false,
94+
},
95+
{
96+
name: "non default int",
97+
fieldName: "integer",
98+
dv: 123,
99+
old: "non-default-val",
100+
new: "0",
101+
result: false,
102+
},
103+
{
104+
name: "non default bool",
105+
fieldName: "bool",
106+
dv: true,
107+
old: "non-default-val",
108+
new: "false",
109+
result: false,
110+
},
111+
{
112+
name: "non default float",
113+
fieldName: "float",
114+
dv: 123.456,
115+
old: "non-default-val",
116+
new: "0",
117+
result: false,
118+
},
119+
{
120+
name: "override in config",
121+
fieldName: "string",
122+
dv: "foobar",
123+
old: "foobar",
124+
new: "new-val-in-config",
125+
result: false,
126+
},
127+
}
128+
129+
for _, tt := range tc {
130+
t.Run(tt.name, func(t *testing.T) {
131+
CustomizeSchemaPath(testCustomizableSchemaScm, tt.fieldName).SetSuppressDiffWithDefault(tt.dv)
132+
assert.Truef(t, testCustomizableSchemaScm[tt.fieldName].DiffSuppressFunc != nil, "DiffSuppressFunc should be set in field: %s", tt.fieldName)
133+
134+
assert.Equal(t, tt.result, testCustomizableSchemaScm[tt.fieldName].DiffSuppressFunc("", tt.old, tt.new, nil))
135+
})
136+
}
137+
}
138+
46139
func TestCustomizableSchemaSetCustomSuppressDiff(t *testing.T) {
47140
CustomizeSchemaPath(testCustomizableSchemaScm, "non_optional").SetCustomSuppressDiff(diffSuppressor("test", CustomizeSchemaPath(testCustomizableSchemaScm, "non_optional").Schema))
48141
assert.Truef(t, testCustomizableSchemaScm["non_optional"].DiffSuppressFunc != nil, "DiffSuppressfunc should be set in field: non_optional")

jobs/resource_job.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ type ForEachNestedTask struct {
132132
TaskKey string `json:"task_key,omitempty"`
133133
Description string `json:"description,omitempty"`
134134
DependsOn []jobs.TaskDependency `json:"depends_on,omitempty"`
135-
RunIf string `json:"run_if,omitempty" tf:"suppress_diff"`
135+
RunIf string `json:"run_if,omitempty"`
136136

137137
ExistingClusterID string `json:"existing_cluster_id,omitempty" tf:"group:cluster_type"`
138138
NewCluster *clusters.Cluster `json:"new_cluster,omitempty" tf:"group:cluster_type"`
@@ -211,7 +211,7 @@ type JobTaskSettings struct {
211211
TaskKey string `json:"task_key,omitempty"`
212212
Description string `json:"description,omitempty"`
213213
DependsOn []jobs.TaskDependency `json:"depends_on,omitempty"`
214-
RunIf string `json:"run_if,omitempty" tf:"suppress_diff"`
214+
RunIf string `json:"run_if,omitempty"`
215215

216216
ExistingClusterID string `json:"existing_cluster_id,omitempty" tf:"group:cluster_type"`
217217
NewCluster *clusters.Cluster `json:"new_cluster,omitempty" tf:"group:cluster_type"`
@@ -766,6 +766,10 @@ var jobSchema = common.StructToSchema(JobSettings{},
766766
fixWebhookNotifications(s)
767767
fixWebhookNotifications(common.MustSchemaMap(s, "task"))
768768

769+
// Suppress diff if the platform returns ALL_SUCCESS for run_if in a task
770+
common.CustomizeSchemaPath(s, "task", "run_if").SetSuppressDiffWithDefault(jobs.RunIfAllSuccess)
771+
common.CustomizeSchemaPath(s, "task", "for_each_task", "task", "run_if").SetSuppressDiffWithDefault(jobs.RunIfAllSuccess)
772+
769773
return s
770774
})
771775

jobs/resource_job_test.go

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2038,6 +2038,183 @@ func TestResourceJobUpdate(t *testing.T) {
20382038
assert.Equal(t, "Featurizer New", d.Get("name"))
20392039
}
20402040

2041+
func TestResourceJobUpdate_RunIfSuppressesDiffIfAllSuccess(t *testing.T) {
2042+
_, err := qa.ResourceFixture{
2043+
Fixtures: []qa.HTTPFixture{
2044+
{
2045+
Method: "POST",
2046+
Resource: "/api/2.1/jobs/reset",
2047+
ExpectedRequest: UpdateJobRequest{
2048+
JobID: 789,
2049+
NewSettings: &JobSettings{
2050+
MaxConcurrentRuns: 1,
2051+
Tasks: []JobTaskSettings{
2052+
{
2053+
NotebookTask: &NotebookTask{
2054+
NotebookPath: "/foo/bar",
2055+
},
2056+
// The diff is suppressed here. The API payload
2057+
// contains the "run_if" value from the terraform
2058+
// state.
2059+
RunIf: "ALL_SUCCESS",
2060+
},
2061+
{
2062+
ForEachTask: &ForEachTask{
2063+
Inputs: "abc",
2064+
Task: ForEachNestedTask{
2065+
NotebookTask: &NotebookTask{NotebookPath: "/bar/foo"},
2066+
// The diff is suppressed here. Value is from
2067+
// the terraform state.
2068+
RunIf: "ALL_SUCCESS",
2069+
},
2070+
},
2071+
},
2072+
},
2073+
Name: "My job",
2074+
},
2075+
},
2076+
},
2077+
{
2078+
Method: "GET",
2079+
Resource: "/api/2.1/jobs/get?job_id=789",
2080+
Response: Job{
2081+
JobID: 789,
2082+
Settings: &JobSettings{
2083+
Name: "My job",
2084+
Tasks: []JobTaskSettings{
2085+
{
2086+
NotebookTask: &NotebookTask{NotebookPath: "/foo/bar"},
2087+
RunIf: "ALL_SUCCESS",
2088+
},
2089+
{
2090+
ForEachTask: &ForEachTask{
2091+
Inputs: "abc",
2092+
Task: ForEachNestedTask{
2093+
NotebookTask: &NotebookTask{NotebookPath: "/bar/foo"},
2094+
RunIf: "ALL_SUCCESS",
2095+
},
2096+
},
2097+
},
2098+
},
2099+
},
2100+
},
2101+
},
2102+
},
2103+
ID: "789",
2104+
Update: true,
2105+
Resource: ResourceJob(),
2106+
InstanceState: map[string]string{
2107+
"task.0.run_if": "ALL_SUCCESS",
2108+
"task.1.for_each_task.0.task.0.run_if": "ALL_SUCCESS",
2109+
},
2110+
HCL: `
2111+
name = "My job"
2112+
task {
2113+
notebook_task {
2114+
notebook_path = "/foo/bar"
2115+
}
2116+
}
2117+
task {
2118+
for_each_task {
2119+
inputs = "abc"
2120+
task {
2121+
notebook_task {
2122+
notebook_path = "/bar/foo"
2123+
}
2124+
}
2125+
}
2126+
}`,
2127+
}.Apply(t)
2128+
assert.NoError(t, err)
2129+
}
2130+
2131+
func TestResourceJobUpdate_RunIfDoesNotSuppressIfNotAllSuccess(t *testing.T) {
2132+
_, err := qa.ResourceFixture{
2133+
Fixtures: []qa.HTTPFixture{
2134+
{
2135+
Method: "POST",
2136+
Resource: "/api/2.1/jobs/reset",
2137+
ExpectedRequest: UpdateJobRequest{
2138+
JobID: 789,
2139+
NewSettings: &JobSettings{
2140+
MaxConcurrentRuns: 1,
2141+
Tasks: []JobTaskSettings{
2142+
{
2143+
NotebookTask: &NotebookTask{
2144+
NotebookPath: "/foo/bar",
2145+
},
2146+
// The diff is not suppressed here. Thus the API payload
2147+
// explicitly does not set run_if here, to unset it in the
2148+
// job definition.
2149+
// RunIf is not set, as implied from the HCL config.
2150+
},
2151+
{
2152+
ForEachTask: &ForEachTask{
2153+
Inputs: "abc",
2154+
Task: ForEachNestedTask{
2155+
NotebookTask: &NotebookTask{NotebookPath: "/bar/foo"},
2156+
// The diff is not suppressed. RunIf is
2157+
// not set, as implied from the HCL config.
2158+
},
2159+
},
2160+
},
2161+
},
2162+
Name: "My job",
2163+
},
2164+
},
2165+
},
2166+
{
2167+
Method: "GET",
2168+
Resource: "/api/2.1/jobs/get?job_id=789",
2169+
Response: Job{
2170+
JobID: 789,
2171+
Settings: &JobSettings{
2172+
Name: "My job",
2173+
Tasks: []JobTaskSettings{
2174+
{
2175+
NotebookTask: &NotebookTask{NotebookPath: "/foo/bar"},
2176+
RunIf: "AT_LEAST_ONE_FAILED",
2177+
},
2178+
{
2179+
ForEachTask: &ForEachTask{
2180+
Task: ForEachNestedTask{
2181+
RunIf: "AT_LEAST_ONE_FAILED",
2182+
},
2183+
},
2184+
},
2185+
},
2186+
},
2187+
},
2188+
},
2189+
},
2190+
ID: "789",
2191+
Update: true,
2192+
InstanceState: map[string]string{
2193+
"task.0.run_if": "AT_LEAST_ONE_FAILED",
2194+
"task.1.for_each_task.0.task.0.run_if": "AT_LEAST_ONE_FAILED",
2195+
},
2196+
Resource: ResourceJob(),
2197+
HCL: `
2198+
name = "My job"
2199+
task {
2200+
notebook_task {
2201+
notebook_path = "/foo/bar"
2202+
}
2203+
}
2204+
task {
2205+
for_each_task {
2206+
inputs = "abc"
2207+
task {
2208+
notebook_task {
2209+
notebook_path = "/bar/foo"
2210+
}
2211+
}
2212+
}
2213+
}`,
2214+
}.Apply(t)
2215+
assert.NoError(t, err)
2216+
}
2217+
20412218
func TestResourceJobUpdate_NodeTypeToInstancePool(t *testing.T) {
20422219
d, err := qa.ResourceFixture{
20432220
Fixtures: []qa.HTTPFixture{

0 commit comments

Comments
 (0)