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(&params)
    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(&params)
   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(&params)
   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(&params)
   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(&params)
   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(&params)
   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 = &regionId
   308  	params.VPC = &vpcParams
   309  	params.HostedZoneId = &hostedZoneId
   310  
   311  	_, err = route53Client.AssociateVPCWithHostedZone(&params)
   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 = &regionId
   328  	params.VPC = &vpcParams
   329  	params.HostedZoneId = &hostedZoneId
   330  
   331  	_, err = route53Client.DisassociateVPCFromHostedZone(&params)
   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  }