Skip to content

Commit 638afa4

Browse files
authored
Merge pull request #18 from hashicorp/bendbennett/issues-17
Implement Using Custom Types for Timeouts
2 parents 2236eb4 + 6058ceb commit 638afa4

File tree

18 files changed

+1588
-883
lines changed

18 files changed

+1588
-883
lines changed

.changelog/18.txt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
```release-note:feature
2+
Introduced `datasource/timeouts` package for use with datasource schema
3+
```
4+
5+
```release-note:feature
6+
Introduced `resource/timeouts` package for use with resource schema
7+
```
8+
9+
```release-note:breaking-change
10+
all: The `Block() tfsdk.Block` method has been removed. Use the resource `Block() schema.Block` or data source `Block() schema.Block` function instead.
11+
```
12+
13+
```release-note:breaking-change
14+
all: The `BlockAll() tfsdk.Block` method has been removed. Use the resource `BlockAll() schema.Block` or data source `Block() schema.Block` function instead.
15+
```
16+
17+
```release-note:breaking-change
18+
all: The `Attributes() tfsdk.Attribute` method has been removed. Use the resource `Attributes() schema.Attribute` or data source `Attributes() schema.Attribute` function instead.
19+
```
20+
21+
```release-note:breaking-change
22+
all: The `AttributesAll() tfsdk.Attribute` method has been removed. Use the resource `AttributesAll() schema.Attribute` or data source `Attributes() schema.Attribute` function instead.
23+
```

README.md

Lines changed: 99 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Terraform configuration.
3636

3737
#### Block
3838

39-
If your configuration is using a nested block to define timeouts, such as the following:
39+
The following illustrates nested block syntax for defining timeouts on a resource and a data source.
4040

4141
```terraform
4242
resource "timeouts_example" "example" {
@@ -48,23 +48,61 @@ resource "timeouts_example" "example" {
4848
}
4949
```
5050

