github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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:  true,
    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 r.UseClientSubnet != nil {
   163  		d.Set("use_client_subnet", *r.UseClientSubnet)
   164  	}
   165  	if len(r.Filters) > 0 {
   166  		filters := make([]map[string]interface{}, len(r.Filters))
   167  		for i, f := range r.Filters {
   168  			m := make(map[string]interface{})
   169  			m["filter"] = f.Type
   170  			if f.Disabled {
   171  				m["disabled"] = true
   172  			}
   173  			if f.Config != nil {
   174  				m["config"] = f.Config
   175  			}
   176  			filters[i] = m
   177  		}
   178  		d.Set("filters", filters)
   179  	}
   180  	if len(r.Answers) > 0 {
   181  		ans := &schema.Set{
   182  			F: genericHasher,
   183  		}
   184  		log.Printf("Got back from ns1 answers: %+v", r.Answers)
   185  		for _, answer := range r.Answers {
   186  			ans.Add(answerToMap(*answer))
   187  		}
   188  		log.Printf("Setting answers %+v", ans)
   189  		err := d.Set("answers", ans)
   190  		if err != nil {
   191  			return fmt.Errorf("[DEBUG] Error setting answers for: %s, error: %#v", r.Domain, err)
   192  		}
   193  	}
   194  	if len(r.Regions) > 0 {
   195  		regions := make([]map[string]interface{}, 0, len(r.Regions))
   196  		for regionName, _ := range r.Regions {
   197  			newRegion := make(map[string]interface{})
   198  			newRegion["name"] = regionName
   199  			// newRegion["meta"] = metaStructToDynamic(&region.Meta)
   200  			regions = append(regions, newRegion)
   201  		}
   202  		log.Printf("Setting regions %+v", regions)
   203  		err := d.Set("regions", regions)
   204  		if err != nil {
   205  			return fmt.Errorf("[DEBUG] Error setting regions for: %s, error: %#v", r.Domain, err)
   206  		}
   207  	}
   208  	return nil
   209  }
   210  
   211  func answerToMap(a dns.Answer) map[string]interface{} {
   212  	m := make(map[string]interface{})
   213  	m["answer"] = strings.Join(a.Rdata, " ")
   214  	if a.RegionName != "" {
   215  		m["region"] = a.RegionName
   216  	}
   217  	// if a.Meta != nil {
   218  	// 	m["meta"] = metaStructToDynamic(a.Meta)
   219  	// }
   220  	return m
   221  }
   222  
   223  func btoi(b bool) int {
   224  	if b {
   225  		return 1
   226  	}
   227  	return 0
   228  }
   229  
   230  func resourceDataToRecord(r *dns.Record, d *schema.ResourceData) error {
   231  	r.ID = d.Id()
   232  	if answers := d.Get("answers").(*schema.Set); answers.Len() > 0 {
   233  		al := make([]*dns.Answer, answers.Len())
   234  		for i, answerRaw := range answers.List() {
   235  			answer := answerRaw.(map[string]interface{})
   236  			var a *dns.Answer
   237  			v := answer["answer"].(string)
   238  			switch d.Get("type") {
   239  			case "TXT":
   240  				a = dns.NewTXTAnswer(v)
   241  			default:
   242  				a = dns.NewAnswer(strings.Split(v, " "))
   243  			}
   244  			if v, ok := answer["region"]; ok {
   245  				a.RegionName = v.(string)
   246  			}
   247  
   248  			// if v, ok := answer["meta"]; ok {
   249  			// 	metaDynamicToStruct(a.Meta, v)
   250  			// }
   251  			al[i] = a
   252  		}
   253  		r.Answers = al
   254  		if _, ok := d.GetOk("link"); ok {
   255  			return errors.New("Cannot have both link and answers in a record")
   256  		}
   257  	}
   258  	if v, ok := d.GetOk("ttl"); ok {
   259  		r.TTL = v.(int)
   260  	}
   261  	if v, ok := d.GetOk("link"); ok {
   262  		r.LinkTo(v.(string))
   263  	}
   264  	// if v, ok := d.GetOk("meta"); ok {
   265  	// 	metaDynamicToStruct(r.Meta, v)
   266  	// }
   267  	useClientSubnet := d.Get("use_client_subnet").(bool)
   268  	r.UseClientSubnet = &useClientSubnet
   269  
   270  	if rawFilters := d.Get("filters").([]interface{}); len(rawFilters) > 0 {
   271  		f := make([]*filter.Filter, len(rawFilters))
   272  		for i, filterRaw := range rawFilters {
   273  			fi := filterRaw.(map[string]interface{})
   274  			config := make(map[string]interface{})
   275  			filter := filter.Filter{
   276  				Type:   fi["filter"].(string),
   277  				Config: config,
   278  			}
   279  			if disabled, ok := fi["disabled"]; ok {
   280  				filter.Disabled = disabled.(bool)
   281  			}
   282  			if rawConfig, ok := fi["config"]; ok {
   283  				for k, v := range rawConfig.(map[string]interface{}) {
   284  					if i, err := strconv.Atoi(v.(string)); err == nil {
   285  						filter.Config[k] = i
   286  					} else {
   287  						filter.Config[k] = v
   288  					}
   289  				}
   290  			}
   291  			f[i] = &filter
   292  		}
   293  		r.Filters = f
   294  	}
   295  	if regions := d.Get("regions").(*schema.Set); regions.Len() > 0 {
   296  		for _, regionRaw := range regions.List() {
   297  			region := regionRaw.(map[string]interface{})
   298  			ns1R := data.Region{
   299  				Meta: data.Meta{},
   300  			}
   301  			// if v, ok := region["meta"]; ok {
   302  			// 	metaDynamicToStruct(&ns1R.Meta, v)
   303  			// }
   304  
   305  			r.Regions[region["name"].(string)] = ns1R
   306  		}
   307  	}
   308  	return nil
   309  }
   310  
   311  // RecordCreate creates DNS record in ns1
   312  func RecordCreate(d *schema.ResourceData, meta interface{}) error {
   313  	client := meta.(*ns1.Client)
   314  	r := dns.NewRecord(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
   315  	if err := resourceDataToRecord(r, d); err != nil {
   316  		return err
   317  	}
   318  	if _, err := client.Records.Create(r); err != nil {
   319  		return err
   320  	}
   321  	return recordToResourceData(d, r)
   322  }
   323  
   324  // RecordRead reads the DNS record from ns1
   325  func RecordRead(d *schema.ResourceData, meta interface{}) error {
   326  	client := meta.(*ns1.Client)
   327  
   328  	r, _, err := client.Records.Get(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
   329  	if err != nil {
   330  		return err
   331  	}
   332  
   333  	return recordToResourceData(d, r)
   334  }
   335  
   336  // RecordDelete deltes the DNS record from ns1
   337  func RecordDelete(d *schema.ResourceData, meta interface{}) error {
   338  	client := meta.(*ns1.Client)
   339  	_, err := client.Records.Delete(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
   340  	d.SetId("")
   341  	return err
   342  }
   343  
   344  // RecordUpdate updates the given dns record in ns1
   345  func RecordUpdate(d *schema.ResourceData, meta interface{}) error {
   346  	client := meta.(*ns1.Client)
   347  	r := dns.NewRecord(d.Get("zone").(string), d.Get("domain").(string), d.Get("type").(string))
   348  	if err := resourceDataToRecord(r, d); err != nil {
   349  		return err
   350  	}
   351  	if _, err := client.Records.Update(r); err != nil {
   352  		return err
   353  	}
   354  	return recordToResourceData(d, r)
   355  }
   356  
   357  func RecordStateFunc(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
   358  	parts := strings.Split(d.Id(), "/")
   359  	if len(parts) != 3 {
   360  		return nil, fmt.Errorf("Invalid record specifier.  Expecting 2 slashes (\"zone/domain/type\"), got %d.", len(parts)-1)
   361  	}
   362  
   363  	d.Set("zone", parts[0])
   364  	d.Set("domain", parts[1])
   365  	d.Set("type", parts[2])
   366  
   367  	return []*schema.ResourceData{d}, nil
   368  }