terraform/builtin/providers/ns1/resource_record.go

368 lines
8.8 KiB
Go
Raw Normal View History

Ns1 provider (#10782) * vendor: update gopkg.in/ns1/ns1-go.v2 * provider/ns1: Port the ns1 provider to Terraform core * docs/ns1: Document the ns1 provider * ns1: rename remaining nsone -> ns1 (#10805) * Ns1 provider (#11300) * provider/ns1: Flesh out support for meta structs. Following the structure outlined by @pashap. Using reflection to reduce copy/paste. Putting metas inside single-item lists. This is clunky, but I couldn't figure out how else to have a nested struct. Maybe the Terraform people know a better way? Inside the meta struct, all fields are always written to the state; I can't figure out how to omit fields that aren't used. This is not just verbose, it actually causes issues because you can't have both "up" and "up_feed" set). Also some minor other changes: - Add "terraform" import support to records and zones. - Create helper class StringEnum. * provider/ns1: Make fmt * provider/ns1: Remove stubbed out RecordRead (used for testing metadata change). * provider/ns1: Need to get interface that m contains from Ptr Value with Elem() * provider/ns1: Use empty string to indicate no feed given. * provider/ns1: Remove old record.regions fields. * provider/ns1: Removes redundant testAccCheckRecordState * provider/ns1: Moves account permissions logic to permissions.go * provider/ns1: Adds tests for team resource. * provider/ns1: Move remaining permissions logic to permissions.go * ns1/provider: Adds datasource.config * provider/ns1: Small clean up of datafeed resource tests * provider/ns1: removes testAccCheckZoneState in favor of explicit name check * provider/ns1: More renaming of nsone -> ns1 * provider/ns1: Comment out metadata for the moment. * Ns1 provider (#11347) * Fix the removal of empty containers from a flatmap Removal of empty nested containers from a flatmap would sometimes fail a sanity check when removed in the wrong order. This would only fail sometimes due to map iteration. There was also an off-by-one error in the prefix check which could match the incorrect keys. * provider/ns1: Adds ns1 go client through govendor. * provider/ns1: Removes unused debug line * docs/ns1: Adds docs around apikey/datasource/datafeed/team/user/record. * provider/ns1: Gets go vet green
2017-01-23 22:41:07 +01:00
package ns1
import (
"errors"
"fmt"
"log"
"strconv"
"strings"
"github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/hashstructure"
ns1 "gopkg.in/ns1/ns1-go.v2/rest"
"gopkg.in/ns1/ns1-go.v2/rest/model/data"
"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
"gopkg.in/ns1/ns1-go.v2/rest/model/filter"
)
var recordTypeStringEnum *StringEnum = NewStringEnum([]string{
"A",
"AAAA",
"ALIAS",
"AFSDB",
"CNAME",
"DNAME",
"HINFO",
"MX",
"NAPTR",
"NS",
"PTR",
"RP",
"SPF",
"SRV",
"TXT",
})
func recordResource() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
// Required
"zone": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"domain": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"type": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: recordTypeStringEnum.ValidateFunc,
},
// Optional
"ttl": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Computed: true,
},
// "meta": metaSchema,
"link": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"use_client_subnet": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"answers": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"answer": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"region": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
// "meta": metaSchema,
},
},
Set: genericHasher,
},
"regions": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
// "meta": metaSchema,
},
},
Set: genericHasher,
},
"filters": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"filter": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"disabled": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"config": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
},
},
},
},
// Computed
"id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
Create: RecordCreate,
Read: RecordRead,
Update: RecordUpdate,
Delete: RecordDelete,
Importer: &schema.ResourceImporter{State: RecordStateFunc},
}
}
func genericHasher(v interface{}) int {
hash, err := hashstructure.Hash(v, nil)
if err != nil {
panic(fmt.Sprintf("error computing hash code for %#v: %s", v, err.Error()))
}
return int(hash)
}
func recordToResourceData(d *schema.ResourceData, r *dns.Record) error {
d.SetId(r.ID)
d.Set("domain", r.Domain)
d.Set("zone", r.Zone)
d.Set("type", r.Type)
d.Set("ttl", r.TTL)
if r.Link != "" {
d.Set("link", r.Link)
}
// if r.Meta != nil {
// d.State()
// t := metaStructToDynamic(r.Meta)
// d.Set("meta", t)
// }
if len(r.Filters) > 0 {
filters := make([]map[string]interface{}, len(r.Filters))
for i, f := range r.Filters {
m := make(map[string]interface{})
m["filter"] = f.Type
if f.Disabled {
m["disabled"] = true
}
if f.Config != nil {
m["config"] = f.Config
}
filters[i] = m
}
d.Set("filters", filters)
}
if len(r.Answers) > 0 {
ans := &schema.Set{
F: genericHasher,
}
log.Printf("Got back from ns1 answers: %+v", r.Answers)
for _, answer := range r.Answers {
ans.Add(answerToMap(*answer))
}
log.Printf("Setting answers %+v", ans)
err := d.Set("answers", ans)
if err != nil {
return fmt.Errorf("[DEBUG] Error setting answers for: %s, error: %#v", r.Domain, err)
}
}
if len(r.Regions) > 0 {
regions := make([]map[string]interface{}, 0, len(r.Regions))
for regionName, _ := range r.Regions {
newRegion := make(map[string]interface{})
newRegion["name"] = regionName
// newRegion["meta"] = metaStructToDynamic(&region.Meta)
regions = append(regions, newRegion)
}
log.Printf("Setting regions %+v", regions)
err := d.Set("regions", regions)
if err != nil {
return fmt.Errorf("[DEBUG] Error setting regions for: %s, error: %#v", r.Domain, err)
}
}
return nil
}
func answerToMap(a dns.Answer) map[string]interface{} {
m := make(map[string]interface{})
m["answer"] = strings.Join(a.Rdata, " ")
if a.RegionName != "" {
m["region"] = a.RegionName
}
// if a.Meta != nil {
// m["meta"] = metaStructToDynamic(a.Meta)
// }
return m
}
func btoi(b bool) int {
if b {
return 1
}
return 0
}
func resourceDataToRecord(r *dns.Record, d *schema.ResourceData) error {
r.ID = d.Id()
if answers := d.Get("answers").(*schema.Set); answers.Len() > 0 {
al := make([]*dns.Answer, answers.Len())
for i, answerRaw := range answers.List() {
answer := answerRaw.(map[string]interface{})
var a *dns.Answer
v := answer["answer"].(string)
switch d.Get("type") {
case "TXT":
a = dns.NewTXTAnswer(v)
default:
a = dns.NewAnswer(strings.Split(v, " "))
}
if v, ok := answer["region"]; ok {
a.RegionName = v.(string)
}
// if v, ok := answer["meta"]; ok {
// metaDynamicToStruct(a.Meta, v)
// }
al[i] = a
}
r.Answers = al
if _, ok := d.GetOk("link"); ok {
return errors.New("Cannot have both link and answers in a record")
}
}
if v, ok := d.GetOk("ttl"); ok {
r.TTL = v.(int)
}
if v, ok := d.GetOk("link"); ok {
r.LinkTo(v.(string))
}
// if v, ok := d.GetOk("meta"); ok {
// metaDynamicToStruct(r.Meta, v)
// }
useClientSubnetVal := d.Get("use_client_subnet").(bool)
if v := strconv.FormatBool(useClientSubnetVal); v != "" {
r.UseClientSubnet = &useClientSubnetVal
}
if rawFilters := d.Get("filters").([]interface{}); len(rawFilters) > 0 {
f := make([]*filter.Filter, len(rawFilters))
for i, filterRaw := range rawFilters {
fi := filterRaw.(map[string]interface{})
config := make(map[string]interface{})
filter := filter.Filter{
Type: fi["filter"].(string),
Config: config,
}
if disabled, ok := fi["disabled"]; ok {
filter.Disabled = disabled.(bool)
}
if rawConfig, ok := fi["config"]; ok {
for k, v := range rawConfig.(map[string]interface{}) {
if i, err := strconv.Atoi(v.(string)); err == nil {
filter.Config[k] = i
} else {
filter.Config[k] = v
}
}
}
f[i] = &filter
}
r.Filters = f
}
if regions := d.Get("regions").(*schema.Set); regions.Len() > 0 {
for _, regionRaw := range regions.List() {
region := regionRaw.(map[string]interface{})
ns1R := data.Region{
Meta: data.Meta{},
}
// if v, ok := region["meta"]; ok {
// metaDynamicToStruct(&ns1R.Meta, v)
// }
r.Regions[region["name"].(string)] = ns1R
}
}
return nil
}
// RecordCreate creates DNS record in ns1
func RecordCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ns1.Client)
r := dns.NewRecord(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
if err := resourceDataToRecord(r, d); err != nil {
return err
}
if _, err := client.Records.Create(r); err != nil {
return err
}
return recordToResourceData(d, r)
}
// RecordRead reads the DNS record from ns1
func RecordRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ns1.Client)
r, _, err := client.Records.Get(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
if err != nil {
return err
}
return recordToResourceData(d, r)
}
// RecordDelete deltes the DNS record from ns1
func RecordDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ns1.Client)
_, err := client.Records.Delete(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
d.SetId("")
return err
}
// RecordUpdate updates the given dns record in ns1
func RecordUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ns1.Client)
r := dns.NewRecord(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
if err := resourceDataToRecord(r, d); err != nil {
return err
}
if _, err := client.Records.Update(r); err != nil {
return err
}
return recordToResourceData(d, r)
}
func RecordStateFunc(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
parts := strings.Split(d.Id(), "/")
if len(parts) != 3 {
return nil, fmt.Errorf("Invalid record specifier. Expecting 2 slashes (\"zone/domain/type\"), got %d.", len(parts)-1)
}
d.Set("zone", parts[0])
d.Set("domain", parts[1])
d.Set("type", parts[2])
return []*schema.ResourceData{d}, nil
}