-
Notifications
You must be signed in to change notification settings - Fork 308
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* support Spaces Keys API * Update func names and permission type --------- Co-authored-by: Andrew Starr-Bochicchio <[email protected]>
- Loading branch information
1 parent
1443692
commit 4a45b95
Showing
3 changed files
with
312 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
package godo | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
) | ||
|
||
const spacesKeysBasePath = "v2/spaces/keys" | ||
|
||
// SpacesKeysService is an interface for managing Spaces keys with the DigitalOcean API. | ||
type SpacesKeysService interface { | ||
List(context.Context, *ListOptions) ([]*SpacesKey, *Response, error) | ||
Update(context.Context, string, *SpacesKeyUpdateRequest) (*SpacesKey, *Response, error) | ||
Create(context.Context, *SpacesKeyCreateRequest) (*SpacesKey, *Response, error) | ||
Delete(context.Context, string) (*Response, error) | ||
} | ||
|
||
// SpacesKeysServiceOp handles communication with the Spaces key related methods of the | ||
// DigitalOcean API. | ||
type SpacesKeysServiceOp struct { | ||
client *Client | ||
} | ||
|
||
var _ SpacesKeysService = &SpacesKeysServiceOp{} | ||
|
||
// SpacesKeyPermission represents a permission for a Spaces grant | ||
type SpacesKeyPermission string | ||
|
||
const ( | ||
// SpacesKeyRead grants read-only access to the Spaces bucket | ||
SpacesKeyRead SpacesKeyPermission = "read" | ||
// SpacesKeyReadWrite grants read and write access to the Spaces bucket | ||
SpacesKeyReadWrite SpacesKeyPermission = "readwrite" | ||
// SpacesKeyFullAccess grants full access to the Spaces bucket | ||
SpacesKeyFullAccess SpacesKeyPermission = "fullaccess" | ||
) | ||
|
||
// Grant represents a Grant for a Spaces key | ||
type Grant struct { | ||
Bucket string `json:"bucket"` | ||
Permission SpacesKeyPermission `json:"permission"` | ||
} | ||
|
||
// SpacesKey represents a DigitalOcean Spaces key | ||
type SpacesKey struct { | ||
Name string `json:"name"` | ||
AccessKey string `json:"access_key"` | ||
SecretKey string `json:"secret_key"` | ||
Grants []*Grant `json:"grants"` | ||
CreatedAt string `json:"created_at"` | ||
} | ||
|
||
// SpacesKeyRoot represents a response from the DigitalOcean API | ||
type spacesKeyRoot struct { | ||
Key *SpacesKey `json:"key"` | ||
} | ||
|
||
// SpacesKeyCreateRequest represents a request to create a Spaces key. | ||
type SpacesKeyCreateRequest struct { | ||
Name string `json:"name"` | ||
Grants []*Grant `json:"grants"` | ||
} | ||
|
||
// SpacesKeyUpdateRequest represents a request to update a Spaces key. | ||
type SpacesKeyUpdateRequest struct { | ||
Name string `json:"name"` | ||
Grants []*Grant `json:"grants"` | ||
} | ||
|
||
// spacesListKeysRoot represents a response from the DigitalOcean API | ||
type spacesListKeysRoot struct { | ||
Keys []*SpacesKey `json:"keys,omitempty"` | ||
Links *Links `json:"links,omitempty"` | ||
Meta *Meta `json:"meta"` | ||
} | ||
|
||
// Create creates a new Spaces key. | ||
func (s *SpacesKeysServiceOp) Create(ctx context.Context, createRequest *SpacesKeyCreateRequest) (*SpacesKey, *Response, error) { | ||
if createRequest == nil { | ||
return nil, nil, NewArgError("createRequest", "cannot be nil") | ||
} | ||
|
||
req, err := s.client.NewRequest(ctx, http.MethodPost, spacesKeysBasePath, createRequest) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
root := new(spacesKeyRoot) | ||
resp, err := s.client.Do(ctx, req, root) | ||
if err != nil { | ||
return nil, resp, err | ||
} | ||
|
||
return root.Key, resp, nil | ||
} | ||
|
||
// Delete deletes a Spaces key. | ||
func (s *SpacesKeysServiceOp) Delete(ctx context.Context, accessKey string) (*Response, error) { | ||
if accessKey == "" { | ||
return nil, NewArgError("accessKey", "cannot be empty") | ||
} | ||
|
||
path := fmt.Sprintf("%s/%s", spacesKeysBasePath, accessKey) | ||
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
resp, err := s.client.Do(ctx, req, nil) | ||
if err != nil { | ||
return resp, err | ||
} | ||
|
||
return resp, nil | ||
} | ||
|
||
// Update updates a Spaces key. | ||
func (s *SpacesKeysServiceOp) Update(ctx context.Context, accessKey string, updateRequest *SpacesKeyUpdateRequest) (*SpacesKey, *Response, error) { | ||
if accessKey == "" { | ||
return nil, nil, NewArgError("accessKey", "cannot be empty") | ||
} | ||
if updateRequest == nil { | ||
return nil, nil, NewArgError("updateRequest", "cannot be nil") | ||
} | ||
|
||
path := fmt.Sprintf("%s/%s", spacesKeysBasePath, accessKey) | ||
req, err := s.client.NewRequest(ctx, http.MethodPut, path, updateRequest) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
root := new(spacesKeyRoot) | ||
resp, err := s.client.Do(ctx, req, root) | ||
if err != nil { | ||
return nil, resp, err | ||
} | ||
|
||
return root.Key, resp, nil | ||
} | ||
|
||
// List returns a list of Spaces keys. | ||
func (s *SpacesKeysServiceOp) List(ctx context.Context, opts *ListOptions) ([]*SpacesKey, *Response, error) { | ||
path, err := addOptions(spacesKeysBasePath, opts) | ||
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(spacesListKeysRoot) | ||
resp, err := s.client.Do(ctx, req, root) | ||
if err != nil { | ||
return nil, resp, err | ||
} | ||
|
||
if root.Links != nil { | ||
resp.Links = root.Links | ||
} | ||
if root.Meta != nil { | ||
resp.Meta = root.Meta | ||
} | ||
|
||
return root.Keys, resp, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package godo | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestSpacesKeyCreate(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
createRequest := &SpacesKeyCreateRequest{ | ||
Name: "test-key", | ||
Grants: []*Grant{ | ||
{ | ||
Bucket: "test-bucket", | ||
Permission: SpacesKeyRead, | ||
}, | ||
}, | ||
} | ||
|
||
mux.HandleFunc("/v2/spaces/keys", func(w http.ResponseWriter, r *http.Request) { | ||
assert.Equal(t, http.MethodPost, r.Method) | ||
w.WriteHeader(http.StatusCreated) | ||
fmt.Fprint(w, `{"key":{"name":"test-key","access_key":"test-access-key","secret_key":"test-secret-key","created_at":"2023-10-01T00:00:00Z","grants":[{"bucket":"test-bucket","permission":"read"}]}}`) | ||
}) | ||
|
||
key, resp, err := client.SpacesKeys.Create(context.Background(), createRequest) | ||
assert.NoError(t, err) | ||
assert.NotNil(t, resp) | ||
assert.Equal(t, "test-key", key.Name) | ||
assert.Equal(t, "test-access-key", key.AccessKey) | ||
assert.Equal(t, "test-secret-key", key.SecretKey) | ||
assert.Len(t, key.Grants, 1) | ||
assert.Equal(t, "test-bucket", key.Grants[0].Bucket) | ||
assert.Equal(t, SpacesKeyRead, key.Grants[0].Permission) | ||
} | ||
|
||
func TestSpacesKeyDelete(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
mux.HandleFunc("/v2/spaces/keys/test-access-key", func(w http.ResponseWriter, r *http.Request) { | ||
assert.Equal(t, http.MethodDelete, r.Method) | ||
w.WriteHeader(http.StatusNoContent) | ||
}) | ||
|
||
resp, err := client.SpacesKeys.Delete(context.Background(), "test-access-key") | ||
assert.NoError(t, err) | ||
assert.NotNil(t, resp) | ||
} | ||
|
||
func TestSpacesKeyUpdate(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
updateRequest := &SpacesKeyUpdateRequest{ | ||
Name: "updated-key", | ||
Grants: []*Grant{ | ||
{ | ||
Bucket: "updated-bucket", | ||
Permission: SpacesKeyReadWrite, | ||
}, | ||
}, | ||
} | ||
|
||
mux.HandleFunc("/v2/spaces/keys/test-access-key", func(w http.ResponseWriter, r *http.Request) { | ||
assert.Equal(t, http.MethodPut, r.Method) | ||
w.WriteHeader(http.StatusOK) | ||
fmt.Fprint(w, `{"key":{"name":"updated-key","access_key":"test-access-key","created_at":"2023-10-01T00:00:00Z","grants":[{"bucket":"updated-bucket","permission":"readwrite"}]}}`) | ||
}) | ||
|
||
key, resp, err := client.SpacesKeys.Update(context.Background(), "test-access-key", updateRequest) | ||
assert.NoError(t, err) | ||
assert.NotNil(t, resp) | ||
assert.Equal(t, "updated-key", key.Name) | ||
assert.Equal(t, "test-access-key", key.AccessKey) | ||
assert.Len(t, key.Grants, 1) | ||
assert.Equal(t, "updated-bucket", key.Grants[0].Bucket) | ||
assert.Equal(t, SpacesKeyReadWrite, key.Grants[0].Permission) | ||
} | ||
|
||
func TestSpacesKeyList(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
mux.HandleFunc("/v2/spaces/keys", func(w http.ResponseWriter, r *http.Request) { | ||
assert.Equal(t, http.MethodGet, r.Method) | ||
w.WriteHeader(http.StatusOK) | ||
fmt.Fprint(w, `{"keys":[{"name":"test-key","access_key":"test-access-key","created_at":"2023-10-01T00:00:00Z","grants":[{"bucket":"test-bucket","permission":"read"}]}]}`) | ||
}) | ||
|
||
keys, resp, err := client.SpacesKeys.List(context.Background(), nil) | ||
assert.NoError(t, err) | ||
assert.NotNil(t, resp) | ||
assert.Len(t, keys, 1) | ||
assert.Equal(t, "test-key", keys[0].Name) | ||
assert.Equal(t, "test-access-key", keys[0].AccessKey) | ||
assert.Len(t, keys[0].Grants, 1) | ||
assert.Equal(t, "test-bucket", keys[0].Grants[0].Bucket) | ||
assert.Equal(t, SpacesKeyRead, keys[0].Grants[0].Permission) | ||
} | ||
|
||
func TestSpacesKeyList_Pagination(t *testing.T) { | ||
setup() | ||
defer teardown() | ||
|
||
mux.HandleFunc("/v2/spaces/keys", func(w http.ResponseWriter, r *http.Request) { | ||
assert.Equal(t, http.MethodGet, r.Method) | ||
page := r.URL.Query().Get("page") | ||
if page == "2" { | ||
w.WriteHeader(http.StatusOK) | ||
fmt.Fprint(w, `{"keys":[{"name":"test-key-2","access_key":"test-access-key-2","created_at":"2023-10-02T00:00:00Z","grants":[{"bucket":"test-bucket-2","permission":"readwrite"}]}]}`) | ||
} else { | ||
w.WriteHeader(http.StatusOK) | ||
fmt.Fprint(w, `{"keys":[{"name":"test-key-1","access_key":"test-access-key-1","created_at":"2023-10-01T00:00:00Z","grants":[{"bucket":"test-bucket-1","permission":"read"}]}]}`) | ||
} | ||
}) | ||
|
||
// Test first page | ||
keys, resp, err := client.SpacesKeys.List(context.Background(), &ListOptions{Page: 1}) | ||
assert.NoError(t, err) | ||
assert.NotNil(t, resp) | ||
assert.Len(t, keys, 1) | ||
assert.Equal(t, "test-key-1", keys[0].Name) | ||
assert.Equal(t, "test-access-key-1", keys[0].AccessKey) | ||
assert.Len(t, keys[0].Grants, 1) | ||
assert.Equal(t, "test-bucket-1", keys[0].Grants[0].Bucket) | ||
assert.Equal(t, SpacesKeyRead, keys[0].Grants[0].Permission) | ||
|
||
// Test second page | ||
keys, resp, err = client.SpacesKeys.List(context.Background(), &ListOptions{Page: 2}) | ||
assert.NoError(t, err) | ||
assert.NotNil(t, resp) | ||
assert.Len(t, keys, 1) | ||
assert.Equal(t, "test-key-2", keys[0].Name) | ||
assert.Equal(t, "test-access-key-2", keys[0].AccessKey) | ||
assert.Len(t, keys[0].Grants, 1) | ||
assert.Equal(t, "test-bucket-2", keys[0].Grants[0].Bucket) | ||
assert.Equal(t, SpacesKeyReadWrite, keys[0].Grants[0].Permission) | ||
} |