github.com/pmoroney/dnscontrol@v0.2.4-0.20171024134423-fad98f73f44a/providers/softlayer/softlayer.go (about) 1 package softlayer 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "regexp" 7 "strings" 8 9 "github.com/StackExchange/dnscontrol/models" 10 "github.com/StackExchange/dnscontrol/providers" 11 "github.com/StackExchange/dnscontrol/providers/diff" 12 "github.com/miekg/dns/dnsutil" 13 14 "github.com/softlayer/softlayer-go/datatypes" 15 "github.com/softlayer/softlayer-go/filter" 16 "github.com/softlayer/softlayer-go/services" 17 "github.com/softlayer/softlayer-go/session" 18 ) 19 20 type SoftLayer struct { 21 Session *session.Session 22 } 23 24 func init() { 25 providers.RegisterDomainServiceProviderType("SOFTLAYER", newReg, providers.CanUseSRV) 26 } 27 28 func newReg(conf map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) { 29 s := session.New(conf["username"], conf["api_key"], conf["endpoint_url"], conf["timeout"]) 30 31 if len(s.UserName) == 0 || len(s.APIKey) == 0 { 32 return nil, fmt.Errorf("SoftLayer UserName and APIKey must be provided") 33 } 34 35 //s.Debug = true 36 37 api := &SoftLayer{ 38 Session: s, 39 } 40 41 return api, nil 42 } 43 44 func (s *SoftLayer) GetNameservers(domain string) ([]*models.Nameserver, error) { 45 // Always use the same nameservers for softlayer 46 nservers := []string{"ns1.softlayer.com", "ns2.softlayer.com"} 47 return models.StringsToNameservers(nservers), nil 48 } 49 50 func (s *SoftLayer) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { 51 corrections := []*models.Correction{} 52 53 domain, err := s.getDomain(&dc.Name) 54 55 if err != nil { 56 return nil, err 57 } 58 59 actual, err := s.getExistingRecords(domain) 60 61 if err != nil { 62 return nil, err 63 } 64 65 _, create, delete, modify := diff.New(dc).IncrementalDiff(actual) 66 67 for _, del := range delete { 68 existing := del.Existing.Original.(datatypes.Dns_Domain_ResourceRecord) 69 corrections = append(corrections, &models.Correction{ 70 Msg: del.String(), 71 F: s.deleteRecordFunc(*existing.Id), 72 }) 73 } 74 75 for _, cre := range create { 76 corrections = append(corrections, &models.Correction{ 77 Msg: cre.String(), 78 F: s.createRecordFunc(cre.Desired, domain), 79 }) 80 } 81 82 for _, mod := range modify { 83 existing := mod.Existing.Original.(datatypes.Dns_Domain_ResourceRecord) 84 corrections = append(corrections, &models.Correction{ 85 Msg: mod.String(), 86 F: s.updateRecordFunc(&existing, mod.Desired), 87 }) 88 } 89 90 return corrections, nil 91 } 92 93 func (s *SoftLayer) getDomain(name *string) (*datatypes.Dns_Domain, error) { 94 domains, err := services.GetAccountService(s.Session). 95 Filter(filter.Path("domains.name").Eq(name).Build()). 96 Mask("resourceRecords"). 97 GetDomains() 98 99 if err != nil { 100 return nil, err 101 } 102 103 if len(domains) == 0 { 104 return nil, fmt.Errorf("Didn't find a domain matching %s", *name) 105 } else if len(domains) > 1 { 106 return nil, fmt.Errorf("Found %d domains matching %s", len(domains), *name) 107 } 108 109 return &domains[0], nil 110 } 111 112 func (s *SoftLayer) getExistingRecords(domain *datatypes.Dns_Domain) ([]*models.RecordConfig, error) { 113 actual := []*models.RecordConfig{} 114 115 for _, record := range domain.ResourceRecords { 116 recType := strings.ToUpper(*record.Type) 117 118 if recType == "SOA" { 119 continue 120 } 121 122 recConfig := &models.RecordConfig{ 123 Type: recType, 124 Target: *record.Data, 125 TTL: uint32(*record.Ttl), 126 Original: record, 127 } 128 129 switch recType { 130 case "SRV": 131 var service, protocol string = "", "_tcp" 132 133 if record.Weight != nil { 134 recConfig.SrvWeight = uint16(*record.Weight) 135 } 136 if record.Port != nil { 137 recConfig.SrvPort = uint16(*record.Port) 138 } 139 if record.Priority != nil { 140 recConfig.SrvPriority = uint16(*record.Priority) 141 } 142 if record.Protocol != nil { 143 protocol = *record.Protocol 144 } 145 if record.Service != nil { 146 service = *record.Service 147 } 148 149 recConfig.Name = fmt.Sprintf("%s.%s", service, strings.ToLower(protocol)) 150 151 case "MX": 152 if record.MxPriority != nil { 153 recConfig.MxPreference = uint16(*record.MxPriority) 154 } 155 156 fallthrough 157 158 default: 159 recConfig.Name = *record.Host 160 } 161 162 recConfig.NameFQDN = dnsutil.AddOrigin(recConfig.Name, *domain.Name) 163 actual = append(actual, recConfig) 164 } 165 166 return actual, nil 167 } 168 169 func (s *SoftLayer) createRecordFunc(desired *models.RecordConfig, domain *datatypes.Dns_Domain) func() error { 170 var ttl, preference, domainId int = int(desired.TTL), int(desired.MxPreference), *domain.Id 171 var weight, priority, port int = int(desired.SrvWeight), int(desired.SrvPriority), int(desired.SrvPort) 172 var host, data, newType string = desired.Name, desired.Target, desired.Type 173 var err error = nil 174 175 srvRegexp := regexp.MustCompile(`^_(?P<Service>\w+)\.\_(?P<Protocol>\w+)$`) 176 177 return func() error { 178 newRecord := datatypes.Dns_Domain_ResourceRecord{ 179 DomainId: &domainId, 180 Ttl: &ttl, 181 Type: &newType, 182 Data: &data, 183 Host: &host, 184 } 185 186 switch newType { 187 case "MX": 188 service := services.GetDnsDomainResourceRecordMxTypeService(s.Session) 189 190 newRecord.MxPriority = &preference 191 192 newMx := datatypes.Dns_Domain_ResourceRecord_MxType{ 193 Dns_Domain_ResourceRecord: newRecord, 194 } 195 196 _, err = service.CreateObject(&newMx) 197 198 case "SRV": 199 service := services.GetDnsDomainResourceRecordSrvTypeService(s.Session) 200 result := srvRegexp.FindStringSubmatch(host) 201 202 if len(result) != 3 { 203 return fmt.Errorf("SRV Record must match format \"_service._protocol\" not %s", host) 204 } 205 206 var serviceName, protocol string = result[1], strings.ToLower(result[2]) 207 208 newSrv := datatypes.Dns_Domain_ResourceRecord_SrvType{ 209 Dns_Domain_ResourceRecord: newRecord, 210 Service: &serviceName, 211 Port: &port, 212 Priority: &priority, 213 Protocol: &protocol, 214 Weight: &weight, 215 } 216 217 _, err = service.CreateObject(&newSrv) 218 219 default: 220 service := services.GetDnsDomainResourceRecordService(s.Session) 221 _, err = service.CreateObject(&newRecord) 222 } 223 224 return err 225 } 226 } 227 228 func (s *SoftLayer) deleteRecordFunc(resId int) func() error { 229 // seems to be no problem deleting MX and SRV records via common interface 230 return func() error { 231 _, err := services.GetDnsDomainResourceRecordService(s.Session). 232 Id(resId). 233 DeleteObject() 234 235 return err 236 } 237 } 238 239 func (s *SoftLayer) updateRecordFunc(existing *datatypes.Dns_Domain_ResourceRecord, desired *models.RecordConfig) func() error { 240 var ttl, preference int = int(desired.TTL), int(desired.MxPreference) 241 var priority, weight, port int = int(desired.SrvPriority), int(desired.SrvWeight), int(desired.SrvPort) 242 243 return func() error { 244 var changes bool = false 245 var err error = nil 246 247 switch desired.Type { 248 case "MX": 249 service := services.GetDnsDomainResourceRecordMxTypeService(s.Session) 250 updated := datatypes.Dns_Domain_ResourceRecord_MxType{} 251 252 if desired.Name != *existing.Host { 253 updated.Host = &desired.Name 254 changes = true 255 } 256 257 if desired.Target != *existing.Data { 258 updated.Data = &desired.Target 259 changes = true 260 } 261 262 if ttl != *existing.Ttl { 263 updated.Ttl = &ttl 264 changes = true 265 } 266 267 if preference != *existing.MxPriority { 268 updated.MxPriority = &preference 269 changes = true 270 } 271 272 if !changes { 273 return fmt.Errorf("Error: Didn't find changes when I expect some.") 274 } 275 276 _, err = service.Id(*existing.Id).EditObject(&updated) 277 278 case "SRV": 279 service := services.GetDnsDomainResourceRecordSrvTypeService(s.Session) 280 updated := datatypes.Dns_Domain_ResourceRecord_SrvType{} 281 282 if desired.Name != *existing.Host { 283 updated.Host = &desired.Name 284 changes = true 285 } 286 287 if desired.Target != *existing.Data { 288 updated.Data = &desired.Target 289 changes = true 290 } 291 292 if ttl != *existing.Ttl { 293 updated.Ttl = &ttl 294 changes = true 295 } 296 297 if priority != *existing.Priority { 298 updated.Priority = &priority 299 changes = true 300 } 301 302 if weight != *existing.Weight { 303 updated.Weight = &weight 304 changes = true 305 } 306 307 if port != *existing.Port { 308 updated.Port = &port 309 changes = true 310 } 311 312 // TODO: handle service & protocol - or does that just result in a 313 // delete and recreate? 314 315 if !changes { 316 return fmt.Errorf("Error: Didn't find changes when I expect some.") 317 } 318 319 _, err = service.Id(*existing.Id).EditObject(&updated) 320 321 default: 322 service := services.GetDnsDomainResourceRecordService(s.Session) 323 updated := datatypes.Dns_Domain_ResourceRecord{} 324 325 if desired.Name != *existing.Host { 326 updated.Host = &desired.Name 327 changes = true 328 } 329 330 if desired.Target != *existing.Data { 331 updated.Data = &desired.Target 332 changes = true 333 } 334 335 if ttl != *existing.Ttl { 336 updated.Ttl = &ttl 337 changes = true 338 } 339 340 if !changes { 341 return fmt.Errorf("Error: Didn't find changes when I expect some.") 342 } 343 344 _, err = service.Id(*existing.Id).EditObject(&updated) 345 } 346 347 return err 348 } 349 }