alibabacloudstack/common.go (925 lines of code) (raw):
package alibabacloudstack
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"math"
"os"
"os/user"
"path/filepath"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
"time"
"github.com/aliyun/fc-go-sdk"
"github.com/google/uuid"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
"github.com/aliyun/terraform-provider-alibabacloudstack/alibabacloudstack/connectivity"
"github.com/aliyun/terraform-provider-alibabacloudstack/alibabacloudstack/errmsgs"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
type InstanceNetWork string
const (
ClassicNet = InstanceNetWork("classic")
VpcNet = InstanceNetWork("vpc")
)
type PayType string
const (
PrePaid = PayType("PrePaid")
PostPaid = PayType("PostPaid")
Prepaid = PayType("Prepaid")
Postpaid = PayType("Postpaid")
)
const (
NormalMode = "normal"
SafetyMode = "safety"
)
type DdosbgpInsatnceType string
const (
Enterprise = DdosbgpInsatnceType("Enterprise")
Professional = DdosbgpInsatnceType("Professional")
)
type DdosbgpInstanceIpType string
const (
IPv4 = DdosbgpInstanceIpType("IPv4")
IPv6 = DdosbgpInstanceIpType("IPv6")
)
type NetType string
const (
Internet = NetType("Internet")
Intranet = NetType("Intranet")
)
type NetworkType string
const (
Classic = NetworkType("Classic")
Vpc = NetworkType("Vpc")
ClassicInternet = NetworkType("classic_internet")
ClassicIntranet = NetworkType("classic_intranet")
PUBLIC = NetworkType("PUBLIC")
PRIVATE = NetworkType("PRIVATE")
)
type NodeType string
const (
WORKER = NodeType("WORKER")
KIBANA = NodeType("KIBANA")
)
type ActionType string
const (
OPEN = ActionType("OPEN")
CLOSE = ActionType("CLOSE")
)
type TimeType string
const (
Hour = TimeType("Hour")
Day = TimeType("Day")
Week = TimeType("Week")
Month = TimeType("Month")
Year = TimeType("Year")
)
type IpVersion string
const (
IPV4 = IpVersion("ipv4")
IPV6 = IpVersion("ipv6")
)
type Status string
const (
Pending = Status("Pending")
Creating = Status("Creating")
Running = Status("Running")
Available = Status("Available")
Unavailable = Status("Unavailable")
Modifying = Status("Modifying")
Deleting = Status("Deleting")
Starting = Status("Starting")
Stopping = Status("Stopping")
Stopped = Status("Stopped")
Normal = Status("Normal")
Changing = Status("Changing")
Online = Status("online")
Configuring = Status("configuring")
Associating = Status("Associating")
Unassociating = Status("Unassociating")
InUse = Status("InUse")
DiskInUse = Status("In_use")
Active = Status("Active")
Inactive = Status("Inactive")
Idle = Status("Idle")
SoldOut = Status("SoldOut")
InService = Status("InService")
Removing = Status("Removing")
EnabledStatus = Status("Enabled")
DisabledStatus = Status("Disabled")
Init = Status("Init")
Provisioning = Status("Provisioning")
Updating = Status("Updating")
FinancialLocked = Status("FinancialLocked")
PUBLISHED = Status("Published")
NOPUBLISHED = Status("NonPublished")
Deleted = Status("Deleted")
Null = Status("Null")
Enable = Status("Enable")
BINDED = Status("BINDED")
)
type IPType string
const (
Inner = IPType("Inner")
Private = IPType("Private")
Public = IPType("Public")
)
type ResourceType string
const (
ResourceTypeInstance = ResourceType("Instance")
ResourceTypeDisk = ResourceType("Disk")
ResourceTypeVSwitch = ResourceType("VSwitch")
ResourceTypeRds = ResourceType("Rds")
ResourceTypePolarDB = ResourceType("PolarDB")
IoOptimized = ResourceType("IoOptimized")
ResourceTypeRkv = ResourceType("KVStore")
ResourceTypeFC = ResourceType("FunctionCompute")
ResourceTypeElasticsearch = ResourceType("Elasticsearch")
ResourceTypeSlb = ResourceType("Slb")
ResourceTypeMongoDB = ResourceType("MongoDB")
ResourceTypeGpdb = ResourceType("Gpdb")
ResourceTypeHBase = ResourceType("HBase")
ResourceTypeAdb = ResourceType("ADB")
ResourceTypeCassandra = ResourceType("Cassandra")
)
type InternetChargeType string
const (
PayByBandwidth = InternetChargeType("PayByBandwidth")
PayByTraffic = InternetChargeType("PayByTraffic")
PayBy95 = InternetChargeType("PayBy95")
)
type AccountSite string
const (
DomesticSite = AccountSite("Domestic")
IntlSite = AccountSite("International")
)
const (
SnapshotCreatingInProcessing = Status("progressing")
SnapshotCreatingAccomplished = Status("accomplished")
SnapshotCreatingFailed = Status("failed")
SnapshotPolicyCreating = Status("Creating")
SnapshotPolicyAvailable = Status("available")
SnapshotPolicyNormal = Status("Normal")
)
// timeout for common product, ecs e.g.
const DefaultTimeout = 300
const Timeout5Minute = 300
const DefaultTimeoutMedium = 500
// timeout for long time progerss product, rds e.g.
const DefaultLongTimeout = 1000
const DefaultIntervalMini = 2
const DefaultIntervalShort = 5
const DefaultIntervalMedium = 10
const DefaultIntervalLong = 20
const (
PageSizeSmall = 10
PageSizeMedium = 20
PageSizeLarge = 50
PageSizeXLarge = 100
)
// Protocol represents network protocol
type Protocol string
// Constants of protocol definition
const (
Http = Protocol("http")
Https = Protocol("https")
Tcp = Protocol("tcp")
Udp = Protocol("udp")
All = Protocol("all")
Icmp = Protocol("icmp")
Gre = Protocol("gre")
)
// ValidProtocols network protocol list
var ValidProtocols = []Protocol{Http, Https, Tcp, Udp}
// simple array value check method, support string type only
func isProtocolValid(value string) bool {
res := false
for _, v := range ValidProtocols {
if string(v) == value {
res = true
}
}
return res
}
// default region for all resource
const DEFAULT_REGION = "cn-beijing"
const INT_MAX = 2147483647
// symbol of multiIZ
const MULTI_IZ_SYMBOL = "MAZ"
const COMMA_SEPARATED = ","
const COLON_SEPARATED = ":"
const SLASH_SEPARATED = "/"
const LOCAL_HOST_IP = "127.0.0.1"
func convertListStringToListInterface(list []string) []interface{} {
vs := make([]interface{}, 0, len(list))
for _, v := range list {
vs = append(vs, v)
}
return vs
}
func addDebug(action, content interface{}, requestInfo ...interface{}) {
if debugOn() {
trace := "[DEBUG TRACE]:\n"
for skip := 1; skip < 5; skip++ {
_, filepath, line, _ := runtime.Caller(skip)
trace += fmt.Sprintf("%s:%d\n", filepath, line)
}
if len(requestInfo) > 0 {
var request = struct {
Domain string
Version string
UserAgent string
ActionName string
Method string
Product string
Region string
AK string
}{}
switch requestInfo[0].(type) {
case *requests.RpcRequest:
tmp := requestInfo[0].(*requests.RpcRequest)
request.Domain = tmp.GetDomain()
request.Version = tmp.GetVersion()
request.ActionName = tmp.GetActionName()
request.Method = tmp.GetMethod()
request.Product = tmp.GetProduct()
request.Region = tmp.GetRegionId()
case *requests.RoaRequest:
tmp := requestInfo[0].(*requests.RoaRequest)
request.Domain = tmp.GetDomain()
request.Version = tmp.GetVersion()
request.ActionName = tmp.GetActionName()
request.Method = tmp.GetMethod()
request.Product = tmp.GetProduct()
request.Region = tmp.GetRegionId()
case *requests.CommonRequest:
tmp := requestInfo[0].(*requests.CommonRequest)
request.Domain = tmp.GetDomain()
request.Version = tmp.GetVersion()
request.ActionName = tmp.GetActionName()
request.Method = tmp.GetMethod()
request.Product = tmp.GetProduct()
request.Region = tmp.GetRegionId()
case *fc.Client:
client := requestInfo[0].(*fc.Client)
request.Version = client.Config.APIVersion
request.Product = "FC"
request.ActionName = fmt.Sprintf("%s", action)
}
requestContent := ""
if len(requestInfo) > 1 {
requestContent = fmt.Sprintf("%#v", requestInfo[1])
}
content = fmt.Sprintf("%vDomain:%v, Version:%v, ActionName:%v, Method:%v, Product:%v, Region:%v\n\n"+
"*************** %s Request ***************\n%#v\n",
content, request.Domain, request.Version, request.ActionName,
request.Method, request.Product, request.Region, request.ActionName, requestContent)
}
//fmt.Printf(DefaultDebugMsg, action, content, trace)
log.Printf(errmsgs.DefaultDebugMsg, action, content, trace)
}
}
func debugOn() bool {
for _, part := range strings.Split(os.Getenv("DEBUG"), ",") {
if strings.TrimSpace(part) == "terraform" {
return true
}
}
return false
}
// Convert the result for an array and returns a Json string
func convertListToJsonString(configured []interface{}) string {
if len(configured) < 1 {
return ""
}
result := "["
for i, v := range configured {
result += "\"" + v.(string) + "\""
if i < len(configured)-1 {
result += ","
}
}
result += "]"
return result
}
func getNextpageNumber(number requests.Integer) (requests.Integer, error) {
page, err := strconv.Atoi(string(number))
if err != nil {
return "", err
}
return requests.NewInteger(page + 1), nil
}
func incrementalWait(firstDuration time.Duration, increaseDuration time.Duration) func() {
// 迁移动作太大,使用重定向
return connectivity.IncrementalWait(firstDuration, increaseDuration)
}
func GetFunc(level int) string {
pc, _, _, ok := runtime.Caller(level)
if !ok {
log.Printf("[ERROR] runtime.Caller error in GetFuncName.")
return ""
}
return strings.TrimPrefix(filepath.Ext(runtime.FuncForPC(pc).Name()), ".")
}
func ParseResourceId(id string, length int) (parts []string, err error) {
parts = strings.Split(id, ":")
if len(parts) != length {
err = errmsgs.WrapError(fmt.Errorf("Invalid Resource Id %s. Expected parts' length %d, got %d", id, length, len(parts)))
}
return parts, err
}
func BuildStateConf(pending, target []string, timeout, delay time.Duration, f resource.StateRefreshFunc) *resource.StateChangeConf {
return &resource.StateChangeConf{
Pending: pending,
Target: target,
Refresh: f,
Timeout: timeout,
Delay: delay,
MinTimeout: 3 * time.Second,
}
}
func BuildStateConfByTimes(pending, target []string, timeout, delay time.Duration, f resource.StateRefreshFunc, notFoundChecks int) *resource.StateChangeConf {
return &resource.StateChangeConf{
Pending: pending,
Target: target,
Refresh: f,
Timeout: timeout,
Delay: delay,
MinTimeout: 3 * time.Second,
NotFoundChecks: notFoundChecks,
}
}
func convertJsonStringToList(configured string) ([]interface{}, error) {
result := make([]interface{}, 0)
if err := json.Unmarshal([]byte(configured), &result); err != nil {
return nil, err
}
return result, nil
}
func StringPointer(s string) *string {
return &s
}
func BoolPointer(b bool) *bool {
return &b
}
func Int32Pointer(i int32) *int32 {
return &i
}
func Int64Pointer(i int64) *int64 {
return &i
}
func IntMin(x, y int) int {
if x < y {
return x
}
return y
}
type Catcher struct {
Reason string
RetryCount int
RetryWaitSeconds int
}
var ClientErrorCatcher = Catcher{errmsgs.AlibabacloudStackGoClientFailure, 10, 5}
var ServiceBusyCatcher = Catcher{"ServiceUnavailable", 10, 5}
var ThrottlingCatcher = Catcher{errmsgs.Throttling, 50, 2}
func NewInvoker() Invoker {
i := Invoker{}
i.AddCatcher(ClientErrorCatcher)
i.AddCatcher(ServiceBusyCatcher)
i.AddCatcher(ThrottlingCatcher)
return i
}
func userDataHashSum(user_data string) string {
// Check whether the user_data is not Base64 encoded.
// Always calculate hash of base64 decoded value since we
// check against double-encoding when setting it
v, base64DecodeError := base64.StdEncoding.DecodeString(user_data)
if base64DecodeError != nil {
v = []byte(user_data)
}
return string(v)
}
const ServerSideEncryptionAes256 = "AES256"
const ServerSideEncryptionKMS = "KMS"
type OptimizedType string
const (
IOOptimized = OptimizedType("optimized")
NoneOptimized = OptimizedType("none")
)
type TagResourceType string
const (
TagResourceImage = TagResourceType("image")
TagResourceInstance = TagResourceType("instance")
TagResourceAcl = TagResourceType("acl")
TagResourceCertificate = TagResourceType("certificate")
TagResourceSnapshot = TagResourceType("snapshot")
TagResourceKeypair = TagResourceType("keypair")
TagResourceDisk = TagResourceType("disk")
TagResourceSecurityGroup = TagResourceType("securitygroup")
TagResourceEni = TagResourceType("eni")
TagResourceCdn = TagResourceType("DOMAIN")
TagResourceVpc = TagResourceType("VPC")
TagResourceVSwitch = TagResourceType("VSWITCH")
TagResourceRouteTable = TagResourceType("ROUTETABLE")
TagResourceEip = TagResourceType("EIP")
TagResourcePlugin = TagResourceType("plugin")
TagResourceApiGroup = TagResourceType("apiGroup")
TagResourceApp = TagResourceType("app")
TagResourceTopic = TagResourceType("topic")
TagResourceConsumerGroup = TagResourceType("consumergroup")
TagResourceCluster = TagResourceType("cluster")
)
type KubernetesNodeType string
const (
KubernetesNodeMaster = ResourceType("Master")
KubernetesNodeWorker = ResourceType("Worker")
)
func GetUserHomeDir() (string, error) {
usr, err := user.Current()
if err != nil {
return "", fmt.Errorf("Get current user got an error: %#v.", err)
}
return usr.HomeDir, nil
}
// writeToFile 函数
func writeToFile(filePath string, data interface{}) error {
var out string
switch v := data.(type) {
case string:
out = v
case nil:
return nil
default:
bs, err := json.MarshalIndent(data, "", "\t")
if err != nil {
return fmt.Errorf("MarshalIndent data %#v got an error: %v", data, err)
}
out = string(bs)
}
// 替换 ~ 为用户主目录
if strings.HasPrefix(filePath, "~") {
home, err := GetUserHomeDir()
if err != nil {
return err
}
if home != "" {
filePath = strings.Replace(filePath, "~", home, 1)
}
}
// 获取当前工作目录
currentDir, err := os.Getwd()
if err != nil {
return fmt.Errorf("failed to get current working directory: %v", err)
}
// 获取用户主目录
home, err := GetUserHomeDir()
if err != nil {
return fmt.Errorf("failed to get user home directory: %v", err)
}
// 获取文件路径的绝对路径
absFilePath, err := filepath.Abs(filePath)
if err != nil {
return fmt.Errorf("failed to get absolute path for %s: %v", filePath, err)
}
// 确保文件路径是相对于当前工作目录或用户主目录
if !strings.HasPrefix(absFilePath, currentDir+string(filepath.Separator)) && !strings.HasPrefix(absFilePath, home+string(filepath.Separator)) {
return fmt.Errorf("file path %s is not within the allowed directories: current directory %s or home directory %s", absFilePath, currentDir, home)
}
// 写入文件
return ioutil.WriteFile(absFilePath, []byte(out), 0644)
}
type Invoker struct {
catchers []*Catcher
}
func (a *Invoker) AddCatcher(catcher Catcher) {
a.catchers = append(a.catchers, &catcher)
}
func (a *Invoker) Run(f func() error) error {
err := f()
if err == nil {
return nil
}
for _, catcher := range a.catchers {
if errmsgs.IsExpectedErrors(err, []string{catcher.Reason}) {
catcher.RetryCount--
if catcher.RetryCount <= 0 {
return fmt.Errorf("Retry timeout and got an error: %#v.", err)
} else {
time.Sleep(time.Duration(catcher.RetryWaitSeconds) * time.Second)
return a.Run(f)
}
}
}
return err
}
func Trim(v string) string {
if len(v) < 1 {
return v
}
return strings.Trim(v, " ")
}
func GetCenChildInstanceType(id string) (c string, e error) {
if strings.HasPrefix(id, "vpc") {
return ChildInstanceTypeVpc, nil
} else if strings.HasPrefix(id, "vbr") {
return ChildInstanceTypeVbr, nil
} else if strings.HasPrefix(id, "ccn") {
return ChildInstanceTypeCcn, nil
} else {
return c, fmt.Errorf("CEN child instance ID invalid. Now, it only supports VPC or VBR or CCN instance.")
}
}
func ParseSlbListenerId(id string) (parts []string, err error) {
parts = strings.Split(id, ":")
if len(parts) != 2 && len(parts) != 3 {
err = errmsgs.WrapError(fmt.Errorf("Invalid alibabacloudstack_slb_listener Id %s. Expected Id format is <slb id>:<protocol>:< frontend>.", id))
}
return parts, err
}
func buildClientToken(action string) string {
token := strings.TrimSpace(fmt.Sprintf("TF-%s-%d-%s", action, time.Now().Unix(), strings.Trim(uuid.New().String(), "-")))
if len(token) > 64 {
token = token[0:64]
}
return token
}
// Takes the result of flatmap.Expand for an array of strings
// and returns a []string
func expandStringList(configured []interface{}) []string {
vs := make([]string, 0, len(configured))
for _, v := range configured {
vs = append(vs, v.(string))
}
return vs
}
func expandIntList(configured []interface{}) []int {
vs := make([]int, 0, len(configured))
for _, v := range configured {
vs = append(vs, v.(int))
}
return vs
}
func computePeriodByUnit(createTime, endTime interface{}, currentPeriod int, periodUnit string) (int, error) {
var createTimeStr, endTimeStr string
switch value := createTime.(type) {
case int64:
createTimeStr = time.Unix(createTime.(int64), 0).Format(time.RFC3339)
endTimeStr = time.Unix(endTime.(int64), 0).Format(time.RFC3339)
case string:
createTimeStr = createTime.(string)
endTimeStr = endTime.(string)
default:
return 0, errmsgs.WrapError(fmt.Errorf("Unsupported time type: %#v", value))
}
// currently, there is time value does not format as standard RFC3339
UnStandardRFC3339 := "2006-01-02T15:04Z07:00"
create, err := time.Parse(time.RFC3339, createTimeStr)
if err != nil {
log.Printf("Parase the CreateTime %#v failed and error is: %#v.", createTime, err)
create, err = time.Parse(UnStandardRFC3339, createTimeStr)
if err != nil {
return 0, errmsgs.WrapError(err)
}
}
end, err := time.Parse(time.RFC3339, endTimeStr)
if err != nil {
log.Printf("Parase the EndTime %#v failed and error is: %#v.", endTime, err)
end, err = time.Parse(UnStandardRFC3339, endTimeStr)
if err != nil {
return 0, errmsgs.WrapError(err)
}
}
var period int
switch periodUnit {
case "Month":
period = int(math.Floor(end.Sub(create).Hours() / 24 / 30))
case "Week":
period = int(math.Floor(end.Sub(create).Hours() / 24 / 7))
case "Year":
period = int(math.Floor(end.Sub(create).Hours() / 24 / 365))
default:
err = fmt.Errorf("Unexpected period unit %s", periodUnit)
}
// The period at least is 1
if period < 1 {
period = 1
}
if period > 12 {
period = 12
}
// period can not be modified and if the new period is changed, using the previous one.
if currentPeriod > 0 && currentPeriod != period {
period = currentPeriod
}
return period, errmsgs.WrapError(err)
}
func terraformToAPI(field string) string {
var result string
for _, v := range strings.Split(field, "_") {
if len(v) > 0 {
result = fmt.Sprintf("%s%s%s", result, strings.ToUpper(string(v[0])), v[1:])
}
}
return result
}
func convertMaptoJsonString(m map[string]interface{}) (string, error) {
sm := make(map[string]string, len(m))
for k, v := range m {
sm[k] = v.(string)
}
if result, err := json.Marshal(sm); err != nil {
return "", err
} else {
return string(result), nil
}
}
func formatInt(src interface{}) int {
if src == nil {
return 0
}
attrType := reflect.TypeOf(src)
switch attrType.String() {
case "float64":
return int(src.(float64))
case "float32":
return int(src.(float32))
case "int64":
return int(src.(int64))
case "int32":
return int(src.(int32))
case "int":
return src.(int)
case "string":
v, err := strconv.Atoi(src.(string))
if err != nil {
panic(err)
}
return v
case "json.Number":
v, err := strconv.Atoi(src.(json.Number).String())
if err != nil {
panic(err)
}
return v
default:
panic(fmt.Sprintf("Not support type %s", attrType.String()))
}
return 0
}
func convertArrayObjectToJsonString(src interface{}) (string, error) {
res, err := json.Marshal(&src)
if err != nil {
return "", err
}
return string(res), nil
}
func convertListToCommaSeparate(configured []interface{}) string {
if len(configured) < 1 {
return ""
}
result := ""
for i, v := range configured {
rail := ","
if i == len(configured)-1 {
rail = ""
}
result += v.(string) + rail
}
return result
}
func compareJsonTemplateAreEquivalent(tem1, tem2 string) (bool, error) {
obj1 := make(map[string]interface{})
err := json.Unmarshal([]byte(tem1), &obj1)
if err != nil {
return false, err
}
canonicalJson1, _ := json.Marshal(obj1)
obj2 := make(map[string]interface{})
err = json.Unmarshal([]byte(tem2), &obj2)
if err != nil {
return false, err
}
canonicalJson2, _ := json.Marshal(obj2)
equal := bytes.Compare(canonicalJson1, canonicalJson2) == 0
if !equal {
log.Printf("[DEBUG] Canonical template are not equal.\nFirst: %s\nSecond: %s\n",
canonicalJson1, canonicalJson2)
}
return equal, nil
}
func convertArrayToString(src interface{}, sep string) string {
if src == nil {
return ""
}
items := make([]string, 0)
for _, v := range src.([]interface{}) {
items = append(items, fmt.Sprint(v))
}
return strings.Join(items, sep)
}
func convertMapFloat64ToJsonString(m map[string]interface{}) (string, error) {
sm := make(map[string]json.Number, len(m))
for k, v := range m {
sm[k] = v.(json.Number)
}
if result, err := json.Marshal(sm); err != nil {
return "", err
} else {
return string(result), nil
}
}
// 合并两个 map,并在遇到相同键时覆盖第一个 map 的值
func mergeMaps(map1, map2 map[string]string) {
// 将第二个 map 的所有键值对复制到 mergedMap,覆盖已存在的键
for key, value := range map2 {
map1[key] = value
}
}
func mapMerge(target, merged map[string]interface{}) map[string]interface{} {
for key, value := range merged {
if _, exist := target[key]; !exist {
target[key] = value
} else {
// key existed in both src,target
switch merged[key].(type) {
case []interface{}:
sourceSlice := value.([]interface{})
targetSlice := make([]interface{}, len(sourceSlice))
copy(targetSlice, target[key].([]interface{}))
for index, val := range sourceSlice {
switch val.(type) {
case map[string]interface{}:
targetMap, ok := targetSlice[index].(map[string]interface{})
if ok {
targetSlice[index] = mapMerge(targetMap, val.(map[string]interface{}))
} else {
targetSlice[index] = mapMerge(map[string]interface{}{}, val.(map[string]interface{}))
}
default:
targetSlice[index] = val
}
}
target[key] = targetSlice
case map[string]interface{}:
target[key] = mapMerge(target[key].(map[string]interface{}), merged[key].(map[string]interface{}))
default:
target[key] = merged[key]
}
}
}
return target
}
func mapSort(target map[string]string) []string {
result := make([]string, 0)
for key := range target {
result = append(result, key)
}
sort.Strings(result)
return result
}
func newInstanceDiff(resourceName string, attributes, attributesDiff map[string]interface{}, state *terraform.InstanceState) (*terraform.InstanceDiff, error) {
p := Provider().ResourcesMap
dOld, _ := schema.InternalMap(p[resourceName].Schema).Data(state, nil)
dNew, _ := schema.InternalMap(p[resourceName].Schema).Data(state, nil)
for key, value := range attributes {
err := dOld.Set(key, value)
if err != nil {
return nil, errmsgs.WrapErrorf(err, "[ERROR] the field %s setting error.", key)
}
}
for key, value := range attributesDiff {
attributes[key] = value
}
for key, value := range attributes {
err := dNew.Set(key, value)
if err != nil {
return nil, errmsgs.WrapErrorf(err, "[ERROR] the field %s setting error.", key)
}
}
diff := terraform.NewInstanceDiff()
objectKey := ""
for _, key := range mapSort(dNew.State().Attributes) {
newValue := dNew.State().Attributes[key]
if objectKey != "" && !strings.HasPrefix(key, objectKey) {
objectKey = ""
}
if objectKey == "" {
for _, suffix := range []string{"#", "%"} {
if strings.HasSuffix(key, suffix) {
objectKey = strings.TrimSuffix(key, suffix)
break
}
}
}
oldValue, ok := dOld.State().Attributes[key]
if ok && oldValue == newValue {
continue
}
if oldValue == "" {
for _, suffix := range []string{"#", "%"} {
if strings.HasSuffix(key, suffix) {
oldValue = "0"
}
}
}
// 使用 SetNew 和 SetOld 方法来设置属性差异
if diff.Attributes == nil {
diff.Attributes = make(map[string]*terraform.ResourceAttrDiff)
}
diff.Attributes[key] = &terraform.ResourceAttrDiff{
Old: oldValue,
New: newValue,
}
if objectKey != "" {
for removeKey, removeValue := range dOld.State().Attributes {
if strings.HasPrefix(removeKey, objectKey) {
if _, ok := dNew.State().Attributes[removeKey]; !ok {
// If the attribue has complex elements, there should remove the key, not setting it to empty
if len(strings.Split(removeKey, ".")) > 2 {
delete(diff.Attributes, removeKey)
} else {
diff.Attributes[removeKey] = &terraform.ResourceAttrDiff{
Old: removeValue,
New: "",
}
}
}
}
}
objectKey = ""
}
}
return diff, nil
}
func setResourceFunc(resource *schema.Resource, createFunc schema.CreateFunc, readFunc schema.ReadFunc, updateFunc schema.UpdateFunc, deleteFunc schema.DeleteFunc) {
resource.CreateContext = func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var err error
err = createFunc(d, meta)
if err != nil {
return diag.FromErr(err)
}
waitSecondsIfWithTest(1)
if updateFunc != nil {
err = updateFunc(d, meta)
}
if err != nil {
waitSecondsIfWithTest(3)
// 如果创建成功但读取加载失败,tf不会终态,为方式残留资源,触发删除
resource.DeleteContext(ctx, d, meta)
return diag.FromErr(err)
}
waitSecondsIfWithTest(1)
retry := 5
for retry > 0 {
// 大批量触发时asapi侧的资源同步会有一定的延迟,如果失败则重试
err = readFunc(d, meta)
if err != nil {
time.Sleep(time.Second * 5)
retry--
continue
}
break
}
if err != nil {
// 如果创建成功但读取加载失败,tf不会终态,为方式残留资源,触发删除
waitSecondsIfWithTest(3)
resource.DeleteContext(ctx, d, meta)
return diag.FromErr(err)
}
return nil
}
resource.ReadContext = func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
err := readFunc(d, meta)
return diag.FromErr(err)
}
if updateFunc != nil {
resource.UpdateContext = func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
update_err := updateFunc(d, meta)
waitSecondsIfWithTest(1)
read_err := readFunc(d, meta)
if update_err != nil {
return diag.FromErr(update_err)
}
return diag.FromErr(read_err)
}
}
resource.DeleteContext = func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
err := deleteFunc(d, meta)
return diag.FromErr(err)
}
if resource.Importer == nil {
resource.Importer = &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
}
}
}
func getRoleIdsAsString(d *schema.ResourceData) (string, error) {
// 获取 role_ids 的值
roleIdsInterface := d.Get("role_ids")
// 类型断言为 *schema.Set
roleSet, ok := roleIdsInterface.(*schema.Set)
if !ok {
return "", fmt.Errorf("Expected role_ids to be a *schema.Set, got %T", roleIdsInterface)
}
// 获取元素列表
roleList := roleSet.List()
// 转换为字符串切片
var stringRoleIds []string
for _, v := range roleList {
roleIdStr, ok := v.(string)
if !ok {
return "", fmt.Errorf("Invalid role_id type, expected string but got %T", v)
}
stringRoleIds = append(stringRoleIds, roleIdStr)
}
// 拼接字符串
return "["+strings.Join(stringRoleIds, ",")+"]", nil
}
func noUpdatesAllowedCheck(d *schema.ResourceData, fields []string) error {
if d.IsNewResource() {
return nil
}
updatefields := make([]string, 0)
for _, field := range fields {
if d.HasChange(field) {
updatefields = append(updatefields, field)
}
}
if len(updatefields) > 0 {
updatefieldsstr := fmt.Sprintf("%s", strings.Join(updatefields, ","))
return errmsgs.Error(errmsgs.UpdateFailedErrorMsg, d.Id(), updatefieldsstr, errmsgs.AlibabacloudStackSdkGoERROR)
}
return nil
}