Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updating a resource with id in the payload #239

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions docs/resources/object.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,23 @@ Acting as a wrapper of cURL, this object supports POST, GET, PUT and DELETE on t
```terraform
resource "restapi_object" "Foo2" {
provider = restapi.restapi_headers
path = "/api/objects"
data = "{ \"id\": \"55555\", \"first\": \"Foo\", \"last\": \"Bar\" }"
path = "/api/objects"
data = "{ \"id\": \"55555\", \"first\": \"Foo\", \"last\": \"Bar\" }"
}
```

We provide a special keyword `{id}` that can be used when updating objects to put the object's id into the update payload, this is for situations where the id_Attribute is set by the create, and is required for the payload as opposed to the URL
```terraform
resource "restapi_object" "Foo2" {
provider = restapi.restapi_headers
path = "/api/objects"
update_path = "/api/objects"
data = "{ \"first\": \"Foo\", \"last\": \"Bar\" }"
update_data = "{ \"id\": \"{id}\", \"first\": \"Foo\", \"last\": \"Bar\" }"
}
```


<!-- schema generated by tfplugindocs -->
## Schema

Expand All @@ -43,7 +55,7 @@ resource "restapi_object" "Foo2" {
- `read_method` (String) Defaults to `read_method` set on the provider. Allows per-resource override of `read_method` (see `read_method` provider config documentation)
- `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.
- `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)
- `update_data` (String) Valid JSON object to pass during to update requests.
- `update_data` (String) Valid JSON object to pass during to update requests. The value `{id}` is available to inject the object id_attribute into the update payload
- `update_method` (String) Defaults to `update_method` set on the provider. Allows per-resource override of `update_method` (see `update_method` provider config documentation)
- `update_path` (String) Defaults to `path/{id}`. The API path that represents where to UPDATE (PUT) objects of this type on the API server. The string `{id}` will be replaced with the terraform ID of the object.

Expand Down
9 changes: 6 additions & 3 deletions restapi/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ type APIClient struct {
oauthConfig *clientcredentials.Config
}

