Skip to content

Commit 5c52e10

Browse files
feat(dns) add timeouts to dns resources and datasources (#1345)
* feat(dns) add timeouts to dns resources and datasources STACKITTPR-542 * fix(docs) generate docs * feat(docs) extend contribution example with timeouts * fix(lint) rename unused param * fix(contrib guide) fix validate import
1 parent 1933b13 commit 5c52e10

File tree

14 files changed

+321
-35
lines changed

14 files changed

+321
-35
lines changed

.github/docs/contribution-guide/resource.go

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"strings"
77

8+
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
89
"github.com/hashicorp/terraform-plugin-framework/resource"
910
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
1011
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
@@ -16,6 +17,7 @@ import (
1617
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
1718
fooUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/foo/utils"
1819
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
20+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
1921

2022
"github.com/stackitcloud/stackit-sdk-go/services/foo" // Import service "foo" from the STACKIT SDK for Go
2123
"github.com/stackitcloud/stackit-sdk-go/services/foo/wait" // Import service "foo" waiters from the STACKIT SDK for Go (in case the service API has asynchronous endpoints)
@@ -32,13 +34,14 @@ var (
3234

3335
// Model is the internal model of the terraform resource
3436
type Model struct {
35-
Id types.String `tfsdk:"id"` // needed by TF
36-
ProjectId types.String `tfsdk:"project_id"`
37-
BarId types.String `tfsdk:"bar_id"`
38-
Region types.String `tfsdk:"region"`
39-
MyRequiredField types.String `tfsdk:"my_required_field"`
40-
MyOptionalField types.String `tfsdk:"my_optional_field"`
41-
MyReadOnlyField types.String `tfsdk:"my_read_only_field"`
37+
Id types.String `tfsdk:"id"` // needed by TF
38+
ProjectId types.String `tfsdk:"project_id"`
39+
BarId types.String `tfsdk:"bar_id"`
40+
Region types.String `tfsdk:"region"`
41+
MyRequiredField types.String `tfsdk:"my_required_field"`
42+
MyOptionalField types.String `tfsdk:"my_optional_field"`
43+
MyReadOnlyField types.String `tfsdk:"my_read_only_field"`
44+
Timeouts timeouts.Value `tfsdk:"timeouts"`
4245
}
4346

4447
// NewBarResource is a helper function to simplify the provider implementation.
@@ -104,7 +107,7 @@ func (r *barResource) Configure(ctx context.Context, req resource.ConfigureReque
104107
}
105108

106109
// Schema defines the schema for the resource.
107-
func (r *barResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
110+
func (r *barResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
108111
descriptions := map[string]string{
109112
"main": "Foo bar resource schema.",
110113
"id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`bar_id`\".",
@@ -173,6 +176,7 @@ func (r *barResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *
173176
Description: descriptions["my_read_only_field"],
174177
Computed: true,
175178
},
179+
"timeouts": timeouts.AttributesAll(ctx),
176180
},
177181
}
178182
}
@@ -185,6 +189,15 @@ func (r *barResource) Create(ctx context.Context, req resource.CreateRequest, re
185189
return
186190
}
187191

192+
waiterTimeout := wait.CreateBarWaitHandler(ctx, r.client, projectId, region, resp.BarId).GetTimeout()
193+
createTimeout, diags := model.Timeouts.Create(ctx, waiterTimeout+core.DefaultTimeoutMargin)
194+
resp.Diagnostics.Append(diags...)
195+
if resp.Diagnostics.HasError() {
196+
return
197+
}
198+
ctx, cancel := context.WithTimeout(ctx, createTimeout)
199+
defer cancel()
200+
188201
ctx = core.InitProviderContext(ctx)
189202

190203
projectId := model.ProjectId.ValueString()
@@ -250,6 +263,14 @@ func (r *barResource) Read(ctx context.Context, req resource.ReadRequest, resp *
250263
return
251264
}
252265

266+
readTimeout, diags := model.Timeouts.Create(ctx, core.DefaultOperationTimeout)
267+
resp.Diagnostics.Append(diags...)
268+
if resp.Diagnostics.HasError() {
269+
return
270+
}
271+
ctx, cancel := context.WithTimeout(ctx, readTimeout)
272+
defer cancel()
273+
253274
ctx = core.InitProviderContext(ctx)
254275

255276
projectId := model.ProjectId.ValueString()
@@ -296,6 +317,15 @@ func (r *barResource) Delete(ctx context.Context, req resource.DeleteRequest, re
296317
return
297318
}
298319

320+
waiterTimeout := wait.DeleteBarWaitHandler(ctx, r.client, projectId, region, barId).GetTimeout()
321+
deleteTimeout, diags := model.Timeouts.Create(ctx, waiterTimeout+core.DefaultTimeoutMargin)
322+
resp.Diagnostics.Append(diags...)
323+
if resp.Diagnostics.HasError() {
324+
return
325+
}
326+
ctx, cancel := context.WithTimeout(ctx, deleteTimeout)
327+
defer cancel()
328+
299329
ctx = core.InitProviderContext(ctx)
300330

301331
projectId := model.ProjectId.ValueString()

docs/data-sources/dns_record_set.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ data "stackit_dns_record_set" "example" {
2929
- `record_set_id` (String) The rr set id.
3030
- `zone_id` (String) The zone ID to which is dns record set is associated.
3131

32+
### Optional
33+
34+
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
35+
3236
### Read-Only
3337

3438
- `active` (Boolean) Specifies if the record set is active or not.
@@ -41,3 +45,10 @@ data "stackit_dns_record_set" "example" {
4145
- `state` (String) Record set state.
4246
- `ttl` (Number) Time to live. E.g. 3600
4347
- `type` (String) The record set type. E.g. `A` or `CNAME`
48+
49+
<a id="nestedatt--timeouts"></a>
50+
### Nested Schema for `timeouts`
51+
52+
Optional:
53+
54+
- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).

docs/data-sources/dns_zone.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ data "stackit_dns_zone" "example" {
2929
### Optional
3030

3131
- `dns_name` (String) The zone name. E.g. `example.com` (must not end with a trailing dot).
32+
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
3233
- `zone_id` (String) The zone ID.
3334

3435
### Read-Only
@@ -52,3 +53,10 @@ data "stackit_dns_zone" "example" {
5253
- `state` (String) Zone state.
5354
- `type` (String) Zone type.
5455
- `visibility` (String) Visibility of the zone.
56+
57+
<a id="nestedatt--timeouts"></a>
58+
### Nested Schema for `timeouts`
59+
60+
Optional:
61+
62+
- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).

docs/resources/dns_record_set.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444

4545
- `active` (Boolean) Specifies if the record set is active or not. Defaults to `true`
4646
- `comment` (String) Comment.
47+
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
4748
- `ttl` (Number) Time to live. E.g. 3600
4849

4950
### Read-Only
@@ -53,3 +54,13 @@ import {
5354
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`zone_id`,`record_set_id`".
5455
- `record_set_id` (String) The rr set id.
5556
- `state` (String) Record set state.
57+
58+
<a id="nestedatt--timeouts"></a>
59+
### Nested Schema for `timeouts`
60+
61+
Optional:
62+
63+
- `create` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).
64+
- `delete` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Setting a timeout for a Delete operation is only applicable if changes are saved into state before the destroy operation occurs.
65+
- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Read operations occur during any refresh or planning operation when refresh is enabled.
66+
- `update` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).

docs/resources/dns_zone.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import {
5353
- `primaries` (List of String) Primary name server for secondary zone. E.g. ["1.2.3.4"]
5454
- `refresh_time` (Number) Refresh time. E.g. 3600
5555
- `retry_time` (Number) Retry time. E.g. 600
56+
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
5657
- `type` (String) Zone type. Defaults to `primary`. Possible values are: `primary`, `secondary`.
5758

5859
### Read-Only
@@ -64,3 +65,13 @@ import {
6465
- `state` (String) Zone state. E.g. `CREATE_SUCCEEDED`.
6566
- `visibility` (String) Visibility of the zone. E.g. `public`.
6667
- `zone_id` (String) The zone ID.
68+
69+
<a id="nestedatt--timeouts"></a>
70+
### Nested Schema for `timeouts`
71+
72+
Optional:
73+
74+
- `create` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).
75+
- `delete` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Setting a timeout for a Delete operation is only applicable if changes are saved into state before the destroy operation occurs.
76+
- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Read operations occur during any refresh or planning operation when refresh is enabled.
77+
- `update` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ require (
77
github.com/google/uuid v1.6.0
88
github.com/gorilla/mux v1.8.1
99
github.com/hashicorp/terraform-plugin-framework v1.18.0
10+
github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0
1011
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0
1112
github.com/hashicorp/terraform-plugin-go v0.30.0
1213
github.com/hashicorp/terraform-plugin-log v0.10.0
1314
github.com/hashicorp/terraform-plugin-testing v1.14.0
14-
github.com/stackitcloud/stackit-sdk-go/core v0.23.0
15+
github.com/stackitcloud/stackit-sdk-go/core v0.24.0
1516
github.com/stackitcloud/stackit-sdk-go/services/alb v0.12.1
1617
github.com/stackitcloud/stackit-sdk-go/services/cdn v1.13.0
1718
github.com/stackitcloud/stackit-sdk-go/services/dns v0.19.1

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ github.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoK
9191
github.com/hashicorp/terraform-json v0.27.2/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE=
9292
github.com/hashicorp/terraform-plugin-framework v1.18.0 h1:Xy6OfqSTZfAAKXSlJ810lYvuQvYkOpSUoNMQ9l2L1RA=
9393
github.com/hashicorp/terraform-plugin-framework v1.18.0/go.mod h1:eeFIf68PME+kenJeqSrIcpHhYQK0TOyv7ocKdN4Z35E=
94+
github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0 h1:jblRy1PkLfPm5hb5XeMa3tezusnMRziUGqtT5epSYoI=
95+
github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0/go.mod h1:5jm2XK8uqrdiSRfD5O47OoxyGMCnwTcl8eoiDgSa+tc=
9496
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0 h1:Zz3iGgzxe/1XBkooZCewS0nJAaCFPFPHdNJd8FgE4Ow=
9597
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0/go.mod h1:GBKTNGbGVJohU03dZ7U8wHqc2zYnMUawgCN+gC0itLc=
9698
github.com/hashicorp/terraform-plugin-go v0.30.0 h1:VmEiD0n/ewxbvV5VI/bYwNtlSEAXtHaZlSnyUUuQK6k=
@@ -153,6 +155,8 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB
153155
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
154156
github.com/stackitcloud/stackit-sdk-go/core v0.23.0 h1:zPrOhf3Xe47rKRs1fg/AqKYUiJJRYjdcv+3qsS50mEs=
155157
github.com/stackitcloud/stackit-sdk-go/core v0.23.0/go.mod h1:osMglDby4csGZ5sIfhNyYq1bS1TxIdPY88+skE/kkmI=
158+
github.com/stackitcloud/stackit-sdk-go/core v0.24.0 h1:kHCcezCJ5OGSP7RRuGOxD5rF2wejpkEiRr/OdvNcuPQ=
159+
github.com/stackitcloud/stackit-sdk-go/core v0.24.0/go.mod h1:osMglDby4csGZ5sIfhNyYq1bS1TxIdPY88+skE/kkmI=
156160
github.com/stackitcloud/stackit-sdk-go/services/alb v0.12.1 h1:RKaxAymxlyxxE0Gta3yRuQWf07LnlcX+mfGnVB96NHA=
157161
github.com/stackitcloud/stackit-sdk-go/services/alb v0.12.1/go.mod h1:FHkV5L9vCQha+5MX+NdMdYjQIHXcLr95+bu1FN91QOM=
158162
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.12.0 h1:HxPgBu04j5tj6nfZ2r0l6v4VXC0/tYOGe4sA5Addra8=

stackit/internal/core/core.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net/http"
77
"strings"
8+
"time"
89

910
"github.com/hashicorp/terraform-plugin-framework/types"
1011
"github.com/stackitcloud/stackit-sdk-go/core/runtime"
@@ -27,6 +28,9 @@ const (
2728
DatasourceRegionFallbackDocstring = "Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on datasource level."
2829
)
2930

31+
var DefaultTimeoutMargin = 3 * time.Minute
32+
var DefaultOperationTimeout = 30 * time.Minute
33+
3034
type EphemeralProviderData struct {
3135
ProviderData
3236
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package dns
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"regexp"
7+
"testing"
8+
"time"
9+
10+
"github.com/google/uuid"
11+
"github.com/hashicorp/terraform-plugin-testing/config"
12+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
13+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
14+
)
15+
16+
func TestCreateTimeout(t *testing.T) {
17+
// only tests create timeout, read/update/delete would need a successful create beforehand. We could do this, but
18+
// these tests would be slow and flaky
19+
projectID := uuid.NewString()
20+
s := testutil.NewMockServer(t)
21+
defer s.Server.Close()
22+
providerConfig := fmt.Sprintf(`
23+
provider "stackit" {
24+
default_region = "eu01"
25+
dns_custom_endpoint = "%s"
26+
service_account_token = "mock-server-needs-no-auth"
27+
}
28+
`, s.Server.URL)
29+
zoneResource := fmt.Sprintf(`
30+
variable "name" {}
31+
32+
resource "stackit_dns_zone" "zone" {
33+
project_id = "%s"
34+
name = var.name
35+
dns_name = "dns.example.com"
36+
timeouts = {
37+
create = "10ms"
38+
read = "10ms"
39+
update = "10ms"
40+
delete = "10ms"
41+
}
42+
}
43+
`, projectID)
44+
45+
resource.UnitTest(t, resource.TestCase{
46+
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
47+
Steps: []resource.TestStep{
48+
{
49+
// create fails
50+
PreConfig: func() {
51+
s.Reset(testutil.MockResponse{
52+
Handler: func(_ http.ResponseWriter, r *http.Request) {
53+
ctx := r.Context()
54+
select {
55+
case <-ctx.Done():
56+
case <-time.After(20 * time.Millisecond):
57+
}
58+
},
59+
})
60+
},
61+
Config: providerConfig + "\n" + zoneResource,
62+
ExpectError: regexp.MustCompile("deadline exceeded"),
63+
ConfigVariables: config.Variables{
64+
"name": config.StringVariable("create-zone"),
65+
},
66+
},
67+
},
68+
})
69+
}

stackit/internal/services/dns/recordset/datasource.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net/http"
77

8+
"github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts"
89
"github.com/stackitcloud/stackit-sdk-go/services/dns/v1api/wait"
910
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
1011
dnsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dns/utils"
@@ -25,6 +26,11 @@ var (
2526
_ datasource.DataSource = &recordSetDataSource{}
2627
)
2728

29+
type DataSourceModel struct {
30+
Model
31+
Timeouts timeouts.Value `tfsdk:"timeouts"`
32+
}
33+
2834
// NewRecordSetDataSource NewZoneDataSource is a helper function to simplify the provider implementation.
2935
func NewRecordSetDataSource() datasource.DataSource {
3036
return &recordSetDataSource{}
@@ -56,7 +62,7 @@ func (d *recordSetDataSource) Configure(ctx context.Context, req datasource.Conf
5662
}
5763

5864
// Schema defines the schema for the data source.
59-
func (d *recordSetDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
65+
func (d *recordSetDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
6066
resp.Schema = schema.Schema{
6167
Description: "DNS Record Set Resource schema.",
6268
Attributes: map[string]schema.Attribute{
@@ -125,19 +131,28 @@ func (d *recordSetDataSource) Schema(_ context.Context, _ datasource.SchemaReque
125131
Description: "Record set state.",
126132
Computed: true,
127133
},
134+
"timeouts": timeouts.Attributes(ctx),
128135
},
129136
}
130137
}
131138

132139
// Read refreshes the Terraform state with the latest data.
133140
func (d *recordSetDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
134-
var model Model
141+
var model DataSourceModel
135142
diags := req.Config.Get(ctx, &model)
136143
resp.Diagnostics.Append(diags...)
137144
if resp.Diagnostics.HasError() {
138145
return
139146
}
140147

148+
readTimeout, diags := model.Timeouts.Read(ctx, core.DefaultOperationTimeout)
149+
resp.Diagnostics.Append(diags...)
150+
if resp.Diagnostics.HasError() {
151+
return
152+
}
153+
ctx, cancel := context.WithTimeout(ctx, readTimeout)
154+
defer cancel()
155+
141156
ctx = core.InitProviderContext(ctx)
142157

143158
projectId := model.ProjectId.ValueString()
@@ -170,7 +185,7 @@ func (d *recordSetDataSource) Read(ctx context.Context, req datasource.ReadReque
170185
return
171186
}
172187

173-
err = mapFields(ctx, recordSetResp, &model)
188+
err = mapFields(ctx, recordSetResp, &model.Model)
174189
if err != nil {
175190
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading record set", fmt.Sprintf("Processing API payload: %v", err))
176191
return

0 commit comments

Comments
 (0)