hashicorp / hashicorp/terraform-plugin-sdk

Proposal: helper/schema TypeEnum type

Open
#79 3 comments 64 reactions 0 assignees View on GitHub
enhancement subsystem/types terraform-plugin-framework
Dominant language
Go
Stars
485
Forks
244
Avg merge
19h 57m
Merged PRs (30d)
4

Description

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:

```go
"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:

```go
"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](https://golang.org/doc/effective_go.html#constants),
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

```go
"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

```hcl
storage_type = "HDD"
```

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

```go
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:

```go
"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:

```go
"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`](https://cloud.google.com/sql/docs/mysql/admin-api/v1beta4/instances#replicaConfiguration.mysqlReplicaConfiguration).
This isn't valid when using a `POSTGRES_9_6` instance.

We would specify it like so;

```go
"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:

```hcl
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"
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.