Skip to content
This repository has been archived by the owner on Mar 8, 2022. It is now read-only.

Support for verifying custom domains #410

Merged
merged 9 commits into from
Nov 9, 2021
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
30 changes: 30 additions & 0 deletions auth0/internal/digitalocean/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package digitalocean

import (
"github.com/digitalocean/godo"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

// Provider returns a schema.Provider for a minimal version of the DigitalOcean
// provider used for testing.
func Provider() *schema.Provider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"token": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
"DIGITALOCEAN_TOKEN",
"DIGITALOCEAN_ACCESS_TOKEN",
}, nil),
Description: "The token key for API operations.",
},
},
ResourcesMap: map[string]*schema.Resource{
"digitalocean_record": resourceDigitalOceanRecord(),
},
ConfigureFunc: func(d *schema.ResourceData) (interface{}, error) {
return godo.NewFromToken(d.Get("token").(string)), nil
},
}
}
323 changes: 323 additions & 0 deletions auth0/internal/digitalocean/resource_digitalocean_record.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
package digitalocean

import (
"context"
"fmt"
"log"
"strconv"
"strings"

"github.com/digitalocean/godo"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
)

func resourceDigitalOceanRecord() *schema.Resource {
return &schema.Resource{
Create: resourceDigitalOceanRecordCreate,
Read: resourceDigitalOceanRecordRead,
Update: resourceDigitalOceanRecordUpdate,
Delete: resourceDigitalOceanRecordDelete,
Importer: &schema.ResourceImporter{
State: resourceDigitalOceanRecordImport,
},

Schema: map[string]*schema.Schema{
"type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{
"A",
"AAAA",
"CAA",
"CNAME",
"MX",
"NS",
"TXT",
"SRV",
}, false),
},

"domain": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.NoZeroValues,
},

"name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.NoZeroValues,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
domain := d.Get("domain").(string) + "."

return old+"."+domain == new
},
},

"port": {
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(0, 65535),
},

"priority": {
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(0, 65535),
},

"weight": {
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(0, 65535),
},

"ttl": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
ValidateFunc: validation.IntAtLeast(1),
},

"value": {
Type: schema.TypeString,
Required: true,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
domain := d.Get("domain").(string) + "."

return (old == "@" && new == domain) || (old == new+"."+domain)
},
},

"fqdn": {
Type: schema.TypeString,
Computed: true,
},

"flags": {
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(0, 255),
},

"tag": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
"issue",
"issuewild",
"iodef",
}, false),
},
},

CustomizeDiff: func(diff *schema.ResourceDiff, v interface{}) error {
recordType := diff.Get("type").(string)

_, hasPriority := diff.GetOkExists("priority")
if recordType == "MX" {
if !hasPriority {
return fmt.Errorf("`priority` is required for when type is `MX`")
}
}

_, hasPort := diff.GetOkExists("port")
_, hasWeight := diff.GetOkExists("weight")
if recordType == "SRV" {
if !hasPriority {
return fmt.Errorf("`priority` is required for when type is `SRV`")
}
if !hasPort {
return fmt.Errorf("`port` is required for when type is `SRV`")
}
if !hasWeight {
return fmt.Errorf("`weight` is required for when type is `SRV`")
}
}

_, hasFlags := diff.GetOkExists("flags")
_, hasTag := diff.GetOk("tag")
if recordType == "CAA" {
if !hasFlags {
return fmt.Errorf("`flags` is required for when type is `CAA`")
}
if !hasTag {
return fmt.Errorf("`tag` is required for when type is `CAA`")
}
}

return nil
},
}
}

func resourceDigitalOceanRecordCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)

newRecord, err := expandDigitalOceanRecordResource(d)
if err != nil {
return fmt.Errorf("Error in constructing record request: %s", err)
}

newRecord.Type = d.Get("type").(string)

log.Printf("[DEBUG] record create configuration: %#v", newRecord)
rec, _, err := client.Domains.CreateRecord(context.Background(), d.Get("domain").(string), newRecord)
if err != nil {
return fmt.Errorf("Failed to create record: %s", err)
}

d.SetId(strconv.Itoa(rec.ID))
log.Printf("[INFO] Record ID: %s", d.Id())

