|
1 | 1 | # terraform-plugin-framework-utils |
2 | | -Utilities for use with the HashiCorp Terraform Plugin Framework |
| 2 | +Utilities for use with the |
| 3 | +[HashiCorp Terraform Plugin Framework](https://github.com/hashicorp/terraform-plugin-framework) |
| 4 | + |
| 5 | +This project, much like the framework itself, is a work in progress. I will try to keep it as up to date with upstream |
| 6 | +changes as possible but, as always, community help is appreciated! |
| 7 | + |
| 8 | +# Index |
| 9 | + |
| 10 | +* [Type Conversion](#type-conversion) |
| 11 | +* [Attribute Validation](#attribute-validation) |
| 12 | +* [Test Utilities](#test-utilities) |
| 13 | + |
| 14 | +# Type Conversion |
| 15 | + |
| 16 | +Converting between types used internally by Terraform and typical Go types can be somewhat tricky and / or tedious. |
| 17 | + |
| 18 | +To help with this, I have created a small suite of type conversion utilities designed to make converting to |
| 19 | +and from Terraform and Go easy and obvious. |
| 20 | + |
| 21 | +You can see the complete list of available conversions here: |
| 22 | +[terraform-plugin-framework-utils/conv](https://github.com/dcarbone/terraform-plugin-framework-utils/blob/main/conv) |
| 23 | + |
| 24 | +# Attribute Validation |
| 25 | + |
| 26 | +The Terraform Plugin Framework has a great [validation](https://www.terraform.io/plugin/framework/validation) interface |
| 27 | +that makes it easy to create custom validation rules for the various schemas you define in your provider. |
| 28 | + |
| 29 | +Being in beta, the HashiCorp team has yet to provide a built-in set of providers. I have created a few that I have |
| 30 | +found useful when creating my own providers, and provided a small wrapper to make creating new providers as |
| 31 | +simple as [defining a function](validation/validators.go#25). |
| 32 | + |
| 33 | +## Provided Validators |
| 34 | + |
| 35 | +### Required |
| 36 | + |
| 37 | +Fails validation if the attribute is null or unknown |
| 38 | + |
| 39 | +```go |
| 40 | +{ |
| 41 | + Validators: []tfsdk.AttributeValidator{ |
| 42 | + validation.Required() |
| 43 | + }, |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +### RegexpMatch |
| 48 | + |
| 49 | +Fails validation if the attribute's value that does not match the user-defined regular expression. This validator |
| 50 | +will attempt to convert the attribute to a string first. |
| 51 | + |
| 52 | +```go |
| 53 | +{ |
| 54 | + Validators: []tfsdk.AttributeValidator{ |
| 55 | + validation.RegexpMatch("{{ your regex here }}") |
| 56 | + }, |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +### RegexpNotMatch |
| 61 | + |
| 62 | +Fails validation if the attribute's value matches the user-defined regular expression. This validator |
| 63 | +will attempt to convert the attribute to a string first. |
| 64 | + |
| 65 | +```go |
| 66 | +v := validation.RegexpNotMatch("{{ your regex here }}") |
| 67 | +``` |
| 68 | + |
| 69 | +### Length |
| 70 | + |
| 71 | +Fails validation if the attribute's value's length is not within the specified bounds. |
| 72 | + |
| 73 | +```go |
| 74 | +{ |
| 75 | + Validators: []tfsdk.AttributeValidator{ |
| 76 | + // lower limit |
| 77 | + validation.Lenght(5, -1), |
| 78 | + |
| 79 | + // upper limit |
| 80 | + validation.Length(-1, 10), |
| 81 | + |
| 82 | + // lower and upper limit |
| 83 | + validation.length(5, 10), |
| 84 | + }, |
| 85 | +} |
| 86 | +``` |
| 87 | + |
| 88 | +### Compare |
| 89 | + |
| 90 | +Fails validation if the attribute's value does not match the configured comparison operation. |
| 91 | + |
| 92 | +See [comparison.go](validation/comparison.go) for details on what comparison operations are available. You can add |
| 93 | +your own [ComparisonFunc](validation/comparison.go#44) using [SetComparisonFunc](validation/comparison.go#229) |
| 94 | + |
| 95 | +```go |
| 96 | +{ |
| 97 | + Validators: []tfsdk.AttributeValidator{ |
| 98 | + // equal |
| 99 | + validation.Compare(validation.Equal, 5), |
| 100 | + |
| 101 | + // less than |
| 102 | + validation.Compare(validation.LessThan, 10), |
| 103 | + |
| 104 | + // less than or equal to |
| 105 | + validation.Compare(validation.LessThanOrEqualTo, 10), |
| 106 | + |
| 107 | + // greater than |
| 108 | + validation.Compare(validation.GreaterThan, 5), |
| 109 | + |
| 110 | + // greater than or equal to |
| 111 | + validation.Compare(validation.GreaterThanOrEqualTo, 5), |
| 112 | + |
| 113 | + // not equal |
| 114 | + validation.Compare(validation.NotEqual, 10). |
| 115 | + } |
| 116 | +} |
| 117 | +``` |
| 118 | + |
| 119 | +### IsURL |
| 120 | + |
| 121 | +Fails validation if the attribute's value is not parseable by `url.Parse` |
| 122 | + |
| 123 | +```go |
| 124 | +{ |
| 125 | + Validators: []tfsdk.AttributeValidator{ |
| 126 | + validation.IsUrl() |
| 127 | + } |
| 128 | +} |
| 129 | +``` |
| 130 | + |
| 131 | +### IsDurationString |
| 132 | + |
| 133 | +Fails validation if the attribute's value is not parseable by `time.ParseDuration` |
| 134 | + |
| 135 | +```go |
| 136 | +{ |
| 137 | + Validators: []tfsdk.AttributeValidator{ |
| 138 | + validation.IsDurationString() |
| 139 | + } |
| 140 | +} |
| 141 | +``` |
| 142 | + |
| 143 | +### EnvVarValued |
| 144 | + |
| 145 | +Fails validation if the environment variable name defined by the attribute's value is, itself, not valued at runtime. |
| 146 | + |
| 147 | +```go |
| 148 | +{ |
| 149 | + Validators: []tfsdk.AttributeValidator{ |
| 150 | + validation.EnvVarValued() |
| 151 | + } |
| 152 | +} |
| 153 | +``` |
| 154 | + |
| 155 | +### FileIsReadable |
| 156 | + |
| 157 | +Fails validation if the file at the path defined in the attribute's value is not readable at runtime. |
| 158 | + |
| 159 | +```go |
| 160 | +{ |
| 161 | + Validators: []tfsdk.AttributeValidator{ |
| 162 | + validation.FileIsReadable() |
| 163 | + } |
| 164 | +} |
| 165 | +``` |
| 166 | + |
| 167 | +### MutuallyExclusiveSibling |
| 168 | + |
| 169 | +Fails validation if the attribute is valued and the configured sibling attribute is also valued. |
| 170 | + |
| 171 | +```go |
| 172 | +{ |
| 173 | + Validators: []tfsdk.AttributeValidator{ |
| 174 | + validation.MutuallyExclusiveSibling("{{ sibling field name }}") |
| 175 | + } |
| 176 | +} |
| 177 | +``` |
| 178 | + |
| 179 | +#### Example |
| 180 | + |
| 181 | +```hcl |
| 182 | +# Example provider Terraform HCL |
| 183 | +provider "whatever" { |
| 184 | + address = "http://example.com" |
| 185 | + address_env = "EXAMPLE_ADDR" |
| 186 | +} |
| 187 | +``` |
| 188 | + |
| 189 | +```go |
| 190 | +// Example validators list defined on the `address` attribute's schema |
| 191 | +{ |
| 192 | + Validators: []tfsdk.AttributeValidator{ |
| 193 | + validation.MutuallyExclusiveSibling("address_env") |
| 194 | + } |
| 195 | +} |
| 196 | +``` |
| 197 | + |
| 198 | +Adding the above validator to the `address` attribute's `Validators` list above will require that the `address_env` |
| 199 | +field must be empty when `address` is defined. You may also add same validator to the `address_env` attribute, this |
| 200 | +time pointing at the `address` field. |
| 201 | + |
| 202 | +### MutuallyInclusiveSibling |
| 203 | + |
| 204 | +Requires that two sibling attributes either both be valued or not valued. |
| 205 | + |
| 206 | +```go |
| 207 | +{ |
| 208 | + Validators: []tfsdk.AttributeValidator{ |
| 209 | + validation.MutuallyInclusiveSibling("{{ sibling field name }}") |
| 210 | + } |
| 211 | +} |
| 212 | +``` |
| 213 | + |
| 214 | +#### Example |
| 215 | + |
| 216 | +```hcl |
| 217 | +# Example provider Terraform HCL |
| 218 | +provider "whatever" { |
| 219 | + ssh_key_file = file("local/filepath/ssh.key") |
| 220 | + ssh_key_password = null |
| 221 | +} |
| 222 | +``` |
| 223 | + |
| 224 | +```go |
| 225 | +// Example validators list defined on the `ssh_key_password` attribute's schema |
| 226 | +{ |
| 227 | + Validators: []tfsdk.AttributeValidator{ |
| 228 | + validation.MutuallyInclusiveSibling("ssh_key") |
| 229 | + } |
| 230 | +} |
| 231 | +``` |
| 232 | + |
| 233 | +Adding the above validator to the `ssh_key_password` attribute's `Validators` list will require that, if the |
| 234 | +`ssh_key_file` attribute is defined so, too, must the `ssh_key_password` attribute be valued. |
| 235 | + |
| 236 | +# Test Utilities |
| 237 | + |
| 238 | +The Terraform Provider Framework provides an excellent suite of |
| 239 | +[test tools](https://www.terraform.io/plugin/framework/acctests) to use when creating unit and acceptance tests for |
| 240 | +provider. |
| 241 | + |
| 242 | +For my uses, I wanted a way to construct hcl config blocks without having to define a heredoc string for each one. |
| 243 | + |
| 244 | +So I created a few [config utilities](acctest/config.go) to assist with this. |
| 245 | + |
| 246 | +## Example |
| 247 | + |
| 248 | +```go |
| 249 | +fieldMap := map[string]interface{}{ |
| 250 | + "address": "http://example.com", |
| 251 | + "token": acctest.Literal("file(\"/location/on/disk/token\")"), |
| 252 | + "number_of_fish_in_the_sea": 3500000000000, |
| 253 | +} |
| 254 | +confHCL := acctest.CompileProviderConfig("my_provider", map[string]interface{}{}) |
| 255 | +``` |
| 256 | + |
| 257 | +```hcl |
| 258 | +provider "my_provider" { |
| 259 | + address = "http://example.com" |
| 260 | + token = file("/location/on/disk/token") |
| 261 | + number_of_fish_in_the_sea = 3500000000000 |
| 262 | +} |
| 263 | +``` |
| 264 | + |
| 265 | +This can be used with the `acctest.JoinConfigs` func to bring together multiple reusable configuration blocks for |
| 266 | +different tests. |
0 commit comments