Skip to content

Commit 51d3d4c

Browse files
authored
direct: Added support for ClassifyChanges (#3690)
## Changes Added support for ClassifyChanges ## Why Needed for #3682 Some resources might have different types of changes based on their remote state. For example, the cluster's resize action becomes an update if the cluster is stopped. In order to do such classification, we need access to the remote state, so we are introducing a new ClassifyChange method. ClassifyChange is called first, if it returns ActionTypeUnset, ClassifyByTriggers called ## Tests <!-- How have you tested the changes? --> <!-- If your PR needs to be included in the release notes for next release, add a separate entry in NEXT_CHANGELOG.md as part of your PR. -->
1 parent 23f811f commit 51d3d4c

File tree

3 files changed

+43
-3
lines changed

3 files changed

+43
-3
lines changed

bundle/direct/bundle_plan.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ func (b *DeploymentBundle) CalculatePlanForDeploy(ctx context.Context, client *d
169169
return false
170170
}
171171

172-
remoteAction, remoteChangeMap = interpretOldStateVsRemoteState(adapter, remoteDiff)
172+
remoteAction, remoteChangeMap = interpretOldStateVsRemoteState(ctx, adapter, remoteDiff, remoteState)
173173
}
174174

175175
entry.Action = max(localAction, remoteAction).String()
@@ -237,7 +237,7 @@ func convertChangesToTriggersMap(adapter *dresources.Adapter, diff []structdiff.
237237
return action, m
238238
}
239239

240-
func interpretOldStateVsRemoteState(adapter *dresources.Adapter, diff []structdiff.Change) (deployplan.ActionType, map[string]deployplan.Trigger) {
240+
func interpretOldStateVsRemoteState(ctx context.Context, adapter *dresources.Adapter, diff []structdiff.Change, remoteState any) (deployplan.ActionType, map[string]deployplan.Trigger) {
241241
action := deployplan.ActionTypeSkip
242242
m := make(map[string]deployplan.Trigger)
243243

@@ -252,7 +252,12 @@ func interpretOldStateVsRemoteState(adapter *dresources.Adapter, diff []structdi
252252
}
253253
continue
254254
}
255-
fieldAction := adapter.ClassifyByTriggers(ch)
255+
fieldAction, err := adapter.ClassifyChange(ch, remoteState)
256+
if err != nil {
257+
logdiag.LogError(ctx, fmt.Errorf("internal error: failed to classify changes: %w", err))
258+
continue
259+
}
260+
256261
if fieldAction > action {
257262
action = fieldAction
258263
}

bundle/direct/dresources/adapter.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ type IResourceNoRefresh interface {
7272

7373
// [Optional] WaitAfterUpdate waits for the resource to become ready after update.
7474
WaitAfterUpdate(ctx context.Context, newState any) error
75+
76+
// [Optional] ClassifyChange classifies a set of changes using custom logic.
77+
ClassifyChange(change structdiff.Change, remoteState any) (deployplan.ActionType, error)
7578
}
7679

7780
// IResourceWithRefresh is an alternative to IResourceNoRefresh but every method can return remoteState.
@@ -111,6 +114,7 @@ type Adapter struct {
111114
doUpdateWithID *calladapt.BoundCaller
112115
waitAfterCreate *calladapt.BoundCaller
113116
waitAfterUpdate *calladapt.BoundCaller
117+
classifyChange *calladapt.BoundCaller
114118

115119
fieldTriggers map[string]deployplan.ActionType
116120
}
@@ -138,6 +142,7 @@ func NewAdapter(typedNil any, client *databricks.WorkspaceClient) (*Adapter, err
138142
doUpdateWithID: nil,
139143
waitAfterCreate: nil,
140144
waitAfterUpdate: nil,
145+
classifyChange: nil,
141146
fieldTriggers: map[string]deployplan.ActionType{},
142147
}
143148

@@ -306,6 +311,10 @@ func (a *Adapter) validate() error {
306311
}
307312
}
308313

314+
if a.classifyChange != nil {
315+
validations = append(validations, "ClassifyChange changes", a.classifyChange.InTypes[1], remoteType)
316+
}
317+
309318
err = validateTypes(validations...)
310319
if err != nil {
311320
return err
@@ -501,6 +510,21 @@ func (a *Adapter) WaitAfterUpdate(ctx context.Context, newState any) (any, error
501510
}
502511
}
503512

513+
func (a *Adapter) ClassifyChange(change structdiff.Change, remoteState any) (deployplan.ActionType, error) {
514+
// If ClassifyChange is not implemented, use FieldTriggers.
515+
if a.classifyChange == nil {
516+
return a.ClassifyByTriggers(change), nil
517+
}
518+
519+
outs, err := a.classifyChange.Call(change, remoteState)
520+
if err != nil {
521+
return deployplan.ActionTypeSkip, err
522+
}
523+
524+
actionType := outs[0].(deployplan.ActionType)
525+
return actionType, nil
526+
}
527+
504528
// prepareCallRequired prepares a call and ensures the method is found.
505529
func prepareCallRequired(resource any, methodName string) (*calladapt.BoundCaller, error) {
506530
caller, err := calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), methodName)

bundle/direct/dresources/all_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/databricks/cli/bundle/config/resources"
1010
"github.com/databricks/cli/bundle/deployplan"
1111
"github.com/databricks/cli/libs/structs/structaccess"
12+
"github.com/databricks/cli/libs/structs/structdiff"
1213
"github.com/databricks/cli/libs/structs/structpath"
1314
"github.com/databricks/cli/libs/structs/structwalk"
1415
"github.com/databricks/cli/libs/testserver"
@@ -205,6 +206,16 @@ func testCRUD(t *testing.T, group string, adapter *Adapter, client *databricks.W
205206
remoteAfterDelete, err := adapter.DoRefresh(ctx, createdID)
206207
require.Error(t, err)
207208
require.Nil(t, remoteAfterDelete)
209+
210+
path, err := structpath.Parse("name")
211+
require.NoError(t, err)
212+
213+
_, err = adapter.ClassifyChange(structdiff.Change{
214+
Path: path,
215+
Old: nil,
216+
New: "mynewname",
217+
}, remote)
218+
require.NoError(t, err)
208219
}
209220

210221
// validateFields uses structwalk to generate all valid field paths and checks membership.

0 commit comments

Comments
 (0)