return resourceDigitalOceanRecordRead(d, meta)
}

func resourceDigitalOceanRecordRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)
domain := d.Get("domain").(string)
id, err := strconv.Atoi(d.Id())
if err != nil {
return fmt.Errorf("invalid record ID: %v", err)
}

rec, resp, err := client.Domains.Record(context.Background(), domain, id)
if err != nil {
// If the record is somehow already destroyed, mark as
// successfully gone
if resp != nil && resp.StatusCode == 404 {
d.SetId("")
return nil
}

return err
}

if t := rec.Type; t == "CNAME" || t == "MX" || t == "NS" || t == "SRV" || t == "CAA" {
if rec.Data != "@" && rec.Tag != "iodef" {
rec.Data += "."
}
}

d.Set("name", rec.Name)
d.Set("type", rec.Type)
d.Set("value", rec.Data)
d.Set("port", rec.Port)
d.Set("priority", rec.Priority)
d.Set("ttl", rec.TTL)
d.Set("weight", rec.Weight)
d.Set("flags", rec.Flags)
d.Set("tag", rec.Tag)

en := constructFqdn(rec.Name, d.Get("domain").(string))
log.Printf("[DEBUG] Constructed FQDN: %s", en)
d.Set("fqdn", en)

return nil
}

func resourceDigitalOceanRecordImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
if strings.Contains(d.Id(), ",") {
s := strings.Split(d.Id(), ",")
// Validate that this is an ID by making sure it can be converted into an int
_, err := strconv.Atoi(s[1])
if err != nil {
return nil, fmt.Errorf("invalid record ID: %v", err)
}

d.SetId(s[1])
d.Set("domain", s[0])
}

return []*schema.ResourceData{d}, nil
}

func resourceDigitalOceanRecordUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)

domain := d.Get("domain").(string)
id, err := strconv.Atoi(d.Id())
if err != nil {
return fmt.Errorf("invalid record ID: %v", err)
}

editRecord, err := expandDigitalOceanRecordResource(d)
if err != nil {
return fmt.Errorf("Error in constructing record request: %s", err)
}

log.Printf("[DEBUG] record update configuration: %#v", editRecord)
_, _, err = client.Domains.EditRecord(context.Background(), domain, id, editRecord)
if err != nil {
return fmt.Errorf("Failed to update record: %s", err)
}

return resourceDigitalOceanRecordRead(d, meta)
}

func resourceDigitalOceanRecordDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)

domain := d.Get("domain").(string)
id, err := strconv.Atoi(d.Id())
if err != nil {
return fmt.Errorf("invalid record ID: %v", err)
}

log.Printf("[INFO] Deleting record: %s, %d", domain, id)

resp, delErr := client.Domains.DeleteRecord(context.Background(), domain, id)
if delErr != nil {
// If the record is somehow already destroyed, mark as
// successfully gone
if resp != nil && resp.StatusCode == 404 {
return nil
}

return fmt.Errorf("Error deleting record: %s", delErr)
}

return nil
}

func expandDigitalOceanRecordResource(d *schema.ResourceData) (*godo.DomainRecordEditRequest, error) {
record := &godo.DomainRecordEditRequest{
Name: d.Get("name").(string),
Data: d.Get("value").(string),
}

if v, ok := d.GetOkExists("port"); ok {
record.Port = v.(int)
}
if v, ok := d.GetOkExists("priority"); ok {
record.Priority = v.(int)
}
if v, ok := d.GetOk("ttl"); ok {
record.TTL = v.(int)
}
if v, ok := d.GetOkExists("weight"); ok {
record.Weight = v.(int)
}
if v, ok := d.GetOkExists("flags"); ok {
record.Flags = v.(int)
}
if v, ok := d.GetOk("tag"); ok {
record.Tag = v.(string)
}

return record, nil
}

func constructFqdn(name, domain string) string {
rn := strings.ToLower(name)
domainSuffix := domain + "."
if strings.HasSuffix(rn, domainSuffix) {
rn = strings.TrimSuffix(rn, ".")
} else {
rn = strings.Join([]string{name, domain}, ".")
}
return rn
}
Loading