yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/qcloud/tags.go (about) 1 // Copyright 2019 Yunion 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package qcloud 16 17 import ( 18 "fmt" 19 "strconv" 20 21 "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" 22 sdkerrors "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" 23 24 "yunion.io/x/jsonutils" 25 "yunion.io/x/pkg/errors" 26 "yunion.io/x/pkg/utils" 27 ) 28 29 const ( 30 QCLOUD_API_VERSION_TAGS = "2018-08-13" 31 ) 32 33 func tagRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) { 34 domain := "tag.tencentcloudapi.com" 35 return _jsonRequest(client, domain, QCLOUD_API_VERSION_TAGS, apiName, params, updateFunc, debug, true) 36 } 37 38 func (client *SQcloudClient) tagRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { 39 cli, err := client.getDefaultClient(params) 40 if err != nil { 41 return nil, err 42 } 43 return tagRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug) 44 } 45 46 type SFetchTagRow struct { 47 ServiceType string `json:"ServiceType"` 48 TagKey string `json:"TagKey"` 49 TagKeyMd5 string `json:"TagKeyMd5"` 50 TagValue string `json:"TagValue"` 51 TagValueMd5 string `json:"TagValueMd5"` 52 ResourceId string `json:"ResourceId"` 53 } 54 55 type SListInfo struct { 56 TotalCount int `json:"TotalCount"` 57 Offset int `json:"Offset"` 58 Limit int `json:"Limit"` 59 } 60 61 type SFetchTagResponse struct { 62 SListInfo 63 Tags []SFetchTagRow `json:"Tags"` 64 } 65 66 func (region *SRegion) rawFetchTags(serviceType, resourceType string, resIds []string, limit, offset int) (*SFetchTagResponse, error) { 67 params := make(map[string]string) 68 params["ServiceType"] = serviceType 69 params["ResourcePrefix"] = resourceType 70 for i, resId := range resIds { 71 params[fmt.Sprintf("ResourceIds.%d", i)] = resId 72 } 73 params["ResourceRegion"] = region.Region 74 if limit > 0 { 75 params["Limit"] = strconv.FormatInt(int64(limit), 10) 76 } 77 if offset > 0 { 78 params["Offset"] = strconv.FormatInt(int64(offset), 10) 79 } 80 apiName := "DescribeResourceTagsByResourceIdsSeq" 81 respJson, err := region.client.tagRequest(apiName, params) 82 if err != nil { 83 return nil, errors.Wrap(err, apiName) 84 } 85 resp := SFetchTagResponse{} 86 err = respJson.Unmarshal(&resp) 87 if err != nil { 88 return nil, errors.Wrap(err, "Unmarshal Response") 89 } 90 return &resp, nil 91 } 92 93 func tagsLen(tags map[string]*map[string]string) int { 94 ret := 0 95 for _, kv := range tags { 96 ret += len(*kv) 97 } 98 return ret 99 } 100 101 func (region *SRegion) FetchResourceTags(serviceType, resourceType string, resIds []string) (map[string]*map[string]string, error) { 102 tags := make(map[string]*map[string]string) 103 total := -1 104 for total < 0 || tagsLen(tags) < total { 105 resp, err := region.rawFetchTags(serviceType, resourceType, resIds, 100, tagsLen(tags)) 106 if err != nil { 107 return nil, errors.Wrap(err, "rawFetchTags") 108 } 109 if total < 0 { 110 total = resp.TotalCount 111 } 112 for _, r := range resp.Tags { 113 if tagMapPtr, ok := tags[r.ResourceId]; !ok { 114 tagMap := map[string]string{ 115 r.TagKey: r.TagValue, 116 } 117 tags[r.ResourceId] = &tagMap 118 } else { 119 tagMap := *tagMapPtr 120 tagMap[r.TagKey] = r.TagValue 121 } 122 } 123 } 124 return tags, nil 125 } 126 127 func (region *SRegion) attachTag(serviceType, resourceType string, resIds []string, key, value string) error { 128 params := make(map[string]string) 129 params["ServiceType"] = serviceType 130 params["ResourcePrefix"] = resourceType 131 params["ResourceRegion"] = region.Region 132 params["TagKey"] = key 133 params["TagValue"] = value 134 for i, resId := range resIds { 135 params[fmt.Sprintf("ResourceIds.%d", i)] = resId 136 } 137 apiName := "AttachResourcesTag" 138 _, err := region.client.tagRequest(apiName, params) 139 if err != nil { 140 return errors.Wrap(err, apiName) 141 } 142 return nil 143 } 144 145 func (region *SRegion) detachTag(serviceType, resourceType string, resIds []string, key string) error { 146 params := make(map[string]string) 147 params["ServiceType"] = serviceType 148 params["ResourcePrefix"] = resourceType 149 params["ResourceRegion"] = region.Region 150 params["TagKey"] = key 151 for i, resId := range resIds { 152 params[fmt.Sprintf("ResourceIds.%d", i)] = resId 153 } 154 apiName := "DetachResourcesTag" 155 _, err := region.client.tagRequest(apiName, params) 156 if err != nil { 157 return errors.Wrap(err, apiName) 158 } 159 return nil 160 } 161 162 func (region *SRegion) modifyTag(serviceType, resourceType string, resIds []string, key, value string) error { 163 params := make(map[string]string) 164 params["ServiceType"] = serviceType 165 params["ResourcePrefix"] = resourceType 166 params["ResourceRegion"] = region.Region 167 params["TagKey"] = key 168 params["TagValue"] = value 169 for i, resId := range resIds { 170 params[fmt.Sprintf("ResourceIds.%d", i)] = resId 171 } 172 apiName := "ModifyResourcesTagValue" 173 _, err := region.client.tagRequest(apiName, params) 174 if err != nil { 175 return errors.Wrap(err, apiName) 176 } 177 return nil 178 } 179 180 func (region *SRegion) createTag(key, value string) error { 181 params := make(map[string]string) 182 params["TagKey"] = key 183 params["TagValue"] = value 184 apiName := "CreateTag" 185 _, err := region.client.tagRequest(apiName, params) 186 if err != nil { 187 if e, ok := errors.Cause(err).(*sdkerrors.TencentCloudSDKError); ok && e.Code == "ResourceInUse.TagDuplicate" { 188 return nil 189 } 190 return errors.Wrap(err, apiName) 191 } 192 return nil 193 } 194 195 func (region *SRegion) deleteTag(key, value string) error { 196 params := make(map[string]string) 197 params["TagKey"] = key 198 params["TagValue"] = value 199 apiName := "DeleteTag" 200 _, err := region.client.tagRequest(apiName, params) 201 if err != nil { 202 return errors.Wrap(err, apiName) 203 } 204 return nil 205 } 206 207 type SDescribeTag struct { 208 TagKey string `json:"TagKey"` 209 TagValue string `json:"TagValue"` 210 } 211 212 type SDescribeTagsSeqResponse struct { 213 SListInfo 214 Tags []SDescribeTag `json:"Tags"` 215 } 216 217 func (region *SRegion) fetchTags(keys []string, limit int, offset int) (int, []SDescribeTag, error) { 218 if len(keys) == 0 { 219 return 0, nil, nil 220 } 221 params := make(map[string]string) 222 for i, k := range keys { 223 params[fmt.Sprintf("TagKeys.%d", i)] = k 224 } 225 if limit > 0 { 226 params["Limit"] = strconv.FormatInt(int64(limit), 10) 227 } 228 if offset > 0 { 229 params["Offset"] = strconv.FormatInt(int64(offset), 10) 230 } 231 apiName := "DescribeTagValuesSeq" 232 respJson, err := region.client.tagRequest(apiName, params) 233 if err != nil { 234 return -1, nil, errors.Wrap(err, apiName) 235 } 236 resp := SDescribeTagsSeqResponse{} 237 err = respJson.Unmarshal(&resp) 238 if err != nil { 239 return -1, nil, errors.Wrap(err, "Unmarshal") 240 } 241 return resp.TotalCount, resp.Tags, nil 242 } 243 244 func (region *SRegion) fetchAllTags(keys []string) ([]SDescribeTag, error) { 245 tags := make([]SDescribeTag, 0) 246 total := -1 247 for total < 0 || len(tags) < total { 248 st, stags, err := region.fetchTags(keys, 100, len(tags)) 249 if err != nil { 250 return nil, errors.Wrap(err, "fetchTags") 251 } 252 if total < 0 { 253 total = st 254 } 255 tags = append(tags, stags...) 256 } 257 return tags, nil 258 } 259 260 func (region *SRegion) tagsExist(tags map[string]string) (map[string]string, map[string]string, error) { 261 tagKeys := make([]string, 0, len(tags)) 262 for k := range tags { 263 tagKeys = append(tagKeys, k) 264 } 265 tagRows, err := region.fetchAllTags(tagKeys) 266 if err != nil { 267 return nil, nil, errors.Wrap(err, "fetchAllTags") 268 } 269 existTags := make(map[string]string) 270 for i := range tagRows { 271 tagkv := tagRows[i] 272 if v, ok := tags[tagkv.TagKey]; ok && (tagkv.TagValue == v || (utils.IsInStringArray(tagkv.TagValue, []string{"", "null"}) && len(v) == 0)) { 273 // exist 274 existTags[tagkv.TagKey] = tagkv.TagValue 275 } 276 } 277 notexistTags := make(map[string]string) 278 for k, v := range tags { 279 if _, ok := existTags[k]; !ok { 280 notexistTags[k] = v 281 } 282 } 283 return existTags, notexistTags, nil 284 } 285 286 func (region *SRegion) SetResourceTags(serviceType, resoureType string, resIds []string, tags map[string]string, replace bool) error { 287 allTags, notExist, err := region.tagsExist(tags) 288 if err != nil { 289 return errors.Wrapf(err, "tagsExist") 290 } 291 var getTagValue = func(k string) string { 292 if len(tags[k]) > 0 { 293 return tags[k] 294 } 295 if v, ok := allTags[k]; ok { 296 return v 297 } 298 return "null" 299 } 300 301 for k, v := range notExist { 302 err := region.createTag(k, getTagValue(k)) 303 if err != nil { 304 return errors.Wrapf(err, "createTag %s %s", k, v) 305 } 306 } 307 308 oldTags, err := region.FetchResourceTags(serviceType, resoureType, resIds) 309 if err != nil { 310 return errors.Wrap(err, "FetchTags") 311 } 312 delKeyIds := make(map[string][]string) 313 modKeyIds := make(map[string][]string) 314 addKeyIds := make(map[string][]string) 315 for id, okvsPtr := range oldTags { 316 okvs := *okvsPtr 317 for k, v := range okvs { 318 if nv, ok := tags[k]; !ok { 319 // key not exist 320 if replace { 321 // need to delete 322 if _, ok := delKeyIds[k]; !ok { 323 delKeyIds[k] = []string{id} 324 } else { 325 delKeyIds[k] = append(delKeyIds[k], id) 326 } 327 } 328 } else if nv != v { 329 // need to modify 330 if _, ok := modKeyIds[k]; !ok { 331 modKeyIds[k] = []string{id} 332 } else { 333 modKeyIds[k] = append(modKeyIds[k], id) 334 } 335 } 336 } 337 for k := range tags { 338 if _, ok := okvs[k]; !ok { 339 // need to add 340 if _, ok := addKeyIds[k]; !ok { 341 addKeyIds[k] = []string{id} 342 } else { 343 addKeyIds[k] = append(addKeyIds[k], id) 344 } 345 } 346 } 347 } 348 for k := range tags { 349 for _, id := range resIds { 350 if _, ok := oldTags[id]; !ok { 351 if _, ok := addKeyIds[k]; !ok { 352 addKeyIds[k] = []string{id} 353 } else { 354 addKeyIds[k] = append(addKeyIds[k], id) 355 } 356 } 357 } 358 } 359 for k, ids := range delKeyIds { 360 err := region.detachTag(serviceType, resoureType, ids, k) 361 if err != nil { 362 return errors.Wrapf(err, "detachTag %s fail %s", k, err) 363 } 364 } 365 for k, ids := range modKeyIds { 366 err := region.modifyTag(serviceType, resoureType, ids, k, getTagValue(k)) 367 if err != nil { 368 return errors.Wrapf(err, "modifyTag %s %s fail %s", k, getTagValue(k), err) 369 } 370 } 371 for k, ids := range addKeyIds { 372 err := region.attachTag(serviceType, resoureType, ids, k, getTagValue(k)) 373 if err != nil { 374 return errors.Wrapf(err, "addTag %s %s fail %s", k, getTagValue(k), err) 375 } 376 } 377 return nil 378 }