Skip to content

Commit 8c887f3

Browse files
authored
Merge pull request #252 from detvdl/feat/read_data_support
feat: add support for read_data
2 parents 73d19cd + 802e293 commit 8c887f3

File tree

5 files changed

+90
-12
lines changed

5 files changed

+90
-12
lines changed

docs/resources/object.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ resource "restapi_object" "Foo2" {
4040
- `id_attribute` (String) Defaults to `id_attribute` set on the provider. Allows per-resource override of `id_attribute` (see `id_attribute` provider config documentation)
4141
- `object_id` (String) Defaults to the id learned by the provider during normal operations and `id_attribute`. Allows you to set the id manually. This is used in conjunction with the `*_path` attributes.
4242
- `query_string` (String) Query string to be included in the path
43+
- `read_data` (String) Valid JSON object to pass during read requests.
4344
- `read_method` (String) Defaults to `read_method` set on the provider. Allows per-resource override of `read_method` (see `read_method` provider config documentation)
4445
- `read_path` (String) Defaults to `path/{id}`. The API path that represents where to READ (GET) objects of this type on the API server. The string `{id}` will be replaced with the terraform ID of the object.
4546
- `read_search` (Map of String) Custom search for `read_path`. This map will take `search_key`, `search_value`, `results_key` and `query_string` (see datasource config documentation)

restapi/api_client.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type apiClientOpt struct {
3030
idAttribute string
3131
createMethod string
3232
readMethod string
33+
readData string
3334
updateMethod string
3435
updateData string
3536
destroyMethod string
@@ -63,6 +64,7 @@ type APIClient struct {
6364
idAttribute string
6465
createMethod string
6566
readMethod string
67+
readData string
6668
updateMethod string
6769
updateData string
6870
destroyMethod string
@@ -76,7 +78,7 @@ type APIClient struct {
7678
oauthConfig *clientcredentials.Config
7779
}
7880

79-
//NewAPIClient makes a new api client for RESTful calls
81+
// NewAPIClient makes a new api client for RESTful calls
8082
func NewAPIClient(opt *apiClientOpt) (*APIClient, error) {
8183
if opt.debug {
8284
log.Printf("api_client.go: Constructing debug api_client\n")
@@ -162,6 +164,7 @@ func NewAPIClient(opt *apiClientOpt) (*APIClient, error) {
162164
idAttribute: opt.idAttribute,
163165
createMethod: opt.createMethod,
164166
readMethod: opt.readMethod,
167+
readData: opt.readData,
165168
updateMethod: opt.updateMethod,
166169
updateData: opt.updateData,
167170
destroyMethod: opt.destroyMethod,
@@ -210,8 +213,11 @@ func (client *APIClient) toString() string {
210213
return buffer.String()
211214
}
212215

213-
/* Helper function that handles sending/receiving and handling
214-
of HTTP data in and out. */
216+
/*
217+
Helper function that handles sending/receiving and handling
218+
219+
of HTTP data in and out.
220+
*/
215221
func (client *APIClient) sendRequest(method string, path string, data string) (string, error) {
216222
fullURI := client.uri + path
217223
var req *http.Request

restapi/api_object.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type apiObjectOpts struct {
1818
putPath string
1919
createMethod string
2020
readMethod string
21+
readData string
2122
updateMethod string
2223
updateData string
2324
destroyMethod string
@@ -52,6 +53,7 @@ type APIObject struct {
5253

5354
/* Set internally */
5455
data map[string]interface{} /* Data as managed by the user */
56+
readData map[string]interface{} /* Read data as managed by the user */
5557
updateData map[string]interface{} /* Update data as managed by the user */
5658
destroyData map[string]interface{} /* Destroy data as managed by the user */
5759
apiData map[string]interface{} /* Data as available from the API */
@@ -79,6 +81,9 @@ func NewAPIObject(iClient *APIClient, opts *apiObjectOpts) (*APIObject, error) {
7981
if opts.readMethod == "" {
8082
opts.readMethod = iClient.readMethod
8183
}
84+
if opts.readData == "" {
85+
opts.readData = iClient.readData
86+
}
8287
if opts.updateMethod == "" {
8388
opts.updateMethod = iClient.updateMethod
8489
}
@@ -124,6 +129,7 @@ func NewAPIObject(iClient *APIClient, opts *apiObjectOpts) (*APIObject, error) {
124129
id: opts.id,
125130
idAttribute: opts.idAttribute,
126131
data: make(map[string]interface{}),
132+
readData: make(map[string]interface{}),
127133
updateData: make(map[string]interface{}),
128134
destroyData: make(map[string]interface{}),
129135
apiData: make(map[string]interface{}),
@@ -157,6 +163,17 @@ func NewAPIObject(iClient *APIClient, opts *apiObjectOpts) (*APIObject, error) {
157163
}
158164
}
159165

166+
if opts.readData != "" {
167+
if opts.debug {
168+
log.Printf("api_object.go: Parsing read data: '%s'", opts.readData)
169+
}
170+
171+
err := json.Unmarshal([]byte(opts.readData), &obj.readData)
172+
if err != nil {
173+
return &obj, fmt.Errorf("api_object.go: error parsing read data provided: %v", err.Error())
174+
}
175+
}
176+
160177
if opts.updateData != "" {
161178
if opts.debug {
162179
log.Printf("api_object.go: Parsing update data: '%s'", opts.updateData)
@@ -202,6 +219,7 @@ func (obj *APIObject) toString() string {
202219
buffer.WriteString(fmt.Sprintf("debug: %t\n", obj.debug))
203220
buffer.WriteString(fmt.Sprintf("read_search: %s\n", spew.Sdump(obj.readSearch)))
204221
buffer.WriteString(fmt.Sprintf("data: %s\n", spew.Sdump(obj.data)))
222+
buffer.WriteString(fmt.Sprintf("read_data: %s\n", spew.Sdump(obj.readData)))
205223
buffer.WriteString(fmt.Sprintf("update_data: %s\n", spew.Sdump(obj.updateData)))
206224
buffer.WriteString(fmt.Sprintf("destroy_data: %s\n", spew.Sdump(obj.destroyData)))
207225
buffer.WriteString(fmt.Sprintf("api_data: %s\n", spew.Sdump(obj.apiData)))
@@ -321,7 +339,16 @@ func (obj *APIObject) readObject() error {
321339
getPath = fmt.Sprintf("%s?%s", obj.getPath, obj.queryString)
322340
}
323341

324-
resultString, err := obj.apiClient.sendRequest(obj.readMethod, strings.Replace(getPath, "{id}", obj.id, -1), "")
342+
b := []byte{}
343+
readData, _ := json.Marshal(obj.readData)
344+
if string(readData) != "" {
345+
if obj.debug {
346+
log.Printf("api_object.go: Using read data '%s'", string(readData))
347+
}
348+
b = readData
349+
}
350+
351+
resultString, err := obj.apiClient.sendRequest(obj.readMethod, strings.Replace(getPath, "{id}", obj.id, -1), string(b))
325352
if err != nil {
326353
if strings.Contains(err.Error(), "unexpected response code '404'") {
327354
log.Printf("api_object.go: 404 error while refreshing state for '%s' at path '%s'. Removing from state.", obj.id, obj.getPath)

restapi/api_object_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,24 @@ func TestAPIObject(t *testing.T) {
187187
}
188188
})
189189

190+
t.Run("read_object_with_read_data", func(t *testing.T) {
191+
if testDebug {
192+
log.Printf("api_object_test.go: Testing read_object() with read_data")
193+
}
194+
for testCase := range testingObjects {
195+
t.Run(testCase, func(t *testing.T) {
196+
if testDebug {
197+
log.Printf("api_object_test.go: Getting data for '%s' test case from server\n", testCase)
198+
}
199+
testingObjects[testCase].readData["path"] = "/" + testCase
200+
err := testingObjects[testCase].readObject()
201+
if err != nil {
202+
t.Fatalf("api_object_test.go: Failed to read data for test case '%s': %s", testCase, err)
203+
}
204+
})
205+
}
206+
})
207+
190208
/* Verify our copy_keys is happy by seeing if Thing made it into the data hash */
191209
t.Run("copy_keys", func(t *testing.T) {
192210
if testDebug {

restapi/resource_api_object.go

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,23 @@ func resourceRestAPI() *schema.Resource {
144144
ForceNew: true,
145145
Description: "Any changes to these values will result in recreating the resource instead of updating.",
146146
},
147+
"read_data": {
148+
Type: schema.TypeString,
149+
Optional: true,
150+
Description: "Valid JSON object to pass during read requests.",
151+
Sensitive: isDataSensitive,
152+
ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) {
153+
v := val.(string)
154+
if v != "" {
155+
data := make(map[string]interface{})
156+
err := json.Unmarshal([]byte(v), &data)
157+
if err != nil {
158+
errs = append(errs, fmt.Errorf("read_data attribute is invalid JSON: %v", err))
159+
}
160+
}
161+
return warns, errs
162+
},
163+
},
147164
"update_data": {
148165
Type: schema.TypeString,
149166
Optional: true,
@@ -183,10 +200,13 @@ func resourceRestAPI() *schema.Resource {
183200
}
184201
}
185202

186-
/* Since there is nothing in the ResourceData structure other
187-
than the "id" passed on the command line, we have to use an opinionated
188-
view of the API paths to figure out how to read that object
189-
from the API */
203+
/*
204+
Since there is nothing in the ResourceData structure other
205+
206+
than the "id" passed on the command line, we have to use an opinionated
207+
view of the API paths to figure out how to read that object
208+
from the API
209+
*/
190210
func resourceRestAPIImport(d *schema.ResourceData, meta interface{}) (imported []*schema.ResourceData, err error) {
191211
input := d.Id()
192212

@@ -339,10 +359,13 @@ func resourceRestAPIExists(d *schema.ResourceData, meta interface{}) (exists boo
339359
return exists, err
340360
}
341361

342-
/* Simple helper routine to build an api_object struct
343-
for the various calls terraform will use. Unfortunately,
344-
terraform cannot just reuse objects, so each CRUD operation
345-
results in a new object created */
362+
/*
363+
Simple helper routine to build an api_object struct
364+
365+
for the various calls terraform will use. Unfortunately,
366+
terraform cannot just reuse objects, so each CRUD operation
367+
results in a new object created
368+
*/
346369
func makeAPIObject(d *schema.ResourceData, meta interface{}) (*APIObject, error) {
347370
opts, err := buildAPIObjectOpts(d)
348371
if err != nil {
@@ -398,6 +421,9 @@ func buildAPIObjectOpts(d *schema.ResourceData) (*apiObjectOpts, error) {
398421
if v, ok := d.GetOk("read_method"); ok {
399422
opts.readMethod = v.(string)
400423
}
424+
if v, ok := d.GetOk("read_data"); ok {
425+
opts.readData = v.(string)
426+
}
401427
if v, ok := d.GetOk("update_method"); ok {
402428
opts.updateMethod = v.(string)
403429
}

0 commit comments

Comments
 (0)