github.com/sarguru/terraform@v0.6.17-0.20160525232901-8fcdfd7e3dc9/builtin/providers/nsone/resource_record.go (about)

     1  package nsone
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"github.com/bobtfish/go-nsone-api"
     8  	"github.com/hashicorp/terraform/helper/hashcode"
     9  	"github.com/hashicorp/terraform/helper/schema"
    10  	"log"
    11  	"regexp"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  )
    16  
    17  func recordResource() *schema.Resource {
    18  	return &schema.Resource{
    19  		Schema: map[string]*schema.Schema{
    20  			"id": &schema.Schema{
    21  				Type:     schema.TypeString,
    22  				Computed: true,
    23  			},
    24  			"zone": &schema.Schema{
    25  				Type:     schema.TypeString,
    26  				Required: true,
    27  				ForceNew: true,
    28  			},
    29  			"domain": &schema.Schema{
    30  				Type:     schema.TypeString,
    31  				Required: true,
    32  				ForceNew: true,
    33  			},
    34  			"ttl": &schema.Schema{
    35  				Type:     schema.TypeInt,
    36  				Optional: true,
    37  				Computed: true,
    38  			},
    39  			"type": &schema.Schema{
    40  				Type:     schema.TypeString,
    41  				Required: true,
    42  				ForceNew: true,
    43  				ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
    44  					value := v.(string)
    45  					if !regexp.MustCompile(`^(A|AAAA|ALIAS|AFSDB|CNAME|DNAME|HINFO|MX|NAPTR|NS|PTR|RP|SPF|SRV|TXT)$`).MatchString(value) {
    46  						es = append(es, fmt.Errorf(
    47  							"only A, AAAA, ALIAS, AFSDB, CNAME, DNAME, HINFO, MX, NAPTR, NS, PTR, RP, SPF, SRV, TXT allowed in %q", k))
    48  					}
    49  					return
    50  				},
    51  			},
    52  			"meta": metaSchema(),
    53  			"link": &schema.Schema{
    54  				Type:     schema.TypeString,
    55  				Optional: true,
    56  				ForceNew: true,
    57  			},
    58  			"use_client_subnet": &schema.Schema{
    59  				Type:     schema.TypeBool,
    60  				Optional: true,
    61  				Default:  false,
    62  			},
    63  			"answers": &schema.Schema{
    64  				Type:     schema.TypeSet,
    65  				Optional: true,
    66  				Elem: &schema.Resource{
    67  					Schema: map[string]*schema.Schema{
    68  						"answer": &schema.Schema{
    69  							Type:     schema.TypeString,
    70  							Optional: true,
    71  						},
    72  						"region": &schema.Schema{
    73  							Type:     schema.TypeString,
    74  							Optional: true,
    75  						},
    76  						"meta": &schema.Schema{
    77  							Type:     schema.TypeSet,
    78  							Optional: true,
    79  							Elem: &schema.Resource{
    80  								Schema: map[string]*schema.Schema{
    81  									"field": &schema.Schema{
    82  										Type:     schema.TypeString,
    83  										Optional: true,
    84  									},
    85  									"feed": &schema.Schema{
    86  										Type:     schema.TypeString,
    87  										Optional: true,
    88  										//ConflictsWith: []string{"value"},
    89  									},
    90  									"value": &schema.Schema{
    91  										Type:     schema.TypeString,
    92  										Optional: true,
    93  										//ConflictsWith: []string{"feed"},
    94  									},
    95  								},
    96  							},
    97  							Set: metaToHash,
    98  						},
    99  					},
   100  				},
   101  				Set: answersToHash,
   102  			},
   103  			"regions": &schema.Schema{
   104  				Type:     schema.TypeSet,
   105  				Optional: true,
   106  				Elem: &schema.Resource{
   107  					Schema: map[string]*schema.Schema{
   108  						"name": &schema.Schema{
   109  							Type:     schema.TypeString,
   110  							Required: true,
   111  						},
   112  						"georegion": &schema.Schema{
   113  							Type:     schema.TypeString,
   114  							Optional: true,
   115  							ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
   116  								value := v.(string)
   117  								if !regexp.MustCompile(`^(US-WEST|US-EAST|US-CENTRAL|EUROPE|AFRICA|ASIAPAC|SOUTH-AMERICA)$`).MatchString(value) {
   118  									es = append(es, fmt.Errorf(
   119  										"only US-WEST, US-EAST, US-CENTRAL, EUROPE, AFRICA, ASIAPAC, SOUTH-AMERICA allowed in %q", k))
   120  								}
   121  								return
   122  							},
   123  						},
   124  						"country": &schema.Schema{
   125  							Type:     schema.TypeString,
   126  							Optional: true,
   127  						},
   128  						"us_state": &schema.Schema{
   129  							Type:     schema.TypeString,
   130  							Optional: true,
   131  						},
   132  						"up": &schema.Schema{
   133  							Type:     schema.TypeBool,
   134  							Optional: true,
   135  						},
   136  					},
   137  				},
   138  				Set: regionsToHash,
   139  			},
   140  			"filters": &schema.Schema{
   141  				Type:     schema.TypeList,
   142  				Optional: true,
   143  				Elem: &schema.Resource{
   144  					Schema: map[string]*schema.Schema{
   145  						"filter": &schema.Schema{
   146  							Type:     schema.TypeString,
   147  							Required: true,
   148  						},
   149  						"disabled": &schema.Schema{
   150  							Type:     schema.TypeBool,
   151  							Optional: true,
   152  						},
   153  						"config": &schema.Schema{
   154  							Type:     schema.TypeMap,
   155  							Optional: true,
   156  						},
   157  					},
   158  				},
   159  			},
   160  		},
   161  		Create: RecordCreate,
   162  		Read:   RecordRead,
   163  		Update: RecordUpdate,
   164  		Delete: RecordDelete,
   165  	}
   166  }
   167  
   168  func regionsToHash(v interface{}) int {
   169  	var buf bytes.Buffer
   170  	r := v.(map[string]interface{})
   171  	buf.WriteString(fmt.Sprintf("%s-", r["name"].(string)))
   172  	buf.WriteString(fmt.Sprintf("%s-", r["georegion"].(string)))
   173  	buf.WriteString(fmt.Sprintf("%s-", r["country"].(string)))
   174  	buf.WriteString(fmt.Sprintf("%s-", r["us_state"].(string)))
   175  	buf.WriteString(fmt.Sprintf("%t-", r["up"].(bool)))
   176  	return hashcode.String(buf.String())
   177  }
   178  
   179  func answersToHash(v interface{}) int {
   180  	var buf bytes.Buffer
   181  	a := v.(map[string]interface{})
   182  	buf.WriteString(fmt.Sprintf("%s-", a["answer"].(string)))
   183  	if a["region"] != nil {
   184  		buf.WriteString(fmt.Sprintf("%s-", a["region"].(string)))
   185  	}
   186  	var metas []int
   187  	switch t := a["meta"].(type) {
   188  	default:
   189  		panic(fmt.Sprintf("unexpected type %T", t))
   190  	case *schema.Set:
   191  		for _, meta := range t.List() {
   192  			metas = append(metas, metaToHash(meta))
   193  		}
   194  	case []map[string]interface{}:
   195  		for _, meta := range t {
   196  			metas = append(metas, metaToHash(meta))
   197  		}
   198  	}
   199  	sort.Ints(metas)
   200  	for _, metahash := range metas {
   201  		buf.WriteString(fmt.Sprintf("%d-", metahash))
   202  	}
   203  	hash := hashcode.String(buf.String())
   204  	log.Printf("Generated answersToHash %d from %+v\n", hash, a)
   205  	return hash
   206  }
   207  
   208  func metaToHash(v interface{}) int {
   209  	var buf bytes.Buffer
   210  	s := v.(map[string]interface{})
   211  	buf.WriteString(fmt.Sprintf("%s-", s["field"].(string)))
   212  	if v, ok := s["feed"]; ok && v.(string) != "" {
   213  		buf.WriteString(fmt.Sprintf("feed%s-", v.(string)))
   214  	}
   215  	if v, ok := s["value"]; ok && v.(string) != "" {
   216  		buf.WriteString(fmt.Sprintf("value%s-", v.(string)))
   217  	}
   218  
   219  	hash := hashcode.String(buf.String())
   220  	log.Printf("Generated metaToHash %d from %+v\n", hash, s)
   221  	return hash
   222  }
   223  
   224  func recordToResourceData(d *schema.ResourceData, r *nsone.Record) error {
   225  	d.SetId(r.Id)
   226  	d.Set("domain", r.Domain)
   227  	d.Set("zone", r.Zone)
   228  	d.Set("type", r.Type)
   229  	d.Set("ttl", r.Ttl)
   230  	if r.Link != "" {
   231  		d.Set("link", r.Link)
   232  	}
   233  	if len(r.Filters) > 0 {
   234  		filters := make([]map[string]interface{}, len(r.Filters))
   235  		for i, f := range r.Filters {
   236  			m := make(map[string]interface{})
   237  			m["filter"] = f.Filter
   238  			if f.Disabled {
   239  				m["disabled"] = true
   240  			}
   241  			if f.Config != nil {
   242  				m["config"] = f.Config
   243  			}
   244  			filters[i] = m
   245  		}
   246  		d.Set("filters", filters)
   247  	}
   248  	if len(r.Answers) > 0 {
   249  		ans := &schema.Set{
   250  			F: answersToHash,
   251  		}
   252  		log.Printf("Got back from nsone answers: %+v", r.Answers)
   253  		for _, answer := range r.Answers {
   254  			ans.Add(answerToMap(answer))
   255  		}
   256  		log.Printf("Setting answers %+v", ans)
   257  		err := d.Set("answers", ans)
   258  		if err != nil {
   259  			return fmt.Errorf("[DEBUG] Error setting answers for: %s, error: %#v", r.Domain, err)
   260  		}
   261  	}
   262  	if len(r.Regions) > 0 {
   263  		regions := make([]map[string]interface{}, 0, len(r.Regions))
   264  		for regionName, region := range r.Regions {
   265  			newRegion := make(map[string]interface{})
   266  			newRegion["name"] = regionName
   267  			if len(region.Meta.GeoRegion) > 0 {
   268  				newRegion["georegion"] = region.Meta.GeoRegion[0]
   269  			}
   270  			if len(region.Meta.Country) > 0 {
   271  				newRegion["country"] = region.Meta.Country[0]
   272  			}
   273  			if len(region.Meta.USState) > 0 {
   274  				newRegion["us_state"] = region.Meta.USState[0]
   275  			}
   276  			if region.Meta.Up {
   277  				newRegion["up"] = region.Meta.Up
   278  			} else {
   279  				newRegion["up"] = false
   280  			}
   281  			regions = append(regions, newRegion)
   282  		}
   283  		log.Printf("Setting regions %+v", regions)
   284  		err := d.Set("regions", regions)
   285  		if err != nil {
   286  			return fmt.Errorf("[DEBUG] Error setting regions for: %s, error: %#v", r.Domain, err)
   287  		}
   288  	}
   289  	return nil
   290  }
   291  
   292  func answerToMap(a nsone.Answer) map[string]interface{} {
   293  	m := make(map[string]interface{})
   294  	m["meta"] = make([]map[string]interface{}, 0)
   295  	m["answer"] = strings.Join(a.Answer, " ")
   296  	if a.Region != "" {
   297  		m["region"] = a.Region
   298  	}
   299  	if a.Meta != nil {
   300  		metas := &schema.Set{
   301  			F: metaToHash,
   302  		}
   303  		for k, v := range a.Meta {
   304  			meta := make(map[string]interface{})
   305  			meta["field"] = k
   306  			switch t := v.(type) {
   307  			case map[string]interface{}:
   308  				meta["feed"] = t["feed"].(string)
   309  			case string:
   310  				meta["value"] = t
   311  			case []interface{}:
   312  				var valArray []string
   313  				for _, pref := range t {
   314  					valArray = append(valArray, pref.(string))
   315  				}
   316  				sort.Strings(valArray)
   317  				stringVal := strings.Join(valArray, ",")
   318  				meta["value"] = stringVal
   319  			case bool:
   320  				intVal := btoi(t)
   321  				meta["value"] = strconv.Itoa(intVal)
   322  			case float64:
   323  				intVal := int(t)
   324  				meta["value"] = strconv.Itoa(intVal)
   325  			}
   326  			metas.Add(meta)
   327  		}
   328  		m["meta"] = metas
   329  	}
   330  	return m
   331  }
   332  
   333  func btoi(b bool) int {
   334  	if b {
   335  		return 1
   336  	}
   337  	return 0
   338  }
   339  
   340  func resourceDataToRecord(r *nsone.Record, d *schema.ResourceData) error {
   341  	r.Id = d.Id()
   342  	if answers := d.Get("answers").(*schema.Set); answers.Len() > 0 {
   343  		al := make([]nsone.Answer, answers.Len())
   344  		for i, answerRaw := range answers.List() {
   345  			answer := answerRaw.(map[string]interface{})
   346  			a := nsone.NewAnswer()
   347  			v := answer["answer"].(string)
   348  			if d.Get("type") != "TXT" {
   349  				a.Answer = strings.Split(v, " ")
   350  			} else {
   351  				a.Answer = []string{v}
   352  			}
   353  			if v, ok := answer["region"]; ok {
   354  				a.Region = v.(string)
   355  			}
   356  			if metas := answer["meta"].(*schema.Set); metas.Len() > 0 {
   357  				for _, metaRaw := range metas.List() {
   358  					meta := metaRaw.(map[string]interface{})
   359  					key := meta["field"].(string)
   360  					if value, ok := meta["feed"]; ok && value.(string) != "" {
   361  						a.Meta[key] = nsone.NewMetaFeed(value.(string))
   362  					}
   363  					if value, ok := meta["value"]; ok && value.(string) != "" {
   364  						metaArray := strings.Split(value.(string), ",")
   365  						if len(metaArray) > 1 {
   366  							sort.Strings(metaArray)
   367  							a.Meta[key] = metaArray
   368  						} else {
   369  							a.Meta[key] = value.(string)
   370  						}
   371  					}
   372  				}
   373  			}
   374  			al[i] = a
   375  		}
   376  		r.Answers = al
   377  		if _, ok := d.GetOk("link"); ok {
   378  			return errors.New("Cannot have both link and answers in a record")
   379  		}
   380  	}
   381  	if v, ok := d.GetOk("ttl"); ok {
   382  		r.Ttl = v.(int)
   383  	}
   384  	if v, ok := d.GetOk("link"); ok {
   385  		r.LinkTo(v.(string))
   386  	}
   387  	useClientSubnetVal := d.Get("use_client_subnet").(bool)
   388  	if v := strconv.FormatBool(useClientSubnetVal); v != "" {
   389  		r.UseClientSubnet = useClientSubnetVal
   390  	}
   391  
   392  	if rawFilters := d.Get("filters").([]interface{}); len(rawFilters) > 0 {
   393  		f := make([]nsone.Filter, len(rawFilters))
   394  		for i, filterRaw := range rawFilters {
   395  			fi := filterRaw.(map[string]interface{})
   396  			config := make(map[string]interface{})
   397  			filter := nsone.Filter{
   398  				Filter: fi["filter"].(string),
   399  				Config: config,
   400  			}
   401  			if disabled, ok := fi["disabled"]; ok {
   402  				filter.Disabled = disabled.(bool)
   403  			}
   404  			if rawConfig, ok := fi["config"]; ok {
   405  				for k, v := range rawConfig.(map[string]interface{}) {
   406  					if i, err := strconv.Atoi(v.(string)); err == nil {
   407  						filter.Config[k] = i
   408  					} else {
   409  						filter.Config[k] = v
   410  					}
   411  				}
   412  			}
   413  			f[i] = filter
   414  		}
   415  		r.Filters = f
   416  	}
   417  	if regions := d.Get("regions").(*schema.Set); regions.Len() > 0 {
   418  		rm := make(map[string]nsone.Region)
   419  		for _, regionRaw := range regions.List() {
   420  			region := regionRaw.(map[string]interface{})
   421  			nsoneR := nsone.Region{
   422  				Meta: nsone.RegionMeta{},
   423  			}
   424  			if g := region["georegion"].(string); g != "" {
   425  				nsoneR.Meta.GeoRegion = []string{g}
   426  			}
   427  			if g := region["country"].(string); g != "" {
   428  				nsoneR.Meta.Country = []string{g}
   429  			}
   430  			if g := region["us_state"].(string); g != "" {
   431  				nsoneR.Meta.USState = []string{g}
   432  			}
   433  			if g := region["up"].(bool); g {
   434  				nsoneR.Meta.Up = g
   435  			}
   436  
   437  			rm[region["name"].(string)] = nsoneR
   438  		}
   439  		r.Regions = rm
   440  	}
   441  	return nil
   442  }
   443  
   444  func setToMapByKey(s *schema.Set, key string) map[string]interface{} {
   445  	result := make(map[string]interface{})
   446  	for _, rawData := range s.List() {
   447  		data := rawData.(map[string]interface{})
   448  		result[data[key].(string)] = data
   449  	}
   450  
   451  	return result
   452  }
   453  
   454  // RecordCreate creates DNS record in ns1
   455  func RecordCreate(d *schema.ResourceData, meta interface{}) error {
   456  	client := meta.(*nsone.APIClient)
   457  	r := nsone.NewRecord(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
   458  	if err := resourceDataToRecord(r, d); err != nil {
   459  		return err
   460  	}
   461  	if err := client.CreateRecord(r); err != nil {
   462  		return err
   463  	}
   464  	return recordToResourceData(d, r)
   465  }
   466  
   467  // RecordRead reads the DNS record from ns1
   468  func RecordRead(d *schema.ResourceData, meta interface{}) error {
   469  	client := meta.(*nsone.APIClient)
   470  	r, err := client.GetRecord(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
   471  	if err != nil {
   472  		return err
   473  	}
   474  	recordToResourceData(d, r)
   475  	return nil
   476  }
   477  
   478  // RecordDelete deltes the DNS record from ns1
   479  func RecordDelete(d *schema.ResourceData, meta interface{}) error {
   480  	client := meta.(*nsone.APIClient)
   481  	err := client.DeleteRecord(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
   482  	d.SetId("")
   483  	return err
   484  }
   485  
   486  // RecordUpdate updates the given dns record in ns1
   487  func RecordUpdate(d *schema.ResourceData, meta interface{}) error {
   488  	client := meta.(*nsone.APIClient)
   489  	r := nsone.NewRecord(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
   490  	if err := resourceDataToRecord(r, d); err != nil {
   491  		return err
   492  	}
   493  	if err := client.UpdateRecord(r); err != nil {
   494  		return err
   495  	}
   496  	recordToResourceData(d, r)
   497  	return nil
   498  }