Skip to content
Draft
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
1 change: 1 addition & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
- kafka
- keymanager
- lb
- mailbox
- marketplace
- mnq
- mongodb
Expand Down
1 change: 1 addition & 0 deletions internal/acctest/acctest.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ var foldersUsingVCRv4 = []string{
"jobs",
"k8s",
"keymanager",
"mailbox",
"marketplace",
"secret",
}
Expand Down
86 changes: 86 additions & 0 deletions internal/services/mailbox/datasource_mailbox.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package mailbox

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
mailboxsdk "github.com/scaleway/scaleway-sdk-go/api/mailbox/v1alpha1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/datasource"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/verify"
)

// DataSourceMailbox lets users look up a mailbox by its ID or by its email address.
func DataSourceMailbox() *schema.Resource {
dsSchema := datasource.SchemaFromResourceSchema(ResourceMailbox().SchemaFunc())

// All fields are computed from the resource schema; expose these lookup keys.
datasource.AddOptionalFieldsToSchema(dsSchema, "email")

dsSchema["mailbox_id"] = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: "UUID of the mailbox. Conflicts with email.",
ValidateDiagFunc: verify.IsUUID(),
ConflictsWith: []string{"email"},
}
dsSchema["email"].ConflictsWith = []string{"mailbox_id"}

return &schema.Resource{
ReadContext: dataSourceMailboxRead,
Schema: dsSchema,
}
}

func dataSourceMailboxRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
api := newMailboxAPI(m)

mailboxID, hasID := d.GetOk("mailbox_id")

if hasID {
d.SetId(mailboxID.(string))
return readMailboxIntoState(ctx, d, m)
}

// Look up by email: list all mailboxes and filter.
email, hasEmail := d.GetOk("email")
if !hasEmail {
return diag.Errorf("one of mailbox_id or email must be provided")
}

var foundID string
page := int32(1)

for {
resp, err := api.ListMailboxes(&mailboxsdk.ListMailboxesRequest{
Page: &page,
}, scw.WithContext(ctx))
if err != nil {
return diag.FromErr(err)
}

for _, mb := range resp.Mailboxes {
if mb.Email == email.(string) {
if foundID != "" {
return diag.Errorf("found multiple mailboxes with email %q", email)
}
foundID = mb.ID
}
}

if uint64(int(page)*50) >= resp.TotalCount {
break
}
page++
}

if foundID == "" {
return diag.FromErr(fmt.Errorf("no mailbox found with email %q", email))
}

d.SetId(foundID)

return readMailboxIntoState(ctx, d, m)
}
82 changes: 82 additions & 0 deletions internal/services/mailbox/datasource_mailbox_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package mailbox_test

import (
"testing"

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

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

resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: tt.ProviderFactories,
Steps: []resource.TestStep{
{
Config: testConfigDomain(testDomainName) + `
resource "scaleway_mailbox_mailbox" "ds_by_id" {
domain_id = scaleway_mailbox_domain.domain.id
local_part = "datasource.byid"
password = "S3cur3P@ssw0rd!"
subscription_period = "monthly"
}

data "scaleway_mailbox_mailbox" "by_id" {
mailbox_id = scaleway_mailbox_mailbox.ds_by_id.id
}
`,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair(
"data.scaleway_mailbox_mailbox.by_id", "id",
"scaleway_mailbox_mailbox.ds_by_id", "id",
),
resource.TestCheckResourceAttrPair(
"data.scaleway_mailbox_mailbox.by_id", "email",
"scaleway_mailbox_mailbox.ds_by_id", "email",
),
resource.TestCheckResourceAttrPair(
"data.scaleway_mailbox_mailbox.by_id", "status",
"scaleway_mailbox_mailbox.ds_by_id", "status",
),
),
},
},
})
}

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

resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: tt.ProviderFactories,
Steps: []resource.TestStep{
{
Config: testConfigDomain(testDomainName) + `
resource "scaleway_mailbox_mailbox" "ds_by_email" {
domain_id = scaleway_mailbox_domain.domain.id
local_part = "datasource.byemail"
password = "S3cur3P@ssw0rd!"
subscription_period = "monthly"
}

data "scaleway_mailbox_mailbox" "by_email" {
email = scaleway_mailbox_mailbox.ds_by_email.email
}
`,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrPair(
"data.scaleway_mailbox_mailbox.by_email", "id",
"scaleway_mailbox_mailbox.ds_by_email", "id",
),
resource.TestCheckResourceAttrPair(
"data.scaleway_mailbox_mailbox.by_email", "domain_id",
"scaleway_mailbox_mailbox.ds_by_email", "domain_id",
),
),
},
},
})
}
191 changes: 191 additions & 0 deletions internal/services/mailbox/domain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package mailbox

import (
"context"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
mailboxsdk "github.com/scaleway/scaleway-sdk-go/api/mailbox/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/identity"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/services/account"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/types"
)