//NewAPIClient makes a new api client for RESTful calls
// NewAPIClient makes a new api client for RESTful calls
func NewAPIClient(opt *apiClientOpt) (*APIClient, error) {
if opt.debug {
log.Printf("api_client.go: Constructing debug api_client\n")
Expand Down Expand Up @@ -210,8 +210,11 @@ func (client *APIClient) toString() string {
return buffer.String()
}

/* Helper function that handles sending/receiving and handling
of HTTP data in and out. */
/*
Helper function that handles sending/receiving and handling

of HTTP data in and out.
*/
func (client *APIClient) sendRequest(method string, path string, data string) (string, error) {
fullURI := client.uri + path
var req *http.Request
Expand Down
3 changes: 3 additions & 0 deletions restapi/api_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,9 @@ func (obj *APIObject) updateObject() error {
b, _ := json.Marshal(obj.data)

updateData, _ := json.Marshal(obj.updateData)
// Replace the "{id}" String with the id attribute of the object
updateData = bytes.ReplaceAll(updateData, []byte(`{id}`), []byte(obj.id))

if string(updateData) != "{}" {
if obj.debug {
log.Printf("api_object.go: Using update data '%s'", string(updateData))
Expand Down
15 changes: 15 additions & 0 deletions restapi/api_object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,21 @@ func TestAPIObject(t *testing.T) {
}
})

/* Update once more with {id} placeholder info in update_data */
t.Run("update_object_with_update_data", func(t *testing.T) {
if testDebug {
log.Printf("api_object_test.go: Testing update_object() with update_data")
}
testingObjects["no Colors"].updateData["update_id"] = "{id}"
testingObjects["no Colors"].updateObject()
if err != nil {
t.Fatalf("api_object_test.go: Failed in update_object() test: %s", err)
} else if testingObjects["no Colors"].apiData["update_id"] != "3" {
t.Fatalf("api_object_test.go: Failed to set an 'update_id' field of 'no Colors' object. Expected it to be '%s' but it is '%s'\nFull obj: %+v\n",
"3", testingObjects["no Colors"].apiData["no Colors"], testingObjects["no Colors"])
}
})

/* Delete one and make sure a 404 follows */
t.Run("delete_object", func(t *testing.T) {
if testDebug {
Expand Down
50 changes: 30 additions & 20 deletions restapi/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

/* After any operation that returns API data, we'll stuff
all the k,v pairs into the api_data map so users can
consume the values elsewhere if they'd like */
/*
After any operation that returns API data, we'll stuff

all the k,v pairs into the api_data map so users can
consume the values elsewhere if they'd like
*/
func setResourceState(obj *APIObject, d *schema.ResourceData) {
apiData := make(map[string]string)
for k, v := range obj.apiData {
Expand All @@ -22,8 +25,11 @@ func setResourceState(obj *APIObject, d *schema.ResourceData) {
d.Set("api_response", obj.apiResponse)
}

/*GetStringAtKey uses GetObjectAtKey to verify the resulting
object is either a JSON string or Number and returns it as a string */
/*
GetStringAtKey uses GetObjectAtKey to verify the resulting

object is either a JSON string or Number and returns it as a string
*/
func GetStringAtKey(data map[string]interface{}, path string, debug bool) (string, error) {
res, err := GetObjectAtKey(data, path, debug)
if err != nil {
Expand All @@ -41,19 +47,21 @@ func GetStringAtKey(data map[string]interface{}, path string, debug bool) (strin
}
}

/*GetObjectAtKey is a handy helper that will dig through a map and find something
at the defined key. The returned data is not type checked
Example:
Given:
{
"attrs": {
"id": 1234
},
"config": {
"foo": "abc",
"bar": "xyz"
}
}
/*
GetObjectAtKey is a handy helper that will dig through a map and find something

at the defined key. The returned data is not type checked
Example:
Given:
{
"attrs": {
"id": 1234
},
"config": {
"foo": "abc",
"bar": "xyz"
}
}

Result:
attrs/id => 1234
Expand Down Expand Up @@ -138,8 +146,10 @@ func GetKeys(hash map[string]interface{}) []string {
return keys
}

/*GetEnvOrDefault is a helper function that returns the value of the
given environment variable, if one exists, or the default value */
/*
GetEnvOrDefault is a helper function that returns the value of the
given environment variable, if one exists, or the default value
*/
func GetEnvOrDefault(k string, defaultvalue string) string {
v := os.Getenv(k)
if v == "" {
Expand Down
2 changes: 1 addition & 1 deletion restapi/datasource_api_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

func dataSourceRestAPI() *schema.Resource {
return &schema.Resource{
Read: dataSourceRestAPIRead,
Read: dataSourceRestAPIRead,
Description: "Performs a cURL get command on the specified url.",

Schema: map[string]*schema.Schema{
Expand Down
22 changes: 14 additions & 8 deletions restapi/resource_api_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,13 @@ func resourceRestAPI() *schema.Resource {
}
}

/* Since there is nothing in the ResourceData structure other
than the "id" passed on the command line, we have to use an opinionated
view of the API paths to figure out how to read that object
from the API */
/*
Since there is nothing in the ResourceData structure other

than the "id" passed on the command line, we have to use an opinionated
view of the API paths to figure out how to read that object
from the API
*/
func resourceRestAPIImport(d *schema.ResourceData, meta interface{}) (imported []*schema.ResourceData, err error) {
input := d.Id()

Expand Down Expand Up @@ -339,10 +342,13 @@ func resourceRestAPIExists(d *schema.ResourceData, meta interface{}) (exists boo
return exists, err
}

/* Simple helper routine to build an api_object struct
for the various calls terraform will use. Unfortunately,
terraform cannot just reuse objects, so each CRUD operation
results in a new object created */
/*
Simple helper routine to build an api_object struct

for the various calls terraform will use. Unfortunately,
terraform cannot just reuse objects, so each CRUD operation
results in a new object created
*/
func makeAPIObject(d *schema.ResourceData, meta interface{}) (*APIObject, error) {
opts, err := buildAPIObjectOpts(d)
if err != nil {
Expand Down
9 changes: 6 additions & 3 deletions restapi/resource_api_object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,12 @@ func TestAccRestApiObject_Basic(t *testing.T) {
svr.Shutdown()
}

/* This function generates a terraform JSON configuration from
a name, JSON data and a list of params to set by coaxing it
all to maps and then serializing to JSON */
/*
This function generates a terraform JSON configuration from

a name, JSON data and a list of params to set by coaxing it
all to maps and then serializing to JSON
*/
func generateTestResource(name string, data string, params map[string]interface{}) string {
strData, _ := json.Marshal(data)
config := []string{
Expand Down