Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions godo.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ type Client struct {
Kubernetes KubernetesService
LoadBalancers LoadBalancersService
Monitoring MonitoringService
Nfs NfsService
OneClick OneClickService
Projects ProjectsService
Regions RegionsService
Expand Down Expand Up @@ -304,6 +305,7 @@ func NewClient(httpClient *http.Client) *Client {
c.Kubernetes = &KubernetesServiceOp{client: c}
c.LoadBalancers = &LoadBalancersServiceOp{client: c}
c.Monitoring = &MonitoringServiceOp{client: c}
c.Nfs = &NfsServiceOp{client: c}
c.VPCNATGateways = &VPCNATGatewaysServiceOp{client: c}
c.OneClick = &OneClickServiceOp{client: c}
c.Projects = &ProjectsServiceOp{client: c}
Expand Down
194 changes: 194 additions & 0 deletions nfs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package godo

import (
"context"
"fmt"
"net/http"
)

const nfsBasePath = "v2/nfs"

type NfsService interface {
// List retrieves a list of NFS shares with optional filtering via ListOptions and region
List(context.Context, *ListOptions, string) ([]*Nfs, *Response, error)
// Create creates a new NFS share with the provided configuration
Create(context.Context, *NfsCreateRequest) (*Nfs, *Response, error)
// Delete removes an NFS share by its ID and region
Delete(context.Context, string, string) (*Response, error)
// Get retrieves a specific NFS share by its ID and region
Get(context.Context, string, string) (*Nfs, *Response, error)
}

// NfsServiceOp handles communication with the NFS related methods of the
// DigitalOcean API.
type NfsServiceOp struct {
client *Client
}

var _ NfsService = &NfsServiceOp{}

// Nfs represents a DigitalOcean NFS share
type Nfs struct {
// ID is the unique identifier for the NFS share
ID string `json:"id"`
// Name is the human-readable name for the NFS share
Name string `json:"name"`
// SizeGib is the size of the NFS share in gibibytes
SizeGib int `json:"size_gib"`
// Region is the datacenter region where the NFS share is located
Region string `json:"region"`
// Status represents the current state of the NFS share
Status string `json:"status"`
// CreatedAt is the timestamp when the NFS share was created
CreatedAt string `json:"created_at"`
// VpcIDs is a list of VPC IDs that have access to the NFS share
VpcIDs []string `json:"vpc_ids"`
}

// NfsCreateRequest represents a request to create an NFS share.
type NfsCreateRequest struct {
Name string `json:"name"`
SizeGib int `json:"size_gib"`
Region string `json:"region"`
VpcIDs []string `json:"vpc_ids,omitempty"`
}

// nfsRoot represents a response from the DigitalOcean API
type nfsRoot struct {
Share *Nfs `json:"share"`
}

// nfsListRoot represents a response from the DigitalOcean API
type nfsListRoot struct {
Shares []*Nfs `json:"shares,omitempty"`
Links *Links `json:"links,omitempty"`
Meta *Meta `json:"meta"`
}

// nfsOptions represents the query param options for NFS operations
type nfsOptions struct {
Region string `url:"region"`
}

// Create creates a new NFS share.
func (s *NfsServiceOp) Create(ctx context.Context, createRequest *NfsCreateRequest) (*Nfs, *Response, error) {
if createRequest == nil {
return nil, nil, NewArgError("createRequest", "cannot be nil")
}

if createRequest.SizeGib < 50 {
return nil, nil, NewArgError("size_gib", "it cannot be less than 50Gib")
}

req, err := s.client.NewRequest(ctx, http.MethodPost, nfsBasePath, createRequest)
if err != nil {
return nil, nil, err
}

root := new(nfsRoot)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}

return root.Share, resp, nil
}

// Get retrieves an NFS share by ID and region.
func (s *NfsServiceOp) Get(ctx context.Context, id string, region string) (*Nfs, *Response, error) {
if id == "" {
return nil, nil, NewArgError("id", "cannot be empty")
}
if region == "" {
return nil, nil, NewArgError("region", "cannot be empty")
}

path := fmt.Sprintf("%s/%s", nfsBasePath, id)

getOpts := &nfsOptions{Region: region}
path, err := addOptions(path, getOpts)
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(nfsRoot)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}

return root.Share, resp, nil
}

