Skip to content

Commit

Permalink
add support for x-terraform-field-status extension
Browse files Browse the repository at this point in the history
  • Loading branch information
dikhan committed Aug 31, 2018
1 parent bdf8a8c commit d17cda4
Show file tree
Hide file tree
Showing 2 changed files with 258 additions and 7 deletions.
48 changes: 46 additions & 2 deletions openapi/resource_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ const extTfForceNew = "x-terraform-force-new"
const extTfSensitive = "x-terraform-sensitive"
const extTfExcludeResource = "x-terraform-exclude-resource"
const extTfFieldName = "x-terraform-field-name"
const extTfFieldStatus = "x-terraform-field-status"
const extTfID = "x-terraform-id"

const idDefaultPropertyName = "id"
const statusDefaultPropertyName = "status"

type resourcesInfo map[string]resourceInfo

// resourceInfo serves as translator between swagger definitions and terraform schemas
Expand Down Expand Up @@ -209,7 +213,7 @@ func (r resourceInfo) getResourceIDURL(id string) (string, error) {

// getResourceIdentifier returns the property name that is supposed to be used as the identifier. The resource id
// is selected as follows:
// 1.If the given schema definition contains a property configured with metadata 'x-terraform-id' set to true, that property value
// 1.If the given schema definition contains a property configured with metadata 'x-terraform-id' set to true, that property
// will be used to set the state ID of the resource. Additionally, the value will be used when performing GET/PUT/DELETE requests to
// identify the resource in question.
// 2. If none of the properties of the given schema definition contain such metadata, it is expected that the payload
Expand Down Expand Up @@ -237,6 +241,38 @@ func (r resourceInfo) getResourceIdentifier() (string, error) {
return identifierProperty, nil
}

// getStatusIdentifier returns the property name that is supposed to be used as the status field. The status field
// is selected as follows:
// 1.If the given schema definition contains a property configured with metadata 'x-terraform-field-status' set to true, that property
// will be used to check the different statues for the asynchronous pooling mechanism.
// 2. If none of the properties of the given schema definition contain such metadata, it is expected that the payload
// will have a property named 'status'
// 3. If none of the above requirements is met, an error will be returned
func (r resourceInfo) getStatusIdentifier() (string, error) {
statusProperty := ""
for propertyName, property := range r.schemaDefinition.Properties {
if r.isIDProperty(propertyName) {
continue
}
if r.isStatusProperty(propertyName) {
statusProperty = propertyName
continue
}
// field with extTfFieldStatus metadata takes preference over 'status' fields as the service provider is the one acknowledging
// the fact that this field should be used as identifier of the resource
if terraformID, ok := property.Extensions.GetBool(extTfFieldStatus); ok && terraformID {
statusProperty = propertyName
break
}
}
// if the id field is missing and there isn't any properties set with extTfFieldStatus, there is not way for the resource
// to be identified and therefore an error is returned
if statusProperty == "" {
return "", fmt.Errorf("could not find any status property in the resource swagger definition. Please make sure the resource definition has either one property named 'status' or one property that contains %s metadata", extTfFieldStatus)
}
return statusProperty, nil
}

// shouldIgnoreResource checks whether the POST operation for a given resource as the 'x-terraform-exclude-resource' extension
// defined with true value. If so, the resource will not be exposed to the OpenAPI Terraform provder; otherwise it will
// be exposed and users will be able to manage such resource via terraform.
Expand All @@ -248,5 +284,13 @@ func (r resourceInfo) shouldIgnoreResource() bool {
}

func (r resourceInfo) isIDProperty(propertyName string) bool {
return terraformutils.ConvertToTerraformCompliantName(propertyName) == "id"
return r.propertyNameMatchesDefaultName(propertyName, idDefaultPropertyName)
}

func (r resourceInfo) isStatusProperty(propertyName string) bool {
return r.propertyNameMatchesDefaultName(propertyName, statusDefaultPropertyName)
}

func (r resourceInfo) propertyNameMatchesDefaultName(propertyName, expectedPropertyName string) bool {
return terraformutils.ConvertToTerraformCompliantName(propertyName) == expectedPropertyName
}
217 changes: 212 additions & 5 deletions openapi/resource_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -997,7 +997,7 @@ func TestGetResourceIdentifier(t *testing.T) {
schemaDefinition: spec.Schema{
SchemaProps: spec.SchemaProps{
Properties: map[string]spec.Schema{
"id": {
idDefaultPropertyName: {
VendorExtensible: spec.VendorExtensible{},
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Expand All @@ -1013,14 +1013,14 @@ func TestGetResourceIdentifier(t *testing.T) {
So(err, ShouldBeNil)
})
Convey("Then the value returned should be 'id'", func() {
So(id, ShouldEqual, "id")
So(id, ShouldEqual, idDefaultPropertyName)
})
})
})

Convey("Given a swagger schema definition that DOES NOT have an 'id' property but has a property configured with x-terraform-id set to TRUE", t, func() {
extensions := spec.Extensions{}
extensions.Add("x-terraform-id", true)
extensions.Add(extTfID, true)
r := resourceInfo{
schemaDefinition: spec.Schema{
SchemaProps: spec.SchemaProps{
Expand Down Expand Up @@ -1048,7 +1048,7 @@ func TestGetResourceIdentifier(t *testing.T) {

Convey("Given a swagger schema definition that HAS BOTH an 'id' property AND ALSO a property configured with x-terraform-id set to true", t, func() {
extensions := spec.Extensions{}
extensions.Add("x-terraform-id", true)
extensions.Add(extTfID, true)
r := resourceInfo{
schemaDefinition: spec.Schema{
SchemaProps: spec.SchemaProps{
Expand Down Expand Up @@ -1082,7 +1082,7 @@ func TestGetResourceIdentifier(t *testing.T) {

Convey("Given a swagger schema definition that DOES NOT have an 'id' property but has a property configured with x-terraform-id set to FALSE", t, func() {
extensions := spec.Extensions{}
extensions.Add("x-terraform-id", false)
extensions.Add(extTfID, false)
r := resourceInfo{
schemaDefinition: spec.Schema{
SchemaProps: spec.SchemaProps{
Expand Down Expand Up @@ -1135,6 +1135,213 @@ func TestGetResourceIdentifier(t *testing.T) {
})
}

func TestGetStatusIdentifier(t *testing.T) {
Convey("Given a swagger schema definition that has an status property", t, func() {
r := resourceInfo{
schemaDefinition: spec.Schema{
SchemaProps: spec.SchemaProps{
Properties: map[string]spec.Schema{
statusDefaultPropertyName: {
VendorExtensible: spec.VendorExtensible{},
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
},
},
},
}
Convey("When getStatusIdentifier method is called", func() {
status, err := r.getStatusIdentifier()
Convey("Then the error returned should be nil", func() {
So(err, ShouldBeNil)
})
Convey("Then the value returned should be 'status'", func() {
So(status, ShouldEqual, statusDefaultPropertyName)
})
})
})

Convey("Given a swagger schema definition that DOES NOT have an 'status' property but has a property configured with x-terraform-field-status set to TRUE", t, func() {
extensions := spec.Extensions{}
extensions.Add(extTfFieldStatus, true)
expectedStatusProperty := "some-other-property-holding-status"
r := resourceInfo{
schemaDefinition: spec.Schema{
SchemaProps: spec.SchemaProps{
Properties: map[string]spec.Schema{
expectedStatusProperty: {
VendorExtensible: spec.VendorExtensible{Extensions: extensions},
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
},
},
},
}
Convey("When getStatusIdentifier method is called", func() {
id, err := r.getStatusIdentifier()
Convey("Then the error returned should be nil", func() {
So(err, ShouldBeNil)
})
Convey("Then the value returned should be 'some-other-property-holding-status'", func() {
So(id, ShouldEqual, expectedStatusProperty)
})
})
})

Convey("Given a swagger schema definition that HAS BOTH an 'status' property AND ALSO a property configured with 'x-terraform-field-status' set to true", t, func() {
extensions := spec.Extensions{}
extensions.Add(extTfFieldStatus, true)
expectedStatusProperty := "some-other-property-holding-status"
r := resourceInfo{
schemaDefinition: spec.Schema{
SchemaProps: spec.SchemaProps{
Properties: map[string]spec.Schema{
"status": {
VendorExtensible: spec.VendorExtensible{},
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
expectedStatusProperty: {
VendorExtensible: spec.VendorExtensible{Extensions: extensions},
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
},
},
},
}
Convey("When getStatusIdentifier method is called", func() {
id, err := r.getStatusIdentifier()
Convey("Then the error returned should be nil", func() {
So(err, ShouldBeNil)
})
Convey("Then the value returned should be 'some-other-property-holding-status' as it takes preference over the default 'status' property", func() {
So(id, ShouldEqual, expectedStatusProperty)
})
})
})

Convey("Given a swagger schema definition that DOES NOT have an 'status' property but has a property configured with 'x-terraform-field-status' set to FALSE", t, func() {
extensions := spec.Extensions{}
extensions.Add(extTfFieldStatus, false)
expectedStatusProperty := "some-other-property-holding-status"
r := resourceInfo{
schemaDefinition: spec.Schema{
SchemaProps: spec.SchemaProps{
Properties: map[string]spec.Schema{
expectedStatusProperty: {
VendorExtensible: spec.VendorExtensible{Extensions: extensions},
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
},
},
},
}
Convey("When getStatusIdentifier method is called", func() {
_, err := r.getStatusIdentifier()
Convey("Then the error returned should not be nil", func() {
So(err, ShouldNotBeNil)
})
})
})

Convey("Given a swagger schema definition that NEITHER HAS an 'status' property NOR a property configured with 'x-terraform-field-status' set to true", t, func() {
r := resourceInfo{
schemaDefinition: spec.Schema{
SchemaProps: spec.SchemaProps{
Properties: map[string]spec.Schema{
"prop-that-is-not-status": {
VendorExtensible: spec.VendorExtensible{},
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
"prop-that-is-not-status-and-does-not-have-status-metadata-either": {
VendorExtensible: spec.VendorExtensible{},
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
},
},
},
},
},
}
Convey("When getStatusIdentifier method is called", func() {
_, err := r.getStatusIdentifier()
Convey("Then the error returned should NOT be nil", func() {
So(err, ShouldNotBeNil)
})
})
})
}

func TestIsIDProperty(t *testing.T) {
Convey("Given a swagger schema definition", t, func() {
r := resourceInfo{}
Convey("When isIDProperty method is called with property named 'id'", func() {
isIDProperty := r.isIDProperty("id")
Convey("Then the error returned should be nil", func() {
So(isIDProperty, ShouldBeTrue)
})
})
Convey("When isIDProperty method is called with property NOT named 'id'", func() {
isIDProperty := r.isIDProperty("something_not_id")
Convey("Then the error returned should be nil", func() {
So(isIDProperty, ShouldBeFalse)
})
})
})
}

func TestIsStatusProperty(t *testing.T) {
Convey("Given a swagger schema definition", t, func() {
r := resourceInfo{}
Convey("When isStatusProperty method is called with property named 'status'", func() {
isStatusProperty := r.isStatusProperty("status")
Convey("Then the error returned should be nil", func() {
So(isStatusProperty, ShouldBeTrue)
})
})
Convey("When isStatusProperty method is called with property NOT named 'status'", func() {
isStatusProperty := r.isStatusProperty("something_not_status")
Convey("Then the error returned should be nil", func() {
So(isStatusProperty, ShouldBeFalse)
})
})
})
}

func TestPropertyNameMatchesDefaultName(t *testing.T) {
Convey("Given a swagger schema definition", t, func() {
r := resourceInfo{}
Convey("When propertyNameMatchesDefaultName method is called with property named 'status' and an expected name matching the property property name", func() {
propertyNameMatchesDefaultName := r.propertyNameMatchesDefaultName("status", "status")
Convey("Then the error returned should be nil", func() {
So(propertyNameMatchesDefaultName, ShouldBeTrue)
})
})
Convey("When propertyNameMatchesDefaultName method is called with property named 'ID' which is not terraform compliant name and an expected property name", func() {
propertyNameMatchesDefaultName := r.propertyNameMatchesDefaultName("ID", "id")
Convey("Then the error returned should be nil", func() {
So(propertyNameMatchesDefaultName, ShouldBeTrue)
})
})
Convey("When propertyNameMatchesDefaultName method is called with property NOT matching the expected property name", func() {
propertyNameMatchesDefaultName := r.propertyNameMatchesDefaultName("something_not_status", "")
Convey("Then the error returned should be nil", func() {
So(propertyNameMatchesDefaultName, ShouldBeFalse)
})
})
})
}

func TestShouldIgnoreResource(t *testing.T) {
Convey("Given a terraform compliant resource that has a POST operation containing the x-terraform-exclude-resource with value true", t, func() {
r := resourceInfo{
Expand Down

0 comments on commit d17cda4

Please sign in to comment.