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(®ion.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 }