diff --git a/README.md b/README.md index a98d9ef8..8ded3123 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,9 @@ Have a look at the [examples directory](examples) for some use cases This provider also exports the following parameters: - `id`: The ID of the object that is being managed. -- `api_data`: After data from the API server is read, this map will include k/v pairs usable in other terraform resources as readable objects. Currently the value is the golang fmt package's representation of the value (simple primitives are set as expected, but complex types like arrays and maps contain golang formatting). +- `api_data`: After data from the API server is read, this map will include k/v pairs usable in other Terraform resources as readable objects. Currently the value is the golang fmt package's representation of the value (simple primitives are set as expected, but complex types like arrays and maps contain golang formatting). +- `api_response`: Contains the raw JSON response read back from the API server. Can be parsed with [`jsondecode`](https://www.terraform.io/docs/configuration/functions/jsondecode.html) to allow access to deeply nested data. +- `create_response`: Contains the raw JSON response from the initial object creation - use when an API only returns required data during create. Can be parsed with [`jsondecode`](https://www.terraform.io/docs/configuration/functions/jsondecode.html) to allow access to deeply nested data. Note that the `*_path` elements are for very specific use cases where one might initially create an object in one location, but read/update/delete it on another path. For this reason, they allow for substitution to be done by the provider internally by injecting the `id` somewhere along the path. This is similar to terraform's substitution syntax in the form of `${variable.name}`, but must be done within the provider due to structure. The only substitution available is to replace the string `{id}` with the internal (terraform) `id` of the object as learned by the `id_attribute`. diff --git a/restapi/api_client.go b/restapi/api_client.go index 15576595..bb8d678e 100644 --- a/restapi/api_client.go +++ b/restapi/api_client.go @@ -98,6 +98,7 @@ func NewAPIClient(opt *apiClientOpt) (*api_client, error) { /* Disable TLS verification if requested */ tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: opt.insecure}, + Proxy: http.ProxyFromEnvironment, } var cookieJar http.CookieJar diff --git a/restapi/api_object.go b/restapi/api_object.go index 735ea818..a3e2c53c 100644 --- a/restapi/api_object.go +++ b/restapi/api_object.go @@ -5,10 +5,11 @@ import ( "encoding/json" "errors" "fmt" - "github.com/davecgh/go-spew/spew" "log" "reflect" "strings" + + "github.com/davecgh/go-spew/spew" ) type apiObjectOpts struct { @@ -36,8 +37,9 @@ type api_object struct { id_attribute string /* Set internally */ - data map[string]interface{} /* Data as managed by the user */ - api_data map[string]interface{} /* Data as available from the API */ + data map[string]interface{} /* Data as managed by the user */ + api_data map[string]interface{} /* Data as available from the API */ + api_response string } // Make an api_object to manage a RESTful object in an API @@ -152,6 +154,9 @@ func (obj *api_object) update_state(state string) error { return err } + /* Store response body for parsing via jsondecode() */ + obj.api_response = state + /* A usable ID was not passed (in constructor or here), so we have to guess what it is from the data structure */ if obj.id == "" { diff --git a/restapi/common.go b/restapi/common.go index b3da5325..47b07a16 100755 --- a/restapi/common.go +++ b/restapi/common.go @@ -2,11 +2,12 @@ package restapi import ( "fmt" - "github.com/hashicorp/terraform/helper/schema" "log" "os" "strconv" "strings" + + "github.com/hashicorp/terraform/helper/schema" ) /* After any operation that returns API data, we'll stuff @@ -18,6 +19,7 @@ func set_resource_state(obj *api_object, d *schema.ResourceData) { api_data[k] = fmt.Sprintf("%v", v) } d.Set("api_data", api_data) + d.Set("api_response", obj.api_response) } /* Using GetObjectAtKey, this function verifies the resulting @@ -33,7 +35,7 @@ func GetStringAtKey(data map[string]interface{}, path string, debug bool) (strin if t == "string" { return res.(string), nil } else if t == "float64" { - return strconv.FormatFloat(res.(float64), 'f', -1, 64), nil + return strconv.FormatFloat(res.(float64), 'f', -1, 64), nil } else { return "", fmt.Errorf("Object at path '%s' is not a JSON string or number (float64). The go fmt package says it is '%T'", path, res) } diff --git a/restapi/import_api_object_test.go b/restapi/import_api_object_test.go index 913e8174..2aba47e2 100644 --- a/restapi/import_api_object_test.go +++ b/restapi/import_api_object_test.go @@ -1,10 +1,11 @@ package restapi import ( - "github.com/Mastercard/terraform-provider-restapi/fakeserver" - "github.com/hashicorp/terraform/helper/resource" "os" "testing" + + "github.com/Mastercard/terraform-provider-restapi/fakeserver" + "github.com/hashicorp/terraform/helper/resource" ) func TestAccRestApiObject_importBasic(t *testing.T) { @@ -25,7 +26,7 @@ func TestAccRestApiObject_importBasic(t *testing.T) { copy_keys: make([]string, 0), write_returns_object: false, create_returns_object: false, - debug: debug, + debug: debug, } client, err := NewAPIClient(opt) if err != nil { @@ -45,12 +46,13 @@ func TestAccRestApiObject_importBasic(t *testing.T) { ), }, { - ResourceName: "restapi_object.Foo", - ImportState: true, - ImportStateId: "1234", - ImportStateIdPrefix: "/api/objects/", - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"debug", "data"}, + ResourceName: "restapi_object.Foo", + ImportState: true, + ImportStateId: "1234", + ImportStateIdPrefix: "/api/objects/", + ImportStateVerify: true, + /* create_response isn't populated during import (we don't know the API response from creation) */ + ImportStateVerifyIgnore: []string{"debug", "data", "create_response"}, }, }, }) diff --git a/restapi/resource_api_object.go b/restapi/resource_api_object.go index 30877ca4..906ca2aa 100644 --- a/restapi/resource_api_object.go +++ b/restapi/resource_api_object.go @@ -2,10 +2,11 @@ package restapi import ( "fmt" - "github.com/hashicorp/terraform/helper/schema" "log" "strconv" "strings" + + "github.com/hashicorp/terraform/helper/schema" ) func resourceRestApi() *schema.Resource { @@ -76,6 +77,16 @@ func resourceRestApi() *schema.Resource { Description: "After data from the API server is read, this map will include k/v pairs usable in other terraform resources as readable objects. Currently the value is the golang fmt package's representation of the value (simple primitives are set as expected, but complex types like arrays and maps contain golang formatting).", Computed: true, }, + "api_response": &schema.Schema{ + Type: schema.TypeString, + Description: "The raw body of the HTTP response from the last read of the object.", + Computed: true, + }, + "create_response": &schema.Schema{ + Type: schema.TypeString, + Description: "The raw body of the HTTP response returned when creating the object.", + Computed: true, + }, "force_new": &schema.Schema{ Type: schema.TypeList, Elem: &schema.Schema{Type: schema.TypeString}, @@ -139,6 +150,8 @@ func resourceRestApiCreate(d *schema.ResourceData, meta interface{}) error { /* Setting terraform ID tells terraform the object was created or it exists */ d.SetId(obj.id) set_resource_state(obj, d) + /* Only set during create for APIs that don't return sensitive data on subsequent retrieval */ + d.Set("create_response", obj.api_response) } return err } diff --git a/restapi/resource_api_object_test.go b/restapi/resource_api_object_test.go index e26f91b5..67a54fb5 100644 --- a/restapi/resource_api_object_test.go +++ b/restapi/resource_api_object_test.go @@ -14,10 +14,11 @@ package restapi import ( "encoding/json" "fmt" - "github.com/Mastercard/terraform-provider-restapi/fakeserver" - "github.com/hashicorp/terraform/helper/resource" "os" "testing" + + "github.com/Mastercard/terraform-provider-restapi/fakeserver" + "github.com/hashicorp/terraform/helper/resource" ) // example.Widget represents a concrete Go type that represents an API resource @@ -39,7 +40,7 @@ func TestAccRestApiObject_Basic(t *testing.T) { copy_keys: make([]string, 0), write_returns_object: false, create_returns_object: false, - debug: debug, + debug: debug, } client, err := NewAPIClient(opt) if err != nil { @@ -61,6 +62,24 @@ func TestAccRestApiObject_Basic(t *testing.T) { resource.TestCheckResourceAttr("restapi_object.Foo", "id", "1234"), resource.TestCheckResourceAttr("restapi_object.Foo", "api_data.first", "Foo"), resource.TestCheckResourceAttr("restapi_object.Foo", "api_data.last", "Bar"), + resource.TestCheckResourceAttr("restapi_object.Foo", "api_response", "{\"first\":\"Foo\",\"id\":\"1234\",\"last\":\"Bar\"}"), + resource.TestCheckResourceAttr("restapi_object.Foo", "create_response", "{\"first\":\"Foo\",\"id\":\"1234\",\"last\":\"Bar\"}"), + ), + }, + /* Try updating the object and check create_response is unmodified */ + { + Config: generate_test_resource( + "Foo", + `{ "id": "1234", "first": "Updated", "last": "Value" }`, + make(map[string]interface{}), + ), + Check: resource.ComposeTestCheckFunc( + testAccCheckRestapiObjectExists("restapi_object.Foo", "1234", client), + resource.TestCheckResourceAttr("restapi_object.Foo", "id", "1234"), + resource.TestCheckResourceAttr("restapi_object.Foo", "api_data.first", "Updated"), + resource.TestCheckResourceAttr("restapi_object.Foo", "api_data.last", "Value"), + resource.TestCheckResourceAttr("restapi_object.Foo", "api_response", "{\"first\":\"Updated\",\"id\":\"1234\",\"last\":\"Value\"}"), + resource.TestCheckResourceAttr("restapi_object.Foo", "create_response", "{\"first\":\"Foo\",\"id\":\"1234\",\"last\":\"Bar\"}"), ), }, /* Make a complex object with id_attribute as a child of another key diff --git a/scripts/do_release.sh b/scripts/do_release.sh index f788663c..200140b0 100755 --- a/scripts/do_release.sh +++ b/scripts/do_release.sh @@ -4,7 +4,8 @@ OSs=("darwin" "linux" "windows") ARCHs=("386" "amd64") export REST_API_URI="http://127.0.0.1:8082" -export GOPATH="$HOME/go" +[[ -z "${GOPATH}" ]] && export GOPATH=$HOME/go +export CGO_ENABLED=0 #Get into the right directory cd $(dirname $0) diff --git a/scripts/test.sh b/scripts/test.sh index dc6be34c..f5955f3a 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -4,7 +4,8 @@ cd $(dirname $0) export GOOS="" export GOARCH="" -export GOPATH="$HOME/go" + +[[ -z "${GOPATH}" ]] && export GOPATH=$HOME/go echo "Synchronizing dependencies..." cd ../