1// Copyright 2017 VMware, Inc. All Rights Reserved. 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 15package tags 16 17import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "io" 22 "net/http" 23 "regexp" 24 "strings" 25 26 "github.com/sirupsen/logrus" 27 "github.com/pkg/errors" 28) 29 30const ( 31 TagURL = "/com/vmware/cis/tagging/tag" 32) 33 34type TagCreateSpec struct { 35 CreateSpec TagCreate `json:"create_spec"` 36} 37 38type TagCreate struct { 39 CategoryID string `json:"category_id"` 40 Description string `json:"description"` 41 Name string `json:"name"` 42} 43 44type TagUpdateSpec struct { 45 UpdateSpec TagUpdate `json:"update_spec"` 46} 47 48type TagUpdate struct { 49 Description string `json:"description"` 50 Name string `json:"name"` 51} 52 53type Tag struct { 54 ID string `json:"id"` 55 Description string `json:"description"` 56 Name string `json:"name"` 57 CategoryID string `json:"category_id"` 58 UsedBy []string `json:"used_by"` 59} 60 61var Logger = logrus.New() 62 63func (c *RestClient) CreateTagIfNotExist(ctx context.Context, name string, description string, categoryID string) (*string, error) { 64 tagCreate := TagCreate{categoryID, description, name} 65 spec := TagCreateSpec{tagCreate} 66 id, err := c.CreateTag(ctx, &spec) 67 if err == nil { 68 return id, nil 69 } 70 Logger.Debugf("Created tag %s failed for %s", errors.WithStack(err)) 71 // if already exists, query back 72 if strings.Contains(err.Error(), ErrAlreadyExists) { 73 tagObjs, err := c.GetTagByNameForCategory(ctx, name, categoryID) 74 if err != nil { 75 return nil, errors.Wrapf(err, "failed to query tag %s for category %s", name, categoryID) 76 } 77 if tagObjs != nil { 78 return &tagObjs[0].ID, nil 79 } 80 81 // should not happen 82 return nil, errors.New("Failed to create tag for it's existed, but could not query back. Please check system") 83 } 84 85 return nil, errors.Wrap(err, "failed to create tag") 86} 87 88func (c *RestClient) DeleteTagIfNoObjectAttached(ctx context.Context, id string) error { 89 objs, err := c.ListAttachedObjects(ctx, id) 90 if err != nil { 91 return errors.Wrap(err, "failed to delete tag") 92 } 93 if objs != nil && len(objs) > 0 { 94 Logger.Debugf("tag %s related objects is not empty, do not delete it.", id) 95 return nil 96 } 97 return c.DeleteTag(ctx, id) 98} 99 100func (c *RestClient) CreateTag(ctx context.Context, spec *TagCreateSpec) (*string, error) { 101 Logger.Debugf("Create Tag %v", spec) 102 stream, _, status, err := c.call(ctx, "POST", TagURL, spec, nil) 103 104 Logger.Debugf("Get status code: %d", status) 105 if status != http.StatusOK || err != nil { 106 Logger.Debugf("Create tag failed with status code: %d, error message: %s", status, errors.WithStack(err)) 107 return nil, errors.Wrapf(err, "Status code: %d", status) 108 } 109 110 type RespValue struct { 111 Value string 112 } 113 114 var pID RespValue 115 if err := json.NewDecoder(stream).Decode(&pID); err != nil { 116 Logger.Debugf("Decode response body failed for: %s", errors.WithStack(err)) 117 return nil, errors.Wrap(err, "create tag failed") 118 } 119 return &pID.Value, nil 120} 121 122func (c *RestClient) GetTag(ctx context.Context, id string) (*Tag, error) { 123 Logger.Debugf("Get tag %s", id) 124 125 stream, _, status, err := c.call(ctx, "GET", fmt.Sprintf("%s/id:%s", TagURL, id), nil, nil) 126 127 if status != http.StatusOK || err != nil { 128 Logger.Debugf("Get tag failed with status code: %s, error message: %s", status, errors.WithStack(err)) 129 return nil, errors.Wrapf(err, "Status code: %d", status) 130 } 131 132 type RespValue struct { 133 Value Tag 134 } 135 136 var pTag RespValue 137 if err := json.NewDecoder(stream).Decode(&pTag); err != nil { 138 Logger.Debugf("Decode response body failed for: %s", errors.WithStack(err)) 139 return nil, errors.Wrapf(err, "failed to get tag %s", id) 140 } 141 return &(pTag.Value), nil 142} 143 144func (c *RestClient) UpdateTag(ctx context.Context, id string, spec *TagUpdateSpec) error { 145 Logger.Debugf("Update tag %v", spec) 146 _, _, status, err := c.call(ctx, "PATCH", fmt.Sprintf("%s/id:%s", TagURL, id), spec, nil) 147 148 Logger.Debugf("Get status code: %d", status) 149 if status != http.StatusOK || err != nil { 150 Logger.Debugf("Update tag failed with status code: %d, error message: %s", status, errors.WithStack(err)) 151 return errors.Wrapf(err, "Status code: %d", status) 152 } 153 154 return nil 155} 156 157func (c *RestClient) DeleteTag(ctx context.Context, id string) error { 158 Logger.Debugf("Delete tag %s", id) 159 160 _, _, status, err := c.call(ctx, "DELETE", fmt.Sprintf("%s/id:%s", TagURL, id), nil, nil) 161 162 if status != http.StatusOK || err != nil { 163 Logger.Debugf("Delete tag failed with status code: %s, error message: %s", status, errors.WithStack(err)) 164 return errors.Wrapf(err, "Status code: %d", status) 165 } 166 return nil 167} 168 169func (c *RestClient) ListTags(ctx context.Context) ([]string, error) { 170 Logger.Debugf("List all tags") 171 172 stream, _, status, err := c.call(ctx, "GET", TagURL, nil, nil) 173 174 if status != http.StatusOK || err != nil { 175 Logger.Debugf("Get tags failed with status code: %s, error message: %s", status, errors.WithStack(err)) 176 return nil, errors.Wrapf(err, "Status code: %d", status) 177 } 178 179 return c.handleTagIDList(stream) 180} 181 182func (c *RestClient) ListTagsForCategory(ctx context.Context, id string) ([]string, error) { 183 Logger.Debugf("List tags for category: %s", id) 184 185 type PostCategory struct { 186 CId string `json:"category_id"` 187 } 188 spec := PostCategory{id} 189 stream, _, status, err := c.call(ctx, "POST", fmt.Sprintf("%s/id:%s?~action=list-tags-for-category", TagURL, id), spec, nil) 190 191 if status != http.StatusOK || err != nil { 192 Logger.Debugf("List tags for category failed with status code: %s, error message: %s", status, errors.WithStack(err)) 193 return nil, errors.Wrapf(err, "Status code: %d", status) 194 } 195 196 return c.handleTagIDList(stream) 197} 198 199func (c *RestClient) handleTagIDList(stream io.ReadCloser) ([]string, error) { 200 type Tags struct { 201 Value []string 202 } 203 204 var pTags Tags 205 if err := json.NewDecoder(stream).Decode(&pTags); err != nil { 206 Logger.Debugf("Decode response body failed for: %s", errors.WithStack(err)) 207 return nil, errors.Wrap(err, "failed to decode json") 208 } 209 return pTags.Value, nil 210} 211 212// Get tag through tag name and category id 213func (c *RestClient) GetTagByNameForCategory(ctx context.Context, name string, id string) ([]Tag, error) { 214 Logger.Debugf("Get tag %s for category %s", name, id) 215 tagIds, err := c.ListTagsForCategory(ctx, id) 216 if err != nil { 217 Logger.Debugf("Get tag failed for %s", errors.WithStack(err)) 218 return nil, errors.Wrapf(err, "get tag failed for name %s category %s", name, id) 219 } 220 221 var tags []Tag 222 for _, tID := range tagIds { 223 tag, err := c.GetTag(ctx, tID) 224 if err != nil { 225 Logger.Debugf("Get tag %s failed for %s", tID, errors.WithStack(err)) 226 return nil, errors.Wrapf(err, "get tag failed for name %s category %s", name, id) 227 } 228 if tag.Name == name { 229 tags = append(tags, *tag) 230 } 231 } 232 return tags, nil 233} 234 235// Get attached tags through tag name pattern 236func (c *RestClient) GetAttachedTagsByNamePattern(ctx context.Context, namePattern string, objID string, objType string) ([]Tag, error) { 237 tagIds, err := c.ListAttachedTags(ctx, objID, objType) 238 if err != nil { 239 Logger.Debugf("Get attached tags failed for %s", errors.WithStack(err)) 240 return nil, errors.Wrap(err, "get attached tags failed") 241 } 242 243 var validName = regexp.MustCompile(namePattern) 244 var tags []Tag 245 for _, tID := range tagIds { 246 tag, err := c.GetTag(ctx, tID) 247 if err != nil { 248 Logger.Debugf("Get tag %s failed for %s", tID, errors.WithStack(err)) 249 } 250 if validName.MatchString(tag.Name) { 251 tags = append(tags, *tag) 252 } 253 } 254 return tags, nil 255} 256