diff --git a/.github/docs/contribution-guide/resource.go b/.github/docs/contribution-guide/resource.go
index 3d604d114..89f210dbc 100644
--- a/.github/docs/contribution-guide/resource.go
+++ b/.github/docs/contribution-guide/resource.go
@@ -5,6 +5,7 @@ import (
"fmt"
"strings"
+ "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
@@ -16,6 +17,7 @@ import (
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
fooUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/foo/utils"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
"github.com/stackitcloud/stackit-sdk-go/services/foo" // Import service "foo" from the STACKIT SDK for Go
"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 (
// Model is the internal model of the terraform resource
type Model struct {
- Id types.String `tfsdk:"id"` // needed by TF
- ProjectId types.String `tfsdk:"project_id"`
- BarId types.String `tfsdk:"bar_id"`
- Region types.String `tfsdk:"region"`
- MyRequiredField types.String `tfsdk:"my_required_field"`
- MyOptionalField types.String `tfsdk:"my_optional_field"`
- MyReadOnlyField types.String `tfsdk:"my_read_only_field"`
+ Id types.String `tfsdk:"id"` // needed by TF
+ ProjectId types.String `tfsdk:"project_id"`
+ BarId types.String `tfsdk:"bar_id"`
+ Region types.String `tfsdk:"region"`
+ MyRequiredField types.String `tfsdk:"my_required_field"`
+ MyOptionalField types.String `tfsdk:"my_optional_field"`
+ MyReadOnlyField types.String `tfsdk:"my_read_only_field"`
+ Timeouts timeouts.Value `tfsdk:"timeouts"`
}
// NewBarResource is a helper function to simplify the provider implementation.
@@ -104,7 +107,7 @@ func (r *barResource) Configure(ctx context.Context, req resource.ConfigureReque
}
// Schema defines the schema for the resource.
-func (r *barResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+func (r *barResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
descriptions := map[string]string{
"main": "Foo bar resource schema.",
"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 *
Description: descriptions["my_read_only_field"],
Computed: true,
},
+ "timeouts": timeouts.AttributesAll(ctx),
},
}
}
@@ -185,6 +189,15 @@ func (r *barResource) Create(ctx context.Context, req resource.CreateRequest, re
return
}
+ waiterTimeout := wait.CreateBarWaitHandler(ctx, r.client, projectId, region, resp.BarId).GetTimeout()
+ createTimeout, diags := model.Timeouts.Create(ctx, waiterTimeout+core.DefaultTimeoutMargin)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ ctx, cancel := context.WithTimeout(ctx, createTimeout)
+ defer cancel()
+
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()
@@ -250,6 +263,14 @@ func (r *barResource) Read(ctx context.Context, req resource.ReadRequest, resp *
return
}
+ readTimeout, diags := model.Timeouts.Create(ctx, core.DefaultOperationTimeout)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ ctx, cancel := context.WithTimeout(ctx, readTimeout)
+ defer cancel()
+
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()
@@ -296,6 +317,15 @@ func (r *barResource) Delete(ctx context.Context, req resource.DeleteRequest, re
return
}
+ waiterTimeout := wait.DeleteBarWaitHandler(ctx, r.client, projectId, region, barId).GetTimeout()
+ deleteTimeout, diags := model.Timeouts.Create(ctx, waiterTimeout+core.DefaultTimeoutMargin)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ ctx, cancel := context.WithTimeout(ctx, deleteTimeout)
+ defer cancel()
+
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()
diff --git a/docs/data-sources/dns_record_set.md b/docs/data-sources/dns_record_set.md
index 6566491f5..f9de75092 100644
--- a/docs/data-sources/dns_record_set.md
+++ b/docs/data-sources/dns_record_set.md
@@ -29,6 +29,10 @@ data "stackit_dns_record_set" "example" {
- `record_set_id` (String) The rr set id.
- `zone_id` (String) The zone ID to which is dns record set is associated.
+### Optional
+
+- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
+
### Read-Only
- `active` (Boolean) Specifies if the record set is active or not.
@@ -41,3 +45,10 @@ data "stackit_dns_record_set" "example" {
- `state` (String) Record set state.
- `ttl` (Number) Time to live. E.g. 3600
- `type` (String) The record set type. E.g. `A` or `CNAME`
+
+
+### Nested Schema for `timeouts`
+
+Optional:
+
+- `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).
diff --git a/docs/data-sources/dns_zone.md b/docs/data-sources/dns_zone.md
index d9527dee8..9ca361d29 100644
--- a/docs/data-sources/dns_zone.md
+++ b/docs/data-sources/dns_zone.md
@@ -29,6 +29,7 @@ data "stackit_dns_zone" "example" {
### Optional
- `dns_name` (String) The zone name. E.g. `example.com` (must not end with a trailing dot).
+- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
- `zone_id` (String) The zone ID.
### Read-Only
@@ -52,3 +53,10 @@ data "stackit_dns_zone" "example" {
- `state` (String) Zone state.
- `type` (String) Zone type.
- `visibility` (String) Visibility of the zone.
+
+
+### Nested Schema for `timeouts`
+
+Optional:
+
+- `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).
diff --git a/docs/resources/dns_record_set.md b/docs/resources/dns_record_set.md
index b52f7e0dc..a2774a545 100644
--- a/docs/resources/dns_record_set.md
+++ b/docs/resources/dns_record_set.md
@@ -44,6 +44,7 @@ import {
- `active` (Boolean) Specifies if the record set is active or not. Defaults to `true`
- `comment` (String) Comment.
+- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
- `ttl` (Number) Time to live. E.g. 3600
### Read-Only
@@ -53,3 +54,13 @@ import {
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`zone_id`,`record_set_id`".
- `record_set_id` (String) The rr set id.
- `state` (String) Record set state.
+
+
+### Nested Schema for `timeouts`
+
+Optional:
+
+- `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).
+- `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.
+- `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.
+- `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).
diff --git a/docs/resources/dns_zone.md b/docs/resources/dns_zone.md
index 25bc0ae8a..b5dd0706d 100644
--- a/docs/resources/dns_zone.md
+++ b/docs/resources/dns_zone.md
@@ -53,6 +53,7 @@ import {
- `primaries` (List of String) Primary name server for secondary zone. E.g. ["1.2.3.4"]
- `refresh_time` (Number) Refresh time. E.g. 3600
- `retry_time` (Number) Retry time. E.g. 600
+- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
- `type` (String) Zone type. Defaults to `primary`. Possible values are: `primary`, `secondary`.
### Read-Only
@@ -64,3 +65,13 @@ import {
- `state` (String) Zone state. E.g. `CREATE_SUCCEEDED`.
- `visibility` (String) Visibility of the zone. E.g. `public`.
- `zone_id` (String) The zone ID.
+
+
+### Nested Schema for `timeouts`
+
+Optional:
+
+- `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).
+- `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.
+- `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.
+- `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).
diff --git a/go.mod b/go.mod
index a6c9a07a7..64fc40a54 100644
--- a/go.mod
+++ b/go.mod
@@ -7,11 +7,12 @@ require (
github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1
github.com/hashicorp/terraform-plugin-framework v1.18.0
+ github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0
github.com/hashicorp/terraform-plugin-go v0.30.0
github.com/hashicorp/terraform-plugin-log v0.10.0
github.com/hashicorp/terraform-plugin-testing v1.14.0
- github.com/stackitcloud/stackit-sdk-go/core v0.23.0
+ github.com/stackitcloud/stackit-sdk-go/core v0.24.0
github.com/stackitcloud/stackit-sdk-go/services/alb v0.12.1
github.com/stackitcloud/stackit-sdk-go/services/cdn v1.13.0
github.com/stackitcloud/stackit-sdk-go/services/dns v0.19.1
diff --git a/go.sum b/go.sum
index 5d055b654..2b4b8674a 100644
--- a/go.sum
+++ b/go.sum
@@ -91,6 +91,8 @@ github.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoK
github.com/hashicorp/terraform-json v0.27.2/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE=
github.com/hashicorp/terraform-plugin-framework v1.18.0 h1:Xy6OfqSTZfAAKXSlJ810lYvuQvYkOpSUoNMQ9l2L1RA=
github.com/hashicorp/terraform-plugin-framework v1.18.0/go.mod h1:eeFIf68PME+kenJeqSrIcpHhYQK0TOyv7ocKdN4Z35E=
+github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0 h1:jblRy1PkLfPm5hb5XeMa3tezusnMRziUGqtT5epSYoI=
+github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0/go.mod h1:5jm2XK8uqrdiSRfD5O47OoxyGMCnwTcl8eoiDgSa+tc=
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0 h1:Zz3iGgzxe/1XBkooZCewS0nJAaCFPFPHdNJd8FgE4Ow=
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0/go.mod h1:GBKTNGbGVJohU03dZ7U8wHqc2zYnMUawgCN+gC0itLc=
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
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/stackitcloud/stackit-sdk-go/core v0.23.0 h1:zPrOhf3Xe47rKRs1fg/AqKYUiJJRYjdcv+3qsS50mEs=
github.com/stackitcloud/stackit-sdk-go/core v0.23.0/go.mod h1:osMglDby4csGZ5sIfhNyYq1bS1TxIdPY88+skE/kkmI=
+github.com/stackitcloud/stackit-sdk-go/core v0.24.0 h1:kHCcezCJ5OGSP7RRuGOxD5rF2wejpkEiRr/OdvNcuPQ=
+github.com/stackitcloud/stackit-sdk-go/core v0.24.0/go.mod h1:osMglDby4csGZ5sIfhNyYq1bS1TxIdPY88+skE/kkmI=
github.com/stackitcloud/stackit-sdk-go/services/alb v0.12.1 h1:RKaxAymxlyxxE0Gta3yRuQWf07LnlcX+mfGnVB96NHA=
github.com/stackitcloud/stackit-sdk-go/services/alb v0.12.1/go.mod h1:FHkV5L9vCQha+5MX+NdMdYjQIHXcLr95+bu1FN91QOM=
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.12.0 h1:HxPgBu04j5tj6nfZ2r0l6v4VXC0/tYOGe4sA5Addra8=
diff --git a/stackit/internal/core/core.go b/stackit/internal/core/core.go
index 2c1d21feb..acc566f1c 100644
--- a/stackit/internal/core/core.go
+++ b/stackit/internal/core/core.go
@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"strings"
+ "time"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/runtime"
@@ -27,6 +28,9 @@ const (
DatasourceRegionFallbackDocstring = "Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on datasource level."
)
+var DefaultTimeoutMargin = 3 * time.Minute
+var DefaultOperationTimeout = 30 * time.Minute
+
type EphemeralProviderData struct {
ProviderData
}
diff --git a/stackit/internal/services/dns/dns_test.go b/stackit/internal/services/dns/dns_test.go
new file mode 100644
index 000000000..75ceb3905
--- /dev/null
+++ b/stackit/internal/services/dns/dns_test.go
@@ -0,0 +1,69 @@
+package dns
+
+import (
+ "fmt"
+ "net/http"
+ "regexp"
+ "testing"
+ "time"
+
+ "github.com/google/uuid"
+ "github.com/hashicorp/terraform-plugin-testing/config"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
+)
+
+func TestCreateTimeout(t *testing.T) {
+ // only tests create timeout, read/update/delete would need a successful create beforehand. We could do this, but
+ // these tests would be slow and flaky
+ projectID := uuid.NewString()
+ s := testutil.NewMockServer(t)
+ defer s.Server.Close()
+ providerConfig := fmt.Sprintf(`
+provider "stackit" {
+ default_region = "eu01"
+ dns_custom_endpoint = "%s"
+ service_account_token = "mock-server-needs-no-auth"
+}
+`, s.Server.URL)
+ zoneResource := fmt.Sprintf(`
+variable "name" {}
+
+resource "stackit_dns_zone" "zone" {
+ project_id = "%s"
+ name = var.name
+ dns_name = "dns.example.com"
+ timeouts = {
+ create = "10ms"
+ read = "10ms"
+ update = "10ms"
+ delete = "10ms"
+ }
+}
+`, projectID)
+
+ resource.UnitTest(t, resource.TestCase{
+ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ // create fails
+ PreConfig: func() {
+ s.Reset(testutil.MockResponse{
+ Handler: func(_ http.ResponseWriter, r *http.Request) {
+ ctx := r.Context()
+ select {
+ case <-ctx.Done():
+ case <-time.After(20 * time.Millisecond):
+ }
+ },
+ })
+ },
+ Config: providerConfig + "\n" + zoneResource,
+ ExpectError: regexp.MustCompile("deadline exceeded"),
+ ConfigVariables: config.Variables{
+ "name": config.StringVariable("create-zone"),
+ },
+ },
+ },
+ })
+}
diff --git a/stackit/internal/services/dns/recordset/datasource.go b/stackit/internal/services/dns/recordset/datasource.go
index 372054ed1..33b56c128 100644
--- a/stackit/internal/services/dns/recordset/datasource.go
+++ b/stackit/internal/services/dns/recordset/datasource.go
@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
+ "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts"
"github.com/stackitcloud/stackit-sdk-go/services/dns/v1api/wait"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
dnsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dns/utils"
@@ -25,6 +26,11 @@ var (
_ datasource.DataSource = &recordSetDataSource{}
)
+type DataSourceModel struct {
+ Model
+ Timeouts timeouts.Value `tfsdk:"timeouts"`
+}
+
// NewRecordSetDataSource NewZoneDataSource is a helper function to simplify the provider implementation.
func NewRecordSetDataSource() datasource.DataSource {
return &recordSetDataSource{}
@@ -56,7 +62,7 @@ func (d *recordSetDataSource) Configure(ctx context.Context, req datasource.Conf
}
// Schema defines the schema for the data source.
-func (d *recordSetDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+func (d *recordSetDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "DNS Record Set Resource schema.",
Attributes: map[string]schema.Attribute{
@@ -125,19 +131,28 @@ func (d *recordSetDataSource) Schema(_ context.Context, _ datasource.SchemaReque
Description: "Record set state.",
Computed: true,
},
+ "timeouts": timeouts.Attributes(ctx),
},
}
}
// Read refreshes the Terraform state with the latest data.
func (d *recordSetDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
- var model Model
+ var model DataSourceModel
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
+ readTimeout, diags := model.Timeouts.Read(ctx, core.DefaultOperationTimeout)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ ctx, cancel := context.WithTimeout(ctx, readTimeout)
+ defer cancel()
+
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()
@@ -170,7 +185,7 @@ func (d *recordSetDataSource) Read(ctx context.Context, req datasource.ReadReque
return
}
- err = mapFields(ctx, recordSetResp, &model)
+ err = mapFields(ctx, recordSetResp, &model.Model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading record set", fmt.Sprintf("Processing API payload: %v", err))
return
diff --git a/stackit/internal/services/dns/recordset/resource.go b/stackit/internal/services/dns/recordset/resource.go
index ad665a3a7..4351937fa 100644
--- a/stackit/internal/services/dns/recordset/resource.go
+++ b/stackit/internal/services/dns/recordset/resource.go
@@ -5,6 +5,7 @@ import (
"fmt"
"strings"
+ "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
"github.com/hashicorp/terraform-plugin-framework-validators/int32validator"
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
@@ -48,6 +49,11 @@ type Model struct {
FQDN types.String `tfsdk:"fqdn"`
}
+type ResourceModel struct {
+ Model
+ Timeouts timeouts.Value `tfsdk:"timeouts"`
+}
+
// NewRecordSetResource is a helper function to simplify the provider implementation.
func NewRecordSetResource() resource.Resource {
return &recordSetResource{}
@@ -79,7 +85,7 @@ func (r *recordSetResource) Configure(ctx context.Context, req resource.Configur
}
// Schema defines the schema for the resource.
-func (r *recordSetResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+func (r *recordSetResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "DNS Record Set Resource schema.",
Attributes: map[string]schema.Attribute{
@@ -186,6 +192,7 @@ func (r *recordSetResource) Schema(_ context.Context, _ resource.SchemaRequest,
Description: "Record set state.",
Computed: true,
},
+ "timeouts": timeouts.AttributesAll(ctx),
},
}
}
@@ -193,13 +200,22 @@ func (r *recordSetResource) Schema(_ context.Context, _ resource.SchemaRequest,
// Create creates the resource and sets the initial Terraform state.
func (r *recordSetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from plan
- var model Model
+ var model ResourceModel
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
+ waiterTimeout := wait.CreateRecordSetWaitHandler(ctx, r.client.DefaultAPI, "", "", "").GetTimeout()
+ createTimeout, diags := model.Timeouts.Create(ctx, waiterTimeout+core.DefaultTimeoutMargin)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ ctx, cancel := context.WithTimeout(ctx, createTimeout)
+ defer cancel()
+
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()
@@ -208,7 +224,7 @@ func (r *recordSetResource) Create(ctx context.Context, req resource.CreateReque
ctx = tflog.SetField(ctx, "zone_id", zoneId)
// Generate API request body from model
- payload, err := toCreatePayload(&model)
+ payload, err := toCreatePayload(&model.Model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating record set", fmt.Sprintf("Creating API payload: %v", err))
return
@@ -239,7 +255,7 @@ func (r *recordSetResource) Create(ctx context.Context, req resource.CreateReque
}
// Map response body to schema
- err = mapFields(ctx, waitResp, &model)
+ err = mapFields(ctx, waitResp, &model.Model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating record set", fmt.Sprintf("Processing API payload: %v", err))
return
@@ -255,13 +271,21 @@ func (r *recordSetResource) Create(ctx context.Context, req resource.CreateReque
// Read refreshes the Terraform state with the latest data.
func (r *recordSetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
- var model Model
+ var model ResourceModel
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
+ readTimeout, diags := model.Timeouts.Read(ctx, core.DefaultOperationTimeout)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ ctx, cancel := context.WithTimeout(ctx, readTimeout)
+ defer cancel()
+
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()
@@ -284,7 +308,7 @@ func (r *recordSetResource) Read(ctx context.Context, req resource.ReadRequest,
ctx = core.LogResponse(ctx)
// Map response body to schema
- err = mapFields(ctx, recordSetResp, &model)
+ err = mapFields(ctx, recordSetResp, &model.Model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading record set", fmt.Sprintf("Processing API payload: %v", err))
return
@@ -302,13 +326,22 @@ func (r *recordSetResource) Read(ctx context.Context, req resource.ReadRequest,
// Update updates the resource and sets the updated Terraform state on success.
func (r *recordSetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from plan
- var model Model
+ var model ResourceModel
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
+ waiterTimeout := wait.PartialUpdateRecordSetWaitHandler(ctx, r.client.DefaultAPI, "", "", "").GetTimeout()
+ updateTimeout, diags := model.Timeouts.Update(ctx, waiterTimeout+core.DefaultTimeoutMargin)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ ctx, cancel := context.WithTimeout(ctx, updateTimeout)
+ defer cancel()
+
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()
@@ -319,7 +352,7 @@ func (r *recordSetResource) Update(ctx context.Context, req resource.UpdateReque
ctx = tflog.SetField(ctx, "record_set_id", recordSetId)
// Generate API request body from model
- payload, err := toUpdatePayload(&model)
+ payload, err := toUpdatePayload(&model.Model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating record set", fmt.Sprintf("Creating API payload: %v", err))
return
@@ -339,7 +372,7 @@ func (r *recordSetResource) Update(ctx context.Context, req resource.UpdateReque
return
}
- err = mapFields(ctx, waitResp, &model)
+ err = mapFields(ctx, waitResp, &model.Model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating record set", fmt.Sprintf("Processing API payload: %v", err))
return
@@ -355,13 +388,22 @@ func (r *recordSetResource) Update(ctx context.Context, req resource.UpdateReque
// Delete deletes the resource and removes the Terraform state on success.
func (r *recordSetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from plan
- var model Model
+ var model ResourceModel
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
+ waiterTimeout := wait.DeleteRecordSetWaitHandler(ctx, r.client.DefaultAPI, "", "", "").GetTimeout()
+ deleteTimeout, diags := model.Timeouts.Delete(ctx, waiterTimeout+core.DefaultTimeoutMargin)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ ctx, cancel := context.WithTimeout(ctx, deleteTimeout)
+ defer cancel()
+
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()
diff --git a/stackit/internal/services/dns/testdata/resource-max.tf b/stackit/internal/services/dns/testdata/resource-max.tf
index 27d6894ee..135e3b5ec 100644
--- a/stackit/internal/services/dns/testdata/resource-max.tf
+++ b/stackit/internal/services/dns/testdata/resource-max.tf
@@ -40,6 +40,13 @@ resource "stackit_dns_zone" "zone" {
refresh_time = var.refresh_time
retry_time = var.retry_time
type = var.type
+
+ timeouts = {
+ create = "20m"
+ read = "20m"
+ update = "20m"
+ delete = "20m"
+ }
}
@@ -55,20 +62,36 @@ resource "stackit_dns_record_set" "record_set" {
comment = var.record_comment
ttl = var.record_ttl
type = var.record_type
+
+ timeouts = {
+ create = "20m"
+ read = "20m"
+ update = "20m"
+ delete = "20m"
+ }
}
data "stackit_dns_zone" "zone" {
project_id = var.project_id
zone_id = stackit_dns_zone.zone.zone_id
+ timeouts = {
+ read = "20m"
+ }
}
data "stackit_dns_zone" "zone_name" {
project_id = var.project_id
dns_name = stackit_dns_zone.zone.dns_name
+ timeouts = {
+ read = "20m"
+ }
}
data "stackit_dns_record_set" "record_set" {
project_id = var.project_id
zone_id = stackit_dns_zone.zone.zone_id
record_set_id = stackit_dns_record_set.record_set.record_set_id
+ timeouts = {
+ read = "20m"
+ }
}
diff --git a/stackit/internal/services/dns/zone/datasource.go b/stackit/internal/services/dns/zone/datasource.go
index 8c5879ba0..434ea456a 100644
--- a/stackit/internal/services/dns/zone/datasource.go
+++ b/stackit/internal/services/dns/zone/datasource.go
@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
+ "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts"
"github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/path"
@@ -28,6 +29,11 @@ var (
_ datasource.DataSource = &zoneDataSource{}
)
+type DataSourceModel struct {
+ Model
+ Timeouts timeouts.Value `tfsdk:"timeouts"`
+}
+
// NewZoneDataSource is a helper function to simplify the provider implementation.
func NewZoneDataSource() datasource.DataSource {
return &zoneDataSource{}
@@ -68,7 +74,7 @@ func (d *zoneDataSource) Configure(ctx context.Context, req datasource.Configure
}
// Schema defines the schema for the data source.
-func (d *zoneDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+func (d *zoneDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "DNS Zone resource schema.",
Attributes: map[string]schema.Attribute{
@@ -177,19 +183,28 @@ func (d *zoneDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, r
Description: "Zone state.",
Computed: true,
},
+ "timeouts": timeouts.Attributes(ctx),
},
}
}
// Read refreshes the Terraform state with the latest data.
func (d *zoneDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
- var model Model
+ var model DataSourceModel
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
+ readTimeout, diags := model.Timeouts.Read(ctx, core.DefaultOperationTimeout)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ ctx, cancel := context.WithTimeout(ctx, readTimeout)
+ defer cancel()
+
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()
@@ -264,7 +279,7 @@ func (d *zoneDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
return
}
- err = mapFields(ctx, zoneResp, &model)
+ err = mapFields(ctx, zoneResp, &model.Model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading zone", fmt.Sprintf("Processing API payload: %v", err))
return
diff --git a/stackit/internal/services/dns/zone/resource.go b/stackit/internal/services/dns/zone/resource.go
index fcd14c4d5..4a49f8bfb 100644
--- a/stackit/internal/services/dns/zone/resource.go
+++ b/stackit/internal/services/dns/zone/resource.go
@@ -7,6 +7,7 @@ import (
"regexp"
"strings"
+ "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
"github.com/hashicorp/terraform-plugin-framework-validators/int32validator"
dnsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dns/utils"
@@ -65,6 +66,11 @@ type Model struct {
State types.String `tfsdk:"state"`
}
+type ResourceModel struct {
+ Model
+ Timeouts timeouts.Value `tfsdk:"timeouts"`
+}
+
// NewZoneResource is a helper function to simplify the provider implementation.
func NewZoneResource() resource.Resource {
return &zoneResource{}
@@ -96,7 +102,7 @@ func (r *zoneResource) Configure(ctx context.Context, req resource.ConfigureRequ
}
// Schema defines the schema for the resource.
-func (r *zoneResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+func (r *zoneResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
primaryOptions := []string{"primary", "secondary"}
resp.Schema = schema.Schema{
@@ -279,6 +285,7 @@ func (r *zoneResource) Schema(_ context.Context, _ resource.SchemaRequest, resp
Description: "Zone state. E.g. `CREATE_SUCCEEDED`.",
Computed: true,
},
+ "timeouts": timeouts.AttributesAll(ctx),
},
}
}
@@ -286,19 +293,28 @@ func (r *zoneResource) Schema(_ context.Context, _ resource.SchemaRequest, resp
// Create creates the resource and sets the initial Terraform state.
func (r *zoneResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from plan
- var model Model
+ var model ResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...)
if resp.Diagnostics.HasError() {
return
}
+ waiterTimeout := wait.CreateZoneWaitHandler(ctx, r.client.DefaultAPI, "", "").GetTimeout()
+ createTimeout, diags := model.Timeouts.Create(ctx, waiterTimeout+core.DefaultTimeoutMargin)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ ctx, cancel := context.WithTimeout(ctx, createTimeout)
+ defer cancel()
+
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
// Generate API request body from model
- payload, err := toCreatePayload(&model)
+ payload, err := toCreatePayload(&model.Model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating zone", fmt.Sprintf("Creating API payload: %v", err))
return
@@ -329,7 +345,7 @@ func (r *zoneResource) Create(ctx context.Context, req resource.CreateRequest, r
}
// Map response body to schema
- err = mapFields(ctx, waitResp, &model)
+ err = mapFields(ctx, waitResp, &model.Model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating zone", fmt.Sprintf("Processing API payload: %v", err))
return
@@ -344,13 +360,21 @@ func (r *zoneResource) Create(ctx context.Context, req resource.CreateRequest, r
// Read refreshes the Terraform state with the latest data.
func (r *zoneResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
- var model Model
+ var model ResourceModel
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
+ readTimeout, diags := model.Timeouts.Read(ctx, core.DefaultOperationTimeout)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ ctx, cancel := context.WithTimeout(ctx, readTimeout)
+ defer cancel()
+
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()
@@ -372,7 +396,7 @@ func (r *zoneResource) Read(ctx context.Context, req resource.ReadRequest, resp
}
// Map response body to schema
- err = mapFields(ctx, zoneResp, &model)
+ err = mapFields(ctx, zoneResp, &model.Model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading zone", fmt.Sprintf("Processing API payload: %v", err))
return
@@ -389,13 +413,22 @@ func (r *zoneResource) Read(ctx context.Context, req resource.ReadRequest, resp
// Update updates the resource and sets the updated Terraform state on success.
func (r *zoneResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from plan
- var model Model
+ var model ResourceModel
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
+ waiterTimeout := wait.PartialUpdateZoneWaitHandler(ctx, r.client.DefaultAPI, "", "").GetTimeout()
+ updateTimeout, diags := model.Timeouts.Update(ctx, waiterTimeout+core.DefaultTimeoutMargin)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ ctx, cancel := context.WithTimeout(ctx, updateTimeout)
+ defer cancel()
+
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()
@@ -404,7 +437,7 @@ func (r *zoneResource) Update(ctx context.Context, req resource.UpdateRequest, r
ctx = tflog.SetField(ctx, "zone_id", zoneId)
// Generate API request body from model
- payload, err := toUpdatePayload(&model)
+ payload, err := toUpdatePayload(&model.Model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating zone", fmt.Sprintf("Creating API payload: %v", err))
return
@@ -424,7 +457,7 @@ func (r *zoneResource) Update(ctx context.Context, req resource.UpdateRequest, r
return
}
- err = mapFields(ctx, waitResp, &model)
+ err = mapFields(ctx, waitResp, &model.Model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating zone", fmt.Sprintf("Processing API payload: %v", err))
return
@@ -440,13 +473,22 @@ func (r *zoneResource) Update(ctx context.Context, req resource.UpdateRequest, r
// Delete deletes the resource and removes the Terraform state on success.
func (r *zoneResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from state
- var model Model
+ var model ResourceModel
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
+ waiterTimeout := wait.DeleteZoneWaitHandler(ctx, r.client.DefaultAPI, "", "").GetTimeout()
+ deleteTimeout, diags := model.Timeouts.Delete(ctx, waiterTimeout+core.DefaultTimeoutMargin)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ ctx, cancel := context.WithTimeout(ctx, deleteTimeout)
+ defer cancel()
+
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()