diff --git a/alicloud/alicloud_sweeper_test.go b/alicloud/alicloud_sweeper_test.go index 4f461e237874..b3e7aec94bd4 100644 --- a/alicloud/alicloud_sweeper_test.go +++ b/alicloud/alicloud_sweeper_test.go @@ -2,8 +2,10 @@ package alicloud import ( "fmt" + "github.com/aliyun/credentials-go/credentials" "log" "os" + "strings" "sync" "testing" @@ -52,7 +54,7 @@ var endpoints sync.Map // sharedClientForRegion returns a common AlicloudClient setup needed for the sweeper // functions for a give n region func sharedClientForRegion(region string) (interface{}, error) { - var accessKey, secretKey string + var accessKey, secretKey, securityToken string if accessKey = os.Getenv("ALICLOUD_ACCESS_KEY"); accessKey == "" { return nil, fmt.Errorf("empty ALICLOUD_ACCESS_KEY") } @@ -61,6 +63,8 @@ func sharedClientForRegion(region string) (interface{}, error) { return nil, fmt.Errorf("empty ALICLOUD_SECRET_KEY") } + securityToken = os.Getenv("ALICLOUD_SECURITY_TOKEN") + conf := connectivity.Config{ Region: connectivity.Region(region), RegionId: region, @@ -69,9 +73,21 @@ func sharedClientForRegion(region string) (interface{}, error) { Protocol: "HTTPS", Endpoints: &endpoints, } + if securityToken != "" { + conf.SecurityToken = securityToken + } if accountId := os.Getenv("ALICLOUD_ACCOUNT_ID"); accountId != "" { conf.AccountId = accountId } + credentialConfig := new(credentials.Config).SetType("access_key").SetAccessKeyId(accessKey).SetAccessKeySecret(secretKey) + if v := strings.TrimSpace(securityToken); v != "" { + credentialConfig.SetType("sts").SetSecurityToken(v) + } + credential, err := credentials.NewCredential(credentialConfig) + if err != nil { + return nil, err + } + conf.Credential = credential // configures a default client for the region, using the above env vars client, err := conf.Client() diff --git a/alicloud/provider_test.go b/alicloud/provider_test.go index 2dc5c45eefb6..a9fe17936409 100644 --- a/alicloud/provider_test.go +++ b/alicloud/provider_test.go @@ -1,10 +1,12 @@ package alicloud import ( + "bytes" "encoding/json" "fmt" "io/ioutil" "log" + "net/http" "os" "testing" "time" @@ -65,6 +67,55 @@ func testAccPreCheck(t *testing.T) { } } +func testAccPreCheckForCleanUpInstances(t *testing.T, instanceRegion, productCode, productType, productCodeIntl, productTypeIntl string) { + rawClient, err := sharedClientForRegion(defaultRegionToTest) + if err != nil { + t.Errorf("error getting AliCloud client: %s", err) + } + client := rawClient.(*connectivity.AliyunClient) + bssOpenApiService := BssOpenApiService{client} + accountId, err := client.AccountId() + if err != nil { + t.Errorf("error getting AliCloud client: %s", err) + } + deadline := time.Now().Add(30 * time.Minute) + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + if time.Now().After(deadline) { + fmt.Println("Deadline reached, stopping waiting.") + return + } + instances, err := bssOpenApiService.QueryAvailableInstanceList(instanceRegion, productCode, productType, productCodeIntl, productTypeIntl) + if err != nil { + t.Errorf("error querying available instances: %s", err) + return + } + instanceId := "" + for _, instance := range instances { + v := instance.(map[string]interface{}) + if v["Status"].(string) != "Normal" { + continue + } + instanceId = v["InstanceID"].(string) + } + if instanceId == "" { + return + } + sendMessage(fmt.Sprintf(` +[Critical] Please cleaning up instance before running integration test. + +AccountId: %s +ProductCode: %s +InstanceId: %s +`, accountId, productCode, instanceId)) + time.Sleep(3 * time.Minute) + } + } +} + // currently not all account site type support create PostPaid resources, PayByBandwidth and other limits. // The setting of account site type can skip some unsupported cases automatically. @@ -416,6 +467,43 @@ func testAccPreCheckWithResourceManagerHandshakesSetting(t *testing.T) { } } +type DingTalkMessage struct { + MsgType string `json:"msgtype"` + Text struct { + Content string `json:"content"` + } `json:"text"` +} + +func sendMessage(msg string) { + // 钉钉机器人的 Webhook 地址 + webhookURL := "https://oapi.dingtalk.com/robot/send?access_token=" + os.Getenv("DINGTALK_WEBHOOK_ACCESS_TOKEN") + + // 构建消息内容 + message := DingTalkMessage{ + MsgType: "text", + Text: struct { + Content string `json:"content"` + }{ + Content: msg, + }, + } + + // 将消息内容转换为 JSON 格式 + jsonData, err := json.Marshal(message) + if err != nil { + fmt.Println("[ERROR] send dingTalk message failed. Error:", err) + return + } + + // 发送 POST 请求 + resp, err := http.Post(webhookURL, "application/json", bytes.NewBuffer(jsonData)) + if err != nil { + fmt.Println("[ERROR] send dingTalk message failed. Error:", err) + return + } + defer resp.Body.Close() +} + func setStsCredential() { // 创建OSSClient实例。 client, err := oss.New("https://oss-cn-zhangjiakou.aliyuncs.com", os.Getenv("ALICLOUD_ACCESS_KEY"), os.Getenv("ALICLOUD_SECRET_KEY")) diff --git a/alicloud/service_alicloud_bss_open_api.go b/alicloud/service_alicloud_bss_open_api.go index b03765c97073..12ea82a0269b 100644 --- a/alicloud/service_alicloud_bss_open_api.go +++ b/alicloud/service_alicloud_bss_open_api.go @@ -22,10 +22,12 @@ func (s *BssOpenApiService) QueryAvailableInstances(id, instanceRegion, productC } action := "QueryAvailableInstances" request := map[string]interface{}{ - "InstanceIDs": id, "ProductCode": productCode, "ProductType": productType, } + if id != "" { + request["InstanceIDs"] = id + } if instanceRegion != "" { request["Region"] = instanceRegion } @@ -78,7 +80,7 @@ func (s *BssOpenApiService) QueryAvailableInstances(id, instanceRegion, productC } if len(v.([]interface{})) < 1 { return object, WrapErrorf(Error(GetNotFoundMessage(productCode+"Instance", id)), NotFoundWithResponse, response) - } else { + } else if id != "" { if fmt.Sprint(v.([]interface{})[0].(map[string]interface{})["InstanceID"]) != id { return object, WrapErrorf(Error(GetNotFoundMessage(productCode+"Instance", id)), NotFoundWithResponse, response) } @@ -320,3 +322,67 @@ func (s *BssOpenApiService) CloudFirewallInstanceOrderDetailStateRefreshFunc(ord return object, object["PaymentStatus"].(string), nil } } + +func (s *BssOpenApiService) QueryAvailableInstanceList(instanceRegion, productCode, productType, productCodeIntl, productTypeIntl string) (object []interface{}, err error) { + var response map[string]interface{} + conn, err := s.client.NewBssopenapiClient() + if err != nil { + return nil, WrapError(err) + } + action := "QueryAvailableInstances" + request := map[string]interface{}{ + "ProductCode": productCode, + "ProductType": productType, + } + if instanceRegion != "" { + request["Region"] = instanceRegion + } + runtime := util.RuntimeOptions{} + runtime.SetAutoretry(true) + wait := incrementalWait(3*time.Second, 3*time.Second) + err = resource.Retry(5*time.Minute, func() *resource.RetryError { + response, err = conn.DoRequest(StringPointer(action), nil, StringPointer("POST"), StringPointer("2017-12-14"), StringPointer("AK"), nil, request, &runtime) + if err != nil { + if NeedRetry(err) { + wait() + return resource.RetryableError(err) + } + if IsExpectedErrors(err, []string{"NotApplicable", "SignatureDoesNotMatch"}) { + conn.Endpoint = String(connectivity.BssOpenAPIEndpointInternational) + request["ProductCode"] = productCodeIntl + request["ProductType"] = productTypeIntl + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + } + resp, _ := jsonpath.Get("$.Data.InstanceList", response) + if len(resp.([]interface{})) < 1 { + request["ProductCode"] = productCodeIntl + if productTypeIntl != "" { + request["ProductType"] = productTypeIntl + } + conn.Endpoint = String(connectivity.BssOpenAPIEndpointInternational) + response, err = conn.DoRequest(StringPointer(action), nil, StringPointer("POST"), StringPointer("2017-12-14"), StringPointer("AK"), nil, request, &runtime) + if err != nil { + if NeedRetry(err) { + wait() + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + } + } + return nil + }) + addDebug(action, response, request) + if err != nil { + return object, WrapError(err) + } + if fmt.Sprint(response["Code"]) != "Success" { + return object, WrapError(fmt.Errorf("%s failed, response: %v", action, response)) + } + v, err := jsonpath.Get("$.Data.InstanceList", response) + if err != nil { + return object, WrapError(err) + } + return v.([]interface{}), nil +}