github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/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(®ion.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", "SPF": 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 }