2014-07-24 17:47:22 +00:00
|
|
|
package digitalocean
|
|
|
|
|
|
|
|
import (
|
2017-05-15 13:54:16 +00:00
|
|
|
"context"
|
2014-07-24 17:47:22 +00:00
|
|
|
"fmt"
|
|
|
|
"log"
|
2015-09-27 04:06:51 +00:00
|
|
|
"strconv"
|
2014-09-05 15:09:52 +00:00
|
|
|
"strings"
|
2014-07-24 17:47:22 +00:00
|
|
|
|
2015-09-27 04:06:51 +00:00
|
|
|
"github.com/digitalocean/godo"
|
2014-09-09 20:29:36 +00:00
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
2014-07-24 17:47:22 +00:00
|
|
|
)
|
|
|
|
|
2014-11-17 17:55:45 +00:00
|
|
|
func resourceDigitalOceanRecord() *schema.Resource {
|
2014-09-09 20:29:36 +00:00
|
|
|
return &schema.Resource{
|
2014-11-17 17:55:45 +00:00
|
|
|
Create: resourceDigitalOceanRecordCreate,
|
|
|
|
Read: resourceDigitalOceanRecordRead,
|
|
|
|
Update: resourceDigitalOceanRecordUpdate,
|
|
|
|
Delete: resourceDigitalOceanRecordDelete,
|
2018-03-11 22:47:02 +00:00
|
|
|
Importer: &schema.ResourceImporter{
|
|
|
|
State: schema.ImportStatePassthrough,
|
|
|
|
},
|
2014-09-09 20:29:36 +00:00
|
|
|
|
|
|
|
Schema: map[string]*schema.Schema{
|
2017-01-20 17:47:54 +00:00
|
|
|
"type": {
|
2014-09-09 20:29:36 +00:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
2017-01-20 17:47:54 +00:00
|
|
|
"domain": {
|
2014-09-09 20:29:36 +00:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
2017-01-20 17:47:54 +00:00
|
|
|
"name": {
|
2014-09-09 20:29:36 +00:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
},
|
|
|
|
|
2017-01-20 17:47:54 +00:00
|
|
|
"port": {
|
2014-09-09 20:29:36 +00:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
2017-01-20 17:47:54 +00:00
|
|
|
"priority": {
|
2014-09-09 20:29:36 +00:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
2017-01-20 17:47:54 +00:00
|
|
|
"weight": {
|
2014-09-09 20:29:36 +00:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
2017-05-30 16:29:56 +00:00
|
|
|
"ttl": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
},
|
|
|
|
|
2017-01-20 17:47:54 +00:00
|
|
|
"value": {
|
2014-09-09 20:29:36 +00:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
2016-02-09 16:57:42 +00:00
|
|
|
|
2017-01-20 17:47:54 +00:00
|
|
|
"fqdn": {
|
2016-02-09 16:57:42 +00:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
},
|
2018-04-18 15:08:01 +00:00
|
|
|
|
|
|
|
"flags": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Computed: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"tag": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
2014-09-09 20:29:36 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-17 17:55:45 +00:00
|
|
|
func resourceDigitalOceanRecordCreate(d *schema.ResourceData, meta interface{}) error {
|
2015-09-27 04:06:51 +00:00
|
|
|
client := meta.(*godo.Client)
|
|
|
|
|
|
|
|
newRecord := godo.DomainRecordEditRequest{
|
|
|
|
Type: d.Get("type").(string),
|
|
|
|
Name: d.Get("name").(string),
|
|
|
|
Data: d.Get("value").(string),
|
2018-04-18 15:08:01 +00:00
|
|
|
Tag: d.Get("tag").(string),
|
2015-09-27 04:06:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
2017-05-30 16:29:56 +00:00
|
|
|
if ttl := d.Get("ttl").(string); ttl != "" {
|
|
|
|
newRecord.TTL, err = strconv.Atoi(ttl)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to parse ttl as an integer: %v", err)
|
|
|
|
}
|
|
|
|
}
|
2015-09-27 04:06:51 +00:00
|
|
|
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)
|
|
|
|
}
|
2014-07-24 17:47:22 +00:00
|
|
|
}
|
2018-04-18 15:08:01 +00:00
|
|
|
if flags := d.Get("flags").(string); flags != "" {
|
|
|
|
newRecord.Flags, err = strconv.Atoi(flags)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to parse flags as an integer: %v", err)
|
|
|
|
}
|
|
|
|
}
|
2014-07-24 17:47:22 +00:00
|
|
|
|
|
|
|
log.Printf("[DEBUG] record create configuration: %#v", newRecord)
|
2017-05-15 13:54:16 +00:00
|
|
|
rec, _, err := client.Domains.CreateRecord(context.Background(), d.Get("domain").(string), &newRecord)
|
2014-07-24 17:47:22 +00:00
|
|
|
if err != nil {
|
2014-09-09 20:29:36 +00:00
|
|
|
return fmt.Errorf("Failed to create record: %s", err)
|
2014-07-24 17:47:22 +00:00
|
|
|
}
|
|
|
|
|
2015-09-27 04:06:51 +00:00
|
|
|
d.SetId(strconv.Itoa(rec.ID))
|
2014-09-09 20:29:36 +00:00
|
|
|
log.Printf("[INFO] Record ID: %s", d.Id())
|
2014-07-24 17:47:22 +00:00
|
|
|
|
2014-11-17 17:55:45 +00:00
|
|
|
return resourceDigitalOceanRecordRead(d, meta)
|
2014-07-24 17:47:22 +00:00
|
|
|
}
|
|
|
|
|
2014-11-17 17:55:45 +00:00
|
|
|
func resourceDigitalOceanRecordRead(d *schema.ResourceData, meta interface{}) error {
|
2015-09-27 04:06:51 +00:00
|
|
|
client := meta.(*godo.Client)
|
2015-02-28 10:55:10 +00:00
|
|
|
domain := d.Get("domain").(string)
|
2015-09-27 04:06:51 +00:00
|
|
|
id, err := strconv.Atoi(d.Id())
|
|
|
|
if err != nil {
|
2018-03-11 22:47:02 +00:00
|
|
|
// Catches an import and sets the id/domain for Record call
|
|
|
|
if strings.Contains(d.Id(), ",") {
|
|
|
|
s := strings.Split(d.Id(), ",")
|
|
|
|
id, err = strconv.Atoi(s[0])
|
|
|
|
domain = s[1]
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("invalid record ID: %v", err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("invalid record ID: %v", err)
|
|
|
|
}
|
2015-09-27 04:06:51 +00:00
|
|
|
}
|
2014-11-17 17:55:45 +00:00
|
|
|
|
2017-05-15 13:54:16 +00:00
|
|
|
rec, resp, err := client.Domains.Record(context.Background(), domain, id)
|
2018-06-26 16:07:57 +00:00
|
|
|
if err != nil && resp != nil {
|
2014-11-17 17:55:45 +00:00
|
|
|
// If the record is somehow already destroyed, mark as
|
2015-09-11 18:56:20 +00:00
|
|
|
// successfully gone
|
2015-12-06 11:52:35 +00:00
|
|
|
if resp.StatusCode == 404 {
|
2014-11-17 17:55:45 +00:00
|
|
|
d.SetId("")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-06-26 16:07:57 +00:00
|
|
|
return err
|
|
|
|
} else if err != nil {
|
2014-11-17 17:55:45 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-04-18 15:08:01 +00:00
|
|
|
if t := rec.Type; t == "CNAME" || t == "MX" || t == "NS" || t == "SRV" || t == "CAA" {
|
2017-05-16 09:10:34 +00:00
|
|
|
if rec.Data == "@" {
|
|
|
|
rec.Data = domain
|
2015-02-28 10:55:10 +00:00
|
|
|
}
|
2017-05-16 09:10:34 +00:00
|
|
|
rec.Data += "."
|
2015-02-28 10:55:10 +00:00
|
|
|
}
|
|
|
|
|
2018-03-11 22:47:02 +00:00
|
|
|
// Sets Id and domain to appropriate values during an import
|
|
|
|
if strings.Contains(d.Id(), ",") {
|
|
|
|
d.SetId(strconv.Itoa(rec.ID))
|
|
|
|
d.Set("domain", domain)
|
|
|
|
}
|
|
|
|
|
2014-11-17 17:55:45 +00:00
|
|
|
d.Set("name", rec.Name)
|
|
|
|
d.Set("type", rec.Type)
|
|
|
|
d.Set("value", rec.Data)
|
2015-09-27 04:06:51 +00:00
|
|
|
d.Set("weight", strconv.Itoa(rec.Weight))
|
|
|
|
d.Set("priority", strconv.Itoa(rec.Priority))
|
|
|
|
d.Set("port", strconv.Itoa(rec.Port))
|
2017-05-30 16:29:56 +00:00
|
|
|
d.Set("ttl", strconv.Itoa(rec.TTL))
|
2018-04-18 15:08:01 +00:00
|
|
|
d.Set("flags", strconv.Itoa(rec.Flags))
|
|
|
|
d.Set("tag", rec.Tag)
|
2014-11-17 17:55:45 +00:00
|
|
|
|
2016-02-09 16:57:42 +00:00
|
|
|
en := constructFqdn(rec.Name, d.Get("domain").(string))
|
2016-02-22 18:31:59 +00:00
|
|
|
log.Printf("[DEBUG] Constructed FQDN: %s", en)
|
2016-02-09 16:57:42 +00:00
|
|
|
d.Set("fqdn", en)
|
|
|
|
|
2014-11-17 17:55:45 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceDigitalOceanRecordUpdate(d *schema.ResourceData, meta interface{}) error {
|
2015-09-27 04:06:51 +00:00
|
|
|
client := meta.(*godo.Client)
|
2014-07-24 17:47:22 +00:00
|
|
|
|
2015-09-27 04:06:51 +00:00
|
|
|
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
|
2014-09-09 20:29:36 +00:00
|
|
|
if v, ok := d.GetOk("name"); ok {
|
2015-09-27 04:06:51 +00:00
|
|
|
editRecord.Name = v.(string)
|
2014-07-24 17:47:22 +00:00
|
|
|
}
|
|
|
|
|
2017-05-30 16:29:56 +00:00
|
|
|
if d.HasChange("ttl") {
|
|
|
|
newTTL := d.Get("ttl").(string)
|
|
|
|
editRecord.TTL, err = strconv.Atoi(newTTL)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Failed to parse ttl as an integer: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-27 04:06:51 +00:00
|
|
|
log.Printf("[DEBUG] record update configuration: %#v", editRecord)
|
2017-05-15 13:54:16 +00:00
|
|
|
_, _, err = client.Domains.EditRecord(context.Background(), domain, id, &editRecord)
|
2014-07-24 17:47:22 +00:00
|
|
|
if err != nil {
|
2014-09-09 20:29:36 +00:00
|
|
|
return fmt.Errorf("Failed to update record: %s", err)
|
2014-07-24 17:47:22 +00:00
|
|
|
}
|
|
|
|
|
2014-11-17 17:55:45 +00:00
|
|
|
return resourceDigitalOceanRecordRead(d, meta)
|
2014-07-24 17:47:22 +00:00
|
|
|
}
|
|
|
|
|
2014-11-17 17:55:45 +00:00
|
|
|
func resourceDigitalOceanRecordDelete(d *schema.ResourceData, meta interface{}) error {
|
2015-09-27 04:06:51 +00:00
|
|
|
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)
|
2014-07-24 17:47:22 +00:00
|
|
|
|
2017-05-15 13:54:16 +00:00
|
|
|
resp, delErr := client.Domains.DeleteRecord(context.Background(), domain, id)
|
2015-12-06 11:52:35 +00:00
|
|
|
if delErr != nil {
|
2014-09-05 15:09:52 +00:00
|
|
|
// If the record is somehow already destroyed, mark as
|
2015-09-11 18:56:20 +00:00
|
|
|
// successfully gone
|
2015-12-06 11:52:35 +00:00
|
|
|
if resp.StatusCode == 404 {
|
2014-09-05 15:09:52 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-12-06 11:52:35 +00:00
|
|
|
return fmt.Errorf("Error deleting record: %s", delErr)
|
2014-07-24 17:47:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2016-02-09 16:57:42 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|