51-
You can use this module to mutate the `tfsdk.Schema` as follows:
51+
```terraform
52+
data "timeouts_example" "example" {
53+
/* ... */
54+
55+
timeouts {
56+
read = "30m"
57+
}
58+
}
59+
```
60+
61+
Use this module to mutate the `schema.Schema`:
62+
63+
You must supply `timeouts.Opts` when calling `timeouts.Block()` on a resource. Alternatively, `timeouts.BlockAll()` will generate attributes for `create`, `read`, `update` and `delete`.
64+
65+
```go
66+
import (
67+
/* ... */
68+
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
69+
)
70+
71+
func (t *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
72+
resp.Schema = schema.Schema{
73+
/* ... */
74+
75+
Blocks: map[string]schema.Block{
76+
"timeouts": timeouts.Block(ctx,
77+
timeouts.Opts{
78+
Create: true,
79+
},
80+
)
81+
},
82+
```
83+
84+
The `timeouts.Block()` call does not accept options on a data source as `read` is the only option.
5285
5386
```go
54-
func (t *exampleResource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
55-
return tfsdk.Schema{
87+
import (
88+
/* ... */
89+
"github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts"
90+
)
91+
92+
func (t exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
93+
resp.Schema = schema.Schema{
5694
/* ... */
5795

58-
Blocks: map[string]tfsdk.Block{
59-
"timeouts": timeouts.Block(ctx, timeouts.Opts{
60-
Create: true,
61-
}),
96+
Blocks: map[string]schema.Block{
97+
"timeouts": timeouts.Block(ctx),
6298
},
99+
}
100+
}
63101
```
64102
65103
#### Attribute
66104
67-
If your configuration is using nested attributes to define timeouts, such as the following:
105+
The following illustrates nested attribute syntax for defining timeouts on a resource and a data source.
68106
69107
```terraform
70108
resource "timeouts_example" "example" {
@@ -76,38 +114,73 @@ resource "timeouts_example" "example" {
76114
}
77115
```
78116
79-
You can use this module to mutate the `tfsdk.Schema` as follows:
117+
```terraform
118+
data "timeouts_example" "example" {
119+
/* ... */
120+
121+
timeouts = {
122+
read = "30m"
123+
}
124+
}
125+
```
126+
127+
Use this module to mutate the `schema.Schema` as follows:
128+
129+
You must supply `timeouts.Opts` when calling `timeouts.Attributes()` on a resource.
80130
81131
```go
82-
func (t *exampleResource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
83-
return tfsdk.Schema{
84-
Attributes: map[string]tfsdk.Attribute{
132+
import (
133+
/* ... */
134+
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
135+
)
136+
137+
func (t *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
138+
resp.Schema = schema.Schema{
139+
Attributes: map[string]schema.Attribute{
85140
/* ... */
86141
"timeouts": timeouts.Attributes(ctx, timeouts.Opts{
87142
Create: true,
88143
}),
89144
},
90145
```
91146
147+
The `timeouts.Attributes()` call does not accept options on a data source as `read` is the only option.
148+
149+
```go
150+
import (
151+
/* ... */
152+
"github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts"
153+
)
154+
155+
func (t exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
156+
resp.Schema = schema.Schema{
157+
Attributes: map[string]schema.Attribute{
158+
/* ... */
159+
"timeouts": timeouts.Attributes(ctx),
160+
},
161+
}
162+
}
163+
```
164+
92165
### Updating Models
93166
94167
In functions in which the config, state or plan is being unmarshalled, for instance, the `Create` function:
95168
96169
```go
97170
func (r exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
98-
var data exampleResourceData
171+
var data exampleResourceData
99172

100-
diags := req.Plan.Get(ctx, &data)
101-
resp.Diagnostics.Append(diags...)
173+
diags := req.Plan.Get(ctx, &data)
174+
resp.Diagnostics.Append(diags...)
102175
```
103176
104177
The model that is being used, `exampleResourceData` in this example, will need to be modified to include a field for
105-
timeouts which is of `types.Object`. For example:
178+
timeouts which is of type `timeouts.Value`. For example:
106179
107180
```go
108181
type exampleResourceData struct {
109182
/* ... */
110-
Timeouts types.Object `tfsdk:"timeouts"`
183+
Timeouts timeouts.Value `tfsdk:"timeouts"`
111184
```
112185
113186
### Accessing Timeouts in CRUD Functions
@@ -124,14 +197,17 @@ func (r exampleResource) Create(ctx context.Context, req resource.CreateRequest,
124197
if resp.Diagnostics.HasError() {
125198
return
126199
}
127-
128-
defaultCreateTimeout := 20 * time.Minutes
129200

130-
createTimeout := timeouts.Create(ctx, data.Timeouts, defaultCreateTimeout)
131-
201+
// Create() is passed a default timeout to use if no value
202+
// has been supplied in the Terraform configuration.
203+
createTimeout, err := data.Timeouts.Create(ctx, 20*time.Minute)
204+
if err != nil {
205+
// handle error
206+
}
207+
132208
ctx, cancel := context.WithTimeout(ctx, createTimeout)
133209
defer cancel()
134-
210+
135211
/* ... */
136212
}
137213
```

datasource/timeouts/schema.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package timeouts
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-framework/attr"
7+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
8+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
9+
"github.com/hashicorp/terraform-plugin-framework/types"
10+
11+
"github.com/hashicorp/terraform-plugin-framework-timeouts/internal/validators"
12+
)
13+
14+
const (
15+
attributeNameRead = "read"
16+
)
17+
18+
// Block returns a schema.Block containing attributes for `Read`, which is
19+
// defined as types.StringType and optional. A validator is used to verify
20+
// that the value assigned to `Read` can be parsed as time.Duration.
21+
func Block(ctx context.Context) schema.Block {
22+
return schema.SingleNestedBlock{
23+
Attributes: attributesMap(),
24+
CustomType: Type{
25+
ObjectType: types.ObjectType{
26+
AttrTypes: attrTypesMap(),
27+
},
28+
},
29+
}
30+
}
31+
32+
// Attributes returns a schema.SingleNestedAttribute which contains an
33+
// attribute for `Read`, which is defined as types.StringType and optional.
34+
// A validator is used to verify that the value assigned to an attribute
35+
// can be parsed as time.Duration.
36+
func Attributes(ctx context.Context) schema.Attribute {
37+
return schema.SingleNestedAttribute{
38+
Attributes: attributesMap(),
39+
CustomType: Type{
40+
ObjectType: types.ObjectType{
41+
AttrTypes: attrTypesMap(),
42+
},
43+
},
44+
Optional: true,
45+
}
46+
}
47+
48+
func attributesMap() map[string]schema.Attribute {
49+
return map[string]schema.Attribute{
50+
attributeNameRead: schema.StringAttribute{
51+
Optional: true,
52+
Validators: []validator.String{
53+
validators.TimeDuration(),
54+
},
55+
},
56+
}
57+
}
58+
59+
func attrTypesMap() map[string]attr.Type {
60+
return map[string]attr.Type{
61+
attributeNameRead: types.StringType,
62+
}
63+
}

datasource/timeouts/schema_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package timeouts_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/google/go-cmp/cmp"
8+
"github.com/hashicorp/terraform-plugin-framework/attr"
9+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
10+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
11+
"github.com/hashicorp/terraform-plugin-framework/types"
12+
13+
"github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts"
14+
"github.com/hashicorp/terraform-plugin-framework-timeouts/internal/validators"
15+
)
16+
17+
func TestBlock(t *testing.T) {
18+
t.Parallel()
19+
20+
type testCase struct {
21+
expected schema.Block
22+
}
23+
tests := map[string]testCase{
24+
"read": {
25+
expected: schema.SingleNestedBlock{
26+
CustomType: timeouts.Type{
27+
ObjectType: types.ObjectType{
28+
AttrTypes: map[string]attr.Type{
29+
"read": types.StringType,
30+
},
31+
},
32+
},
33+
Attributes: map[string]schema.Attribute{
34+
"read": schema.StringAttribute{
35+
Optional: true,
36+
Validators: []validator.String{
37+
validators.TimeDuration(),
38+
},
39+
},
40+
},
41+
},
42+
},
43+
}
44+
45+
for name, test := range tests {
46+
name, test := name, test
47+
t.Run(name, func(t *testing.T) {
48+
actual := timeouts.Block(context.Background())
49+
50+
if diff := cmp.Diff(actual, test.expected); diff != "" {
51+
t.Errorf("unexpected block difference: %s", diff)
52+
}
53+
})
54+
}
55+
}
56+
57+
func TestAttributes(t *testing.T) {
58+
t.Parallel()
59+
60+
type testCase struct {
61+
expected schema.Attribute
62+
}
63+
tests := map[string]testCase{
64+
"read": {
65+
expected: schema.SingleNestedAttribute{
66+
Attributes: map[string]schema.Attribute{
67+
"read": schema.StringAttribute{
68+
Optional: true,
69+
Validators: []validator.String{
70+
validators.TimeDuration(),
71+
},
72+
},
73+
},
74+
CustomType: timeouts.Type{
75+
ObjectType: types.ObjectType{
76+
AttrTypes: map[string]attr.Type{
77+
"read": types.StringType,
78+
},
79+
},
80+
},
81+
Optional: true,
82+
},
83+
},
84+
}
85+
86+
for name, test := range tests {
87+
name, test := name, test
88+
t.Run(name, func(t *testing.T) {
89+
actual := timeouts.Attributes(context.Background())
90+
91+
if diff := cmp.Diff(actual, test.expected); diff != "" {
92+
t.Errorf("unexpected block difference: %s", diff)
93+
}
94+
})
95+
}
96+
}

0 commit comments

Comments
 (0)