// ResourceDomain manages a mailbox service domain.
// A domain must be created before mailboxes can be provisioned under it.
// After creation, DNS records are available as computed attributes and must be
// configured in your DNS zone. The domain status will reflect validation progress.
func ResourceDomain() *schema.Resource {
return &schema.Resource{
CreateContext: resourceMailboxDomainCreate,
ReadContext: resourceMailboxDomainRead,
DeleteContext: resourceMailboxDomainDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(defaultDomainTimeout),
Delete: schema.DefaultTimeout(defaultDomainTimeout),
Default: schema.DefaultTimeout(defaultDomainTimeout),
},
SchemaVersion: 0,
SchemaFunc: domainSchema,
Identity: identity.DefaultGlobal(),
}
}

func domainSchema() map[string]*schema.Schema {
return map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Fully qualified domain name (e.g. mail.example.com)",
},
"project_id": account.ProjectIDSchema(),
"status": {
Type: schema.TypeString,
Computed: true,
Description: "Domain status: creating, waiting_validation, validating, validation_failed, provisioning, ready, deleting",
},
"mailbox_total_count": {
Type: schema.TypeInt,
Computed: true,
Description: "Number of mailboxes provisioned on this domain",
},
"webmail_url": {
Type: schema.TypeString,
Computed: true,
Description: "URL of the webmail interface",
},
"imap_url": {
Type: schema.TypeString,
Computed: true,
Description: "IMAP server URL for email clients",
},
"jmap_url": {
Type: schema.TypeString,
Computed: true,
Description: "JMAP server URL for email clients",
},
"pop3_url": {
Type: schema.TypeString,
Computed: true,
Description: "POP3 server URL for email clients",
},
"smtp_url": {
Type: schema.TypeString,
Computed: true,
Description: "SMTP server URL for email clients",
},
"dns_records": {
Type: schema.TypeList,
Computed: true,
Description: "DNS records that must be configured in your DNS zone to validate the domain and enable mailbox features. Required records must be set before the domain can be used.",
Elem: dnsRecordSchema(),
},
"created_at": {
Type: schema.TypeString,
Computed: true,
Description: "Date and time of domain creation (RFC 3339 format)",
},
"updated_at": {
Type: schema.TypeString,
Computed: true,
Description: "Date and time of last update (RFC 3339 format)",
},
}
}

func resourceMailboxDomainCreate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
api := newMailboxAPI(m)

domain, err := api.CreateDomain(&mailboxsdk.CreateDomainRequest{
ProjectID: d.Get("project_id").(string),
Name: d.Get("name").(string),
}, scw.WithContext(ctx))
if err != nil {
return diag.FromErr(err)
}

if err := identity.SetGlobalIdentity(d, domain.ID); err != nil {
return diag.FromErr(err)
}

timeout := d.Timeout(schema.TimeoutCreate)
domain, err = api.WaitForDomain(&mailboxsdk.WaitForDomainRequest{
DomainID: domain.ID,
Timeout: &timeout,
}, scw.WithContext(ctx))
if err != nil {
return diag.FromErr(err)
}

return setDomainState(ctx, d, api, domain)
}

func resourceMailboxDomainRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
api := newMailboxAPI(m)

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

return setDomainState(ctx, d, api, domain)
}

func resourceMailboxDomainDelete(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
api := newMailboxAPI(m)

_, err := api.DeleteDomain(&mailboxsdk.DeleteDomainRequest{DomainID: d.Id()}, scw.WithContext(ctx))
if err != nil {
if httperrors.Is404(err) {
return nil
}
return diag.FromErr(err)
}

timeout := d.Timeout(schema.TimeoutDelete)
_, err = api.WaitForDomain(&mailboxsdk.WaitForDomainRequest{
DomainID: d.Id(),
Timeout: &timeout,
}, scw.WithContext(ctx))
if err != nil && !httperrors.Is404(err) {
return diag.FromErr(err)
}

return nil
}

// setDomainState writes all API-returned domain fields into the Terraform state.
func setDomainState(ctx context.Context, d *schema.ResourceData, api *mailboxsdk.API, domain *mailboxsdk.Domain) diag.Diagnostics {
_ = d.Set("name", domain.Name)
_ = d.Set("project_id", domain.ProjectID)
_ = d.Set("status", domain.Status.String())
_ = d.Set("mailbox_total_count", int(domain.MailboxTotalCount))
_ = d.Set("webmail_url", domain.WebmailURL)
_ = d.Set("imap_url", domain.ImapURL)
_ = d.Set("jmap_url", domain.JmapURL)
_ = d.Set("pop3_url", domain.Pop3URL)
_ = d.Set("smtp_url", domain.SMTPURL)
_ = d.Set("created_at", types.FlattenTime(domain.CreatedAt))
_ = d.Set("updated_at", types.FlattenTime(domain.UpdatedAt))

records, err := api.GetDomainRecords(&mailboxsdk.GetDomainRecordsRequest{DomainID: domain.ID}, scw.WithContext(ctx))
if err != nil && !httperrors.Is404(err) {
return diag.FromErr(err)
}

if records != nil {
_ = d.Set("dns_records", flattenDNSRecords(records))
}

return nil
}
Loading
Loading