Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .changelog/18.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
```release-note:feature
Introduced `datasource/timeouts` package for use with datasource schema
```

```release-note:feature
Introduced `resource/timeouts` package for use with resource schema
```

```release-note:breaking-change
all: The `Block() tfsdk.Block` method has been removed. Use the resource `Block() schema.Block` or data source `Block() schema.Block` function instead.
```

```release-note:breaking-change
all: The `BlockAll() tfsdk.Block` method has been removed. Use the resource `BlockAll() schema.Block` or data source `Block() schema.Block` function instead.
```

```release-note:breaking-change
all: The `Attributes() tfsdk.Attribute` method has been removed. Use the resource `Attributes() schema.Attribute` or data source `Attributes() schema.Attribute` function instead.
```

```release-note:breaking-change
all: The `AttributesAll() tfsdk.Attribute` method has been removed. Use the resource `AttributesAll() schema.Attribute` or data source `Attributes() schema.Attribute` function instead.
```
122 changes: 99 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Terraform configuration.

#### Block

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

```terraform
resource "timeouts_example" "example" {
Expand All @@ -48,23 +48,61 @@ resource "timeouts_example" "example" {
}
```

You can use this module to mutate the `tfsdk.Schema` as follows:
```terraform
data "timeouts_example" "example" {
/* ... */

timeouts {
read = "30m"
}
}
```

Use this module to mutate the `schema.Schema`:

You must supply `timeouts.Opts` when calling `timeouts.Block()` on a resource. Alternatively, `timeouts.BlockAll()` will generate attributes for `create`, `read`, `update` and `delete`.

```go
import (
/* ... */
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
)

func (t *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
/* ... */

Blocks: map[string]schema.Block{
"timeouts": timeouts.Block(ctx,
timeouts.Opts{
Create: true,
},
)
},
```

The `timeouts.Block()` call does not accept options on a data source as `read` is the only option.

```go
func (t *exampleResource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
return tfsdk.Schema{
import (
/* ... */
"github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts"
)

func (t exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
/* ... */

Blocks: map[string]tfsdk.Block{
"timeouts": timeouts.Block(ctx, timeouts.Opts{
Create: true,
}),
Blocks: map[string]schema.Block{
"timeouts": timeouts.Block(ctx),
},
}
}
```

#### Attribute

If your configuration is using nested attributes to define timeouts, such as the following:
The following illustrates nested attribute syntax for defining timeouts on a resource and a data source.

```terraform
resource "timeouts_example" "example" {
Expand All @@ -76,38 +114,73 @@ resource "timeouts_example" "example" {
}
```

You can use this module to mutate the `tfsdk.Schema` as follows:
```terraform
data "timeouts_example" "example" {
/* ... */

timeouts = {
read = "30m"
}
}
```

Use this module to mutate the `schema.Schema` as follows:

You must supply `timeouts.Opts` when calling `timeouts.Attributes()` on a resource.

```go
func (t *exampleResource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
return tfsdk.Schema{
Attributes: map[string]tfsdk.Attribute{
import (
/* ... */
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
)

func (t *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
/* ... */
"timeouts": timeouts.Attributes(ctx, timeouts.Opts{
Create: true,
}),
},
```

The `timeouts.Attributes()` call does not accept options on a data source as `read` is the only option.

```go
import (
/* ... */
"github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts"
)

func (t exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
/* ... */
"timeouts": timeouts.Attributes(ctx),
},
}
}
```

### Updating Models

In functions in which the config, state or plan is being unmarshalled, for instance, the `Create` function:

```go
func (r exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data exampleResourceData
var data exampleResourceData

diags := req.Plan.Get(ctx, &data)
resp.Diagnostics.Append(diags...)
diags := req.Plan.Get(ctx, &data)
resp.Diagnostics.Append(diags...)
```

The model that is being used, `exampleResourceData` in this example, will need to be modified to include a field for
timeouts which is of `types.Object`. For example:
timeouts which is of type `timeouts.Value`. For example:

```go
type exampleResourceData struct {
/* ... */
Timeouts types.Object `tfsdk:"timeouts"`
Timeouts timeouts.Value `tfsdk:"timeouts"`
```

