terraform-provider-greenhost/digitalocean/resource_digitalocean_recor...

324 lines
7.9 KiB
Go

package digitalocean
import (
"context"
"fmt"
"log"
"strconv"
"strings"
"github.com/digitalocean/godo"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/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(ctx context.Context, 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.(*CombinedConfig).godoClient()
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.(*CombinedConfig).godoClient()
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.(*CombinedConfig).godoClient()
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.(*CombinedConfig).godoClient()
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
}