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  }