Utilities for use with the HashiCorp Terraform Plugin Framework
This project, much like the framework itself, is a work in progress. I will try to keep it as up to date with upstream changes as possible but, as always, community help is appreciated!
Terraform Plugin Framework | Framework Utils |
---|---|
v0.7.0-v0.9.0 | v1 |
v0.10.x-v0.15.x | v2 |
v1.x | v3 |
go get -u github.com/dcarbone/terraform-plugin-framework-utils/v3@latest
Converting between types used internally by Terraform and typical Go types can be somewhat tricky and / or tedious.
To help with this, I have created a small suite of type conversion utilities designed to make converting to and from Terraform and Go easy and obvious.
You can see the complete list of available conversions here: terraform-plugin-framework-utils/conv
The Terraform Plugin Framework has a great set of per-value type validator interfaces that you may implement as needed: validators. This does not always fit the need, however, as some validations need not be aware of type, or may benefit from being applicable to multiple types.
To facilitate this, I have created a few that I have found useful when creating my own providers, and defined a small wrapper to make creating new validators as simple as defining a function.
Fails validation if the attribute is null or unknown
{ Validators: []validator.{Type}{ validation.Required() }, }
Fails validation if the attribute's value that does not match the user-defined regular expression. This validator will attempt to convert the attribute to a string first.
{ Validators: []validator.{Type}{ validation.RegexpMatch("{{ your regex here }}") }, }
Fails validation if the attribute's value matches the user-defined regular expression. This validator will attempt to convert the attribute to a string first.
{ Validators: []validator.{Type}{ validation.RegexpNotMatch("{{ your regex here }}") }, }
Fails validation if the attribute's value's length is not within the specified bounds.
{ Validators: []validator.{Type}{ // lower limit validation.Length(5, -1), // upper limit validation.Length(-1, 10), // lower and upper limit validation.Length(5, 10), }, }
Fails validation if the attribute's value does not match the configured comparison operation.
See comparison.go for details on what comparison operations are available. You can add your own ComparisonFunc using SetComparisonFunc
{ Validators: []validator.{Type}{ // equal validation.Compare(validation.Equal, 5), // string comparisons are case sensitive by default validation.Compare(validation.Equal, "five"), // passing true as the 3rd arg executes a case-insensitive comparison with strings validation.Compare(validation.Equal, "fIve", true), // you may also equate string slices validation.Compare(validation.Equal, []string{"one", "two"}), validation.Compare(validation.Equal, []string{"oNe", "twO"}, true), // you can also assert that a list of ints is equivalent validation.Compare(validation.Equal, []int{1, 2}), // less than validation.Compare(validation.LessThan, 10), // less than or equal to validation.Compare(validation.LessThanOrEqualTo, 10), // greater than validation.Compare(validation.GreaterThan, 5), // greater than or equal to validation.Compare(validation.GreaterThanOrEqualTo, 5), // not equal validation.Compare(validation.NotEqual, 10), // string comparisons are case sensitive by default validation.Compare(validation.NotEqual, "ten"), // passing true as the 3rd arg executes a case-insensitive comparison with strings validation.Compare(validation.NotEqual, "tEn", true), // you may also compare string slices validation.Compare(validation.NotEqual, []string{"one", "two"}), validation.Compare(validation.NotEqual, []string{"oNe", "twO"}, true), // you can also assert that a list of ints is not equivalent validation.Compare(validation.NotEqual, []int{1, 2}), // one of // currently OneOf only works with strings and ints validation.Compare(validation.OneOf, []string{"one", "two"}), // you can provide true for the 3rd parameter to perform a case-insensitive comparison validation.Compare(validation.OneOf, []string{"one", "two"}, true), validation.Compare(validation.OneOf []int{1, 2}), // not one of // currently NotOneOf only works with strings and ints validation.Compare(validation.NotOneOf, []string{"one", "two"}), // you can provide true for the 3rd parameter to perform a case-insensitive comparison validation.Compare(validation.NotOneOf, []string{"one", "two"}, true), validation.Compare(validation.NotOneOf []int{1, 2}), } }
Fails validation if the attribute's value is not parseable by url.Parse
{ Validators: []validator.{Type}{ validation.IsUrl() } }
Fails validation if the attribute's value is not parseable by time.ParseDuration
{ Validators: []validator.{Type}{ validation.IsDurationString() } }
Fails validation if the environment variable name defined by the attribute's value is, itself, not valued at runtime.
{ Validators: []validator.{Type}{ validation.EnvVarValued() } }
Fails validation if the file at the path defined in the attribute's value is not readable at runtime.
{ Validators: []validator.{Type}{ validation.FileIsReadable() } }
Fails validation if the attribute is valued and the configured sibling attribute is also valued.
{ Validators: []validator.{Type}{ validation.MutuallyExclusiveSibling("{{ sibling field name }}") } }
# Example provider Terraform HCL provider "whatever" { address = "http://example.com" address_env = "EXAMPLE_ADDR" }
// Example validators list defined on the `address` attribute's schema { Validators: []validator.{Type}{ validation.MutuallyExclusiveSibling("address_env") } }
Adding the above validator to the address
attribute's Validators
list above will require that the address_env
field must be empty when address
is defined. You may also add same validator to the address_env
attribute, this time pointing at the address
field.
Requires that two sibling attributes either both be valued or not valued.
{ Validators: []validator.{Type}{ validation.MutuallyInclusiveSibling("{{ sibling field name }}") } }
# Example provider Terraform HCL provider "whatever" { ssh_key_file = file("local/filepath/ssh.key") ssh_key_password = null }
// Example validators list defined on the `ssh_key_password` attribute's schema { Validators: []validator.{Type}{ validation.MutuallyInclusiveSibling("ssh_key") } }
Adding the above validator to the ssh_key_password
attribute's Validators
list will require that, if the ssh_key_file
attribute is defined so, too, must the ssh_key_password
attribute be valued.
The Terraform Provider Framework provides an excellent suite of test tools to use when creating unit and acceptance tests for provider.
For my uses, I wanted a way to construct hcl config blocks without having to define a heredoc string for each one.
So I created a few config utilities to assist with this.
fieldMap := map[string]interface{}{ "address": "http://example.com", "token": acctest.ConfigLiteral(`file("/location/on/disk/token")`), "number_of_fish_in_the_sea": 3500000000000, } confHCL := acctest.CompileProviderConfig("my_provider", fieldMap)
provider "my_provider" { address = "http://example.com" token = file("/location/on/disk/token") number_of_fish_in_the_sea = 3500000000000 }
This can be used with the acctest.JoinConfigs
func to bring together multiple reusable configuration blocks for different tests.