Skip to content

Commit b773c71

Browse files
MNFS-164: Added NFS APIs (#912)
* MNFS-164: Added NFS APIs * Fixed formatting * Added doc lines for struct and interface, added region query param for List, validate size_gib, added tests * Removed user_id * Added region query param to get and delete endpoints --------- Co-authored-by: SSharma-10 <shivanisharma@digitalocean.com>
1 parent f552722 commit b773c71

File tree

3 files changed

+313
-0
lines changed

3 files changed

+313
-0
lines changed

godo.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ type Client struct {
7878
Kubernetes KubernetesService
7979
LoadBalancers LoadBalancersService
8080
Monitoring MonitoringService
81+
Nfs NfsService
8182
OneClick OneClickService
8283
Projects ProjectsService
8384
Regions RegionsService
@@ -304,6 +305,7 @@ func NewClient(httpClient *http.Client) *Client {
304305
c.Kubernetes = &KubernetesServiceOp{client: c}
305306
c.LoadBalancers = &LoadBalancersServiceOp{client: c}
306307
c.Monitoring = &MonitoringServiceOp{client: c}
308+
c.Nfs = &NfsServiceOp{client: c}
307309
c.VPCNATGateways = &VPCNATGatewaysServiceOp{client: c}
308310
c.OneClick = &OneClickServiceOp{client: c}
309311
c.Projects = &ProjectsServiceOp{client: c}

nfs.go

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package godo
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
)
8+
9+
const nfsBasePath = "v2/nfs"
10+
11+
type NfsService interface {
12+
// List retrieves a list of NFS shares with optional filtering via ListOptions and region
13+
List(context.Context, *ListOptions, string) ([]*Nfs, *Response, error)
14+
// Create creates a new NFS share with the provided configuration
15+
Create(context.Context, *NfsCreateRequest) (*Nfs, *Response, error)
16+
// Delete removes an NFS share by its ID and region
17+
Delete(context.Context, string, string) (*Response, error)
18+
// Get retrieves a specific NFS share by its ID and region
19+
Get(context.Context, string, string) (*Nfs, *Response, error)
20+
}
21+
22+
// NfsServiceOp handles communication with the NFS related methods of the
23+
// DigitalOcean API.
24+
type NfsServiceOp struct {
25+
client *Client
26+
}
27+
28+
var _ NfsService = &NfsServiceOp{}
29+
30+
// Nfs represents a DigitalOcean NFS share
31+
type Nfs struct {
32+
// ID is the unique identifier for the NFS share
33+
ID string `json:"id"`
34+
// Name is the human-readable name for the NFS share
35+
Name string `json:"name"`
36+
// SizeGib is the size of the NFS share in gibibytes
37+
SizeGib int `json:"size_gib"`
38+
// Region is the datacenter region where the NFS share is located
39+
Region string `json:"region"`
40+
// Status represents the current state of the NFS share
41+
Status string `json:"status"`
42+
// CreatedAt is the timestamp when the NFS share was created
43+
CreatedAt string `json:"created_at"`
44+
// VpcIDs is a list of VPC IDs that have access to the NFS share
45+
VpcIDs []string `json:"vpc_ids"`
46+
}
47+
48+
// NfsCreateRequest represents a request to create an NFS share.
49+
type NfsCreateRequest struct {
50+
Name string `json:"name"`
51+
SizeGib int `json:"size_gib"`
52+
Region string `json:"region"`
53+
VpcIDs []string `json:"vpc_ids,omitempty"`
54+
}
55+
56+
// nfsRoot represents a response from the DigitalOcean API
57+
type nfsRoot struct {
58+
Share *Nfs `json:"share"`
59+
}
60+
61+
// nfsListRoot represents a response from the DigitalOcean API
62+
type nfsListRoot struct {
63+
Shares []*Nfs `json:"shares,omitempty"`
64+
Links *Links `json:"links,omitempty"`
65+
Meta *Meta `json:"meta"`
66+
}
67+
68+
// nfsOptions represents the query param options for NFS operations
69+
type nfsOptions struct {
70+
Region string `url:"region"`
71+
}
72+
73+
// Create creates a new NFS share.
74+
func (s *NfsServiceOp) Create(ctx context.Context, createRequest *NfsCreateRequest) (*Nfs, *Response, error) {
75+
if createRequest == nil {
76+
return nil, nil, NewArgError("createRequest", "cannot be nil")
77+
}
78+
79+
if createRequest.SizeGib < 50 {
80+
return nil, nil, NewArgError("size_gib", "it cannot be less than 50Gib")
81+
}
82+
83+
req, err := s.client.NewRequest(ctx, http.MethodPost, nfsBasePath, createRequest)
84+
if err != nil {
85+
return nil, nil, err
86+
}
87+
88+
root := new(nfsRoot)
89+
resp, err := s.client.Do(ctx, req, root)
90+
if err != nil {
91+
return nil, resp, err
92+
}
93+
94+
return root.Share, resp, nil
95+
}
96+
97+
// Get retrieves an NFS share by ID and region.
98+
func (s *NfsServiceOp) Get(ctx context.Context, id string, region string) (*Nfs, *Response, error) {
99+
if id == "" {
100+
return nil, nil, NewArgError("id", "cannot be empty")
101+
}
102+
if region == "" {
103+
return nil, nil, NewArgError("region", "cannot be empty")
104+
}
105+
106+
path := fmt.Sprintf("%s/%s", nfsBasePath, id)
107+
108+
getOpts := &nfsOptions{Region: region}
109+
path, err := addOptions(path, getOpts)
110+
if err != nil {
111+
return nil, nil, err
112+
}
113+
114+
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
115+
if err != nil {
116+
return nil, nil, err
117+
}
118+
119+
root := new(nfsRoot)
120+
resp, err := s.client.Do(ctx, req, root)
121+
if err != nil {
122+
return nil, resp, err
123+
}
124+
125+
return root.Share, resp, nil
126+
}
127+
128+
// List returns a list of NFS shares.
129+
func (s *NfsServiceOp) List(ctx context.Context, opts *ListOptions, region string) ([]*Nfs, *Response, error) {
130+
if region == "" {
131+
return nil, nil, NewArgError("region", "cannot be empty")
132+
}
133+
134+
path, err := addOptions(nfsBasePath, opts)
135+
if err != nil {
136+
return nil, nil, err
137+
}
138+
139+
listOpts := &nfsOptions{Region: region}
140+
path, err = addOptions(path, listOpts)
141+
if err != nil {
142+
return nil, nil, err
143+
}
144+
145+
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
146+
if err != nil {
147+
return nil, nil, err
148+
}
149+
150+
root := new(nfsListRoot)
151+
resp, err := s.client.Do(ctx, req, root)
152+
if err != nil {
153+
return nil, resp, err
154+
}
155+
156+
if root.Links != nil {
157+
resp.Links = root.Links
158+
}
159+
if root.Meta != nil {
160+
resp.Meta = root.Meta
161+
}
162+
163+
return root.Shares, resp, nil
164+
}
165+
166+
// Delete deletes an NFS share by ID and region.
167+
func (s *NfsServiceOp) Delete(ctx context.Context, id string, region string) (*Response, error) {
168+
if id == "" {
169+
return nil, NewArgError("id", "cannot be empty")
170+
}
171+
if region == "" {
172+
return nil, NewArgError("region", "cannot be empty")
173+
}
174+
175+
path := fmt.Sprintf("%s/%s", nfsBasePath, id)
176+
177+
deleteOpts := &nfsOptions{Region: region}
178+
path, err := addOptions(path, deleteOpts)
179+
if err != nil {
180+
return nil, err
181+
}
182+
183+
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil)
184+
if err != nil {
185+
return nil, err
186+
}
187+
188+
resp, err := s.client.Do(ctx, req, nil)
189+
if err != nil {
190+
return resp, err
191+
}
192+
193+
return resp, nil
194+
}

nfs_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package godo
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestNfsCreate(t *testing.T) {
13+
setup()
14+
defer teardown()
15+
16+
createRequest := &NfsCreateRequest{
17+
Name: "test-nfs-share",
18+
SizeGib: 50,
19+
Region: "atl1",
20+
}
21+
22+
mux.HandleFunc("/v2/nfs", func(w http.ResponseWriter, r *http.Request) {
23+
assert.Equal(t, http.MethodPost, r.Method)
24+
w.WriteHeader(http.StatusCreated)
25+
fmt.Fprint(w, `{"share": {"id": "test-nfs-id", "name": "test-nfs-share", "size_gib": 50, "region": "atl1", "status": "PROVISIONING", "created_at":"2023-10-01T00:00:00Z", "vpc_ids": []}}`)
26+
})
27+
28+
share, resp, err := client.Nfs.Create(context.Background(), createRequest)
29+
assert.NoError(t, err)
30+
assert.NotNil(t, resp)
31+
assert.Equal(t, "test-nfs-share", share.Name)
32+
assert.Equal(t, "atl1", share.Region)
33+
assert.Equal(t, 50, share.SizeGib)
34+
assert.Equal(t, "PROVISIONING", share.Status)
35+
36+
invalidCreateRequest := &NfsCreateRequest{
37+
Name: "test-nfs-share-invalid-size",
38+
SizeGib: 20,
39+
Region: "atl1",
40+
}
41+
42+
share, resp, err = client.Nfs.Create(context.Background(), invalidCreateRequest)
43+
assert.Error(t, err)
44+
assert.Equal(t, "size_gib is invalid because it cannot be less than 50Gib", err.Error())
45+
assert.Nil(t, share)
46+
assert.Nil(t, resp)
47+
}
48+
49+
func TestNfsDelete(t *testing.T) {
50+
setup()
51+
defer teardown()
52+
53+
mux.HandleFunc("/v2/nfs/test-nfs-id", func(w http.ResponseWriter, r *http.Request) {
54+
assert.Equal(t, http.MethodDelete, r.Method)
55+
w.WriteHeader(http.StatusNoContent)
56+
})
57+
58+
resp, err := client.Nfs.Delete(context.Background(), "test-nfs-id", "atl1")
59+
assert.NoError(t, err)
60+
assert.NotNil(t, resp)
61+
}
62+
63+
func TestNfsGet(t *testing.T) {
64+
setup()
65+
defer teardown()
66+
67+
mux.HandleFunc("/v2/nfs/test-nfs-id", func(w http.ResponseWriter, r *http.Request) {
68+
assert.Equal(t, http.MethodGet, r.Method)
69+
w.WriteHeader(http.StatusOK)
70+
fmt.Fprint(w, `{"share": {"id": "test-nfs-id", "name": "test-nfs-share", "size_gib": 50, "region": "atl1", "status": "PROVISIONING", "created_at":"2023-10-01T00:00:00Z", "vpc_ids": []}}`)
71+
})
72+
73+
share, resp, err := client.Nfs.Get(context.Background(), "test-nfs-id", "atl1")
74+
assert.NoError(t, err)
75+
assert.NotNil(t, resp)
76+
assert.Equal(t, "test-nfs-share", share.Name)
77+
assert.Equal(t, "atl1", share.Region)
78+
assert.Equal(t, 50, share.SizeGib)
79+
assert.Equal(t, "PROVISIONING", share.Status)
80+
}
81+
82+
func TestNfsList(t *testing.T) {
83+
setup()
84+
defer teardown()
85+
86+
mux.HandleFunc("/v2/nfs", func(w http.ResponseWriter, r *http.Request) {
87+
assert.Equal(t, http.MethodGet, r.Method)
88+
page := r.URL.Query().Get("page")
89+
if page == "2" {
90+
w.WriteHeader(http.StatusOK)
91+
fmt.Fprint(w, `{"shares": [{"id": "test-nfs-id-2", "name": "test-nfs-share-2", "size_gib": 50, "region": "atl1", "status": "PROVISIONING", "created_at":"2023-10-01T00:00:00Z", "vpc_ids": []}]}`)
92+
} else {
93+
w.WriteHeader(http.StatusOK)
94+
fmt.Fprint(w, `{"shares": [{"id": "test-nfs-id-1", "name": "test-nfs-share-1", "size_gib": 50, "region": "atl1", "status": "PROVISIONING", "created_at":"2023-10-01T00:00:00Z", "vpc_ids": []}]}`)
95+
}
96+
})
97+
98+
// Test first page
99+
shares, resp, err := client.Nfs.List(context.Background(), &ListOptions{Page: 1}, "atl1")
100+
assert.NoError(t, err)
101+
assert.NotNil(t, resp)
102+
assert.Len(t, shares, 1)
103+
assert.Equal(t, "test-nfs-share-1", shares[0].Name)
104+
assert.Equal(t, "atl1", shares[0].Region)
105+
assert.Equal(t, 50, shares[0].SizeGib)
106+
assert.Equal(t, "PROVISIONING", shares[0].Status)
107+
108+
// Test second page
109+
shares, resp, err = client.Nfs.List(context.Background(), &ListOptions{Page: 2}, "atl1")
110+
assert.NoError(t, err)
111+
assert.NotNil(t, resp)
112+
assert.Len(t, shares, 1)
113+
assert.Equal(t, "test-nfs-share-2", shares[0].Name)
114+
assert.Equal(t, "atl1", shares[0].Region)
115+
assert.Equal(t, 50, shares[0].SizeGib)
116+
assert.Equal(t, "PROVISIONING", shares[0].Status)
117+
}

0 commit comments

Comments
 (0)