### Accessing Timeouts in CRUD Functions
Expand All @@ -124,14 +197,17 @@ func (r exampleResource) Create(ctx context.Context, req resource.CreateRequest,
if resp.Diagnostics.HasError() {
return
}

defaultCreateTimeout := 20 * time.Minutes

createTimeout := timeouts.Create(ctx, data.Timeouts, defaultCreateTimeout)

// Create() is passed a default timeout to use if no value
// has been supplied in the Terraform configuration.
createTimeout, err := data.Timeouts.Create(ctx, 20*time.Minute)
if err != nil {
// handle error
}

ctx, cancel := context.WithTimeout(ctx, createTimeout)
defer cancel()

/* ... */
}
```
Expand Down
63 changes: 63 additions & 0 deletions datasource/timeouts/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package timeouts

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"

"github.com/hashicorp/terraform-plugin-framework-timeouts/internal/validators"
)

const (
attributeNameRead = "read"
)

// Block returns a schema.Block containing attributes for `Read`, which is
// defined as types.StringType and optional. A validator is used to verify
// that the value assigned to `Read` can be parsed as time.Duration.
func Block(ctx context.Context) schema.Block {
return schema.SingleNestedBlock{
Attributes: attributesMap(),
CustomType: Type{
ObjectType: types.ObjectType{
AttrTypes: attrTypesMap(),
},
},
}
}

// Attributes returns a schema.SingleNestedAttribute which contains an
// attribute for `Read`, which is defined as types.StringType and optional.
// A validator is used to verify that the value assigned to an attribute
// can be parsed as time.Duration.
func Attributes(ctx context.Context) schema.Attribute {
return schema.SingleNestedAttribute{
Attributes: attributesMap(),
CustomType: Type{
ObjectType: types.ObjectType{
AttrTypes: attrTypesMap(),
},
},
Optional: true,
}
}

func attributesMap() map[string]schema.Attribute {
return map[string]schema.Attribute{
attributeNameRead: schema.StringAttribute{
Optional: true,
Validators: []validator.String{
validators.TimeDuration(),
},
},
}
}

func attrTypesMap() map[string]attr.Type {
return map[string]attr.Type{
attributeNameRead: types.StringType,
}
}
96 changes: 96 additions & 0 deletions datasource/timeouts/schema_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package timeouts_test

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"

"github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts"
"github.com/hashicorp/terraform-plugin-framework-timeouts/internal/validators"
)

func TestBlock(t *testing.T) {
t.Parallel()

type testCase struct {
expected schema.Block
}
tests := map[string]testCase{
"read": {
expected: schema.SingleNestedBlock{
CustomType: timeouts.Type{
ObjectType: types.ObjectType{
AttrTypes: map[string]attr.Type{
"read": types.StringType,
},
},
},
Attributes: map[string]schema.Attribute{
"read": schema.StringAttribute{
Optional: true,
Validators: []validator.String{
validators.TimeDuration(),
},
},
},
},
},
}

for name, test := range tests {
name, test := name, test
t.Run(name, func(t *testing.T) {
actual := timeouts.Block(context.Background())

if diff := cmp.Diff(actual, test.expected); diff != "" {
t.Errorf("unexpected block difference: %s", diff)
}
})
}
}

func TestAttributes(t *testing.T) {
t.Parallel()

type testCase struct {
expected schema.Attribute
}
tests := map[string]testCase{
"read": {
expected: schema.SingleNestedAttribute{
Attributes: map[string]schema.Attribute{
"read": schema.StringAttribute{
Optional: true,
Validators: []validator.String{
validators.TimeDuration(),
},
},
},
CustomType: timeouts.Type{
ObjectType: types.ObjectType{
AttrTypes: map[string]attr.Type{
"read": types.StringType,
},
},
},
Optional: true,
},
},
}

for name, test := range tests {
name, test := name, test
t.Run(name, func(t *testing.T) {
actual := timeouts.Attributes(context.Background())

if diff := cmp.Diff(actual, test.expected); diff != "" {
t.Errorf("unexpected block difference: %s", diff)
}
})
}
}
Loading