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
46 changes: 38 additions & 8 deletions .github/docs/contribution-guide/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
Expand All @@ -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.
Expand Down Expand Up @@ -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`\".",
Expand Down Expand Up @@ -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),
},
}
}
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
11 changes: 11 additions & 0 deletions docs/data-sources/dns_record_set.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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`

<a id="nestedatt--timeouts"></a>
### 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).
8 changes: 8 additions & 0 deletions docs/data-sources/dns_zone.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -52,3 +53,10 @@ data "stackit_dns_zone" "example" {
- `state` (String) Zone state.
- `type` (String) Zone type.
- `visibility` (String) Visibility of the zone.

<a id="nestedatt--timeouts"></a>
### 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).
11 changes: 11 additions & 0 deletions docs/resources/dns_record_set.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.

<a id="nestedatt--timeouts"></a>
### 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).
11 changes: 11 additions & 0 deletions docs/resources/dns_zone.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.

<a id="nestedatt--timeouts"></a>
### 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).
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
4 changes: 4 additions & 0 deletions stackit/internal/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
}
Expand Down
69 changes: 69 additions & 0 deletions stackit/internal/services/dns/dns_test.go
Original file line number Diff line number Diff line change
@@ -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"),
},
},
},
})
}
21 changes: 18 additions & 3 deletions stackit/internal/services/dns/recordset/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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{}
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
Loading
Loading