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 Feb 27, 2024
1 parent 1fb2370 commit b541fa3
Show file tree
Hide file tree
Showing 10 changed files with 2,420 additions and 33 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.

7 changes: 4 additions & 3 deletions scaleway/helpers_tem.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import (
)

const (
defaultTemDomainTimeout = 5 * time.Minute
defaultTemDomainRetryInterval = 15 * time.Second
defaultTemDomainTimeout = 5 * time.Minute
defaultTemDomainValidationTimeout = 60 * time.Minute
defaultTemDomainRetryInterval = 15 * time.Second
)

// temAPIWithRegion returns a new Tem API and the region for a Create request
// teemAPIWithRegion returns a new Tem API and the region for a Create request
func temAPIWithRegion(d *schema.ResourceData, m interface{}) (*tem.API, scw.Region, error) {
meta := m.(*Meta)
api := tem.NewAPI(meta.scwClient)
Expand Down
1 change: 1 addition & 0 deletions scaleway/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ func Provider(config *ProviderConfig) plugin.ProviderFunc {
"scaleway_lb_route": resourceScalewayLbRoute(),
"scaleway_registry_namespace": resourceScalewayRegistryNamespace(),
"scaleway_tem_domain": resourceScalewayTemDomain(),
"scaleway_tem_domain_validation": resourceScalewayTemDomainValidation(),
"scaleway_container": resourceScalewayContainer(),
"scaleway_container_token": resourceScalewayContainerToken(),
"scaleway_rdb_acl": resourceScalewayRdbACL(),
Expand Down
30 changes: 0 additions & 30 deletions scaleway/resource_tem_domain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,6 @@ func init() {
})
}

func testSweepTemDomain(_ string) error {
return sweepRegions([]scw.Region{scw.RegionFrPar, scw.RegionNlAms}, func(scwClient *scw.Client, region scw.Region) error {
temAPI := tem.NewAPI(scwClient)
l.Debugf("sweeper: revoking the tem domains in (%s)", region)

listDomains, err := temAPI.ListDomains(&tem.ListDomainsRequest{Region: region}, scw.WithAllPages())
if err != nil {
return fmt.Errorf("error listing domains in (%s) in sweeper: %s", region, err)
}

for _, ns := range listDomains.Domains {
if ns.Name == "test.scaleway-terraform.com" {
l.Debugf("sweeper: skipping deletion of domain %s", ns.Name)
continue
}
_, err := temAPI.RevokeDomain(&tem.RevokeDomainRequest{
DomainID: ns.ID,
Region: region,
})
if err != nil {
l.Debugf("sweeper: error (%s)", err)

return fmt.Errorf("error revoking domain in sweeper: %s", err)
}
}

return nil
})
}

func TestAccScalewayTemDomain_Basic(t *testing.T) {
tt := NewTestTools(t)
defer tt.Cleanup()
Expand Down
132 changes: 132 additions & 0 deletions scaleway/resource_tem_domain_validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package scaleway

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"
)

func resourceScalewayTemDomainValidation() *schema.Resource {
return &schema.Resource{
CreateContext: resourceScalewayTemDomainValidationCreate,
ReadContext: resourceScalewayTemDomainValidationRead,
DeleteContext: resourceScalewayTemDomainValidationDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(defaultTemDomainValidationTimeout),
Delete: schema.DefaultTimeout(defaultTemDomainValidationTimeout),
Default: schema.DefaultTimeout(defaultTemDomainValidationTimeout),
},
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": regionSchema(),
"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 resourceScalewayTemDomainValidationCreate(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 resourceScalewayTemDomainValidationRead(ctx, d, meta)
}

func resourceScalewayTemDomainValidationRead(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 is404Error(err) {
d.SetId("")
return nil
}
return diag.FromErr(err)
}

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

return nil
}

func resourceScalewayTemDomainValidationDelete(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 is404Error(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
}
92 changes: 92 additions & 0 deletions scaleway/resource_tem_domain_validation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package scaleway

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

const domainName = "terraform-rs.test.local"

func init() {
resource.AddTestSweepers("scaleway_tem_domain_validation", &resource.Sweeper{
Name: "scaleway_tem_domain_validation",
F: testSweepTemDomain,
})
}

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

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: tt.ProviderFactories,
CheckDestroy: testAccCheckScalewayTemDomainDestroy(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 TestAccScalewayTemDomainValidation_Validation(t *testing.T) {
tt := NewTestTools(t)
defer tt.Cleanup()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: tt.ProviderFactories,
CheckDestroy: testAccCheckScalewayTemDomainDestroy(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"),
),
},
},
})
}
32 changes: 32 additions & 0 deletions scaleway/sweeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package scaleway

import (
"context"
"fmt"
"strings"
"testing"

"github.com/aws/aws-sdk-go/service/s3"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
tem "github.com/scaleway/scaleway-sdk-go/api/tem/v1alpha1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -93,3 +95,33 @@ func sharedS3ClientForRegion(region scw.Region) (*s3.S3, error) {
func TestIsTestResource(t *testing.T) {
assert.True(t, isTestResource("tf_tests_mnq_sqs_queue_default_project"))
}

func testSweepTemDomain(_ string) error {
return sweepRegions([]scw.Region{scw.RegionFrPar, scw.RegionNlAms}, func(scwClient *scw.Client, region scw.Region) error {
temAPI := tem.NewAPI(scwClient)
l.Debugf("sweeper: revoking the tem domains in (%s)", region)

listDomains, err := temAPI.ListDomains(&tem.ListDomainsRequest{Region: region}, scw.WithAllPages())
if err != nil {
return fmt.Errorf("error listing domains in (%s) in sweeper: %s", region, err)
}

for _, ns := range listDomains.Domains {
if ns.Name == "test.scaleway-terraform.com" {
l.Debugf("sweeper: skipping deletion of domain %s", ns.Name)
continue
}
_, err := temAPI.RevokeDomain(&tem.RevokeDomainRequest{
DomainID: ns.ID,
Region: region,
})
if err != nil {
l.Debugf("sweeper: error (%s)", err)

return fmt.Errorf("error revoking domain in sweeper: %s", err)
}
}

return nil
})
}
Loading

0 comments on commit b541fa3

Please sign in to comment.