// List returns a list of NFS shares.
func (s *NfsServiceOp) List(ctx context.Context, opts *ListOptions, region string) ([]*Nfs, *Response, error) {
if region == "" {
return nil, nil, NewArgError("region", "cannot be empty")
}

path, err := addOptions(nfsBasePath, opts)
if err != nil {
return nil, nil, err
}

listOpts := &nfsOptions{Region: region}
path, err = addOptions(path, listOpts)
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(nfsListRoot)
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.Shares, resp, nil
}

// Delete deletes an NFS share by ID and region.
func (s *NfsServiceOp) Delete(ctx context.Context, id string, region string) (*Response, error) {
if id == "" {
return nil, NewArgError("id", "cannot be empty")
}
if region == "" {
return nil, NewArgError("region", "cannot be empty")
}

path := fmt.Sprintf("%s/%s", nfsBasePath, id)

deleteOpts := &nfsOptions{Region: region}
path, err := addOptions(path, deleteOpts)
if err != nil {
return nil, err
}

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
}
117 changes: 117 additions & 0 deletions nfs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package godo

import (
"context"
"fmt"
"net/http"
"testing"

"github.com/stretchr/testify/assert"
)

func TestNfsCreate(t *testing.T) {
setup()
defer teardown()

createRequest := &NfsCreateRequest{
Name: "test-nfs-share",
SizeGib: 50,
Region: "atl1",
}

mux.HandleFunc("/v2/nfs", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method)
w.WriteHeader(http.StatusCreated)
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": []}}`)
})

share, resp, err := client.Nfs.Create(context.Background(), createRequest)
assert.NoError(t, err)
assert.NotNil(t, resp)
assert.Equal(t, "test-nfs-share", share.Name)
assert.Equal(t, "atl1", share.Region)
assert.Equal(t, 50, share.SizeGib)
assert.Equal(t, "PROVISIONING", share.Status)

invalidCreateRequest := &NfsCreateRequest{
Name: "test-nfs-share-invalid-size",
SizeGib: 20,
Region: "atl1",
}

share, resp, err = client.Nfs.Create(context.Background(), invalidCreateRequest)
assert.Error(t, err)
assert.Equal(t, "size_gib is invalid because it cannot be less than 50Gib", err.Error())
assert.Nil(t, share)
assert.Nil(t, resp)
}

func TestNfsDelete(t *testing.T) {
setup()
defer teardown()

mux.HandleFunc("/v2/nfs/test-nfs-id", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodDelete, r.Method)
w.WriteHeader(http.StatusNoContent)
})

resp, err := client.Nfs.Delete(context.Background(), "test-nfs-id", "atl1")
assert.NoError(t, err)
assert.NotNil(t, resp)
}

func TestNfsGet(t *testing.T) {
setup()
defer teardown()

mux.HandleFunc("/v2/nfs/test-nfs-id", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodGet, r.Method)
w.WriteHeader(http.StatusOK)
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": []}}`)
})

share, resp, err := client.Nfs.Get(context.Background(), "test-nfs-id", "atl1")
assert.NoError(t, err)
assert.NotNil(t, resp)
assert.Equal(t, "test-nfs-share", share.Name)
assert.Equal(t, "atl1", share.Region)
assert.Equal(t, 50, share.SizeGib)
assert.Equal(t, "PROVISIONING", share.Status)
}

func TestNfsList(t *testing.T) {
setup()
defer teardown()

mux.HandleFunc("/v2/nfs", 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, `{"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": []}]}`)
} else {
w.WriteHeader(http.StatusOK)
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": []}]}`)
}
})

// Test first page
shares, resp, err := client.Nfs.List(context.Background(), &ListOptions{Page: 1}, "atl1")
assert.NoError(t, err)
assert.NotNil(t, resp)
assert.Len(t, shares, 1)
assert.Equal(t, "test-nfs-share-1", shares[0].Name)
assert.Equal(t, "atl1", shares[0].Region)
assert.Equal(t, 50, shares[0].SizeGib)
assert.Equal(t, "PROVISIONING", shares[0].Status)

// Test second page
shares, resp, err = client.Nfs.List(context.Background(), &ListOptions{Page: 2}, "atl1")
assert.NoError(t, err)
assert.NotNil(t, resp)
assert.Len(t, shares, 1)
assert.Equal(t, "test-nfs-share-2", shares[0].Name)
assert.Equal(t, "atl1", shares[0].Region)
assert.Equal(t, 50, shares[0].SizeGib)
assert.Equal(t, "PROVISIONING", shares[0].Status)
}