Skip to content

Commit 5079ce5

Browse files
tokens: Add initial support for new API.
1 parent 39a259e commit 5079ce5

File tree

3 files changed

+696
-0
lines changed

3 files changed

+696
-0
lines changed

godo.go

+2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ type Client struct {
8181
Storage StorageService
8282
StorageActions StorageActionsService
8383
Tags TagsService
84+
Tokens TokensService
8485
VPCs VPCsService
8586

8687
// Optional function called after every successful request made to the DO APIs
@@ -250,6 +251,7 @@ func NewClient(httpClient *http.Client) *Client {
250251
c.Storage = &StorageServiceOp{client: c}
251252
c.StorageActions = &StorageActionsServiceOp{client: c}
252253
c.Tags = &TagsServiceOp{client: c}
254+
c.Tokens = &TokensServiceOp{client: c}
253255
c.VPCs = &VPCsServiceOp{client: c}
254256

255257
c.headers = make(map[string]string)

tokens.go

+228
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
package godo
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"time"
8+
)
9+
10+
const (
11+
accessTokensBasePath = "v2/tokens"
12+
tokenScopesBasePath = accessTokensBasePath + "/scopes"
13+
)
14+
15+
// TokensService is an interface for managing DigitalOcean API access tokens.
16+
// It is not currently generally available. Follow the release notes for
17+
// updates: https://docs.digitalocean.com/release-notes/api/
18+
type TokensService interface {
19+
List(context.Context, *ListOptions) ([]Token, *Response, error)
20+
Get(context.Context, int) (*Token, *Response, error)
21+
Create(context.Context, *TokenCreateRequest) (*Token, *Response, error)
22+
Update(context.Context, int, *TokenUpdateRequest) (*Token, *Response, error)
23+
Revoke(context.Context, int) (*Response, error)
24+
ListScopes(context.Context, *ListOptions) ([]TokenScope, *Response, error)
25+
ListScopesByNamespace(context.Context, string, *ListOptions) ([]TokenScope, *Response, error)
26+
}
27+
28+
// TokensServiceOp handles communication with the tokens related methods of the
29+
// DigitalOcean API.
30+
type TokensServiceOp struct {
31+
client *Client
32+
}
33+
34+
var _ TokensService = &TokensServiceOp{}
35+
36+
// Token represents a DigitalOcean API token.
37+
type Token struct {
38+
ID int `json:"id"`
39+
Name string `json:"name"`
40+
Scopes []string `json:"scopes"`
41+
ExpirySeconds *int `json:"expiry_seconds"`
42+
CreatedAt time.Time `json:"created_at"`
43+
LastUsedAt string `json:"last_used_at"`
44+
45+
// AccessToken contains the actual Oauth token string. It is only included
46+
// in the create response.
47+
AccessToken string `json:"access_token,omitempty"`
48+
}
49+
50+
// tokenRoot represents a response from the DigitalOcean API
51+
type tokenRoot struct {
52+
Token *Token `json:"token"`
53+
}
54+
55+
type tokensRoot struct {
56+
Tokens []Token `json:"tokens"`
57+
Links *Links `json:"links"`
58+
Meta *Meta `json:"meta"`
59+
}
60+
61+
// TokenCreateRequest represents a request to create a token.
62+
type TokenCreateRequest struct {
63+
Name string `json:"name"`
64+
Scopes []string `json:"scopes"`
65+
ExpirySeconds *int `json:"expiry_seconds,omitempty"`
66+
}
67+
68+
// TokenUpdateRequest represents a request to update a token.
69+
type TokenUpdateRequest struct {
70+
Name string `json:"name,omitempty"`
71+
Scopes []string `json:"scopes,omitempty"`
72+
}
73+
74+
// TokenScope is a representation of a scope for the public API.
75+
type TokenScope struct {
76+
Name string `json:"name"`
77+
}
78+
79+
type tokenScopesRoot struct {
80+
TokenScopes []TokenScope `json:"scopes"`
81+
Links *Links `json:"links"`
82+
Meta *Meta `json:"meta"`
83+
}
84+
85+
type tokenScopeNamespaceParam struct {
86+
Namespace string `url:"namespace,omitempty"`
87+
}
88+
89+
// List all DigitalOcean API access tokens.
90+
func (c TokensServiceOp) List(ctx context.Context, opt *ListOptions) ([]Token, *Response, error) {
91+
path, err := addOptions(accessTokensBasePath, opt)
92+
if err != nil {
93+
return nil, nil, err
94+
}
95+
96+
req, err := c.client.NewRequest(ctx, http.MethodGet, path, nil)
97+
if err != nil {
98+
return nil, nil, err
99+
}
100+
101+
root := new(tokensRoot)
102+
resp, err := c.client.Do(ctx, req, root)
103+
if err != nil {
104+
return nil, resp, err
105+
}
106+
if l := root.Links; l != nil {
107+
resp.Links = l
108+
}
109+
if m := root.Meta; m != nil {
110+
resp.Meta = m
111+
}
112+
113+
return root.Tokens, resp, err
114+
}
115+
116+
// Get a specific DigitalOcean API access token.
117+
func (c TokensServiceOp) Get(ctx context.Context, tokenID int) (*Token, *Response, error) {
118+
path := fmt.Sprintf("%s/%d", accessTokensBasePath, tokenID)
119+
req, err := c.client.NewRequest(ctx, http.MethodGet, path, nil)
120+
if err != nil {
121+
return nil, nil, err
122+
}
123+
124+
root := new(tokenRoot)
125+
resp, err := c.client.Do(ctx, req, root)
126+
if err != nil {
127+
return nil, resp, err
128+
}
129+
130+
return root.Token, resp, err
131+
}
132+
133+
// Create a new DigitalOcean API access token.
134+
func (c TokensServiceOp) Create(ctx context.Context, createRequest *TokenCreateRequest) (*Token, *Response, error) {
135+
req, err := c.client.NewRequest(ctx, http.MethodPost, accessTokensBasePath, createRequest)
136+
if err != nil {
137+
return nil, nil, err
138+
}
139+
140+
root := new(tokenRoot)
141+
resp, err := c.client.Do(ctx, req, root)
142+
if err != nil {
143+
return nil, resp, err
144+
}
145+
146+
return root.Token, resp, err
147+
}
148+
149+
// Update the name or scopes of a specific DigitalOcean API access token.
150+
func (c TokensServiceOp) Update(ctx context.Context, tokenID int, updateRequest *TokenUpdateRequest) (*Token, *Response, error) {
151+
path := fmt.Sprintf("%s/%d", accessTokensBasePath, tokenID)
152+
req, err := c.client.NewRequest(ctx, http.MethodPatch, path, updateRequest)
153+
if err != nil {
154+
return nil, nil, err
155+
}
156+
157+
root := new(tokenRoot)
158+
resp, err := c.client.Do(ctx, req, root)
159+
if err != nil {
160+
return nil, resp, err
161+
}
162+
163+
return root.Token, resp, err
164+
}
165+
166+
// Revoke a specific DigitalOcean API access token.
167+
func (c TokensServiceOp) Revoke(ctx context.Context, id int) (*Response, error) {
168+
path := fmt.Sprintf("%s/%d", accessTokensBasePath, id)
169+
req, err := c.client.NewRequest(ctx, http.MethodDelete, path, nil)
170+
if err != nil {
171+
return nil, err
172+
}
173+
174+
resp, err := c.client.Do(ctx, req, nil)
175+
176+
return resp, err
177+
}
178+
179+
// ListScopes lists all available scopes that can be granted to a token.
180+
func (c TokensServiceOp) ListScopes(ctx context.Context, opt *ListOptions) ([]TokenScope, *Response, error) {
181+
path, err := addOptions(tokenScopesBasePath, opt)
182+
if err != nil {
183+
return nil, nil, err
184+
}
185+
186+
return listTokenScopes(ctx, c, path)
187+
}
188+
189+
// ListScopesByNamespace lists available scopes in a namespace that can be granted
190+
// to a token (e.g. the namespace for the `droplet:read“ scope is `droplet`).
191+
func (c TokensServiceOp) ListScopesByNamespace(ctx context.Context, namespace string, opt *ListOptions) ([]TokenScope, *Response, error) {
192+
path, err := addOptions(tokenScopesBasePath, opt)
193+
if err != nil {
194+
return nil, nil, err
195+
}
196+
197+
namespaceOpt := tokenScopeNamespaceParam{
198+
Namespace: namespace,
199+
}
200+
201+
path, err = addOptions(path, namespaceOpt)
202+
if err != nil {
203+
return nil, nil, err
204+
}
205+
206+
return listTokenScopes(ctx, c, path)
207+
}
208+
209+
func listTokenScopes(ctx context.Context, c TokensServiceOp, path string) ([]TokenScope, *Response, error) {
210+
req, err := c.client.NewRequest(ctx, http.MethodGet, path, nil)
211+
if err != nil {
212+
return nil, nil, err
213+
}
214+
215+
root := new(tokenScopesRoot)
216+
resp, err := c.client.Do(ctx, req, root)
217+
if err != nil {
218+
return nil, resp, err
219+
}
220+
if l := root.Links; l != nil {
221+
resp.Links = l
222+
}
223+
if m := root.Meta; m != nil {
224+
resp.Meta = m
225+
}
226+
227+
return root.TokenScopes, resp, err
228+
}

0 commit comments

Comments
 (0)