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/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 596c1c3c6618..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(), @@ -1140,6 +1141,7 @@ func Provider() terraform.ResourceProvider { "alicloud_alikafka_sasl_user": resourceAliCloudAlikafkaSaslUser(), "alicloud_alikafka_sasl_acl": resourceAlicloudAlikafkaSaslAcl(), "alicloud_dns_record": resourceAlicloudDnsRecord(), + "alicloud_alidns_record_weight": resourceAlicloudAlidnsRecordWeight(), "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..11ba4bd6ffe7 --- /dev/null +++ b/alicloud/resource_alicloud_alidns_record_weight.go @@ -0,0 +1,409 @@ +package alicloud + +import ( + "fmt" + "log" + "strings" + "time" + + "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, // This makes domain_name mandatory + }, + "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} + + 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) + + // 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(ttl) + + response, err := client.WithAlidnsClient(func(alidnsClient *alidns.Client) (interface{}, error) { + return alidnsClient.AddDomainRecord(request) + }) + if err != nil { + return WrapError(err) + } + + resp, ok := response.(*alidns.AddDomainRecordResponse) + if !ok { + return fmt.Errorf("unexpected response type for AddDomainRecord") + } + + d.SetId(resp.RecordId) + + 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) + } + } + + // 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) + } + } + + return resourceAlicloudAlidnsRecordWeightRead(d, meta) +} + +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) + + 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") { + 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 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) + } + } + + return resourceAlicloudAlidnsRecordWeightRead(d, meta) +} + +func resourceAlicloudAlidnsRecordWeightRead(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) + + // 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 + } + + if err != nil { + if IsExpectedErrors(err, []string{"InvalidRecordId.NotFound"}) { + log.Printf("[DEBUG] Record not found: %s", recordID) + d.SetId("") // Mark resource as deleted + return nil + } + return WrapError(err) + } + + // 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("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 = recordID + + _, 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() + 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 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, fmt.Errorf("failed to list all domains: %w", 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) + } + + for _, record := range records { + if record.RecordId == recordID { + domainName = domain.DomainName + break + } + } + + if domainName != "" { + break + } + } + + if domainName == "" { + return nil, fmt.Errorf("record with ID %s not found in any domain", recordID) + } + } else { + return nil, fmt.Errorf("invalid import ID format: %s", importID) + } + + // 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) + } + + // Fetch WRR configuration + weight := 0 + wrrStatus := "DISABLE" + + slbRecords, err := alidnsService.GetSLBSubDomains(domainName) + if err != nil { + return nil, fmt.Errorf("failed to fetch WRR configuration for domain %s: %w", domainName, err) + } + + 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 + } + } + + // 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 ced0c4a72429..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" @@ -435,3 +436,373 @@ func (s *AlidnsService) DescribeAlidnsMonitorConfig(id string) (object map[strin object = v.(map[string]interface{}) 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 + + response, err := s.client.WithAlidnsClient(func(client *alidns.Client) (interface{}, error) { + return client.DescribeDomainRecords(request) + }) + if err != nil { + 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 + } + } + + // Default to DISABLE if not found + return "DISABLE", nil +}