terraform-provider-greenhost/internal/datalist/schema.go

151 lines
4.4 KiB
Go

package datalist
import (
"fmt"
"log"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
// This is the configuration for a "data list" resource. It represents the schema and operations
// needed to create the data list resource.
type ResourceConfig struct {
// The schema for a single instance of the resource.
RecordSchema map[string]*schema.Schema
// The name of the attribute in the resource through which to expose results.
ResultAttributeName string
// Given a record returned from the GetRecords function, flatten the record to a
// map acceptable to the Set method on schema.ResourceData.
FlattenRecord func(record, meta interface{}) (map[string]interface{}, error)
// Return all of the records on which the data list resource should operate.
// The `meta` argument is the same meta argument passed into the resource's Read
// function.
GetRecords func(meta interface{}) ([]interface{}, error)
}
// Returns a new "data list" resource given the specified configuration. This
// is a resource with `filter` and `sort` attributes that can select a subset
// of records from a list of records for a particular type of resource.
func NewResource(config *ResourceConfig) *schema.Resource {
err := validateResourceConfig(config)
if err != nil {
// Panic if the resource config is invalid since this will prevent the resource
// from operating.
log.Panicf("datalist.NewResource: invalid resource configuration: %v", err)
}
recordSchema := map[string]*schema.Schema{}
for attributeName, attributeSchema := range config.RecordSchema {
newAttributeSchema := &schema.Schema{}
*newAttributeSchema = *attributeSchema
newAttributeSchema.Computed = true
newAttributeSchema.Required = false
newAttributeSchema.Optional = false
recordSchema[attributeName] = newAttributeSchema
}
filterKeys := computeFilterKeys(recordSchema)
sortKeys := computeSortKeys(recordSchema)
return &schema.Resource{
Read: dataListResourceRead(config),
Schema: map[string]*schema.Schema{
"filter": filterSchema(filterKeys),
"sort": sortSchema(sortKeys),
config.ResultAttributeName: {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: recordSchema,
},
},
},
}
}
func dataListResourceRead(config *ResourceConfig) schema.ReadFunc {
return func(d *schema.ResourceData, meta interface{}) error {
records, err := config.GetRecords(meta)
if err != nil {
return fmt.Errorf("Unable to load records: %s", err)
}
flattenedRecords := make([]map[string]interface{}, len(records))
for i, record := range records {
flattenedRecord, err := config.FlattenRecord(record, meta)
if err != nil {
return err
}
flattenedRecords[i] = flattenedRecord
}
if v, ok := d.GetOk("filter"); ok {
filters, err := expandFilters(config.RecordSchema, v.(*schema.Set).List())
if err != nil {
return err
}
flattenedRecords = applyFilters(config.RecordSchema, flattenedRecords, filters)
}
if v, ok := d.GetOk("sort"); ok {
sorts := expandSorts(v.([]interface{}))
flattenedRecords = applySorts(config.RecordSchema, flattenedRecords, sorts)
}
d.SetId(resource.UniqueId())
if err := d.Set(config.ResultAttributeName, flattenedRecords); err != nil {
return fmt.Errorf("unable to set `%s` attribute: %s", config.ResultAttributeName, err)
}
return nil
}
}
// Compute the set of filter keys for the resource.
func computeFilterKeys(recordSchema map[string]*schema.Schema) []string {
var filterKeys []string
for key, schemaForKey := range recordSchema {
if schemaForKey.Type != schema.TypeMap {
filterKeys = append(filterKeys, key)
}
}
return filterKeys
}
// Compute the set of sort keys for the source.
func computeSortKeys(recordSchema map[string]*schema.Schema) []string {
var sortKeys []string
for key, schemaForKey := range recordSchema {
supported := false
switch schemaForKey.Type {
case schema.TypeString, schema.TypeBool, schema.TypeInt, schema.TypeFloat:
supported = true
}
if supported {
sortKeys = append(sortKeys, key)
}
}
return sortKeys
}
// Validate a ResourceConfig to ensure it conforms to this package's assumptions.
func validateResourceConfig(config *ResourceConfig) error {
// Ensure that ResultAttributeName exists.
if config.ResultAttributeName == "" {
return fmt.Errorf("ResultAttributeName must be specified")
}
return nil
}