yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/aws/dnshostedzone.go (about) 1 // Copyright 2019 Yunion 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package aws 16 17 import ( 18 "time" 19 20 "github.com/aws/aws-sdk-go/aws/awserr" 21 "github.com/aws/aws-sdk-go/service/route53" 22 23 "yunion.io/x/jsonutils" 24 "yunion.io/x/pkg/errors" 25 26 api "yunion.io/x/cloudmux/pkg/apis/compute" 27 "yunion.io/x/cloudmux/pkg/cloudprovider" 28 "yunion.io/x/cloudmux/pkg/multicloud" 29 ) 30 31 type HostedZoneConfig struct { 32 Comment string `json:"Comment"` 33 PrivateZone bool `json:"PrivateZone"` 34 } 35 36 type AssociatedVPC struct { 37 VPCId string `json:"VPCId"` 38 VPCRegion string `json:"VPCRegion"` 39 } 40 41 type SHostedZone struct { 42 multicloud.SResourceBase 43 AwsTags 44 client *SAwsClient 45 46 ID string `json:"Id"` 47 Name string `json:"Name"` 48 Config HostedZoneConfig `json:"Config"` 49 ResourceRecordSetCount int64 `json:"ResourceRecordSetCount"` 50 } 51 52 func (self *SHostedZone) GetId() string { 53 return self.ID 54 } 55 56 func (self *SHostedZone) GetName() string { 57 return self.Name 58 } 59 60 func (self *SHostedZone) GetGlobalId() string { 61 return self.ID 62 } 63 64 func (self *SHostedZone) GetStatus() string { 65 return api.DNS_ZONE_STATUS_AVAILABLE 66 } 67 68 func (self *SHostedZone) Refresh() error { 69 hostedZone, err := self.client.GetHostedZoneById(self.ID) 70 if err != nil { 71 return errors.Wrapf(err, "self.client.GetHostedZoneById(%s)", self.ID) 72 } 73 74 return jsonutils.Update(self, hostedZone) 75 } 76 77 func (client *SAwsClient) ListGeoLocations() ([]*route53.GeoLocationDetails, error) { 78 s, err := client.getAwsRoute53Session() 79 if err != nil { 80 return nil, errors.Wrap(err, "region.getAwsRoute53Session()") 81 } 82 route53Client := route53.New(s) 83 locations := []*route53.GeoLocationDetails{} 84 params := route53.ListGeoLocationsInput{} 85 max := "100" 86 for { 87 params.MaxItems = &max 88 ret, err := route53Client.ListGeoLocations(¶ms) 89 if err != nil { 90 return nil, errors.Wrapf(err, "route53Client.ListGeoLocations(%s)", jsonutils.Marshal(params).String()) 91 } 92 locations = append(locations, ret.GeoLocationDetailsList...) 93 if ret.IsTruncated != nil && !*ret.IsTruncated { 94 break 95 } 96 97 params.StartContinentCode = ret.NextContinentCode 98 params.StartCountryCode = ret.NextCountryCode 99 params.StartSubdivisionCode = ret.NextSubdivisionCode 100 } 101 return locations, nil 102 } 103 104 func (client *SAwsClient) CreateHostedZone(opts *cloudprovider.SDnsZoneCreateOptions) (*SHostedZone, error) { 105 s, err := client.getAwsRoute53Session() 106 if err != nil { 107 return nil, errors.Wrap(err, "region.getAwsRoute53Session()") 108 } 109 route53Client := route53.New(s) 110 params := route53.CreateHostedZoneInput{} 111 timeStirng := time.Now().String() 112 params.CallerReference = &timeStirng 113 params.Name = &opts.Name 114 115 Config := route53.HostedZoneConfig{} 116 var IsPrivate bool 117 if opts.ZoneType == cloudprovider.PrivateZone { 118 IsPrivate = true 119 } 120 Config.Comment = &opts.Desc 121 Config.PrivateZone = &IsPrivate 122 params.HostedZoneConfig = &Config 123 124 if len(opts.Vpcs) > 0 { 125 vpc := route53.VPC{} 126 vpc.VPCId = &opts.Vpcs[0].Id 127 vpc.VPCRegion = &opts.Vpcs[0].RegionId 128 params.SetVPC(&vpc) 129 } 130 131 ret, err := route53Client.CreateHostedZone(¶ms) 132 if err != nil { 133 return nil, errors.Wrap(err, "route53Client.GetHostedZone()") 134 } 135 hostedzone := SHostedZone{} 136 err = unmarshalAwsOutput(ret, "HostedZone", &hostedzone) 137 if err != nil { 138 return nil, errors.Wrap(err, "unmarshalAwsOutput(HostedZone)") 139 } 140 for i := 1; i < len(opts.Vpcs); i++ { 141 err := client.AssociateVPCWithHostedZone(opts.Vpcs[i].Id, opts.Vpcs[i].RegionId, hostedzone.ID) 142 if err != nil { 143 return nil, errors.Wrapf(err, "client.AssociateVPCWithHostedZone(%s,%s,%s)", opts.Vpcs[i].Id, opts.Vpcs[i].RegionId, hostedzone.ID) 144 } 145 } 146 return client.GetHostedZoneById(hostedzone.ID) 147 } 148 149 func (client *SAwsClient) DeleteHostedZone(Id string) error { 150 // client 151 s, err := client.getAwsRoute53Session() 152 if err != nil { 153 return errors.Wrap(err, "region.getAwsRoute53Session()") 154 } 155 route53Client := route53.New(s) 156 157 // fetch records 158 resourceRecordSets, err := client.GetRoute53ResourceRecordSets(Id) 159 if err != nil { 160 return errors.Wrapf(err, "client.GetRoute53ResourceRecordSets(%s)", Id) 161 } 162 // prepare batch and delete 163 deleteRecordSets := []*route53.ResourceRecordSet{} 164 for i := 0; i < len(resourceRecordSets); i++ { 165 var dnsType string 166 if resourceRecordSets[i].Type != nil { 167 dnsType = *resourceRecordSets[i].Type 168 } 169 if dnsType == "NS" || dnsType == "SOA" { 170 continue 171 } 172 deleteRecordSets = append(deleteRecordSets, resourceRecordSets[i]) 173 } 174 if len(deleteRecordSets) > 0 { 175 err = client.ChangeResourceRecordSets("DELETE", Id, deleteRecordSets...) 176 if err != nil { 177 return errors.Wrapf(err, "client.ChangeResourceRecordSets(DELETE, %s, deleteRecordSets)", Id) 178 } 179 } 180 // delete hostedzone 181 params := route53.DeleteHostedZoneInput{} 182 params.Id = &Id 183 _, err = route53Client.DeleteHostedZone(¶ms) 184 if err != nil { 185 return errors.Wrapf(err, "route53Client.DeleteHostedZone(%s)", Id) 186 } 187 return nil 188 } 189 190 func (client *SAwsClient) CreateICloudDnsZone(opts *cloudprovider.SDnsZoneCreateOptions) (cloudprovider.ICloudDnsZone, error) { 191 return client.CreateHostedZone(opts) 192 } 193 194 func (client *SAwsClient) GetHostedZones() ([]SHostedZone, error) { 195 s, err := client.getAwsRoute53Session() 196 if err != nil { 197 return nil, errors.Wrap(err, "region.getAwsRoute53Session()") 198 } 199 route53Client := route53.New(s) 200 result := []SHostedZone{} 201 Marker := "" 202 MaxItems := "100" 203 params := route53.ListHostedZonesInput{} 204 for true { 205 if len(Marker) > 0 { 206 params.Marker = &Marker 207 } 208 params.MaxItems = &MaxItems 209 ret, err := route53Client.ListHostedZones(¶ms) 210 if err != nil { 211 return nil, errors.Wrap(err, "route53Client.ListHostedZones(nil)") 212 } 213 hostedZones := []SHostedZone{} 214 err = unmarshalAwsOutput(ret, "HostedZones", &hostedZones) 215 if err != nil { 216 return nil, errors.Wrap(err, "unmarshalAwsOutput(HostedZones)") 217 } 218 result = append(result, hostedZones...) 219 if !*ret.IsTruncated { 220 break 221 } 222 if ret.Marker != nil { 223 Marker = *ret.Marker 224 } 225 226 } 227 for i := 0; i < len(result); i++ { 228 result[i].client = client 229 } 230 231 return result, nil 232 } 233 234 func (client *SAwsClient) GetHostedZoneVpcs(hostedzoneId string) ([]AssociatedVPC, error) { 235 s, err := client.getAwsRoute53Session() 236 if err != nil { 237 return nil, errors.Wrap(err, "region.getAwsRoute53Session()") 238 } 239 route53Client := route53.New(s) 240 params := route53.GetHostedZoneInput{} 241 params.Id = &hostedzoneId 242 ret, err := route53Client.GetHostedZone(¶ms) 243 if err != nil { 244 if err, ok := err.(awserr.Error); ok { 245 if err.Code() == route53.ErrCodeNoSuchHostedZone { 246 return nil, errors.Wrap(cloudprovider.ErrNotFound, err.Error()) 247 } 248 } 249 return nil, errors.Wrap(err, "route53Client.GetHostedZone()") 250 } 251 vpcs := []AssociatedVPC{} 252 err = unmarshalAwsOutput(ret.VPCs, "", &vpcs) 253 if err != nil { 254 return nil, errors.Wrap(err, "unmarshalAwsOutput(HostedZones)") 255 } 256 return vpcs, nil 257 } 258 259 func (client *SAwsClient) GetICloudDnsZones() ([]cloudprovider.ICloudDnsZone, error) { 260 hostedZones, err := client.GetHostedZones() 261 if err != nil { 262 return nil, errors.Wrap(err, "client.GetHostedZones()") 263 } 264 result := []cloudprovider.ICloudDnsZone{} 265 for i := 0; i < len(hostedZones); i++ { 266 hostedZones[i].client = client 267 result = append(result, &hostedZones[i]) 268 } 269 return result, nil 270 } 271 272 func (client *SAwsClient) GetHostedZoneById(ID string) (*SHostedZone, error) { 273 s, err := client.getAwsRoute53Session() 274 if err != nil { 275 return nil, errors.Wrap(err, "region.getAwsRoute53Session()") 276 } 277 route53Client := route53.New(s) 278 params := route53.GetHostedZoneInput{} 279 params.Id = &ID 280 ret, err := route53Client.GetHostedZone(¶ms) 281 if err != nil { 282 if aerr, ok := err.(awserr.Error); ok { 283 if aerr.Code() == route53.ErrCodeNoSuchHostedZone { 284 return nil, errors.Wrap(cloudprovider.ErrNotFound, err.Error()) 285 } 286 } 287 return nil, errors.Wrap(err, "route53Client.GetHostedZone()") 288 } 289 290 result := SHostedZone{client: client} 291 err = unmarshalAwsOutput(ret, "HostedZone", &result) 292 if err != nil { 293 return nil, errors.Wrap(err, "unmarshalAwsOutput(HostedZone)") 294 } 295 return &result, nil 296 } 297 298 func (client *SAwsClient) AssociateVPCWithHostedZone(vpcId string, regionId string, hostedZoneId string) error { 299 s, err := client.getAwsRoute53Session() 300 if err != nil { 301 return errors.Wrap(err, "region.getAwsRoute53Session()") 302 } 303 route53Client := route53.New(s) 304 params := route53.AssociateVPCWithHostedZoneInput{} 305 vpcParams := route53.VPC{} 306 vpcParams.VPCId = &vpcId 307 vpcParams.VPCRegion = ®ionId 308 params.VPC = &vpcParams 309 params.HostedZoneId = &hostedZoneId 310 311 _, err = route53Client.AssociateVPCWithHostedZone(¶ms) 312 if err != nil { 313 return errors.Wrap(err, "route53Client.AssociateVPCWithHostedZone()") 314 } 315 return nil 316 } 317 318 func (client *SAwsClient) DisassociateVPCFromHostedZone(vpcId string, regionId string, hostedZoneId string) error { 319 s, err := client.getAwsRoute53Session() 320 if err != nil { 321 return errors.Wrap(err, "region.getAwsRoute53Session()") 322 } 323 route53Client := route53.New(s) 324 params := route53.DisassociateVPCFromHostedZoneInput{} 325 vpcParams := route53.VPC{} 326 vpcParams.VPCId = &vpcId 327 vpcParams.VPCRegion = ®ionId 328 params.VPC = &vpcParams 329 params.HostedZoneId = &hostedZoneId 330 331 _, err = route53Client.DisassociateVPCFromHostedZone(¶ms) 332 if err != nil { 333 return errors.Wrap(err, "route53Client.AssociateVPCWithHostedZone()") 334 } 335 return nil 336 } 337 338 func (self *SHostedZone) Delete() error { 339 return self.client.DeleteHostedZone(self.ID) 340 } 341 342 func (self *SHostedZone) GetZoneType() cloudprovider.TDnsZoneType { 343 if self.Config.PrivateZone { 344 return cloudprovider.PrivateZone 345 } 346 return cloudprovider.PublicZone 347 } 348 349 func (self *SHostedZone) GetOptions() *jsonutils.JSONDict { 350 return nil 351 } 352 353 func (self *SHostedZone) GetICloudVpcIds() ([]string, error) { 354 ret := []string{} 355 if self.Config.PrivateZone { 356 vpcs, err := self.client.GetHostedZoneVpcs(self.ID) 357 if err != nil { 358 return nil, errors.Wrapf(err, "self.client.GetHostedZoneVpcs(%s)", self.ID) 359 } 360 for i := range vpcs { 361 ret = append(ret, vpcs[i].VPCId) 362 } 363 return ret, nil 364 } 365 return ret, errors.Wrapf(cloudprovider.ErrNotSupported, "not a private hostedzone") 366 } 367 368 func (self *SHostedZone) AddVpc(vpc *cloudprovider.SPrivateZoneVpc) error { 369 if self.Config.PrivateZone { 370 err := self.client.AssociateVPCWithHostedZone(vpc.Id, vpc.RegionId, self.ID) 371 if err != nil { 372 return errors.Wrapf(err, "self.client.associateVPCWithHostedZone(%s,%s,%s)", vpc.Id, vpc.RegionId, self.ID) 373 } 374 } else { 375 return errors.Wrap(cloudprovider.ErrNotSupported, "public hostedZone not support associate vpc") 376 } 377 return nil 378 } 379 380 func (self *SHostedZone) RemoveVpc(vpc *cloudprovider.SPrivateZoneVpc) error { 381 if self.Config.PrivateZone { 382 err := self.client.DisassociateVPCFromHostedZone(vpc.Id, vpc.RegionId, self.ID) 383 if err != nil { 384 return errors.Wrapf(err, "self.client.disassociateVPCFromHostedZone(%s,%s,%s)", vpc.Id, vpc.RegionId, self.ID) 385 } 386 } else { 387 return errors.Wrap(cloudprovider.ErrNotSupported, "public hostedZone not support disassociate vpc") 388 } 389 return nil 390 } 391 392 func (self *SHostedZone) GetIDnsRecordSets() ([]cloudprovider.ICloudDnsRecordSet, error) { 393 recordSets, err := self.client.GetSdnsRecordSets(self.ID) 394 if err != nil { 395 return nil, errors.Wrapf(err, "self.client.GetSdnsRecordSets(%s)", self.ID) 396 } 397 398 result := []cloudprovider.ICloudDnsRecordSet{} 399 for i := 0; i < len(recordSets); i++ { 400 recordSets[i].hostedZone = self 401 result = append(result, &recordSets[i]) 402 } 403 return result, nil 404 } 405 406 func (self *SHostedZone) SyncDnsRecordSets(common, add, del, update []cloudprovider.DnsRecordSet) error { 407 for i := 0; i < len(del); i++ { 408 err := self.RemoveDnsRecordSet(&del[i]) 409 if err != nil { 410 return errors.Wrap(err, "self.RemoveDnsRecordSet()") 411 } 412 } 413 for i := 0; i < len(add); i++ { 414 err := self.AddDnsRecordSet(&add[i]) 415 if err != nil { 416 return errors.Wrap(err, "self.AddDnsRecordSet()") 417 } 418 } 419 for i := 0; i < len(update); i++ { 420 err := self.UpdateDnsRecordSet(&update[i]) 421 if err != nil { 422 return errors.Wrap(err, "self.UpdateDnsRecordSet()") 423 } 424 } 425 return nil 426 } 427 428 func (self *SHostedZone) AddDnsRecordSet(opts *cloudprovider.DnsRecordSet) error { 429 if len(opts.DnsName) < 1 || opts.DnsName == "@" { 430 opts.DnsName = self.Name 431 } else { 432 opts.DnsName = opts.DnsName + "." + self.Name 433 } 434 return self.client.AddDnsRecordSet(self.ID, opts) 435 } 436 437 func (self *SHostedZone) UpdateDnsRecordSet(opts *cloudprovider.DnsRecordSet) error { 438 if len(opts.DnsName) < 1 || opts.DnsName == "@" { 439 opts.DnsName = self.Name 440 } else { 441 opts.DnsName = opts.DnsName + "." + self.Name 442 } 443 return self.client.UpdateDnsRecordSet(self.ID, opts) 444 } 445 446 func (self *SHostedZone) RemoveDnsRecordSet(opts *cloudprovider.DnsRecordSet) error { 447 if len(opts.DnsName) < 1 || opts.DnsName == "@" { 448 opts.DnsName = self.Name 449 } else { 450 opts.DnsName = opts.DnsName + "." + self.Name 451 } 452 return self.client.RemoveDnsRecordSet(self.ID, opts) 453 } 454 455 func (self *SHostedZone) GetDnsProductType() cloudprovider.TDnsProductType { 456 return "" 457 }