Skip to content

Commit

Permalink
feat(tem) : add ressource for validate tem domain
Browse files Browse the repository at this point in the history
  • Loading branch information
jremy42 committed Apr 10, 2024
1 parent b565b53 commit c40cf30
Show file tree
Hide file tree
Showing 16 changed files with 5,010 additions and 13,085 deletions.
38 changes: 38 additions & 0 deletions docs/resources/tem_domain_validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
subcategory: "Transactional Email"
page_title: "Scaleway: scaleway_tem_domain"
---

# Resource: scaleway_tem_domain_validation

This Terraform resource manages the validation of domains for use with Scaleway's Transactional Email Management (TEM) service. It ensures that domains used for sending emails are verified and comply with Scaleway's requirements for email sending.
For more information see [the documentation](https://developers.scaleway.com/en/products/transactional_email/api/).

## Example Usage

### Basic

```terraform
resource "scaleway_tem_domain_validation" "example" {
domain_id = "your-domain-id"
region = "fr-par"
timeout = 300
}
```

## Argument Reference

The following arguments are supported:

- `domain_id` - (Required) The ID of the domain name used when sending emails. This ID must correspond to a domain already registered with Scaleway's Transactional Email service.

- `region` - (Defaults to [provider](../index.md#region) `region`). Specifies the [region](../guides/regions_and_zones.md#regions) where the domain is registered. If not specified, it defaults to the provider's region.

- `timeout` - (Optional) The maximum wait time in seconds before returning an error if the domain validation does not complete. The default is 300 seconds.

## Attributes Reference

In addition to all arguments above, the following attributes are exported:

- `validated` - Indicates if the domain has been verified for email sending. This is computed after the creation or update of the domain validation resource.

1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ func Provider(config *Config) plugin.ProviderFunc {
"scaleway_secret": secret.ResourceSecret(),
"scaleway_secret_version": secret.ResourceVersion(),
"scaleway_tem_domain": tem.ResourceDomain(),
"scaleway_tem_domain_validation": tem.ResourceDomainValidation(),
"scaleway_vpc": vpc.ResourceVPC(),
"scaleway_vpc_gateway_network": vpcgw.ResourceNetwork(),
"scaleway_vpc_private_network": vpc.ResourcePrivateNetwork(),
Expand Down
134 changes: 134 additions & 0 deletions internal/services/tem/domain_validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package tem

import (
"context"
"errors"
"strings"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
tem "github.com/scaleway/scaleway-sdk-go/api/tem/v1alpha1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional"
)

func ResourceDomainValidation() *schema.Resource {
return &schema.Resource{
CreateContext: ResourceDomainValidationCreate,
ReadContext: ResourceDomainValidationRead,
DeleteContext: ResourceDomainValidationDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(defaultDomainValidationTimeout),
Delete: schema.DefaultTimeout(defaultDomainValidationTimeout),
Default: schema.DefaultTimeout(defaultDomainValidationTimeout),
},
SchemaVersion: 0,
Schema: map[string]*schema.Schema{
"domain_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The id of domain name used when sending emails.",
},
"region": regional.Schema(),
"timeout": {
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
Default: 300,
Description: "Maximum wait time in second before returning an error.",
},
"validated": {
Type: schema.TypeBool,
Computed: true,
Description: "Indicates if the domain is verified for email sending",
},
},
}
}

func ResourceDomainValidationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
api, region, err := temAPIWithRegion(d, meta)
if err != nil {
return diag.FromErr(err)
}
d.SetId(d.Get("domain_id").(string))
diagnostics := validateDomain(ctx, d, err, api, region)
if diagnostics != nil {
return diagnostics
}
return ResourceDomainValidationRead(ctx, d, meta)
}

func ResourceDomainValidationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
api, region, err := temAPIWithRegion(d, meta)
if err != nil {
return diag.FromErr(err)
}

domainID := d.Id()
getDomainRequest := &tem.GetDomainRequest{
Region: region,
DomainID: extractAfterSlash(domainID),
}
domain, err := api.GetDomain(getDomainRequest, scw.WithContext(ctx))
if err != nil {
if httperrors.Is404(err) {
d.SetId("")
return nil
}
return diag.FromErr(err)
}

_ = d.Set("validated", domain.Status == "checked")

return nil
}

func ResourceDomainValidationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
_ = ctx
_ = meta
d.SetId("")
return nil
}

