// © Broadcom. All Rights Reserved. // The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. // SPDX-License-Identifier: Apache-2.0 package tags import ( "context" "fmt" "net/http" "strings" "github.com/vmware/govmomi/vapi/internal" "github.com/vmware/govmomi/vapi/rest" ) // Manager extends rest.Client, adding tag related methods. type Manager struct { *rest.Client } // NewManager creates a new Manager instance with the given client. func NewManager(client *rest.Client) *Manager { return &Manager{ Client: client, } } // isName returns true if the id is not a urn. func isName(id string) bool { return !strings.HasPrefix(id, "urn:") } // Tag provides methods to create, read, update, delete, and enumerate tags. type Tag struct { ID string `json:"id,omitempty"` Description string `json:"description,omitempty"` Name string `json:"name,omitempty"` CategoryID string `json:"category_id,omitempty"` UsedBy []string `json:"used_by,omitempty"` TagID string `json:"tag_id,omitempty"` } // Patch merges updates from the given src. func (t *Tag) Patch(src *Tag) { if src.Name != "" { t.Name = src.Name } if src.Description != "" { t.Description = src.Description } if src.CategoryID != "" { t.CategoryID = src.CategoryID } } // CreateTag creates a new tag with the given Name, Description and CategoryID. func (c *Manager) CreateTag(ctx context.Context, tag *Tag) (string, error) { // create avoids the annoyance of CreateTag requiring a "description" key to be included in the request, // even though the field value can be empty. type create struct { Name string `json:"name"` Description string `json:"description"` CategoryID string `json:"category_id"` TagID string `json:"tag_id,omitempty"` } spec := struct { Tag create `json:"create_spec"` }{ Tag: create{ Name: tag.Name, Description: tag.Description, CategoryID: tag.CategoryID, TagID: tag.TagID, }, } if isName(tag.CategoryID) { cat, err := c.GetCategory(ctx, tag.CategoryID) if err != nil { return "", err } spec.Tag.CategoryID = cat.ID } url := c.Resource(internal.TagPath) var res string if err := c.Do(ctx, url.Request(http.MethodPost, spec), &res); err != nil { return "", err } return res, nil } // UpdateTag can update one or both of the tag Description and Name fields. func (c *Manager) UpdateTag(ctx context.Context, tag *Tag) error { spec := struct { Tag Tag `json:"update_spec"` }{ Tag: Tag{ Name: tag.Name, Description: tag.Description, }, } url := c.Resource(internal.TagPath).WithID(tag.ID) return c.Do(ctx, url.Request(http.MethodPatch, spec), nil) } // DeleteTag deletes an existing tag. func (c *Manager) DeleteTag(ctx context.Context, tag *Tag) error { url := c.Resource(internal.TagPath).WithID(tag.ID) return c.Do(ctx, url.Request(http.MethodDelete), nil) } // GetTag fetches the tag information for the given identifier. // The id parameter can be a Tag ID or Tag Name. func (c *Manager) GetTag(ctx context.Context, id string) (*Tag, error) { if isName(id) { tags, err := c.GetTags(ctx) if err != nil { return nil, err } for i := range tags { if tags[i].Name == id { return &tags[i], nil } } } url := c.Resource(internal.TagPath).WithID(id) var res Tag if err := c.Do(ctx, url.Request(http.MethodGet), &res); err != nil { return nil, err } return &res, nil } // GetTagForCategory fetches the tag information for the given identifier in the given category. func (c *Manager) GetTagForCategory(ctx context.Context, id, category string) (*Tag, error) { if category == "" { return c.GetTag(ctx, id) } ids, err := c.ListTagsForCategory(ctx, category) if err != nil { return nil, err } for _, tagid := range ids { tag, err := c.GetTag(ctx, tagid) if err != nil { return nil, fmt.Errorf("get tag for category %s %s: %s", category, tagid, err) } if tag.ID == id || tag.Name == id { return tag, nil } } return nil, fmt.Errorf("tag %q not found in category %q", id, category) } // ListTags returns all tag IDs in the system. func (c *Manager) ListTags(ctx context.Context) ([]string, error) { url := c.Resource(internal.TagPath) var res []string if err := c.Do(ctx, url.Request(http.MethodGet), &res); err != nil { return nil, err } return res, nil } // GetTags fetches an array of tag information in the system. func (c *Manager) GetTags(ctx context.Context) ([]Tag, error) { ids, err := c.ListTags(ctx) if err != nil { return nil, fmt.Errorf("get tags failed for: %s", err) } var tags []Tag for _, id := range ids { tag, err := c.GetTag(ctx, id) if err != nil { return nil, fmt.Errorf("get category %s failed for %s", id, err) } tags = append(tags, *tag) } return tags, nil } // The id parameter can be a Category ID or Category Name. func (c *Manager) ListTagsForCategory(ctx context.Context, id string) ([]string, error) { if isName(id) { cat, err := c.GetCategory(ctx, id) if err != nil { return nil, err } id = cat.ID } body := struct { ID string `json:"category_id"` }{id} url := c.Resource(internal.TagPath).WithID(id).WithAction("list-tags-for-category") var res []string if err := c.Do(ctx, url.Request(http.MethodPost, body), &res); err != nil { return nil, err } return res, nil } // The id parameter can be a Category ID or Category Name. func (c *Manager) GetTagsForCategory(ctx context.Context, id string) ([]Tag, error) { ids, err := c.ListTagsForCategory(ctx, id) if err != nil { return nil, err } var tags []Tag for _, id := range ids { tag, err := c.GetTag(ctx, id) if err != nil { return nil, fmt.Errorf("get tag %s: %s", id, err) } tags = append(tags, *tag) } return tags, nil }