terraform-plugin-sdk icon indicating copy to clipboard operation
terraform-plugin-sdk copied to clipboard

Proposal: helper/schema TypeEnum type

Open rileykarson opened this issue 8 years ago • 3 comments
trafficstars

This proposal is for a new type in helper/schema to represent an enum, schema.TypeEnum, as well as highlighting two possible extensions/use cases for the type.

To other provider contributors reading this; if you have use cases or ideas that you can add, feel free to add them below as comments.


It's a common pattern in the Google provider for us to want to consume an enum value from a Terraform configuration. Using google_bigtable_instance's storage_type field as an example, we specify it like this right now:

"storage_type": {
    Type:         schema.TypeString,
    Optional:     true,
    ForceNew:     true,
    Default:      "SSD",
    ValidateFunc: validation.StringInSlice([]string{"SSD", "HDD"}, false),
},

A schema.TypeEnum would be represented as a string; that means that it would not be an object type, and could not have child elements like a TypeList or TypeSet. It would have a Values attribute which is a slice of strings.

Terraform will automatically run validation.StringInSlice with ignoreCase: false on the value, and also run any additional ValidateFunc that is set, if any, displaying the set of errors if there are any.

Terraform will validate that the Default value is present in Values when performing internal validation.

This means that we would have roughly the same behaviour, as well as validation that our Default is correct. We could specify storage_type like:

"storage_type": {
    Type:         schema.TypeEnum,
    Optional:     true,
    ForceNew:     true,
    Default:      "SSD",
    Values: []string{"SSD", "HDD"},
},

This functionality alone is what this issue is asking for; like this, schema.TypeEnum is a nice alias over schema.TypeString that attaches some more semantic significance. There are two ways we could extend it, though;


This extension would be a nice-to-have, and would provide a lot of benefit for us. It's not necessary for schema.TypeEnum as a whole, but is something I would like to see as a provider developer.

Past Google Go clients have used string to represent enums in the API. Newer Google clients under the cloud.google.com/go/ like bigtable use constants to represent enum values, and we need to perform awkward mappings from string -> enum.

Instead of Values accepting a slice of strings, it would take in a map[string]interface{} of config-side keys to arbitrary values. The responsibility of casting to the correct type would be on the client code. The default value specified in schema would be the string key. If the enum is represented as a string, like Google's older clients such as for Compute, you would map from string to string.

For context for this example, Bigtable's enums are bigtable.HDD and bigtable.SSD and have the type bigtable.StorageType; that means we would then write storage_type as

"storage_type": {
    Type:         schema.TypeEnum,
    Optional:     true,
    ForceNew:     true,
    Default:      "SSD",
    Values: map[string]interface{}{
        "SSD": bigtable.SSD,
        "HDD": bigtable.HDD
    },
},

If a user specified

storage_type = "HDD"

then we would get it in code in the correct type like:

storageType := d.Get("storage_type").(bigtable.StorageType)

This feature is just as much asking if it's possible as it is a nice-to-have; it is completely unnecessary for the rest of the proposal, but it lets us handle a gross edge case that comes up a few times in schema instead of code.

Having a constrained set of values would also let us perform validations based on what the user has specified in their config file. As a specific example, google_sql_database_instance has a field database_version that could be used as an enum. It looks like this right now:

"database_version": &schema.Schema{
    Type:     schema.TypeString,
    Optional: true,
    Default:  "MYSQL_5_6",
    ForceNew: true,
},

Even though we currently allow free-form input, and rely on the API for validation, it has a constrained set of potential values. They are: MYSQL_5_5, MYSQL_5_6, MYSQL_5_7, and POSTGRES_9_6. So, it would look like:

"database_version": &schema.Schema{
    Type:     schema.TypeEnum,
    Optional: true,
    Default:  "MYSQL_5_6",
    ForceNew: true,
    Values: []string{"MYSQL_5_5", "MYSQL_5_6", "MYSQL_5_7", "POSTGRES_9_6"},
},

Because we know exhaustively the set of potential values and schema.TypeEnum cannot have children, we should be able to use ConflictsWith with specific enum values. This would give plan-time errors with invalid configurations.

For google_sql_database_instance, we support a replica_configuration object which represents the API-side mysqlReplicaConfiguration. This isn't valid when using a POSTGRES_9_6 instance.

We would specify it like so;

"replica_configuration": &schema.Schema{
    Type:     schema.TypeList,
    Optional: true,
    MaxItems: 1,
    ConflictsWith: []string{"database_version.POSTGRES_9_6"},
    Elem: &schema.Resource{ /* omitted */ },
}

So if we specified this in our google_sql_database_instance body:

database_version = "POSTGRES_9_6"
replica_configuration {
}

We would receive an error at plan time like:

Cannot specify `replica_configuration` when `database_version` has value "POSTGRES_9_6"

rileykarson avatar Jul 28 '17 16:07 rileykarson

Really could have used this today for the Heroku data provider.

joestump avatar Jun 04 '18 21:06 joestump

Seems like this would be massively useful

jasondamour avatar Jan 22 '23 04:01 jasondamour

+1

joekendal avatar Feb 05 '23 06:02 joekendal