func extractAfterSlash(s string) string {
lastIndex := strings.LastIndex(s, "/")
if lastIndex == -1 {
return s
}
return s[lastIndex+1:]
}

func validateDomain(ctx context.Context, d *schema.ResourceData, err error, api *tem.API, region scw.Region) diag.Diagnostics {
domain, err := api.GetDomain(&tem.GetDomainRequest{
Region: region,
DomainID: extractAfterSlash(d.Get("domain_id").(string)),
}, scw.WithContext(ctx))
if err != nil {
if httperrors.Is404(err) {
d.SetId("")
return nil
}
return diag.FromErr(err)
}
duration := d.Get("timeout").(int)
timeout := time.Duration(duration) * time.Second
_ = retry.RetryContext(ctx, timeout, func() *retry.RetryError {
domainCheck, _ := api.CheckDomain(&tem.CheckDomainRequest{
Region: region,
DomainID: domain.ID,
})
if domainCheck == nil || domainCheck.Status == "pending" || domainCheck.Status == "unchecked" {
return retry.RetryableError(errors.New("retry"))
}
return nil
})
return nil
}
86 changes: 86 additions & 0 deletions internal/services/tem/domain_validation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package tem_test

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/acctest"
)

const domainName = "scaleway-terraform.com"

func TestAccDomainValidation_NoValidation(t *testing.T) {
tt := acctest.NewTestTools(t)
defer tt.Cleanup()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProviderFactories: tt.ProviderFactories,
CheckDestroy: isDomainDestroyed(tt),
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(`
resource scaleway_tem_domain cr01 {
name = "%s"
accept_tos = true
}
resource scaleway_tem_domain_validation valid {
domain_id = scaleway_tem_domain.cr01.id
region = scaleway_tem_domain.cr01.region
timeout = 1
}
`, domainName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("scaleway_tem_domain_validation.valid", "validated", "false"),
),
},
},
})
}

func TestAccDomainValidation_Validation(t *testing.T) {
tt := acctest.NewTestTools(t)
defer tt.Cleanup()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProviderFactories: tt.ProviderFactories,
CheckDestroy: isDomainDestroyed(tt),
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(`
resource scaleway_tem_domain cr01 {
name = "%s"
accept_tos = true
}
resource "scaleway_domain_record" "spf" {
dns_zone = "%s"
type = "TXT"
data = "v=spf1 ${scaleway_tem_domain.cr01.spf_config} -all"
}
resource "scaleway_domain_record" "dkim" {
dns_zone = "%s"
name = "${scaleway_tem_domain.cr01.project_id}._domainkey"
type = "TXT"
data = scaleway_tem_domain.cr01.dkim_config
}
resource "scaleway_domain_record" "mx" {
dns_zone = "%s"
type = "MX"
data = "."
}
resource scaleway_tem_domain_validation valid {
domain_id = scaleway_tem_domain.cr01.id
region = scaleway_tem_domain.cr01.region
timeout = 3600
}
`, domainName, domainName, domainName, domainName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("scaleway_tem_domain_validation.valid", "validated", "true"),
),
},
},
})
}
23 changes: 21 additions & 2 deletions internal/services/tem/helpers.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package tem

import (
"context"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
tem "github.com/scaleway/scaleway-sdk-go/api/tem/v1alpha1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/meta"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/transport"
)

const (
DefaultDomainTimeout = 5 * time.Minute
defaultDomainRetryInterval = 15 * time.Second
DefaultDomainTimeout = 5 * time.Minute
defaultDomainValidationTimeout = 60 * time.Minute
defaultDomainRetryInterval = 15 * time.Second
)

// temAPIWithRegion returns a new Tem API and the region for a Create request
Expand All @@ -36,3 +39,19 @@ func NewAPIWithRegionAndID(m interface{}, id string) (*tem.API, scw.Region, stri
}
return api, region, id, nil
}

func waitForTemDomain(ctx context.Context, api *tem.API, region scw.Region, id string, timeout time.Duration) (*tem.Domain, error) {
retryInterval := defaultDomainRetryInterval
if transport.DefaultWaitRetryInterval != nil {
retryInterval = *transport.DefaultWaitRetryInterval
}

domain, err := api.WaitForDomain(&tem.WaitForDomainRequest{
Region: region,
DomainID: id,
RetryInterval: &retryInterval,
Timeout: scw.TimeDurationPtr(timeout),
}, scw.WithContext(ctx))

return domain, err
}
Loading

0 comments on commit c40cf30

Please sign in to comment.