Skip to content

Commit

Permalink
Merge branch 'master' into feature/refresh-token-authenticator
Browse files Browse the repository at this point in the history
  • Loading branch information
dikhan committed Oct 8, 2019
2 parents f91faa8 + 6796f8d commit dc49dfd
Show file tree
Hide file tree
Showing 18 changed files with 1,137 additions and 167 deletions.
34 changes: 30 additions & 4 deletions docs/how_to.md
Expand Up @@ -256,18 +256,44 @@ with a link to the same definition (e,g: `$ref: "#/definitions/resource`). The r
as described in the [OpenAPI documentation for $ref](https://swagger.io/docs/specification/using-ref/).

- The schema object must have a property that uniquely identifies the resource instance. This can be done by either
having a computed property (readOnly) called ```id``` or by adding the ```x-terraform-id``` extension to one of the
existing properties. Read
having a computed property (readOnly) called ```id``` or by adding the [x-terraform-id](#attributeDetails) extension to one of the
existing properties.

###### Data source instance

Any resources that are deemed terraform compatible as per the previous section, will also expose a terraform data source
that internally will be mapped to the GET operation (in the previous example that would be GET ```/resource/{id}```).

This type of data source is named data source instance. The data source name will be formed from the resource name
plus the ```_instance``` string attach to it.

####### Argument Reference

````
data "openapi_resource_v1_instance" "my_resource_data_source" {
id = "resourceID"
}
````

- id: string value of the resource instance id to be fetched

####### Attributes Reference

The data source state will be filled with the corresponding properties defined in the resource model definition, in the
example above that would be ```resourceV1```. Please note that all the properties from the model will be configured as computed
in the data source schema and will be available as attributes.



##### Terraform data source compliant requirements

The OpenAPI provider is able to export data sources from paths that are data source compatible.

An endpoint (path) to be considered terraform data source compliant must meet the following criteria:

- The path must be a root level path (e,g: /v1/cdns) not an instance path (e,g: /api/v1/cdn/{id})
- The path must be a root level path (e,g: /v1/cdns) not an instance path (e,g: /api/v1/cdn/{id}). Subresource data source paths are also supported (e,g: /v1/cdns/{id}/firewalls)
- The path must contain a GET operation with a response 200 which contains a schema of type 'array'. The items schema must be of type 'object' and must specify at least one property.
- The items schema object definitnio must contain a property called ```id``` which will be used internally to uniquely identify the data source. If
- The items schema object definition must contain a property called ```id``` which will be used internally to uniquely identify the data source. If
the object schema does not have a property called ```id```, then at least one property should have the ```x-terraform-id``` extension
so the OpenAPI Terraform provider knows which property should be used to unique identifier instead.

Expand Down
4 changes: 2 additions & 2 deletions docs/installing_openapi_provider.md
Expand Up @@ -41,7 +41,7 @@ used as follows:

````
$ git clone git@github.com:dikhan/terraform-provider-openapi.git
$ cd ./scripts
$ cd terraform-provider-openapi/scripts
$ PROVIDER_NAME=goa ./install.sh --provider-name $PROVIDER_NAME
````

Expand All @@ -61,4 +61,4 @@ total 29656
drwxr-xr-x 4 dikhan staff 128 3 Jul 15:13 .
drwxr-xr-x 4 dikhan staff 128 3 Jul 13:53 ..
-rwxr-xr-x 1 dikhan staff 15182644 29 Jun 16:21 terraform-provider-goa
````
````
10 changes: 5 additions & 5 deletions go.mod
Expand Up @@ -34,12 +34,12 @@ require (
github.com/spf13/cobra v0.0.3
github.com/stretchr/testify v1.3.0
github.com/zach-klippenstein/goregen v0.0.0-20160303162051-795b5e3961ea // indirect
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 // indirect
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac // indirect
golang.org/x/net v0.0.0-20190916140828-c8589233b77d // indirect
golang.org/x/crypto v0.0.0-20191001170739-f9e2070545dc // indirect
golang.org/x/lint v0.0.0-20190930215403-16217165b5de // indirect
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0 // indirect
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 // indirect
golang.org/x/tools v0.0.0-20190917215024-905c8ffbfa41
golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed // indirect
golang.org/x/tools v0.0.0-20191002161851-3769738f410b
gopkg.in/mgo.v2 v2.0.0-20160818020120-3f83fa500528 // indirect
gopkg.in/yaml.v2 v2.2.2
)
Expand Down
19 changes: 19 additions & 0 deletions go.sum
Expand Up @@ -461,13 +461,17 @@ golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsu
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191001170739-f9e2070545dc h1:KyTYo8xkh/2WdbFLUyQwBS0Jfn3qfZ9QmuPbok2oENE=
golang.org/x/crypto v0.0.0-20191001170739-f9e2070545dc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac h1:8R1esu+8QioDxo4E4mX6bFztO+dMTM49DNAaWfO5OeY=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down Expand Up @@ -497,6 +501,12 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190916140828-c8589233b77d h1:mCMDWKhNO37A7GAhOpHPbIw1cjd0V86kX1/WA9c7FZ8=
golang.org/x/net v0.0.0-20190916140828-c8589233b77d/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190918130420-a8b05e9114ab h1:h5tBRKZ1aY/bo6GNqe/4zWC8GkaLOFQ5wPKIOQ0i2sA=
golang.org/x/net v0.0.0-20190918130420-a8b05e9114ab/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3 h1:6KET3Sqa7fkVfD63QnAM81ZeYg5n4HwApOJkufONnHA=
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0 h1:2mqDk8w/o6UmeUCu5Qiq2y7iMf6anbx+YA8d1JFoFrs=
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
Expand Down Expand Up @@ -531,6 +541,9 @@ golang.org/x/sys v0.0.0-20190730183949-1393eb018365/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190902133755-9109b7679e13 h1:tdsQdquKbTNMsSZLqnLELJGzCANp9oXhu6zFBW6ODx4=
golang.org/x/sys v0.0.0-20190902133755-9109b7679e13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190920190810-ef0ce1748380/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down Expand Up @@ -580,6 +593,12 @@ golang.org/x/tools v0.0.0-20190830223141-573d9926052a h1:XAHT1kdPpnU8Hk+FPi42KZF
golang.org/x/tools v0.0.0-20190830223141-573d9926052a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190917215024-905c8ffbfa41 h1:b81roplyyD40MvaAPpAaKtN/Aahd9L3t35zoiycwjRI=
golang.org/x/tools v0.0.0-20190917215024-905c8ffbfa41/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190920130846-1081e67f6b77 h1:m8wdt3dAJGnS+/iNksl3CpN6Y/ot01FQMblGuEzK02c=
golang.org/x/tools v0.0.0-20190920130846-1081e67f6b77/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191001184121-329c8d646ebe h1:hFr8KcN0dM0/dqbUW0KZYN+YXJeZBpBWIG9ZkMuX1vQ=
golang.org/x/tools v0.0.0-20191001184121-329c8d646ebe/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191002161851-3769738f410b h1:H3xgcuTEMnlg2n7BT6mWCOGuMchcPTaZ/uCC9GuV05M=
golang.org/x/tools v0.0.0-20191002161851-3769738f410b/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
Expand Down
1 change: 0 additions & 1 deletion openapi/data_source_factory.go
Expand Up @@ -112,7 +112,6 @@ func (d dataSourceFactory) read(data *schema.ResourceData, i interface{}) error

err = setStateID(d.openAPIResource, data, filteredResults[0])
if err != nil {
fmt.Println(err)
return err
}

Expand Down
147 changes: 140 additions & 7 deletions openapi/data_source_factory_test.go
Expand Up @@ -119,6 +119,7 @@ func TestDataSourceRead(t *testing.T) {
Properties: specSchemaDefinitionProperties{
newStringSchemaDefinitionPropertyWithDefaults("id", "", false, true, nil),
newStringSchemaDefinitionPropertyWithDefaults("label", "", false, false, nil),
newListSchemaDefinitionPropertyWithDefaults("owners", "", true, false, false, []string{"value1"}, typeString, nil),
},
},
},
Expand All @@ -131,20 +132,21 @@ func TestDataSourceRead(t *testing.T) {
expectedResult map[string]interface{}
expectedError error
}{
// TODO: add a test to cover sub-resource use case too
{
name: "fetch selected data source as per filter configuration (label=someLabel)",
filtersInput: []map[string]interface{}{
newFilter("label", []string{"someLabel"}),
},
responsePayload: []map[string]interface{}{
{
"id": "someID",
"label": "someLabel",
"id": "someID",
"label": "someLabel",
"owners": []string{"someOwner"},
},
{
"id": "someOtherID",
"label": "someOtherLabel",
"id": "someOtherID",
"label": "someOtherLabel",
"owners": []string{},
},
},
expectedError: nil,
Expand Down Expand Up @@ -206,15 +208,146 @@ func TestDataSourceRead(t *testing.T) {
if tc.expectedError == nil {
assert.Nil(t, err, tc.name)
// assert that the filtered data source contains the same values as the ones returned by the API
assert.Equal(t, client.responseListPayload[0]["label"], resourceData.Get("label"), tc.name)
assert.Equal(t, 6, len(resourceData.State().Attributes), tc.name) //this asserts that ONLY 1 element is returned when the filter is applied (2 prop of the elelemnt + 4 prop given by the filter)
assert.Equal(t, 8, len(resourceData.State().Attributes), tc.name) //this asserts that ONLY 1 element is returned when the filter is applied (2 prop of the elelemnt + 4 prop given by the filter)
assert.Equal(t, client.responseListPayload[0]["id"], resourceData.Id(), tc.name) //resourceData.Id() is being called instead of resourceData.Get("id") because id property is a special one kept by Terraform
assert.Equal(t, client.responseListPayload[0]["label"], resourceData.Get("label"), tc.name)
expectedOwners := client.responseListPayload[0]["owners"].([]string)
owners := resourceData.Get("owners").([]interface{})
assert.NotNil(t, owners, tc.name)
assert.NotNil(t, len(expectedOwners), len(owners), tc.name)
assert.Equal(t, expectedOwners[0], owners[0], tc.name)
} else {
assert.Equal(t, tc.expectedError.Error(), err.Error(), tc.name)
}
}
}

func TestDataSourceRead_Subresource(t *testing.T) {

dataSourceFactory := dataSourceFactory{
openAPIResource: &specStubResource{
path: "/v1/cdns/{id}/firewall",
schemaDefinition: &specSchemaDefinition{
Properties: specSchemaDefinitionProperties{
newStringSchemaDefinitionPropertyWithDefaults("id", "", false, true, nil),
newStringSchemaDefinitionPropertyWithDefaults("label", "", false, true, nil),
newStringSchemaDefinitionPropertyWithDefaults("cdns_v1_id", "", false, true, nil), // This simulates an openAPIResource that is subresource and the schema has already been populated with the parent property
},
},
fullParentResourceName: "cdns_v1",
parentResourceNames: []string{"cdns_v1"},
parentPropertyNames: []string{"cdns_v1_id"},
},
}

resourceSchema, err := dataSourceFactory.createTerraformDataSourceSchema()
require.NoError(t, err)

filtersInput := map[string]interface{}{
"cdns_v1_id": "parentPropertyID", // Since the path is a sub-resource, the user is expected to provide the id of the parent
dataSourceFilterPropertyName: []map[string]interface{}{
newFilter("label", []string{"my_label"}),
},
}
resourceData := schema.TestResourceDataRaw(t, resourceSchema, filtersInput)

client := &clientOpenAPIStub{
responseListPayload: []map[string]interface{}{
{
"id": "someID",
"label": "my_label",
},
},
}
err = dataSourceFactory.read(resourceData, client)
require.NoError(t, err)
assert.Equal(t, []string{"parentPropertyID"}, client.parentIDsReceived) // check that the parent id is passed as expected
assert.Equal(t, "someID", resourceData.Id())
assert.Equal(t, "my_label", resourceData.Get("label"))
}

func TestDataSourceRead_ForNestedObjects(t *testing.T) {
// Given ...
// ... a schema describing a nested object which is used to ...
nestedObjectSchemaDefinition := &specSchemaDefinition{
Properties: specSchemaDefinitionProperties{
newIntSchemaDefinitionPropertyWithDefaults("origin_port", "", true, false, 80),
newStringSchemaDefinitionPropertyWithDefaults("protocol", "", true, false, "http"),
},
}
nestedObjectDefault := map[string]interface{}{
"origin_port": nestedObjectSchemaDefinition.Properties[0].Default,
"protocol": nestedObjectSchemaDefinition.Properties[1].Default,
}
nestedObject := newObjectSchemaDefinitionPropertyWithDefaults("nested_object", "", true, false, false, nestedObjectDefault, nestedObjectSchemaDefinition)
propertyWithNestedObjectSchemaDefinition := &specSchemaDefinition{
Properties: specSchemaDefinitionProperties{
idProperty,
nestedObject,
},
}
dataValue := map[string]interface{}{
"id": propertyWithNestedObjectSchemaDefinition.Properties[0].Default,
"nested_object": propertyWithNestedObjectSchemaDefinition.Properties[1].Default,
}

objectProperty := newObjectSchemaDefinitionPropertyWithDefaults("nested-oobj", "", true, false, false, dataValue, propertyWithNestedObjectSchemaDefinition)

// ... build a data source (using a dataSourceFactory)
dataSourceFactory := dataSourceFactory{
openAPIResource: &specStubResource{
schemaDefinition: &specSchemaDefinition{
Properties: specSchemaDefinitionProperties{
idProperty,
objectProperty,
},
},
},
}
dataSourceTFSchema, err := dataSourceFactory.createTerraformDataSourceSchema()
require.NotNil(t, dataSourceTFSchema)
require.NoError(t, err)

filtersInput := map[string]interface{}{
dataSourceFilterPropertyName: []map[string]interface{}{
newFilter("id", []string{"someID"}),
},
}
resourceData := schema.TestResourceDataRaw(t, dataSourceTFSchema, filtersInput)
client := &clientOpenAPIStub{
responseListPayload: []map[string]interface{}{
{
"id": "someID",
"nested-oobj": map[string]interface{}{
"id": "uuid",
"nested_object": map[string]interface{}{
"origin_port": 80,
"protocol": "http",
},
},
},
{
"id": "someOtherID",
"nested-oobj": map[string]interface{}{
"id": "other-uuid",
"nested_object": map[string]interface{}{
"origin_port": 443,
"protocol": "https",
},
},
},
},
}
// When
err = dataSourceFactory.read(resourceData, client)
// Then
assert.Nil(t, err)
// assert that the filtered data source contains the same values as the ones returned by the API
assert.Equal(t, 10, len(resourceData.State().Attributes)) //this asserts that ONLY 1 element is returned when the filter is applied (2 prop of the elelemnt + 4 prop given by the filter)
assert.Equal(t, client.responseListPayload[0]["id"], resourceData.Id()) //resourceData.Id() is being called instead of resourceData.Get("id") because id property is a special one kept by Terraform
assert.Equal(t, client.responseListPayload[0]["label"], resourceData.Get("nested_object"))
}

func TestDataSourceRead_Fails_Because_Cannot_extract_ParentsID(t *testing.T) {
err := dataSourceFactory{}.read(nil, &clientOpenAPIStub{})
assert.EqualError(t, err, "can't get parent ids from a resourceFactory with no openAPIResource")
Expand Down

0 comments on commit dc49dfd

Please sign in to comment.