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