From bef1b6b62eb27fbf66de6cb05db3e28f59a17435 Mon Sep 17 00:00:00 2001 From: Hamid Ostadvali Date: Sat, 28 Dec 2024 12:54:19 +0330 Subject: [PATCH 1/2] feature: added alidns weighted records to provider --- .gitignore | 3 +- alicloud/provider.go | 2 + .../resource_alicloud_alidns_record_weight.go | 368 ++++++++++++++++++ alicloud/service_alicloud_alidns.go | 18 + 4 files changed, 390 insertions(+), 1 deletion(-) create mode 100644 alicloud/resource_alicloud_alidns_record_weight.go diff --git a/.gitignore b/.gitignore index 56eafabe9831..1a57fa6b1e35 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ website/vendor !command/test-fixtures/**/*.tfstate !command/test-fixtures/**/.terraform/ diff_*.zip -scan.py \ No newline at end of file +scan.py +terraform-provider-alicloud diff --git a/alicloud/provider.go b/alicloud/provider.go index 596c1c3c6618..c0e860536fdb 100644 --- a/alicloud/provider.go +++ b/alicloud/provider.go @@ -1140,6 +1140,8 @@ func Provider() terraform.ResourceProvider { "alicloud_alikafka_sasl_user": resourceAliCloudAlikafkaSaslUser(), "alicloud_alikafka_sasl_acl": resourceAlicloudAlikafkaSaslAcl(), "alicloud_dns_record": resourceAlicloudDnsRecord(), + "alicloud_alidns_record_weight": resourceAlicloudAlidnsRecordWeight(), + "alicloud_alidns_wrr": resourceAlicloudAlidnsWRR(), "alicloud_dns": resourceAlicloudDns(), "alicloud_dns_group": resourceAlicloudDnsGroup(), "alicloud_key_pair": resourceAliCloudEcsKeyPair(), diff --git a/alicloud/resource_alicloud_alidns_record_weight.go b/alicloud/resource_alicloud_alidns_record_weight.go new file mode 100644 index 000000000000..134de51e6467 --- /dev/null +++ b/alicloud/resource_alicloud_alidns_record_weight.go @@ -0,0 +1,368 @@ +package alicloud + +import ( + "fmt" + "log" + "strings" + + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" + "github.com/aliyun/alibaba-cloud-sdk-go/services/alidns" + "github.com/aliyun/terraform-provider-alicloud/alicloud/connectivity" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourceAlicloudAlidnsRecordWeight() *schema.Resource { + return &schema.Resource{ + Create: resourceAlicloudAlidnsRecordWeightCreate, + Read: resourceAlicloudAlidnsRecordWeightRead, + Update: resourceAlicloudAlidnsRecordWeightUpdate, + Delete: resourceAlicloudAlidnsRecordWeightDelete, + Importer: &schema.ResourceImporter{ + State: resourceAlicloudAlidnsRecordWeightImport, + }, + Schema: map[string]*schema.Schema{ + "domain_name": { + Type: schema.TypeString, + Required: true, + }, + "line": { + Type: schema.TypeString, + Optional: true, + Default: "default", + }, + "priority": { + Type: schema.TypeInt, + Optional: true, + DiffSuppressFunc: dnsPriorityDiffSuppressFunc, + }, + "rr": { + Type: schema.TypeString, + Required: true, + }, + "remark": { + Type: schema.TypeString, + Optional: true, + }, + "status": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"ENABLE", "DISABLE"}, false), + Default: "ENABLE", + }, + "ttl": { + Type: schema.TypeInt, + Optional: true, + Default: 600, + }, + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"A", "NS", "MX", "TXT", "CNAME", "SRV", "AAAA", "CAA", "REDIRECT_URL", "FORWORD_URL"}, false), + }, + "value": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: dnsValueDiffSuppressFunc, + }, + "weight": { + Type: schema.TypeInt, + Optional: true, + Default: 0, + ValidateFunc: validation.IntBetween(0, 100), + }, + "wrr_status": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"ENABLE", "DISABLE"}, false), + Default: "ENABLE", + }, + }, + } +} + +func resourceAlicloudAlidnsRecordWeightCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*connectivity.AliyunClient) + alidnsService := AlidnsService{client} + + // Step 1: Check for existing records + domainName := d.Get("domain_name").(string) + rr := d.Get("rr").(string) + recordType := d.Get("type").(string) + recordValue := d.Get("value").(string) + + records, err := alidnsService.DescribeDomainRecords(domainName) + if err != nil { + return WrapError(err) + } + + // Look for a matching record + var existingRecord *alidns.Record + for _, record := range records { + if record.RR == rr && record.Type == recordType && record.Value == recordValue { + existingRecord = &record + break + } + } + + // Step 2: If record exists, update weight and WRR status + if existingRecord != nil { + log.Printf("[DEBUG] Found existing record: %s", existingRecord.RecordId) + + // Enable WRR for the subdomain if needed + wrrStatus := d.Get("wrr_status").(string) + if wrrStatus == "ENABLE" { + err = alidnsService.SetWRRStatus(domainName, rr, wrrStatus) + if err != nil { + return WrapError(err) + } + + // Update the weight for the existing record + if weight, ok := d.GetOk("weight"); ok && weight.(int) > 0 { + err = alidnsService.SetRecordWeight(existingRecord.RecordId, weight.(int)) + if err != nil { + return WrapError(err) + } + } + } + + // Set the existing record's ID + d.SetId(existingRecord.RecordId) + return resourceAlicloudAlidnsRecordWeightRead(d, meta) + } + + // Step 3: If no existing record, create a new one + log.Printf("[DEBUG] No existing record found. Creating a new record for %s", rr) + + request := alidns.CreateAddDomainRecordRequest() + request.RegionId = client.RegionId + request.DomainName = domainName + request.RR = rr + request.Type = recordType + request.Value = recordValue + request.TTL = requests.NewInteger(d.Get("ttl").(int)) + request.Line = d.Get("line").(string) + + response, err := client.WithAlidnsClient(func(alidnsClient *alidns.Client) (interface{}, error) { + return alidnsClient.AddDomainRecord(request) + }) + if err != nil { + return WrapError(err) + } + + resp, _ := response.(*alidns.AddDomainRecordResponse) + d.SetId(resp.RecordId) + + // Enable WRR and set weight for the new record + wrrStatus := d.Get("wrr_status").(string) + if wrrStatus == "ENABLE" { + err = alidnsService.SetWRRStatus(domainName, rr, wrrStatus) + if err != nil { + return WrapError(err) + } + + if weight, ok := d.GetOk("weight"); ok && weight.(int) > 0 { + err = alidnsService.SetRecordWeight(resp.RecordId, weight.(int)) + if err != nil { + return WrapError(err) + } + } + } + + return resourceAlicloudAlidnsRecordWeightRead(d, meta) +} + +func resourceAlicloudAlidnsRecordWeightUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*connectivity.AliyunClient) + alidnsService := AlidnsService{client} + + // Step 1: Update WRR status if changed + if d.HasChange("wrr_status") { + wrrStatus := d.Get("wrr_status").(string) + err := alidnsService.SetWRRStatus(d.Get("domain_name").(string), d.Get("rr").(string), wrrStatus) + if err != nil { + return WrapError(err) + } + } + + // Step 2: Update weight only if WRR is enabled + if d.HasChange("weight") && d.Get("wrr_status").(string) == "ENABLE" { + err := alidnsService.SetRecordWeight(d.Id(), d.Get("weight").(int)) + if err != nil { + return WrapError(err) + } + } else if d.Get("wrr_status").(string) == "DISABLE" { + log.Printf("[DEBUG] WRR is disabled, skipping weight update for record ID: %s", d.Id()) + } + + return resourceAlicloudAlidnsRecordWeightRead(d, meta) +} + +func resourceAlicloudAlidnsRecordWeightRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*connectivity.AliyunClient) + alidnsService := AlidnsService{client} + + recordID := d.Id() + + // Use DescribeDomainRecordInfo to fetch detailed record info + record, err := alidnsService.DescribeDomainRecordById(recordID) + if err != nil { + if IsExpectedErrors(err, []string{"InvalidRecordId.NotFound"}) { + log.Printf("[DEBUG] Record not found: %s", recordID) + d.SetId("") + return nil + } + return WrapError(err) + } + + // Populate resource data + d.Set("domain_name", record.DomainName) + d.Set("rr", record.RR) + d.Set("type", record.Type) + d.Set("value", record.Value) + d.Set("ttl", record.TTL) + d.Set("line", record.Line) + d.Set("weight", record.Weight) + d.Set("priority", record.Priority) // Map Priority + d.Set("remark", record.Remark) // Map Remark + + // Determine WRR status based on weight + if record.Weight > 0 { + d.Set("wrr_status", "ENABLE") + } else { + d.Set("wrr_status", "DISABLE") + } + + return nil +} + +func resourceAlicloudAlidnsRecordWeightDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*connectivity.AliyunClient) + + request := alidns.CreateDeleteDomainRecordRequest() + request.RegionId = client.RegionId + request.RecordId = d.Id() + + _, err := client.WithAlidnsClient(func(alidnsClient *alidns.Client) (interface{}, error) { + return alidnsClient.DeleteDomainRecord(request) + }) + if err != nil { + if IsExpectedErrors(err, []string{"DomainRecordNotBelongToUser", "InvalidRecordId.NotFound"}) { + return nil + } + return WrapError(err) + } + + d.SetId("") + return nil +} + +func resourceAlicloudAlidnsRecordWeightImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + client := meta.(*connectivity.AliyunClient) + alidnsService := AlidnsService{client} + + importID := d.Id() + parts := strings.Split(importID, "/") + var domainName, recordID string + if len(parts) == 2 { + domainName = parts[0] + recordID = parts[1] + } else { + recordID = importID + record, err := alidnsService.DescribeDomainRecordById(recordID) + if err != nil { + return nil, WrapError(err) + } + domainName = record.DomainName + } + + records, err := alidnsService.DescribeDomainRecords(domainName) + if err != nil { + return nil, WrapError(err) + } + + var importedRecord *alidns.Record + for _, record := range records { + if record.RecordId == recordID { + importedRecord = &record + break + } + } + if importedRecord == nil { + return nil, fmt.Errorf("record with ID %s not found in domain %s", recordID, domainName) + } + + d.SetId(importedRecord.RecordId) + d.Set("domain_name", domainName) + d.Set("rr", importedRecord.RR) + d.Set("type", importedRecord.Type) + d.Set("value", importedRecord.Value) + d.Set("ttl", importedRecord.TTL) + d.Set("line", importedRecord.Line) + d.Set("weight", importedRecord.Weight) + d.Set("priority", 0) // Default or API-provided value + d.Set("remark", "") // Default or API-provided value + + // Fetch WRR status + if importedRecord.Weight > 0 { + d.Set("wrr_status", "ENABLE") + } else { + d.Set("wrr_status", "DISABLE") + } + + return []*schema.ResourceData{d}, nil +} + +func (s *AlidnsService) SetWRRStatus(domainName, rr, status string) error { + request := alidns.CreateSetDNSSLBStatusRequest() + request.RegionId = s.client.RegionId + request.DomainName = domainName + request.SubDomain = fmt.Sprintf("%s.%s", rr, domainName) + request.Open = requests.NewBoolean(status == "ENABLE") + + _, err := s.client.WithAlidnsClient(func(alidnsClient *alidns.Client) (interface{}, error) { + return alidnsClient.SetDNSSLBStatus(request) + }) + return WrapError(err) +} + +func (s *AlidnsService) SetRecordWeight(recordID string, weight int) error { + request := alidns.CreateUpdateDNSSLBWeightRequest() + request.RegionId = s.client.RegionId + request.RecordId = recordID + request.Weight = requests.NewInteger(weight) + + _, err := s.client.WithAlidnsClient(func(alidnsClient *alidns.Client) (interface{}, error) { + return alidnsClient.UpdateDNSSLBWeight(request) + }) + return WrapError(err) +} + +func (s *AlidnsService) DescribeDomainRecordById(recordID string) (*alidns.Record, error) { + request := alidns.CreateDescribeDomainRecordInfoRequest() + request.RecordId = recordID + + response, err := s.client.WithAlidnsClient(func(alidnsClient *alidns.Client) (interface{}, error) { + return alidnsClient.DescribeDomainRecordInfo(request) + }) + if err != nil { + return nil, WrapError(err) + } + + resp, ok := response.(*alidns.DescribeDomainRecordInfoResponse) + if !ok { + return nil, fmt.Errorf("failed to cast response to DescribeDomainRecordInfoResponse") + } + + return &alidns.Record{ + RecordId: resp.RecordId, + DomainName: resp.DomainName, + RR: resp.RR, + Type: resp.Type, + Value: resp.Value, + TTL: resp.TTL, + Line: resp.Line, + Priority: resp.Priority, // Ensure this is set + Remark: resp.Remark, // Ensure this is set + }, nil +} diff --git a/alicloud/service_alicloud_alidns.go b/alicloud/service_alicloud_alidns.go index ced0c4a72429..211667d3a773 100644 --- a/alicloud/service_alicloud_alidns.go +++ b/alicloud/service_alicloud_alidns.go @@ -435,3 +435,21 @@ func (s *AlidnsService) DescribeAlidnsMonitorConfig(id string) (object map[strin object = v.(map[string]interface{}) return object, nil } + +func (s *AlidnsService) DescribeDomainRecords(domainName string) ([]alidns.Record, error) { + request := alidns.CreateDescribeDomainRecordsRequest() + request.RegionId = s.client.RegionId + request.DomainName = domainName + + raw, err := s.client.WithAlidnsClient(func(alidnsClient *alidns.Client) (interface{}, error) { + return alidnsClient.DescribeDomainRecords(request) + }) + if err != nil { + err = WrapErrorf(err, DefaultErrorMsg, domainName, request.GetActionName(), AlibabaCloudSdkGoERROR) + return nil, err + } + addDebug(request.GetActionName(), raw, request.RpcRequest, request) + response, _ := raw.(*alidns.DescribeDomainRecordsResponse) + + return response.DomainRecords.Record, nil +} From e2f6d150673d70d64970d38075a86ecda59314ee Mon Sep 17 00:00:00 2001 From: Hamid Ostadvali Date: Sun, 29 Dec 2024 18:57:22 +0330 Subject: [PATCH 2/2] feature: added alidns weighted records to provider --- ...a_source_alicloud_alidns_records_weight.go | 189 +++++++++ ...rce_alicloud_alidns_records_weight_test.go | 118 ++++++ alicloud/provider.go | 2 +- .../resource_alicloud_alidns_record_weight.go | 371 ++++++++++-------- ...urce_alicloud_alidns_record_weight_test.go | 135 +++++++ alicloud/service_alicloud_alidns.go | 367 ++++++++++++++++- 6 files changed, 1009 insertions(+), 173 deletions(-) create mode 100644 alicloud/data_source_alicloud_alidns_records_weight.go create mode 100644 alicloud/data_source_alicloud_alidns_records_weight_test.go create mode 100644 alicloud/resource_alicloud_alidns_record_weight_test.go diff --git a/alicloud/data_source_alicloud_alidns_records_weight.go b/alicloud/data_source_alicloud_alidns_records_weight.go new file mode 100644 index 000000000000..80231d6f9b41 --- /dev/null +++ b/alicloud/data_source_alicloud_alidns_records_weight.go @@ -0,0 +1,189 @@ +package alicloud + +import ( + "log" + + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" + "github.com/aliyun/alibaba-cloud-sdk-go/services/alidns" + "github.com/aliyun/terraform-provider-alicloud/alicloud/connectivity" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func dataSourceAlicloudAlidnsRecordsWeight() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAlicloudAlidnsRecordsWeightRead, + Schema: map[string]*schema.Schema{ + "domain_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "line": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "default", + }, + "status": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"ENABLE", "DISABLE"}, false), + Default: "ENABLE", + }, + "weight_greater_than": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + Default: 0, + }, + "output_file": { + Type: schema.TypeString, + Optional: true, + }, + "ids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "records": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "domain_name": { + Type: schema.TypeString, + Computed: true, + }, + "line": { + Type: schema.TypeString, + Computed: true, + }, + "rr": { + Type: schema.TypeString, + Computed: true, + }, + "record_id": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "ttl": { + Type: schema.TypeInt, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + "value": { + Type: schema.TypeString, + Computed: true, + }, + "weight": { + Type: schema.TypeInt, + Computed: true, + }, + "wrr_status": { + Type: schema.TypeString, + Computed: true, + }, + "remark": { + Type: schema.TypeString, + Computed: true, + }, + "priority": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceAlicloudAlidnsRecordsWeightRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*connectivity.AliyunClient) + + request := alidns.CreateDescribeDomainRecordsRequest() + request.DomainName = d.Get("domain_name").(string) + request.PageSize = requests.NewInteger(PageSizeLarge) + request.PageNumber = requests.NewInteger(1) + + var records []map[string]interface{} + var recordIDs []string // To store unique IDs for hash generation + + for { + raw, err := client.WithAlidnsClient(func(client *alidns.Client) (interface{}, error) { + return client.DescribeDomainRecords(request) + }) + if err != nil { + return WrapError(err) + } + + response := raw.(*alidns.DescribeDomainRecordsResponse) + for _, record := range response.DomainRecords.Record { + wrrStatus, err := fetchWRRStatus(client, record.DomainName, record.RR) + if err != nil { + log.Printf("[DEBUG] Failed to fetch WRR status for record %s: %v", record.RecordId, err) + wrrStatus = "DISABLE" + } + + detailedRecord, err := client.WithAlidnsClient(func(client *alidns.Client) (interface{}, error) { + request := alidns.CreateDescribeDomainRecordInfoRequest() + request.RecordId = record.RecordId + return client.DescribeDomainRecordInfo(request) + }) + + remark := "" + if err == nil { + remark = detailedRecord.(*alidns.DescribeDomainRecordInfoResponse).Remark + } else { + log.Printf("[DEBUG] Failed to fetch remark for record %s: %v", record.RecordId, err) + } + + records = append(records, map[string]interface{}{ + "domain_name": record.DomainName, + "line": record.Line, + "rr": record.RR, + "record_id": record.RecordId, + "status": record.Status, + "ttl": record.TTL, + "type": record.Type, + "value": record.Value, + "weight": record.Weight, + "remark": remark, + "wrr_status": wrrStatus, + }) + + recordIDs = append(recordIDs, record.RecordId) // Add the record ID to the list + } + + if len(response.DomainRecords.Record) < PageSizeLarge { + break + } + + page, err := getNextpageNumber(request.PageNumber) + if err != nil { + return WrapError(err) + } + request.PageNumber = page + } + + if err := d.Set("records", records); err != nil { + return WrapError(err) + } + + if output, ok := d.GetOk("output_file"); ok && output.(string) != "" { + writeToFile(output.(string), records) + } + + // Use the list of record IDs to generate a unique hash + d.SetId(dataResourceIdHash(recordIDs)) + return nil +} diff --git a/alicloud/data_source_alicloud_alidns_records_weight_test.go b/alicloud/data_source_alicloud_alidns_records_weight_test.go new file mode 100644 index 000000000000..65485219abbd --- /dev/null +++ b/alicloud/data_source_alicloud_alidns_records_weight_test.go @@ -0,0 +1,118 @@ +package alicloud + +import ( + "fmt" + "testing" + + "github.com/aliyun/alibaba-cloud-sdk-go/services/alidns" + "github.com/aliyun/terraform-provider-alicloud/alicloud/connectivity" + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccAlicloudAlidnsRecordsWeightDataSource(t *testing.T) { + rand := acctest.RandInt() + resourceId := "data.alicloud_alidns_records_weight.default" + + testAccConfig := dataSourceTestAccConfigFunc(resourceId, + fmt.Sprintf("tf-testacc%salidnsweight%v.abc", defaultRegionToTest, rand), + dataSourceAlidnsRecordsWeightConfigDependence) + + testAccCheck := func(expected map[string]string) resource.TestCheckFunc { + return resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceId, "records.#", "1"), + resource.TestCheckResourceAttr(resourceId, "records.0.domain_name", expected["domain_name"]), + resource.TestCheckResourceAttr(resourceId, "records.0.rr", expected["rr"]), + resource.TestCheckResourceAttr(resourceId, "records.0.type", expected["type"]), + resource.TestCheckResourceAttr(resourceId, "records.0.value", expected["value"]), + resource.TestCheckResourceAttr(resourceId, "records.0.ttl", expected["ttl"]), + resource.TestCheckResourceAttr(resourceId, "records.0.weight", expected["weight"]), + resource.TestCheckResourceAttr(resourceId, "records.0.wrr_status", expected["wrr_status"]), + resource.TestCheckResourceAttr(resourceId, "records.0.remark", expected["remark"]), + resource.TestCheckResourceAttr(resourceId, "records.0.priority", expected["priority"]), + ) + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAliCloudAlidnsRecordWeightDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConfig(map[string]interface{}{ + "domain_name": "${alicloud_dns_domain.default.name}", + "rr": "test1", + "type": "A", + "value": "1.1.1.1", + "ttl": "900", + "weight": "60", + "wrr_status": "ENABLE", + "remark": "test new record", + "priority": "5", + }), + Check: testAccCheck(map[string]string{ + "domain_name": fmt.Sprintf("tf-testacc%salidnsweight%v.abc", defaultRegionToTest, rand), + "rr": "test1", + "type": "A", + "value": "1.1.1.1", + "ttl": "900", + "weight": "60", + "wrr_status": "ENABLE", + "remark": "test new record", + "priority": "5", + }), + }, + }, + }) +} + +func dataSourceAlidnsRecordsWeightConfigDependence(name string) string { + return fmt.Sprintf(` +resource "alicloud_dns_domain" "default" { + domain_name = "%s" +} + +resource "alicloud_alidns_record_weight" "default" { + domain_name = "${alicloud_dns_domain.default.domain_name}" + rr = "test1" + type = "A" + value = "1.1.1.1" + ttl = 900 + weight = 60 + wrr_status = "ENABLE" + remark = "test new record" + priority = 5 +} +`, name) +} + +func testAccCheckAliCloudAlidnsRecordWeightDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*connectivity.AliyunClient) + for _, rs := range s.RootModule().Resources { + if rs.Type != "alicloud_alidns_record_weight" { + continue + } + + // Check if the record still exists + request := alidns.CreateDescribeDomainRecordInfoRequest() + request.RecordId = rs.Primary.ID + + _, err := client.WithAlidnsClient(func(alidnsClient *alidns.Client) (interface{}, error) { + return alidnsClient.DescribeDomainRecordInfo(request) + }) + + if err == nil { + return fmt.Errorf("record weight %s still exists", rs.Primary.ID) + } + + // If error is not related to a missing record, return it + if !IsExpectedErrors(err, []string{"InvalidRecordId.NotFound"}) { + return err + } + } + + return nil +} diff --git a/alicloud/provider.go b/alicloud/provider.go index c0e860536fdb..a3b427278aa0 100644 --- a/alicloud/provider.go +++ b/alicloud/provider.go @@ -381,6 +381,7 @@ func Provider() terraform.ResourceProvider { "alicloud_alidns_domain_groups": dataSourceAlicloudAlidnsDomainGroups(), "alicloud_kms_key_versions": dataSourceAlicloudKmsKeyVersions(), "alicloud_alidns_records": dataSourceAlicloudAlidnsRecords(), + "alicloud_alidns_records_weight": dataSourceAlicloudAlidnsRecordsWeight(), "alicloud_resource_manager_accounts": dataSourceAlicloudResourceManagerAccounts(), "alicloud_resource_manager_resource_directories": dataSourceAlicloudResourceManagerResourceDirectories(), "alicloud_resource_manager_handshakes": dataSourceAlicloudResourceManagerHandshakes(), @@ -1141,7 +1142,6 @@ func Provider() terraform.ResourceProvider { "alicloud_alikafka_sasl_acl": resourceAlicloudAlikafkaSaslAcl(), "alicloud_dns_record": resourceAlicloudDnsRecord(), "alicloud_alidns_record_weight": resourceAlicloudAlidnsRecordWeight(), - "alicloud_alidns_wrr": resourceAlicloudAlidnsWRR(), "alicloud_dns": resourceAlicloudDns(), "alicloud_dns_group": resourceAlicloudDnsGroup(), "alicloud_key_pair": resourceAliCloudEcsKeyPair(), diff --git a/alicloud/resource_alicloud_alidns_record_weight.go b/alicloud/resource_alicloud_alidns_record_weight.go index 134de51e6467..11ba4bd6ffe7 100644 --- a/alicloud/resource_alicloud_alidns_record_weight.go +++ b/alicloud/resource_alicloud_alidns_record_weight.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "strings" + "time" "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" "github.com/aliyun/alibaba-cloud-sdk-go/services/alidns" @@ -24,7 +25,7 @@ func resourceAlicloudAlidnsRecordWeight() *schema.Resource { Schema: map[string]*schema.Schema{ "domain_name": { Type: schema.TypeString, - Required: true, + Required: true, // This makes domain_name mandatory }, "line": { Type: schema.TypeString, @@ -85,63 +86,20 @@ func resourceAlicloudAlidnsRecordWeightCreate(d *schema.ResourceData, meta inter client := meta.(*connectivity.AliyunClient) alidnsService := AlidnsService{client} - // Step 1: Check for existing records domainName := d.Get("domain_name").(string) rr := d.Get("rr").(string) recordType := d.Get("type").(string) recordValue := d.Get("value").(string) + ttl := d.Get("ttl").(int) - records, err := alidnsService.DescribeDomainRecords(domainName) - if err != nil { - return WrapError(err) - } - - // Look for a matching record - var existingRecord *alidns.Record - for _, record := range records { - if record.RR == rr && record.Type == recordType && record.Value == recordValue { - existingRecord = &record - break - } - } - - // Step 2: If record exists, update weight and WRR status - if existingRecord != nil { - log.Printf("[DEBUG] Found existing record: %s", existingRecord.RecordId) - - // Enable WRR for the subdomain if needed - wrrStatus := d.Get("wrr_status").(string) - if wrrStatus == "ENABLE" { - err = alidnsService.SetWRRStatus(domainName, rr, wrrStatus) - if err != nil { - return WrapError(err) - } - - // Update the weight for the existing record - if weight, ok := d.GetOk("weight"); ok && weight.(int) > 0 { - err = alidnsService.SetRecordWeight(existingRecord.RecordId, weight.(int)) - if err != nil { - return WrapError(err) - } - } - } - - // Set the existing record's ID - d.SetId(existingRecord.RecordId) - return resourceAlicloudAlidnsRecordWeightRead(d, meta) - } - - // Step 3: If no existing record, create a new one - log.Printf("[DEBUG] No existing record found. Creating a new record for %s", rr) - + // Step 1: Create the record request := alidns.CreateAddDomainRecordRequest() request.RegionId = client.RegionId request.DomainName = domainName request.RR = rr request.Type = recordType request.Value = recordValue - request.TTL = requests.NewInteger(d.Get("ttl").(int)) - request.Line = d.Get("line").(string) + request.TTL = requests.NewInteger(ttl) response, err := client.WithAlidnsClient(func(alidnsClient *alidns.Client) (interface{}, error) { return alidnsClient.AddDomainRecord(request) @@ -150,22 +108,55 @@ func resourceAlicloudAlidnsRecordWeightCreate(d *schema.ResourceData, meta inter return WrapError(err) } - resp, _ := response.(*alidns.AddDomainRecordResponse) + resp, ok := response.(*alidns.AddDomainRecordResponse) + if !ok { + return fmt.Errorf("unexpected response type for AddDomainRecord") + } + d.SetId(resp.RecordId) - // Enable WRR and set weight for the new record - wrrStatus := d.Get("wrr_status").(string) - if wrrStatus == "ENABLE" { - err = alidnsService.SetWRRStatus(domainName, rr, wrrStatus) + err = alidnsService.WaitForRecordReady(resp.RecordId, domainName, rr, 2*time.Minute) + if err != nil { + return WrapError(err) + } + + recordID := d.Id() + + err = alidnsService.WaitForLastOperation(recordID, domainName, 2*time.Minute) + if err != nil { + return WrapError(err) + } + + // Step 2: Update remark + if d.HasChange("remark") { + newRemark := d.Get("remark").(string) + log.Printf("[DEBUG] Updating remark: RecordId=%s, NewRemark=%s", recordID, newRemark) + + err := alidnsService.UpdateRecordRemark(recordID, newRemark) if err != nil { return WrapError(err) } + } - if weight, ok := d.GetOk("weight"); ok && weight.(int) > 0 { - err = alidnsService.SetRecordWeight(resp.RecordId, weight.(int)) - if err != nil { - return WrapError(err) - } + // Step 3: Set WRR status + if wrrStatus, ok := d.GetOk("wrr_status"); ok && wrrStatus.(string) == "ENABLE" { + err = alidnsService.SetWRRStatus(domainName, rr, "ENABLE") + if err != nil { + return WrapError(err) + } + + // Wait for WRR status to be set + err = alidnsService.WaitForLastOperation(resp.RecordId, domainName, 2*time.Minute) + if err != nil { + return WrapError(err) + } + } + + // Step 4: Set weight + if weight, ok := d.GetOk("weight"); ok && weight.(int) > 0 { + err = alidnsService.SetRecordWeight(resp.RecordId, weight.(int), domainName, rr) + if err != nil { + return WrapError(err) } } @@ -175,24 +166,74 @@ func resourceAlicloudAlidnsRecordWeightCreate(d *schema.ResourceData, meta inter func resourceAlicloudAlidnsRecordWeightUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*connectivity.AliyunClient) alidnsService := AlidnsService{client} + recordID := d.Id() + domainName := d.Get("domain_name").(string) + rr := d.Get("rr").(string) + + // Step 1: Update remark + if d.HasChange("remark") { + newRemark := d.Get("remark").(string) + log.Printf("[DEBUG] Updating remark: RecordId=%s, NewRemark=%s", recordID, newRemark) - // Step 1: Update WRR status if changed + err := alidnsService.UpdateRecordRemark(recordID, newRemark) + if err != nil { + return WrapError(err) + } + } + + // Step 2: Update status + if d.HasChange("status") { + newStatus := d.Get("status").(string) + log.Printf("[DEBUG] Updating status: RecordId=%s, NewStatus=%s", recordID, newStatus) + + err := alidnsService.SetRecordStatus(recordID, newStatus) + if err != nil { + return WrapError(err) + } + } + + // Step 3: Update other record attributes (ttl, value, type) + if d.HasChange("ttl") || d.HasChange("value") || d.HasChange("type") { + updateRequest := alidns.CreateUpdateDomainRecordRequest() + updateRequest.RecordId = recordID + updateRequest.RR = rr + updateRequest.Type = d.Get("type").(string) + updateRequest.Value = d.Get("value").(string) + updateRequest.TTL = requests.NewInteger(d.Get("ttl").(int)) + + _, err := client.WithAlidnsClient(func(alidnsClient *alidns.Client) (interface{}, error) { + return alidnsClient.UpdateDomainRecord(updateRequest) + }) + if err != nil { + return WrapError(err) + } + } + + // Step 4: Update WRR status if d.HasChange("wrr_status") { - wrrStatus := d.Get("wrr_status").(string) - err := alidnsService.SetWRRStatus(d.Get("domain_name").(string), d.Get("rr").(string), wrrStatus) + newWRRStatus := d.Get("wrr_status").(string) + log.Printf("[DEBUG] Updating WRR status: DomainName=%s, SubDomain=%s, NewStatus=%s", domainName, rr, newWRRStatus) + + err := alidnsService.SetWRRStatus(domainName, rr, newWRRStatus) + if err != nil { + return WrapError(err) + } + + err = alidnsService.WaitForLastOperation(recordID, domainName, 2*time.Minute) if err != nil { return WrapError(err) } } - // Step 2: Update weight only if WRR is enabled - if d.HasChange("weight") && d.Get("wrr_status").(string) == "ENABLE" { - err := alidnsService.SetRecordWeight(d.Id(), d.Get("weight").(int)) + // Step 5: Update weight + if d.HasChange("weight") { + newWeight := d.Get("weight").(int) + log.Printf("[DEBUG] Updating weight: RecordId=%s, NewWeight=%d", recordID, newWeight) + + err := alidnsService.SetRecordWeight(recordID, newWeight, domainName, rr) if err != nil { return WrapError(err) } - } else if d.Get("wrr_status").(string) == "DISABLE" { - log.Printf("[DEBUG] WRR is disabled, skipping weight update for record ID: %s", d.Id()) } return resourceAlicloudAlidnsRecordWeightRead(d, meta) @@ -203,47 +244,70 @@ func resourceAlicloudAlidnsRecordWeightRead(d *schema.ResourceData, meta interfa alidnsService := AlidnsService{client} recordID := d.Id() + domainName := d.Get("domain_name").(string) + rr := d.Get("rr").(string) + + // Retry logic for API response delay + var record *alidns.Record + var err error + for i := 0; i < 5; i++ { // Retry up to 5 times + record, err = alidnsService.DescribeDomainRecordById(recordID, domainName) + if err == nil && record != nil { + break + } + log.Printf("[DEBUG] Retrying to fetch record: RecordId=%s, Attempt=%d", recordID, i+1) + time.Sleep(2 * time.Second) // Wait 2 seconds before retrying + } - // Use DescribeDomainRecordInfo to fetch detailed record info - record, err := alidnsService.DescribeDomainRecordById(recordID) if err != nil { if IsExpectedErrors(err, []string{"InvalidRecordId.NotFound"}) { log.Printf("[DEBUG] Record not found: %s", recordID) - d.SetId("") + d.SetId("") // Mark resource as deleted return nil } return WrapError(err) } - // Populate resource data - d.Set("domain_name", record.DomainName) + // Fetch WRR status + wrrStatus, err := alidnsService.GetWRRStatus(domainName, rr) + if err != nil { + log.Printf("[WARN] Failed to fetch WRR status: %s", err) + wrrStatus = "DISABLE" // Default to DISABLE if fetch fails + } + d.Set("wrr_status", wrrStatus) + + // Update Terraform state + d.Set("status", record.Status) + d.Set("remark", record.Remark) + d.Set("weight", record.Weight) d.Set("rr", record.RR) d.Set("type", record.Type) d.Set("value", record.Value) d.Set("ttl", record.TTL) d.Set("line", record.Line) - d.Set("weight", record.Weight) - d.Set("priority", record.Priority) // Map Priority - d.Set("remark", record.Remark) // Map Remark - - // Determine WRR status based on weight - if record.Weight > 0 { - d.Set("wrr_status", "ENABLE") - } else { - d.Set("wrr_status", "DISABLE") - } + d.Set("priority", record.Priority) return nil } func resourceAlicloudAlidnsRecordWeightDelete(d *schema.ResourceData, meta interface{}) error { client := meta.(*connectivity.AliyunClient) + alidnsService := AlidnsService{client} + recordID := d.Id() + domainName := d.Get("domain_name").(string) + // Wait for the last operation to finish + err := alidnsService.WaitForLastOperation(recordID, domainName, 2*time.Minute) + if err != nil { + return WrapError(err) + } + + // Proceed with the delete operation request := alidns.CreateDeleteDomainRecordRequest() request.RegionId = client.RegionId - request.RecordId = d.Id() + request.RecordId = recordID - _, err := client.WithAlidnsClient(func(alidnsClient *alidns.Client) (interface{}, error) { + _, err = client.WithAlidnsClient(func(alidnsClient *alidns.Client) (interface{}, error) { return alidnsClient.DeleteDomainRecord(request) }) if err != nil { @@ -262,107 +326,84 @@ func resourceAlicloudAlidnsRecordWeightImport(d *schema.ResourceData, meta inter alidnsService := AlidnsService{client} importID := d.Id() - parts := strings.Split(importID, "/") var domainName, recordID string + + // Parse the import ID to support both formats + parts := strings.Split(importID, "/") if len(parts) == 2 { domainName = parts[0] recordID = parts[1] - } else { - recordID = importID - record, err := alidnsService.DescribeDomainRecordById(recordID) + } else if len(parts) == 1 { + recordID = parts[0] + + // If domainName is not provided, attempt to discover it + domains, err := alidnsService.ListAllDomains() if err != nil { - return nil, WrapError(err) + return nil, fmt.Errorf("failed to list all domains: %w", err) } - domainName = record.DomainName - } - records, err := alidnsService.DescribeDomainRecords(domainName) - if err != nil { - return nil, WrapError(err) - } + for _, domain := range domains { + records, err := alidnsService.DescribeDomainRecords(domain.DomainName) + if err != nil { + return nil, fmt.Errorf("failed to describe records for domain %s: %w", domain.DomainName, err) + } - var importedRecord *alidns.Record - for _, record := range records { - if record.RecordId == recordID { - importedRecord = &record - break - } - } - if importedRecord == nil { - return nil, fmt.Errorf("record with ID %s not found in domain %s", recordID, domainName) - } + for _, record := range records { + if record.RecordId == recordID { + domainName = domain.DomainName + break + } + } - d.SetId(importedRecord.RecordId) - d.Set("domain_name", domainName) - d.Set("rr", importedRecord.RR) - d.Set("type", importedRecord.Type) - d.Set("value", importedRecord.Value) - d.Set("ttl", importedRecord.TTL) - d.Set("line", importedRecord.Line) - d.Set("weight", importedRecord.Weight) - d.Set("priority", 0) // Default or API-provided value - d.Set("remark", "") // Default or API-provided value + if domainName != "" { + break + } + } - // Fetch WRR status - if importedRecord.Weight > 0 { - d.Set("wrr_status", "ENABLE") + if domainName == "" { + return nil, fmt.Errorf("record with ID %s not found in any domain", recordID) + } } else { - d.Set("wrr_status", "DISABLE") + return nil, fmt.Errorf("invalid import ID format: %s", importID) } - return []*schema.ResourceData{d}, nil -} - -func (s *AlidnsService) SetWRRStatus(domainName, rr, status string) error { - request := alidns.CreateSetDNSSLBStatusRequest() - request.RegionId = s.client.RegionId - request.DomainName = domainName - request.SubDomain = fmt.Sprintf("%s.%s", rr, domainName) - request.Open = requests.NewBoolean(status == "ENABLE") - - _, err := s.client.WithAlidnsClient(func(alidnsClient *alidns.Client) (interface{}, error) { - return alidnsClient.SetDNSSLBStatus(request) - }) - return WrapError(err) -} - -func (s *AlidnsService) SetRecordWeight(recordID string, weight int) error { - request := alidns.CreateUpdateDNSSLBWeightRequest() - request.RegionId = s.client.RegionId - request.RecordId = recordID - request.Weight = requests.NewInteger(weight) - - _, err := s.client.WithAlidnsClient(func(alidnsClient *alidns.Client) (interface{}, error) { - return alidnsClient.UpdateDNSSLBWeight(request) - }) - return WrapError(err) -} + // Fetch the record details + record, err := alidnsService.DescribeDomainRecordById(recordID, domainName) + if err != nil { + return nil, fmt.Errorf("failed to fetch record %s in domain %s: %w", recordID, domainName, err) + } -func (s *AlidnsService) DescribeDomainRecordById(recordID string) (*alidns.Record, error) { - request := alidns.CreateDescribeDomainRecordInfoRequest() - request.RecordId = recordID + // Fetch WRR configuration + weight := 0 + wrrStatus := "DISABLE" - response, err := s.client.WithAlidnsClient(func(alidnsClient *alidns.Client) (interface{}, error) { - return alidnsClient.DescribeDomainRecordInfo(request) - }) + slbRecords, err := alidnsService.GetSLBSubDomains(domainName) if err != nil { - return nil, WrapError(err) + return nil, fmt.Errorf("failed to fetch WRR configuration for domain %s: %w", domainName, err) } - resp, ok := response.(*alidns.DescribeDomainRecordInfoResponse) - if !ok { - return nil, fmt.Errorf("failed to cast response to DescribeDomainRecordInfoResponse") + for _, subRecord := range slbRecords { + if subRecord.SubDomain == fmt.Sprintf("%s.%s", record.RR, domainName) { + wrrStatus = "ENABLE" + weight, err = alidnsService.GetWeight(recordID, domainName) + if err != nil { + return nil, fmt.Errorf("failed to fetch weight for record ID %s: %w", recordID, err) + } + break + } } - return &alidns.Record{ - RecordId: resp.RecordId, - DomainName: resp.DomainName, - RR: resp.RR, - Type: resp.Type, - Value: resp.Value, - TTL: resp.TTL, - Line: resp.Line, - Priority: resp.Priority, // Ensure this is set - Remark: resp.Remark, // Ensure this is set - }, nil + // Set Terraform resource data + d.SetId(recordID) + d.Set("domain_name", domainName) + d.Set("rr", record.RR) + d.Set("type", record.Type) + d.Set("value", record.Value) + d.Set("ttl", record.TTL) + d.Set("line", record.Line) + d.Set("remark", record.Remark) + d.Set("weight", weight) + d.Set("wrr_status", wrrStatus) + + return []*schema.ResourceData{d}, nil } diff --git a/alicloud/resource_alicloud_alidns_record_weight_test.go b/alicloud/resource_alicloud_alidns_record_weight_test.go new file mode 100644 index 000000000000..7c0a477a8b65 --- /dev/null +++ b/alicloud/resource_alicloud_alidns_record_weight_test.go @@ -0,0 +1,135 @@ +package alicloud + +import ( + "fmt" + "testing" + + "github.com/aliyun/alibaba-cloud-sdk-go/services/alidns" + "github.com/aliyun/terraform-provider-alicloud/alicloud/connectivity" + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccAlicloudAlidnsRecordWeight_basic(t *testing.T) { + var v alidns.DescribeDomainRecordInfoResponse + + resourceId := "alicloud_alidns_record_weight.test1_record_weight_1" + ra := resourceAttrInit(resourceId, alidnsWeightBasicMap) + + serviceFunc := func() interface{} { + return &AlidnsService{testAccProvider.Meta().(*connectivity.AliyunClient)} + } + rc := resourceCheckInit(resourceId, &v, serviceFunc) + + rac := resourceAttrCheckInit(rc, ra) + + testAccCheck := rac.resourceAttrMapUpdateSet() + rand := acctest.RandInt() + name := fmt.Sprintf("tf-testacc%salidnsrecordweightbasic%v.abc", defaultRegionToTest, rand) + testAccConfig := resourceTestAccConfigFunc(resourceId, name, resourceAlidnsRecordWeightConfigDependence) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + // module name + IDRefreshName: resourceId, + Providers: testAccProviders, + CheckDestroy: rac.checkResourceDestroy(), + Steps: []resource.TestStep{ + { + Config: testAccConfig(map[string]interface{}{ + "domain_name": "${alicloud_dns.default.name}", + "rr": "test1", + "type": "A", + "value": "1.1.1.1", + "ttl": "900", + "priority": "5", + "line": "default", + "status": "ENABLE", + "remark": "test1 new record 1", + "weight": "60", + "wrr_status": "ENABLE", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "domain_name": fmt.Sprintf("tf-testacc%salidnsrecordweightbasic%v.abc", defaultRegionToTest, rand), + "rr": "test1", + "type": "A", + "value": "1.1.1.1", + "ttl": "900", + "priority": "5", + "line": "default", + "status": "ENABLE", + "remark": "test1 new record 1", + "weight": "60", + "wrr_status": "ENABLE", + }), + ), + }, + { + ResourceName: resourceId, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"remark", "weight", "wrr_status"}, + }, + { + Config: testAccConfig(map[string]interface{}{ + "rr": "test1change", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{"rr": "test1change"}), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "type": "CNAME", + "priority": "10", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "type": "CNAME", + "priority": "10", + }), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "weight": "70", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{"weight": "70"}), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "ttl": "800", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{"ttl": "800"}), + ), + }, + { + Config: testAccConfig(map[string]interface{}{ + "remark": "test updated remark", + }), + Check: resource.ComposeTestCheckFunc( + testAccCheck(map[string]string{ + "remark": "test updated remark", + }), + ), + }, + }, + }) + +} + +func resourceAlidnsRecordWeightConfigDependence(name string) string { + return fmt.Sprintf(` +resource "alicloud_dns" "default" { + name = "%s" +} +`, name) +} + +var alidnsWeightBasicMap = map[string]string{} diff --git a/alicloud/service_alicloud_alidns.go b/alicloud/service_alicloud_alidns.go index 211667d3a773..fa20bd99c4d9 100644 --- a/alicloud/service_alicloud_alidns.go +++ b/alicloud/service_alicloud_alidns.go @@ -2,6 +2,7 @@ package alicloud import ( "fmt" + "log" "time" "github.com/PaesslerAG/jsonpath" @@ -436,20 +437,372 @@ func (s *AlidnsService) DescribeAlidnsMonitorConfig(id string) (object map[strin return object, nil } +func (s *AlidnsService) DescribeDomainRecordById(recordID, domainName string) (*alidns.Record, error) { + request := alidns.CreateDescribeDomainRecordInfoRequest() + request.RecordId = recordID + + response, err := s.client.WithAlidnsClient(func(client *alidns.Client) (interface{}, error) { + return client.DescribeDomainRecordInfo(request) + }) + if err != nil { + return nil, WrapError(err) + } + + resp, ok := response.(*alidns.DescribeDomainRecordInfoResponse) + if !ok { + return nil, fmt.Errorf("unexpected response type for DescribeDomainRecordInfo") + } + + // Fetch weight from DescribeDomainRecords + records, err := s.DescribeDomainRecords(domainName) + if err != nil { + return nil, WrapError(err) + } + + var weight int + for _, r := range records { + if r.RecordId == recordID { + weight = r.Weight + break + } + } + + return &alidns.Record{ + RecordId: resp.RecordId, + DomainName: domainName, + RR: resp.RR, + Type: resp.Type, + Value: resp.Value, + TTL: resp.TTL, + Line: resp.Line, + Priority: resp.Priority, + Remark: resp.Remark, + Status: resp.Status, + Weight: weight, // Set weight + }, nil +} + +func (s *AlidnsService) UpdateRecordRemark(recordID, remark string) error { + request := alidns.CreateUpdateDomainRecordRemarkRequest() + request.RecordId = recordID + request.Remark = remark + + log.Printf("[DEBUG] Setting record remark: RecordId=%s, Remark=%s", recordID, remark) + + _, err := s.client.WithAlidnsClient(func(client *alidns.Client) (interface{}, error) { + return client.UpdateDomainRecordRemark(request) + }) + if err != nil { + return WrapError(err) + } + + return nil +} + +func (s *AlidnsService) IsOperationFinished(recordID string) (bool, error) { + request := alidns.CreateDescribeDomainRecordInfoRequest() + request.RecordId = recordID + + response, err := s.client.WithAlidnsClient(func(client *alidns.Client) (interface{}, error) { + return client.DescribeDomainRecordInfo(request) + }) + if err != nil { + if IsExpectedErrors(err, []string{"LastOperationNotFinished"}) { + return false, nil + } + return false, WrapError(err) + } + + resp, ok := response.(*alidns.DescribeDomainRecordInfoResponse) + if !ok { + return false, fmt.Errorf("unexpected response type for DescribeDomainRecordInfo") + } + + // Check operation status based on API response (adjust this as per API documentation) + return resp.RecordId != "", nil +} + func (s *AlidnsService) DescribeDomainRecords(domainName string) ([]alidns.Record, error) { request := alidns.CreateDescribeDomainRecordsRequest() request.RegionId = s.client.RegionId request.DomainName = domainName - raw, err := s.client.WithAlidnsClient(func(alidnsClient *alidns.Client) (interface{}, error) { - return alidnsClient.DescribeDomainRecords(request) + response, err := s.client.WithAlidnsClient(func(client *alidns.Client) (interface{}, error) { + return client.DescribeDomainRecords(request) }) if err != nil { - err = WrapErrorf(err, DefaultErrorMsg, domainName, request.GetActionName(), AlibabaCloudSdkGoERROR) - return nil, err + return nil, WrapError(err) + } + + resp, ok := response.(*alidns.DescribeDomainRecordsResponse) + if !ok { + return nil, fmt.Errorf("unexpected response type for DescribeDomainRecords") + } + + return resp.DomainRecords.Record, nil +} + +func (s *AlidnsService) UpdateRecordStatus(recordID, status string) error { + request := alidns.CreateSetDomainRecordStatusRequest() + request.RecordId = recordID + request.Status = status + + _, err := s.client.WithAlidnsClient(func(client *alidns.Client) (interface{}, error) { + return client.SetDomainRecordStatus(request) + }) + if err != nil { + return WrapError(err) + } + + return nil +} + +func (s *AlidnsService) SetRecordWeight(recordID string, weight int, domainName, rr string) error { + // Ensure WRR is enabled before setting weight + wrrStatus, err := s.GetWRRStatus(domainName, rr) + if err != nil { + return WrapError(err) + } + + if wrrStatus != "ENABLE" { + log.Printf("[DEBUG] WRR is disabled for subdomain %s.%s. Enabling it now.", rr, domainName) + err = s.SetWRRStatus(domainName, rr, "ENABLE") + if err != nil { + return WrapError(err) + } + } + + request := alidns.CreateUpdateDNSSLBWeightRequest() + request.RegionId = s.client.RegionId + request.RecordId = recordID + request.Weight = requests.NewInteger(weight) + + log.Printf("[DEBUG] Setting record weight: RecordId=%s, Weight=%d", recordID, weight) + + _, err = s.client.WithAlidnsClient(func(client *alidns.Client) (interface{}, error) { + resp, err := client.UpdateDNSSLBWeight(request) + if err != nil { + log.Printf("[ERROR] Failed to update weight: %s", err) + } else { + log.Printf("[DEBUG] Successfully updated weight: %+v", resp) + } + return resp, err + }) + + return WrapError(err) +} + +func (s *AlidnsService) SetRecordStatus(recordID, status string) error { + request := alidns.CreateSetDomainRecordStatusRequest() + request.RecordId = recordID + request.Status = status // "ENABLE" or "DISABLE" + + log.Printf("[DEBUG] Setting record status: RecordId=%s, Status=%s", recordID, status) + + _, err := s.client.WithAlidnsClient(func(client *alidns.Client) (interface{}, error) { + return client.SetDomainRecordStatus(request) + }) + if err != nil { + return WrapError(err) + } + + return nil +} + +func (s *AlidnsService) SetWRRStatus(domainName, rr, status string) error { + request := alidns.CreateSetDNSSLBStatusRequest() + request.RegionId = s.client.RegionId + request.DomainName = domainName + request.SubDomain = fmt.Sprintf("%s.%s", rr, domainName) + request.Open = requests.NewBoolean(status == "ENABLE") // Convert status to boolean + + log.Printf("[DEBUG] Setting WRR status: SubDomain=%s, Status=%s", request.SubDomain, status) + + _, err := s.client.WithAlidnsClient(func(client *alidns.Client) (interface{}, error) { + return client.SetDNSSLBStatus(request) + }) + if err != nil { + return WrapError(err) + } + + return nil +} + +func (s *AlidnsService) GetWRRStatus(domainName, rr string) (string, error) { + request := alidns.CreateDescribeDNSSLBSubDomainsRequest() + request.RegionId = s.client.RegionId + request.DomainName = domainName + + log.Printf("[DEBUG] Fetching WRR status for domain: %s, subdomain: %s", domainName, rr) + + response, err := s.client.WithAlidnsClient(func(client *alidns.Client) (interface{}, error) { + return client.DescribeDNSSLBSubDomains(request) + }) + if err != nil { + return "", WrapError(err) + } + + resp, ok := response.(*alidns.DescribeDNSSLBSubDomainsResponse) + if !ok { + return "", fmt.Errorf("unexpected response type for DescribeDNSSLBSubDomains") + } + + for _, sub := range resp.SlbSubDomains.SlbSubDomain { + if sub.SubDomain == fmt.Sprintf("%s.%s", rr, domainName) { + if sub.Open { + return "ENABLE", nil + } + return "DISABLE", nil + } + } + + log.Printf("[DEBUG] Subdomain %s.%s not found, defaulting WRR status to DISABLE", rr, domainName) + return "DISABLE", nil +} + +func (s *AlidnsService) WaitForRecordReady(recordID, domainName, rr string, timeout time.Duration) error { + start := time.Now() + for { + if time.Since(start) > timeout { + return fmt.Errorf("timeout while waiting for record %s to be ready", recordID) + } + + wrrStatus, err := s.GetWRRStatus(domainName, rr) + if err != nil { + log.Printf("[DEBUG] Waiting for record readiness: RecordId=%s, Error=%s", recordID, err) + } else if wrrStatus != "" { + log.Printf("[DEBUG] Record %s is ready with WRR status: %s", recordID, wrrStatus) + return nil + } + + time.Sleep(2 * time.Second) // Polling interval + } +} + +func (s *AlidnsService) WaitForLastOperation(recordID, domainName string, timeout time.Duration) error { + if domainName == "" { + return fmt.Errorf("domainName is required for WaitForLastOperation") + } + + start := time.Now() + for { + if time.Since(start) > timeout { + return fmt.Errorf("timeout while waiting for last operation to finish for record ID %s in domain %s", recordID, domainName) + } + + // Use DescribeDomainRecords instead of DescribeDomainRecordById to avoid issues with missing domainName + records, err := s.DescribeDomainRecords(domainName) + if err != nil { + if IsExpectedErrors(err, []string{"LastOperationNotFinished"}) { + log.Printf("[DEBUG] Last operation not finished for record ID %s, retrying...", recordID) + time.Sleep(2 * time.Second) + continue + } + return WrapError(err) + } + + // Check if the record exists and is ready + for _, record := range records { + if record.RecordId == recordID { + log.Printf("[DEBUG] Last operation finished for record ID %s in domain %s", recordID, domainName) + return nil + } + } + + log.Printf("[DEBUG] Record ID %s not found in domain %s, retrying...", recordID, domainName) + time.Sleep(2 * time.Second) + } +} + +func (s *AlidnsService) ListAllDomains() ([]alidns.DomainInDescribeDomains, error) { + var allDomains []alidns.DomainInDescribeDomains + pageNumber := 1 + pageSize := 50 + + for { + request := alidns.CreateDescribeDomainsRequest() + request.RegionId = s.client.RegionId + request.PageNumber = requests.NewInteger(pageNumber) + request.PageSize = requests.NewInteger(pageSize) + + response, err := s.client.WithAlidnsClient(func(client *alidns.Client) (interface{}, error) { + return client.DescribeDomains(request) + }) + if err != nil { + return nil, WrapError(err) + } + + resp, ok := response.(*alidns.DescribeDomainsResponse) + if !ok { + return nil, fmt.Errorf("unexpected response type for DescribeDomains") + } + + allDomains = append(allDomains, resp.Domains.Domain...) + if len(resp.Domains.Domain) < pageSize { + break + } + pageNumber++ + } + + return allDomains, nil +} + +func (s *AlidnsService) GetSLBSubDomains(domainName string) ([]alidns.SlbSubDomain, error) { + request := alidns.CreateDescribeDNSSLBSubDomainsRequest() + request.DomainName = domainName + + response, err := s.client.WithAlidnsClient(func(client *alidns.Client) (interface{}, error) { + return client.DescribeDNSSLBSubDomains(request) + }) + if err != nil { + return nil, WrapError(err) + } + + resp, ok := response.(*alidns.DescribeDNSSLBSubDomainsResponse) + if !ok { + return nil, fmt.Errorf("unexpected response type for DescribeDNSSLBSubDomains") + } + + return resp.SlbSubDomains.SlbSubDomain, nil +} + +func (s *AlidnsService) GetWeight(recordID, domainName string) (int, error) { + records, err := s.DescribeDomainRecords(domainName) + if err != nil { + return 0, WrapError(err) + } + + for _, record := range records { + if record.RecordId == recordID { + return record.Weight, nil + } + } + + return 0, fmt.Errorf("record with ID %s not found in domain %s", recordID, domainName) +} + +// fetchWRRStatus retrieves the WRR status for a given domain and RR. +func fetchWRRStatus(client *connectivity.AliyunClient, domainName, rr string) (string, error) { + request := alidns.CreateDescribeDNSSLBSubDomainsRequest() + request.DomainName = domainName + + raw, err := client.WithAlidnsClient(func(alidnsClient *alidns.Client) (interface{}, error) { + return alidnsClient.DescribeDNSSLBSubDomains(request) + }) + if err != nil { + return "", err + } + + response := raw.(*alidns.DescribeDNSSLBSubDomainsResponse) + for _, subDomain := range response.SlbSubDomains.SlbSubDomain { + if subDomain.SubDomain == fmt.Sprintf("%s.%s", rr, domainName) { + if subDomain.Open { + return "ENABLE", nil + } + return "DISABLE", nil + } } - addDebug(request.GetActionName(), raw, request.RpcRequest, request) - response, _ := raw.(*alidns.DescribeDomainRecordsResponse) - return response.DomainRecords.Record, nil + // Default to DISABLE if not found + return "DISABLE", nil }