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

fix #257 Adding option to set the obj id from a non JSON response #263

Open
wants to merge 3 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
25 changes: 25 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Change these variables as necessary.
MAIN_PACKAGE_PATH := ./
BINARY_NAME := terraform-provider-restapi
GO := go
GO_VERSION ?= 1.21

# ==================================================================================== #
# DEVELOPMENT
# ==================================================================================== #

## test: run all tests
.PHONY: test
test:
bash ./scripts/set-local-testing.rc
bash ./scripts/test.sh

## build: build the application
.PHONY: build
build:
# Include additional build steps compilation here...
$(GO) build -o=${BINARY_NAME}.o ${MAIN_PACKAGE_PATH}

.PHONY: clean
clean :
rm ${BINARY_NAME}.o
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ provider "restapi" {
- `debug` (Boolean) Enabling this will cause lots of debug information to be printed to STDOUT by the API client.
- `destroy_method` (String) Defaults to `DELETE`. The HTTP method used to DELETE objects of this type on the API server.
- `headers` (Map of String) A map of header names and values to set on all outbound requests. This is useful if you want to use a script via the 'external' provider or provide a pre-approved token or change Content-Type from `application/json`. If `username` and `password` are set and Authorization is one of the headers defined here, the BASIC auth credentials take precedence.
- `id_attribute` (String) When set, this key will be used to operate on REST objects. For example, if the ID is set to 'name', changes to the API object will be to http://foo.com/bar/VALUE_OF_NAME. This value may also be a '/'-delimeted path to the id attribute if it is multple levels deep in the data (such as `attributes/id` in the case of an object `{ "attributes": { "id": 1234 }, "config": { "name": "foo", "something": "bar"}}`
- `id_attribute` (String) When set, this key will be used to operate on REST objects. For example, if the ID is set to 'name', changes to the API object will be to http://foo.com/bar/VALUE_OF_NAME. This value may also be a '/'-delimeted path to the id attribute if it is multple levels deep in the data (such as `attributes/id` in the case of an object `{ "attributes": { "id": 1234 }, "config": { "name": "foo", "something": "bar"}}`. Use `*` if the response from your API is a non JSON object, the response (i.e `1234` or `my-string`) will be used as the obj ID.
- `insecure` (Boolean) When using https, this disables TLS verification of the host.
- `key_file` (String) When set with the cert_file parameter, the provider will load a client certificate as a file for mTLS authentication. Note that this mechanism simply delegates to golang's tls.LoadX509KeyPair which does not support passphrase protected private keys. The most robust security protections available to the key_file are simple file system permissions.
- `key_string` (String) When set with the cert_string parameter, the provider will load a client certificate as a string for mTLS authentication. Note that this mechanism simply delegates to golang's tls.LoadX509KeyPair which does not support passphrase protected private keys. The most robust security protections available to the key_file are simple file system permissions.
Expand Down
18 changes: 14 additions & 4 deletions fakeserver/fakeserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,20 @@ func (svr *Fakeserver) handleAPIObject(w http.ResponseWriter, r *http.Request) {
}
svr.objects[id] = obj

/* Coax the data we were sent back to JSON and send it to the user */
b, _ := json.Marshal(obj)
w.Write(b)
return
/* Edge case to test a response from the server as not a JSON object */
if val, ok := obj["No_json"]; ok {
if val == true {
log.Printf("fakeserver.go: Returning a non-JSON response\n")
b, _ := json.Marshal(obj["Id"])
w.Write(b)
return
}
} else {
/* Coax the data we were sent back to JSON and send it to the user */
b, _ := json.Marshal(obj)
w.Write(b)
return
}
}
/* No data was sent... must be just a retrieval */
if svr.debug {
Expand Down
31 changes: 30 additions & 1 deletion restapi/api_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,37 @@ func (obj *APIObject) createObject() error {
obj.apiClient.writeReturnsObject, obj.apiClient.createReturnsObject)
}
err = obj.updateState(resultString)

/* Checking if the response is not a normal JSON but probabbly an int or string and the id_attribute is set to '*'
Setting that response value as the ID */
var result interface{}
err = json.Unmarshal([]byte(resultString), &result)
if err != nil {
return fmt.Errorf("internal validation failed; couldnt unmarshal response from API: %s", err)
}

if _, ok := result.(map[string]interface{}); !ok {
/*Check for not json responses like plain strings or ints */
var id string
switch tp := result.(type) {
case string:
id = fmt.Sprintf("%v", result)
case float64:
id = fmt.Sprintf("%.0f", result)
default:
fmt.Printf("api_object.go: Falling default response type is '%T'\n", tp)
}

if obj.idAttribute == "*" {
if obj.debug {
log.Printf("api_object.go: Getting ID from response as id_attribute is set to '*'. Response is '%v'\n", result)
}
obj.id = id
}
}

/* Yet another failsafe. In case something terrible went wrong internally,
bail out so the user at least knows that the ID did not get set. */
bail out so the user at least knows that the ID did not get set. */
if obj.id == "" {
return fmt.Errorf("internal validation failed; object ID is not set, but *may* have been created; this should never happen")
}
Expand Down
47 changes: 47 additions & 0 deletions restapi/api_object_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,53 @@ func TestAPIObject(t *testing.T) {
}
})

/* Crete an oject with no JSON response, saving int or string as ID */
t.Run("create_object_no_json_response", func(t *testing.T) {
var err error
if testDebug {
log.Printf("api_object_test.go: Testing create_object() with no JSON response")
}

objectOpts := &apiObjectOpts{
path: "/api/objects",
debug: apiObjectDebug,
data: `{
"Test_case": "no_JSON_response",
"Id": "6",
"No_json": true,
"Name": "cat"
}`,
}
// Important to set the new idAttribute to "*" for non JSON responses
client.idAttribute = "*"

object, err := NewAPIObject(client, objectOpts)
if err != nil {
t.Fatalf("api_object_test.go: Failed to create new api_object")
}

err = object.createObject()
if err != nil {
t.Fatalf("api_object_test.go: Failed in create_object() test: %s", err)
}

if object.id != "6" {
t.Errorf("expected populated object id from creation to be %s but got %s", "6", object.id)
}

object.data["Name"] = "dog"
object.updateObject()

if object.data["Name"] != "dog" {
t.Fatalf("api_object_test.go: Failed to update 'Name' field. Expected it to be '%s' but it is '%s'\nFull obj: %+v\n", "dog", object.data["Name"], object)
}

if testDebug {
log.Printf("api_object_test.go: Object created %v", object)
}

})

if testDebug {
log.Println("api_object_test.go: Stopping HTTP server")
}
Expand Down