package godo import ( "context" "fmt" "net/http" ) const tagsBasePath = "v2/tags" // TagsService is an interface for interfacing with the tags // endpoints of the DigitalOcean API // See: https://docs.digitalocean.com/reference/api/api-reference/#tag/Tags type TagsService interface { List(context.Context, *ListOptions) ([]Tag, *Response, error) Get(context.Context, string) (*Tag, *Response, error) Create(context.Context, *TagCreateRequest) (*Tag, *Response, error) Delete(context.Context, string) (*Response, error) TagResources(context.Context, string, *TagResourcesRequest) (*Response, error) UntagResources(context.Context, string, *UntagResourcesRequest) (*Response, error) } // TagsServiceOp handles communication with tag related method of the // DigitalOcean API. type TagsServiceOp struct { client *Client } var _ TagsService = &TagsServiceOp{} // ResourceType represents a class of resource, currently only droplet are supported type ResourceType string const ( // DropletResourceType holds the string representing our ResourceType of Droplet. DropletResourceType ResourceType = "droplet" // ImageResourceType holds the string representing our ResourceType of Image. ImageResourceType ResourceType = "image" // VolumeResourceType holds the string representing our ResourceType of Volume. VolumeResourceType ResourceType = "volume" // LoadBalancerResourceType holds the string representing our ResourceType of LoadBalancer. LoadBalancerResourceType ResourceType = "load_balancer" // VolumeSnapshotResourceType holds the string representing our ResourceType for storage Snapshots. VolumeSnapshotResourceType ResourceType = "volume_snapshot" // DatabaseResourceType holds the string representing our ResourceType of Database. DatabaseResourceType ResourceType = "database" ) // Resource represent a single resource for associating/disassociating with tags type Resource struct { ID string `json:"resource_id,omitempty"` Type ResourceType `json:"resource_type,omitempty"` } // TaggedResources represent the set of resources a tag is attached to type TaggedResources struct { Count int `json:"count"` LastTaggedURI string `json:"last_tagged_uri,omitempty"` Droplets *TaggedDropletsResources `json:"droplets,omitempty"` Images *TaggedImagesResources `json:"images"` Volumes *TaggedVolumesResources `json:"volumes"` VolumeSnapshots *TaggedVolumeSnapshotsResources `json:"volume_snapshots"` Databases *TaggedDatabasesResources `json:"databases"` } // TaggedDropletsResources represent the droplet resources a tag is attached to type TaggedDropletsResources struct { Count int `json:"count,float64,omitempty"` LastTagged *Droplet `json:"last_tagged,omitempty"` LastTaggedURI string `json:"last_tagged_uri,omitempty"` } // TaggedResourcesData represent the generic resources a tag is attached to type TaggedResourcesData struct { Count int `json:"count,float64,omitempty"` LastTaggedURI string `json:"last_tagged_uri,omitempty"` } // TaggedImagesResources represent the image resources a tag is attached to type TaggedImagesResources TaggedResourcesData // TaggedVolumesResources represent the volume resources a tag is attached to type TaggedVolumesResources TaggedResourcesData // TaggedVolumeSnapshotsResources represent the volume snapshot resources a tag is attached to type TaggedVolumeSnapshotsResources TaggedResourcesData // TaggedDatabasesResources represent the database resources a tag is attached to type TaggedDatabasesResources TaggedResourcesData // Tag represent DigitalOcean tag type Tag struct { Name string `json:"name,omitempty"` Resources *TaggedResources `json:"resources,omitempty"` } //TagCreateRequest represents the JSON structure of a request of that type. type TagCreateRequest struct { Name string `json:"name"` } // TagResourcesRequest represents the JSON structure of a request of that type. type TagResourcesRequest struct { Resources []Resource `json:"resources"` } // UntagResourcesRequest represents the JSON structure of a request of that type. type UntagResourcesRequest struct { Resources []Resource `json:"resources"` } type tagsRoot struct { Tags []Tag `json:"tags"` Links []*LinkAction `json:"links"` Meta *Meta `json:"meta"` } type tagRoot struct { Tag *Tag `json:"tag"` } // List all tags func (s *TagsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Tag, *Response, error) { path := tagsBasePath path, err := addOptions(path, opt) if err != nil { return nil, nil, err } req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) if err != nil { return nil, nil, err } root := new(tagsRoot) resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } if l := root.Links; l != nil { resp.Links = l } if m := root.Meta; m != nil { resp.Meta = m } return root.Tags, resp, err } // Get a single tag func (s *TagsServiceOp) Get(ctx context.Context, name string) (*Tag, *Response, error) { path := fmt.Sprintf("%s/%s", tagsBasePath, name) req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) if err != nil { return nil, nil, err } root := new(tagRoot) resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } return root.Tag, resp, err } // Create a new tag func (s *TagsServiceOp) Create(ctx context.Context, createRequest *TagCreateRequest) (*Tag, *Response, error) { if createRequest == nil { return nil, nil, NewArgError("createRequest", "cannot be nil") } req, err := s.client.NewRequest(ctx, http.MethodPost, tagsBasePath, createRequest) if err != nil { return nil, nil, err } root := new(tagRoot) resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, resp, err } return root.Tag, resp, err } // Delete an existing tag func (s *TagsServiceOp) Delete(ctx context.Context, name string) (*Response, error) { if name == "" { return nil, NewArgError("name", "cannot be empty") } path := fmt.Sprintf("%s/%s", tagsBasePath, name) req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) if err != nil { return nil, err } resp, err := s.client.Do(ctx, req, nil) return resp, err } // TagResources associates resources with a given Tag. func (s *TagsServiceOp) TagResources(ctx context.Context, name string, tagRequest *TagResourcesRequest) (*Response, error) { if name == "" { return nil, NewArgError("name", "cannot be empty") } if tagRequest == nil { return nil, NewArgError("tagRequest", "cannot be nil") } path := fmt.Sprintf("%s/%s/resources", tagsBasePath, name) req, err := s.client.NewRequest(ctx, http.MethodPost, path, tagRequest) if err != nil { return nil, err } resp, err := s.client.Do(ctx, req, nil) return resp, err } // UntagResources dissociates resources with a given Tag. func (s *TagsServiceOp) UntagResources(ctx context.Context, name string, untagRequest *UntagResourcesRequest) (*Response, error) { if name == "" { return nil, NewArgError("name", "cannot be empty") } if untagRequest == nil { return nil, NewArgError("tagRequest", "cannot be nil") } path := fmt.Sprintf("%s/%s/resources", tagsBasePath, name) req, err := s.client.NewRequest(ctx, http.MethodDelete, path, untagRequest) if err != nil { return nil, err } resp, err := s.client.Do(ctx, req, nil) return resp, err }