-
Notifications
You must be signed in to change notification settings - Fork 214
Expand file tree
/
Copy pathapply.go
More file actions
224 lines (184 loc) · 6.55 KB
/
Copy pathapply.go
File metadata and controls
224 lines (184 loc) · 6.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package direct
import (
"context"
"encoding/json"
"errors"
"fmt"
"reflect"
"github.com/databricks/cli/bundle/deployplan"
"github.com/databricks/cli/bundle/direct/dstate"
"github.com/databricks/cli/libs/log"
"github.com/databricks/databricks-sdk-go/apierr"
)
func (d *DeploymentUnit) Destroy(ctx context.Context, db *dstate.DeploymentState) error {
id := db.GetResourceID(d.ResourceKey)
if id == "" {
log.Infof(ctx, "Cannot delete %s: missing from state", d.ResourceKey)
return nil
}
return d.Delete(ctx, db, id)
}
func (d *DeploymentUnit) Deploy(ctx context.Context, db *dstate.DeploymentState, newState any, actionType deployplan.ActionType, planEntry *deployplan.PlanEntry) error {
if actionType == deployplan.Create {
return d.Create(ctx, db, newState)
}
oldID := db.GetResourceID(d.ResourceKey)
if oldID == "" {
return errors.New("state entry not found")
}
switch actionType {
case deployplan.Recreate:
return d.Recreate(ctx, db, oldID, newState)
case deployplan.Update:
return d.Update(ctx, db, oldID, newState, planEntry)
case deployplan.UpdateWithID:
return d.UpdateWithID(ctx, db, oldID, newState)
case deployplan.Resize:
return d.Resize(ctx, db, oldID, newState)
default:
return fmt.Errorf("internal error: unexpected actionType: %#v", actionType)
}
}
func (d *DeploymentUnit) Create(ctx context.Context, db *dstate.DeploymentState, newState any) error {
newID, remoteState, err := d.Adapter.DoCreate(ctx, newState)
if err != nil {
// No need to prefix error, there is no ambiguity (only one operation - DoCreate) and no additional context (like id)
return err
}
log.Infof(ctx, "Created %s id=%#v", d.ResourceKey, newID)
err = d.SetRemoteState(remoteState)
if err != nil {
return err
}
err = db.SaveState(d.ResourceKey, newID, newState, d.DependsOn)
if err != nil {
return fmt.Errorf("saving state after creating id=%s: %w", newID, err)
}
waitRemoteState, err := d.Adapter.WaitAfterCreate(ctx, newID, newState)
if err != nil {
return fmt.Errorf("waiting after creating id=%s: %w", newID, err)
}
err = d.SetRemoteState(waitRemoteState)
if err != nil {
return err
}
return nil
}
func (d *DeploymentUnit) Recreate(ctx context.Context, db *dstate.DeploymentState, oldID string, newState any) error {
// Note, unlike Delete(), we hard error on 403 here intentionally
err := d.Adapter.DoDelete(ctx, oldID)
if err != nil && !isResourceGone(err) {
return fmt.Errorf("deleting old id=%s: %w", oldID, err)
}
// Drop the state entry so a subsequent failure of Create leaves no malformed
// (empty-id) entry behind. The next plan will see "no state" and retry as Create.
err = db.DeleteState(d.ResourceKey)
if err != nil {
return fmt.Errorf("deleting state: %w", err)
}
return d.Create(ctx, db, newState)
}
func (d *DeploymentUnit) Update(ctx context.Context, db *dstate.DeploymentState, id string, newState any, planEntry *deployplan.PlanEntry) error {
if !d.Adapter.HasDoUpdate() {
return fmt.Errorf("internal error: DoUpdate not implemented for resource %s", d.ResourceKey)
}
remoteState, err := d.Adapter.DoUpdate(ctx, id, newState, planEntry)
if err != nil {
return fmt.Errorf("updating id=%s: %w", id, err)
}
err = d.SetRemoteState(remoteState)
if err != nil {
return err
}
err = db.SaveState(d.ResourceKey, id, newState, d.DependsOn)
if err != nil {
return fmt.Errorf("saving state id=%s: %w", id, err)
}
waitRemoteState, err := d.Adapter.WaitAfterUpdate(ctx, id, newState)
if err != nil {
return fmt.Errorf("waiting after updating id=%s: %w", id, err)
}
// Update remote state with the result from wait operation
err = d.SetRemoteState(waitRemoteState)
if err != nil {
return err
}
return nil
}
func (d *DeploymentUnit) UpdateWithID(ctx context.Context, db *dstate.DeploymentState, oldID string, newState any) error {
newID, remoteState, err := d.Adapter.DoUpdateWithID(ctx, oldID, newState)
if err != nil {
return fmt.Errorf("updating id=%s: %w", oldID, err)
}
if oldID != newID {
log.Infof(ctx, "Updated %s id=%#v (previously %#v)", d.ResourceKey, newID, oldID)
} else {
log.Infof(ctx, "Updated %s id=%#v", d.ResourceKey, newID)
}
err = d.SetRemoteState(remoteState)
if err != nil {
return err
}
err = db.SaveState(d.ResourceKey, newID, newState, d.DependsOn)
if err != nil {
return fmt.Errorf("saving state id=%s: %w", oldID, err)
}
waitRemoteState, err := d.Adapter.WaitAfterUpdate(ctx, newID, newState)
if err != nil {
return fmt.Errorf("waiting after updating id=%s: %w", newID, err)
}
// Update remote state with the result from wait operation
err = d.SetRemoteState(waitRemoteState)
if err != nil {
return err
}
return nil
}
func (d *DeploymentUnit) Delete(ctx context.Context, db *dstate.DeploymentState, oldID string) error {
err := d.Adapter.DoDelete(ctx, oldID)
if err != nil && !isResourceGone(err) {
// Rather than failing delete and requiring user to unbind, we perform unbind automatically there.
// Some services, e.g. jobs, return 403 for missing resources if caller did not have permissions to it when job existed.
// In those cases 403 hides 404. In other cases, user not having permissions to resource but having in the bundle might
// mean configuration error that user is trying to fix by removing resource from their bundle.
if errors.Is(err, apierr.ErrPermissionDenied) {
log.Warnf(ctx, "Ignoring permission error when deleting %s id=%s: %s", d.ResourceKey, oldID, err)
} else {
return fmt.Errorf("deleting id=%s: %w", oldID, err)
}
}
err = db.DeleteState(d.ResourceKey)
if err != nil {
return fmt.Errorf("deleting state id=%s: %w", oldID, err)
}
return nil
}
func (d *DeploymentUnit) Resize(ctx context.Context, db *dstate.DeploymentState, id string, newState any) error {
err := d.Adapter.DoResize(ctx, id, newState)
if err != nil {
return fmt.Errorf("resizing id=%s: %w", id, err)
}
err = db.SaveState(d.ResourceKey, id, newState, d.DependsOn)
if err != nil {
return fmt.Errorf("saving state id=%s: %w", id, err)
}
return nil
}
func parseState(destType reflect.Type, raw json.RawMessage) (any, error) {
destPtr := reflect.New(destType).Interface()
err := json.Unmarshal(raw, destPtr)
if err != nil {
return nil, fmt.Errorf("unmarshalling into %s: %w", destType, err)
}
return reflect.ValueOf(destPtr).Elem().Interface(), nil
}
func (d *DeploymentUnit) refreshRemoteState(ctx context.Context, id string) error {
if d.RemoteState != nil {
return nil
}
remoteState, err := d.Adapter.DoRead(ctx, id)
if err != nil {
return fmt.Errorf("failed to refresh remote state id=%s: %w", id, err)
}
return d.SetRemoteState(remoteState)
}