Skip to content

Commit 1e9b7ef

Browse files
committed
Autogen API operations customization support
1 parent 021f912 commit 1e9b7ef

File tree

40 files changed

+1180
-479
lines changed

40 files changed

+1180
-479
lines changed

.github/workflows/acceptance-tests-runner.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ jobs:
305305
- 'internal/serviceapi/databaseuserapi/*.go'
306306
- 'internal/serviceapi/maintenancewindowapi/*.go'
307307
- 'internal/serviceapi/orgserviceaccountapi/*.go'
308+
- 'internal/serviceapi/orgserviceaccountsecretapi/*.go'
308309
- 'internal/serviceapi/projectapi/*.go'
309310
- 'internal/serviceapi/projectsettingsapi/*.go'
310311
- 'internal/serviceapi/resourcepolicyapi/*.go'

internal/common/autogen/handle_operations.go renamed to internal/common/autogen/resource_api_operations.go

Lines changed: 48 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type WaitReq struct {
4141
}
4242

4343
type HandleCreateReq struct {
44+
APIOperations ResourceAPIOperations
4445
Resp *resource.CreateResponse
4546
Client *config.MongoDBClient
4647
Plan any
@@ -52,12 +53,8 @@ type HandleCreateReq struct {
5253

5354
func HandleCreate(ctx context.Context, req HandleCreateReq) {
5455
d := &req.Resp.Diagnostics
55-
bodyReq, err := Marshal(req.Plan, false)
56-
if err != nil {
57-
addError(d, opCreate, errBuildingAPIRequest, err)
58-
return
59-
}
60-
bodyResp, _, err := callAPIWithBody(ctx, req.Client, req.CallParams, bodyReq)
56+
57+
bodyResp, _, err := req.APIOperations.PerformCreate(ctx, &req)
6158
if err != nil {
6259
addError(d, opCreate, errCallingAPI, err)
6360
return
@@ -72,12 +69,12 @@ func HandleCreate(ctx context.Context, req HandleCreateReq) {
7269
addError(d, opCreate, errResolvingResponse, err)
7370
return
7471
}
75-
errWait := handleWaitCreateUpdate(ctx, req.Wait, req.Client, req.Plan)
72+
errWait := handleWaitCreateUpdate(ctx, req.Wait, req.APIOperations, req.Client, req.Plan)
7673
if req.DeleteReq != nil {
7774
// Handle timeout with cleanup if delete_on_create_timeout is enabled.
7875
errWait = cleanup.HandleCreateTimeout(req.DeleteOnCreateTimeout, errWait, func(ctxCleanup context.Context) error {
7976
deleteReq := req.DeleteReq(req.Plan)
80-
return callDelete(ctxCleanup, deleteReq)
77+
return req.APIOperations.PerformDelete(ctxCleanup, deleteReq)
8178
})
8279
}
8380
if errWait != nil {
@@ -88,16 +85,17 @@ func HandleCreate(ctx context.Context, req HandleCreateReq) {
8885
}
8986

9087
type HandleReadReq struct {
91-
Resp *resource.ReadResponse
92-
Client *config.MongoDBClient
93-
State any
94-
CallParams *config.APICallParams
88+
APIOperations ResourceAPIOperations
89+
Resp *resource.ReadResponse
90+
Client *config.MongoDBClient
91+
State any
92+
CallParams *config.APICallParams
9593
}
9694

9795
func HandleRead(ctx context.Context, req HandleReadReq) {
9896
d := &req.Resp.Diagnostics
99-
bodyResp, apiResp, err := callAPIWithoutBody(ctx, req.Client, req.CallParams)
100-
if notFound(bodyResp, apiResp) {
97+
bodyResp, apiResp, err := req.APIOperations.PerformRead(ctx, &req)
98+
if NotFound(bodyResp, apiResp) {
10199
req.Resp.State.RemoveResource(ctx)
102100
return
103101
}
@@ -119,21 +117,18 @@ func HandleRead(ctx context.Context, req HandleReadReq) {
119117
}
120118

121119
type HandleUpdateReq struct {
122-
Resp *resource.UpdateResponse
123-
Client *config.MongoDBClient
124-
Plan any
125-
CallParams *config.APICallParams
126-
Wait *WaitReq
120+
APIOperations ResourceAPIOperations
121+
Resp *resource.UpdateResponse
122+
Client *config.MongoDBClient
123+
Plan any
124+
CallParams *config.APICallParams
125+
Wait *WaitReq
127126
}
128127

129128
func HandleUpdate(ctx context.Context, req HandleUpdateReq) {
130129
d := &req.Resp.Diagnostics
131-
bodyReq, err := Marshal(req.Plan, true)
132-
if err != nil {
133-
addError(d, opUpdate, errBuildingAPIRequest, err)
134-
return
135-
}
136-
bodyResp, _, err := callAPIWithBody(ctx, req.Client, req.CallParams, bodyReq)
130+
131+
bodyResp, _, err := req.APIOperations.PerformUpdate(ctx, &req)
137132
if err != nil {
138133
addError(d, opUpdate, errCallingAPI, err)
139134
return
@@ -148,14 +143,15 @@ func HandleUpdate(ctx context.Context, req HandleUpdateReq) {
148143
addError(d, opUpdate, errResolvingResponse, err)
149144
return
150145
}
151-
if err := handleWaitCreateUpdate(ctx, req.Wait, req.Client, req.Plan); err != nil {
146+
if err := handleWaitCreateUpdate(ctx, req.Wait, req.APIOperations, req.Client, req.Plan); err != nil {
152147
addError(d, opUpdate, errWaitingForChanges, err)
153148
return
154149
}
155150
req.Resp.Diagnostics.Append(req.Resp.State.Set(ctx, req.Plan)...)
156151
}
157152

158153
type HandleDeleteReq struct {
154+
APIOperations ResourceAPIOperations
159155
Diags *diag.Diagnostics
160156
Client *config.MongoDBClient
161157
State any
@@ -165,22 +161,22 @@ type HandleDeleteReq struct {
165161
}
166162

167163
func HandleDelete(ctx context.Context, req HandleDeleteReq) {
168-
if err := callDelete(ctx, &req); err != nil {
164+
if err := req.APIOperations.PerformDelete(ctx, &req); err != nil {
169165
addError(req.Diags, opDelete, errCallingAPI, err)
170166
return
171167
}
172-
if errWait := handleWaitDelete(ctx, req.Wait, req.Client, req.State); errWait != nil {
168+
if errWait := handleWaitDelete(ctx, req.Wait, req.APIOperations, req.Client, req.State); errWait != nil {
173169
addError(req.Diags, opDelete, errWaitingForChanges, errWait)
174170
}
175171
}
176172

177173
// handleWaitCreateUpdate waits until a long-running operation is done if needed.
178174
// It also updates the model with the latest JSON response from the API.
179-
func handleWaitCreateUpdate(ctx context.Context, wait *WaitReq, client *config.MongoDBClient, model any) error {
175+
func handleWaitCreateUpdate(ctx context.Context, wait *WaitReq, apiOps ResourceAPIOperations, client *config.MongoDBClient, model any) error {
180176
if wait == nil {
181177
return nil
182178
}
183-
bodyResp, err := waitForChanges(ctx, wait, client, model)
179+
bodyResp, err := waitForChanges(ctx, wait, apiOps, client, model)
184180
if err != nil || isEmptyJSON(bodyResp) {
185181
return err
186182
}
@@ -191,23 +187,25 @@ func handleWaitCreateUpdate(ctx context.Context, wait *WaitReq, client *config.M
191187
}
192188

193189
// handleWaitDelete waits until a long-running operation to delete a resource if neeed.
194-
func handleWaitDelete(ctx context.Context, wait *WaitReq, client *config.MongoDBClient, model any) error {
190+
func handleWaitDelete(ctx context.Context, wait *WaitReq, apiOps ResourceAPIOperations, client *config.MongoDBClient, model any) error {
195191
if wait == nil {
196192
return nil
197193
}
198-
if _, err := waitForChanges(ctx, wait, client, model); err != nil {
194+
if _, err := waitForChanges(ctx, wait, apiOps, client, model); err != nil {
199195
return err
200196
}
201197
return nil
202198
}
203199

204-
func addError(d *diag.Diagnostics, opName, errSummary string, err error) {
205-
d.AddError(fmt.Sprintf("Error %s in %s", errSummary, opName), err.Error())
200+
// NotFound returns if the resource is not found (API response is 404 or response body is empty JSON).
201+
// That is because some resources like search_deployment can return an ok status code with empty json when resource doesn't exist.
202+
func NotFound(bodyResp []byte, apiResp *http.Response) bool {
203+
return validate.StatusNotFound(apiResp) || isEmptyJSON(bodyResp)
206204
}
207205

208-
// callAPIWithBody makes a request to the API with the given request body and returns the response body.
206+
// CallAPIWithBody makes a request to the API with the given request body and returns the response body.
209207
// It is used for POST, PUT, PATCH and DELETE with static content.
210-
func callAPIWithBody(ctx context.Context, client *config.MongoDBClient, callParams *config.APICallParams, bodyReq []byte) ([]byte, *http.Response, error) {
208+
func CallAPIWithBody(ctx context.Context, client *config.MongoDBClient, callParams *config.APICallParams, bodyReq []byte) ([]byte, *http.Response, error) {
211209
apiResp, err := client.UntypedAPICall(ctx, callParams, bodyReq)
212210
if err != nil {
213211
return nil, apiResp, err
@@ -220,9 +218,9 @@ func callAPIWithBody(ctx context.Context, client *config.MongoDBClient, callPara
220218
return bodyResp, apiResp, nil
221219
}
222220

223-
// callAPIWithoutBody makes a request to the API without a request body and returns the response body.
221+
// CallAPIWithoutBody makes a request to the API without a request body and returns the response body.
224222
// It is used for GET or DELETE requests where no request body is required.
225-
func callAPIWithoutBody(ctx context.Context, client *config.MongoDBClient, callParams *config.APICallParams) ([]byte, *http.Response, error) {
223+
func CallAPIWithoutBody(ctx context.Context, client *config.MongoDBClient, callParams *config.APICallParams) ([]byte, *http.Response, error) {
226224
apiResp, err := client.UntypedAPICall(ctx, callParams, nil)
227225
if err != nil {
228226
return nil, apiResp, err
@@ -235,26 +233,13 @@ func callAPIWithoutBody(ctx context.Context, client *config.MongoDBClient, callP
235233
return bodyResp, apiResp, nil
236234
}
237235

238-
// callDelete makes a DELETE request to the API, supporting both requests with and without a body.
239-
// Returns nil if the resource is not found (already deleted).
240-
func callDelete(ctx context.Context, req *HandleDeleteReq) error {
241-
var err error
242-
var bodyResp []byte
243-
var apiResp *http.Response
244-
if req.StaticRequestBody == "" {
245-
bodyResp, apiResp, err = callAPIWithoutBody(ctx, req.Client, req.CallParams)
246-
} else {
247-
bodyResp, apiResp, err = callAPIWithBody(ctx, req.Client, req.CallParams, []byte(req.StaticRequestBody))
248-
}
249-
if notFound(bodyResp, apiResp) { // Resource is already deleted, don't fail.
250-
return nil
251-
}
252-
return err
236+
func addError(d *diag.Diagnostics, opName, errSummary string, err error) {
237+
d.AddError(fmt.Sprintf("Error %s in %s", errSummary, opName), err.Error())
253238
}
254239

255240
// waitForChanges waits until a long-running operation is done.
256241
// It returns the latest JSON response from the API so it can be used to update the response state.
257-
func waitForChanges(ctx context.Context, wait *WaitReq, client *config.MongoDBClient, model any) ([]byte, error) {
242+
func waitForChanges(ctx context.Context, wait *WaitReq, apiOps ResourceAPIOperations, client *config.MongoDBClient, model any) ([]byte, error) {
258243
if len(wait.TargetStates) == 0 {
259244
return nil, fmt.Errorf("wait must have at least one target state, pending states: %v", wait.PendingStates)
260245
}
@@ -264,7 +249,7 @@ func waitForChanges(ctx context.Context, wait *WaitReq, client *config.MongoDBCl
264249
Timeout: wait.Timeout,
265250
MinTimeout: time.Duration(wait.MinTimeoutSeconds) * time.Second,
266251
Delay: time.Duration(wait.DelaySeconds) * time.Second,
267-
Refresh: refreshFunc(ctx, wait, client, model),
252+
Refresh: refreshFunc(ctx, wait, apiOps, client, model),
268253
}
269254
bodyResp, err := stateConf.WaitForStateContext(ctx)
270255
if err != nil || bodyResp == nil {
@@ -275,11 +260,16 @@ func waitForChanges(ctx context.Context, wait *WaitReq, client *config.MongoDBCl
275260

276261
// refreshFunc retries until a target state or error happens.
277262
// It uses a special state value of "DELETED" when the API returns 404 or empty object
278-
func refreshFunc(ctx context.Context, wait *WaitReq, client *config.MongoDBClient, model any) retry.StateRefreshFunc {
263+
func refreshFunc(ctx context.Context, wait *WaitReq, apiOps ResourceAPIOperations, client *config.MongoDBClient, model any) retry.StateRefreshFunc {
279264
return func() (result any, state string, err error) {
280265
callParams := wait.CallParams(model)
281-
bodyResp, httpResp, err := callAPIWithoutBody(ctx, client, callParams)
282-
if notFound(bodyResp, httpResp) {
266+
bodyResp, httpResp, err := apiOps.PerformRead(ctx, &HandleReadReq{
267+
APIOperations: apiOps,
268+
Client: client,
269+
State: model,
270+
CallParams: callParams,
271+
})
272+
if NotFound(bodyResp, httpResp) {
283273
// if "artificial" states continue to grow we can evaluate using a prefix to clearly separate states coming from API and those defined by refreshFunc
284274
return emptyJSON, retrystrategy.RetryStrategyDeletedState, nil
285275
}
@@ -302,12 +292,6 @@ func refreshFunc(ctx context.Context, wait *WaitReq, client *config.MongoDBClien
302292
}
303293
}
304294

305-
// notFound returns if the resource is not found (API response is 404 or response body is empty JSON).
306-
// That is because some resources like search_deployment can return an ok status code with empty json when resource doesn't exist.
307-
func notFound(bodyResp []byte, apiResp *http.Response) bool {
308-
return validate.StatusNotFound(apiResp) || isEmptyJSON(bodyResp)
309-
}
310-
311295
func isEmptyJSON(raw []byte) bool {
312296
return len(raw) == 0 || bytes.Equal(raw, emptyJSON)
313297
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package autogen
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
)
8+
9+
var _ ResourceAPIOperations = &DefaultResourceAPIOperations{}
10+
11+
// ResourceAPIOperations defines the interface for API operations in autogen resources.
12+
type ResourceAPIOperations interface {
13+
PerformRead(ctx context.Context, req *HandleReadReq) ([]byte, *http.Response, error)
14+
PerformCreate(ctx context.Context, req *HandleCreateReq) ([]byte, *http.Response, error)
15+
PerformUpdate(ctx context.Context, req *HandleUpdateReq) ([]byte, *http.Response, error)
16+
PerformDelete(ctx context.Context, req *HandleDeleteReq) error
17+
}
18+
19+
// DefaultResourceAPIOperations is used as an embedded struct for all autogen resources and provides default implementations for all API operations.
20+
type DefaultResourceAPIOperations struct{}
21+
22+
func (d *DefaultResourceAPIOperations) PerformRead(ctx context.Context, req *HandleReadReq) ([]byte, *http.Response, error) {
23+
return CallAPIWithoutBody(ctx, req.Client, req.CallParams)
24+
}
25+
26+
func (d *DefaultResourceAPIOperations) PerformCreate(ctx context.Context, req *HandleCreateReq) ([]byte, *http.Response, error) {
27+
bodyReq, err := Marshal(req.Plan, false)
28+
if err != nil {
29+
return nil, nil, fmt.Errorf("%s: %w", errBuildingAPIRequest, err)
30+
}
31+
return CallAPIWithBody(ctx, req.Client, req.CallParams, bodyReq)
32+
}
33+
34+
func (d *DefaultResourceAPIOperations) PerformUpdate(ctx context.Context, req *HandleUpdateReq) ([]byte, *http.Response, error) {
35+
bodyReq, err := Marshal(req.Plan, true)
36+
if err != nil {
37+
return nil, nil, fmt.Errorf("%s: %w", errBuildingAPIRequest, err)
38+
}
39+
return CallAPIWithBody(ctx, req.Client, req.CallParams, bodyReq)
40+
}
41+
42+
func (d *DefaultResourceAPIOperations) PerformDelete(ctx context.Context, req *HandleDeleteReq) error {
43+
var err error
44+
var bodyResp []byte
45+
var apiResp *http.Response
46+
if req.StaticRequestBody == "" {
47+
bodyResp, apiResp, err = CallAPIWithoutBody(ctx, req.Client, req.CallParams)
48+
} else {
49+
bodyResp, apiResp, err = CallAPIWithBody(ctx, req.Client, req.CallParams, []byte(req.StaticRequestBody))
50+
}
51+
if NotFound(bodyResp, apiResp) {
52+
return nil // Already deleted
53+
}
54+
return err
55+
}

0 commit comments

Comments
 (0)