opentofu/builtin/providers/digitalocean/resource_digitalocean_record.go
mjsteger 9c0888ca88 Fix parsing of digitalocean dns records (#14215)
This changeset fixes how some digitalocean dns records were getting
parsed. In particular, it allows for understanding "@" as shorthand for
the domain itself, preventing terraform from suggesting changes that
wouldn't have any actual effect. This changeset also adds a trailing "."
to certain record types which are required to be submitted with a
trailing dot, but which digitalocean does not return with a trailing
dot, again preventing changes that wouldn't have an effect.

Tests have been added for the above, and with just adding the tests, the
current code is failing, as it is handling some records(e.g. MX)
incorrectly
2017-05-16 12:10:34 +03:00

212 lines
4.8 KiB
Go

package digitalocean
import (
"fmt"
"log"
"strconv"
"strings"
"github.com/digitalocean/godo"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceDigitalOceanRecord() *schema.Resource {
return &schema.Resource{
Create: resourceDigitalOceanRecordCreate,
Read: resourceDigitalOceanRecordRead,
Update: resourceDigitalOceanRecordUpdate,
Delete: resourceDigitalOceanRecordDelete,
Schema: map[string]*schema.Schema{
"type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"domain": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"name": {
Type: schema.TypeString,
Optional: true,
},
"port": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"priority": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"weight": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"value": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"fqdn": {
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourceDigitalOceanRecordCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)
newRecord := godo.DomainRecordEditRequest{
Type: d.Get("type").(string),
Name: d.Get("name").(string),
Data: d.Get("value").(string),
}
var err error
if priority := d.Get("priority").(string); priority != "" {
newRecord.Priority, err = strconv.Atoi(priority)
if err != nil {
return fmt.Errorf("Failed to parse priority as an integer: %v", err)
}
}
if port := d.Get("port").(string); port != "" {
newRecord.Port, err = strconv.Atoi(port)
if err != nil {
return fmt.Errorf("Failed to parse port as an integer: %v", err)
}
}
if weight := d.Get("weight").(string); weight != "" {
newRecord.Weight, err = strconv.Atoi(weight)
if err != nil {
return fmt.Errorf("Failed to parse weight as an integer: %v", err)
}
}
log.Printf("[DEBUG] record create configuration: %#v", newRecord)
rec, _, err := client.Domains.CreateRecord(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(domain, id)
if err != nil {
// If the record is somehow already destroyed, mark as
// successfully gone
if resp.StatusCode == 404 {
d.SetId("")
return nil
}
return err
}
if t := rec.Type; t == "CNAME" || t == "MX" || t == "NS" || t == "SRV" {
if rec.Data == "@" {
rec.Data = domain
}
rec.Data += "."
}
d.Set("name", rec.Name)
d.Set("type", rec.Type)
d.Set("value", rec.Data)
d.Set("weight", strconv.Itoa(rec.Weight))
d.Set("priority", strconv.Itoa(rec.Priority))
d.Set("port", strconv.Itoa(rec.Port))
en := constructFqdn(rec.Name, d.Get("domain").(string))
log.Printf("[DEBUG] Constructed FQDN: %s", en)
d.Set("fqdn", en)
return 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)
}
var editRecord godo.DomainRecordEditRequest
if v, ok := d.GetOk("name"); ok {
editRecord.Name = v.(string)
}
log.Printf("[DEBUG] record update configuration: %#v", editRecord)
_, _, err = client.Domains.EditRecord(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(domain, id)
if delErr != nil {
// If the record is somehow already destroyed, mark as
// successfully gone
if resp.StatusCode == 404 {
return nil
}
return fmt.Errorf("Error deleting record: %s", delErr)
}
return nil
}
func constructFqdn(name, domain string) string {
rn := strings.ToLower(strings.TrimSuffix(name, "."))
domain = strings.TrimSuffix(domain, ".")
if !strings.HasSuffix(rn, domain) {
rn = strings.Join([]string{name, domain}, ".")
}
return rn
}