Skip to content

Commit 08f8dc8

Browse files
committed
initial commit
1 parent 6d3b47f commit 08f8dc8

File tree

14 files changed

+2191
-1
lines changed

14 files changed

+2191
-1
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/.idea
2+
/vendor

README.md

Lines changed: 265 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,266 @@
11
# 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

Comments
 (0)