github.com/pbthorste/terraform@v0.8.6-0.20170127005045-deb56bd93da2/builtin/providers/ns1/resource_record.go (about)

     1  package ns1
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"log"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/terraform/helper/schema"
    11  
    12  	"github.com/mitchellh/hashstructure"
    13  	ns1 "gopkg.in/ns1/ns1-go.v2/rest"
    14  	"gopkg.in/ns1/ns1-go.v2/rest/model/data"
    15  	"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
    16  	"gopkg.in/ns1/ns1-go.v2/rest/model/filter"
    17  )
    18  
    19  var recordTypeStringEnum *StringEnum = NewStringEnum([]string{
    20  	"A",
    21  	"AAAA",
    22  	"ALIAS",
    23  	"AFSDB",
    24  	"CNAME",
    25  	"DNAME",
    26  	"HINFO",
    27  	"MX",
    28  	"NAPTR",
    29  	"NS",
    30  	"PTR",
    31  	"RP",
    32  	"SPF",
    33  	"SRV",
    34  	"TXT",
    35  })
    36  
    37  func recordResource() *schema.Resource {
    38  	return &schema.Resource{
    39  		Schema: map[string]*schema.Schema{
    40  			// Required
    41  			"zone": &schema.Schema{
    42  				Type:     schema.TypeString,
    43  				Required: true,
    44  				ForceNew: true,
    45  			},
    46  			"domain": &schema.Schema{
    47  				Type:     schema.TypeString,
    48  				Required: true,
    49  				ForceNew: true,
    50  			},
    51  			"type": &schema.Schema{
    52  				Type:         schema.TypeString,
    53  				Required:     true,
    54  				ForceNew:     true,
    55  				ValidateFunc: recordTypeStringEnum.ValidateFunc,
    56  			},
    57  			// Optional
    58  			"ttl": &schema.Schema{
    59  				Type:     schema.TypeInt,
    60  				Optional: true,
    61  				Computed: true,
    62  			},
    63  			// "meta": metaSchema,
    64  			"link": &schema.Schema{
    65  				Type:     schema.TypeString,
    66  				Optional: true,
    67  				ForceNew: true,
    68  			},
    69  			"use_client_subnet": &schema.Schema{
    70  				Type:     schema.TypeBool,
    71  				Optional: true,
    72  				Default:  false,
    73  			},
    74  			"answers": &schema.Schema{
    75  				Type:     schema.TypeSet,
    76  				Optional: true,
    77  				Elem: &schema.Resource{
    78  					Schema: map[string]*schema.Schema{
    79  						"answer": &schema.Schema{
    80  							Type:     schema.TypeString,
    81  							Optional: true,
    82  						},
    83  						"region": &schema.Schema{
    84  							Type:     schema.TypeString,
    85  							Optional: true,
    86  						},
    87  						// "meta": metaSchema,
    88  					},
    89  				},
    90  				Set: genericHasher,
    91  			},
    92  			"regions": &schema.Schema{
    93  				Type:     schema.TypeSet,
    94  				Optional: true,
    95  				Elem: &schema.Resource{
    96  					Schema: map[string]*schema.Schema{
    97  						"name": &schema.Schema{
    98  							Type:     schema.TypeString,
    99  							Required: true,
   100  						},
   101  						// "meta": metaSchema,
   102  					},
   103  				},
   104  				Set: genericHasher,
   105  			},
   106  			"filters": &schema.Schema{
   107  				Type:     schema.TypeList,
   108  				Optional: true,
   109  				Elem: &schema.Resource{
   110  					Schema: map[string]*schema.Schema{
   111  						"filter": &schema.Schema{
   112  							Type:     schema.TypeString,
   113  							Required: true,
   114  						},
   115  						"disabled": &schema.Schema{
   116  							Type:     schema.TypeBool,
   117  							Optional: true,
   118  						},
   119  						"config": &schema.Schema{
   120  							Type:     schema.TypeMap,
   121  							Optional: true,
   122  						},
   123  					},
   124  				},
   125  			},
   126  			// Computed
   127  			"id": &schema.Schema{
   128  				Type:     schema.TypeString,
   129  				Computed: true,
   130  			},
   131  		},
   132  		Create:   RecordCreate,
   133  		Read:     RecordRead,
   134  		Update:   RecordUpdate,
   135  		Delete:   RecordDelete,
   136  		Importer: &schema.ResourceImporter{State: RecordStateFunc},
   137  	}
   138  }
   139  
   140  func genericHasher(v interface{}) int {
   141  	hash, err := hashstructure.Hash(v, nil)
   142  	if err != nil {
   143  		panic(fmt.Sprintf("error computing hash code for %#v: %s", v, err.Error()))
   144  	}
   145  	return int(hash)
   146  }
   147  
   148  func recordToResourceData(d *schema.ResourceData, r *dns.Record) error {
   149  	d.SetId(r.ID)
   150  	d.Set("domain", r.Domain)
   151  	d.Set("zone", r.Zone)
   152  	d.Set("type", r.Type)
   153  	d.Set("ttl", r.TTL)
   154  	if r.Link != "" {
   155  		d.Set("link", r.Link)
   156  	}
   157  	// if r.Meta != nil {
   158  	// 	d.State()
   159  	// 	t := metaStructToDynamic(r.Meta)
   160  	// 	d.Set("meta", t)
   161  	// }
   162  	if len(r.Filters) > 0 {
   163  		filters := make([]map[string]interface{}, len(r.Filters))
   164  		for i, f := range r.Filters {
   165  			m := make(map[string]interface{})
   166  			m["filter"] = f.Type
   167  			if f.Disabled {
   168  				m["disabled"] = true
   169  			}
   170  			if f.Config != nil {
   171  				m["config"] = f.Config
   172  			}
   173  			filters[i] = m
   174  		}
   175  		d.Set("filters", filters)
   176  	}
   177  	if len(r.Answers) > 0 {
   178  		ans := &schema.Set{
   179  			F: genericHasher,
   180  		}
   181  		log.Printf("Got back from ns1 answers: %+v", r.Answers)
   182  		for _, answer := range r.Answers {
   183  			ans.Add(answerToMap(*answer))
   184  		}
   185  		log.Printf("Setting answers %+v", ans)
   186  		err := d.Set("answers", ans)
   187  		if err != nil {
   188  			return fmt.Errorf("[DEBUG] Error setting answers for: %s, error: %#v", r.Domain, err)
   189  		}
   190  	}
   191  	if len(r.Regions) > 0 {
   192  		regions := make([]map[string]interface{}, 0, len(r.Regions))
   193  		for regionName, _ := range r.Regions {
   194  			newRegion := make(map[string]interface{})
   195  			newRegion["name"] = regionName
   196  			// newRegion["meta"] = metaStructToDynamic(&region.Meta)
   197  			regions = append(regions, newRegion)
   198  		}
   199  		log.Printf("Setting regions %+v", regions)
   200  		err := d.Set("regions", regions)
   201  		if err != nil {
   202  			return fmt.Errorf("[DEBUG] Error setting regions for: %s, error: %#v", r.Domain, err)
   203  		}
   204  	}
   205  	return nil
   206  }
   207  
   208  func answerToMap(a dns.Answer) map[string]interface{} {
   209  	m := make(map[string]interface{})
   210  	m["answer"] = strings.Join(a.Rdata, " ")
   211  	if a.RegionName != "" {
   212  		m["region"] = a.RegionName
   213  	}
   214  	// if a.Meta != nil {
   215  	// 	m["meta"] = metaStructToDynamic(a.Meta)
   216  	// }
   217  	return m
   218  }
   219  
   220  func btoi(b bool) int {
   221  	if b {
   222  		return 1
   223  	}
   224  	return 0
   225  }
   226  
   227  func resourceDataToRecord(r *dns.Record, d *schema.ResourceData) error {
   228  	r.ID = d.Id()
   229  	if answers := d.Get("answers").(*schema.Set); answers.Len() > 0 {
   230  		al := make([]*dns.Answer, answers.Len())
   231  		for i, answerRaw := range answers.List() {
   232  			answer := answerRaw.(map[string]interface{})
   233  			var a *dns.Answer
   234  			v := answer["answer"].(string)
   235  			switch d.Get("type") {
   236  			case "TXT":
   237  				a = dns.NewTXTAnswer(v)
   238  			default:
   239  				a = dns.NewAnswer(strings.Split(v, " "))
   240  			}
   241  			if v, ok := answer["region"]; ok {
   242  				a.RegionName = v.(string)
   243  			}
   244  
   245  			// if v, ok := answer["meta"]; ok {
   246  			// 	metaDynamicToStruct(a.Meta, v)
   247  			// }
   248  			al[i] = a
   249  		}
   250  		r.Answers = al
   251  		if _, ok := d.GetOk("link"); ok {
   252  			return errors.New("Cannot have both link and answers in a record")
   253  		}
   254  	}
   255  	if v, ok := d.GetOk("ttl"); ok {
   256  		r.TTL = v.(int)
   257  	}
   258  	if v, ok := d.GetOk("link"); ok {
   259  		r.LinkTo(v.(string))
   260  	}
   261  	// if v, ok := d.GetOk("meta"); ok {
   262  	// 	metaDynamicToStruct(r.Meta, v)
   263  	// }
   264  	useClientSubnetVal := d.Get("use_client_subnet").(bool)
   265  	if v := strconv.FormatBool(useClientSubnetVal); v != "" {
   266  		r.UseClientSubnet = &useClientSubnetVal
   267  	}
   268  
   269  	if rawFilters := d.Get("filters").([]interface{}); len(rawFilters) > 0 {
   270  		f := make([]*filter.Filter, len(rawFilters))
   271  		for i, filterRaw := range rawFilters {
   272  			fi := filterRaw.(map[string]interface{})
   273  			config := make(map[string]interface{})
   274  			filter := filter.Filter{
   275  				Type:   fi["filter"].(string),
   276  				Config: config,
   277  			}
   278  			if disabled, ok := fi["disabled"]; ok {
   279  				filter.Disabled = disabled.(bool)
   280  			}
   281  			if rawConfig, ok := fi["config"]; ok {
   282  				for k, v := range rawConfig.(map[string]interface{}) {
   283  					if i, err := strconv.Atoi(v.(string)); err == nil {
   284  						filter.Config[k] = i
   285  					} else {
   286  						filter.Config[k] = v
   287  					}
   288  				}
   289  			}
   290  			f[i] = &filter
   291  		}
   292  		r.Filters = f
   293  	}
   294  	if regions := d.Get("regions").(*schema.Set); regions.Len() > 0 {
   295  		for _, regionRaw := range regions.List() {
   296  			region := regionRaw.(map[string]interface{})
   297  			ns1R := data.Region{
   298  				Meta: data.Meta{},
   299  			}
   300  			// if v, ok := region["meta"]; ok {
   301  			// 	metaDynamicToStruct(&ns1R.Meta, v)
   302  			// }
   303  
   304  			r.Regions[region["name"].(string)] = ns1R
   305  		}
   306  	}
   307  	return nil
   308  }
   309  
   310  // RecordCreate creates DNS record in ns1
   311  func RecordCreate(d *schema.ResourceData, meta interface{}) error {
   312  	client := meta.(*ns1.Client)
   313  	r := dns.NewRecord(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
   314  	if err := resourceDataToRecord(r, d); err != nil {
   315  		return err
   316  	}
   317  	if _, err := client.Records.Create(r); err != nil {
   318  		return err
   319  	}
   320  	return recordToResourceData(d, r)
   321  }
   322  
   323  // RecordRead reads the DNS record from ns1
   324  func RecordRead(d *schema.ResourceData, meta interface{}) error {
   325  	client := meta.(*ns1.Client)
   326  
   327  	r, _, err := client.Records.Get(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
   328  	if err != nil {
   329  		return err
   330  	}
   331  
   332  	return recordToResourceData(d, r)
   333  }
   334  
   335  // RecordDelete deltes the DNS record from ns1
   336  func RecordDelete(d *schema.ResourceData, meta interface{}) error {
   337  	client := meta.(*ns1.Client)
   338  	_, err := client.Records.Delete(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
   339  	d.SetId("")
   340  	return err
   341  }
   342  
   343  // RecordUpdate updates the given dns record in ns1
   344  func RecordUpdate(d *schema.ResourceData, meta interface{}) error {
   345  	client := meta.(*ns1.Client)
   346  	r := dns.NewRecord(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
   347  	if err := resourceDataToRecord(r, d); err != nil {
   348  		return err
   349  	}
   350  	if _, err := client.Records.Update(r); err != nil {
   351  		return err
   352  	}
   353  	return recordToResourceData(d, r)
   354  }
   355  
   356  func RecordStateFunc(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
   357  	parts := strings.Split(d.Id(), "/")
   358  	if len(parts) != 3 {
   359  		return nil, fmt.Errorf("Invalid record specifier.  Expecting 2 slashes (\"zone/domain/type\"), got %d.", len(parts)-1)
   360  	}
   361  
   362  	d.Set("zone", parts[0])
   363  	d.Set("domain", parts[1])
   364  	d.Set("type", parts[2])
   365  
   366  	return []*schema.ResourceData{d}, nil
   367  }