Skip to content

Commit

Permalink
Add reserved ipv6 changes
Browse files Browse the repository at this point in the history
  • Loading branch information
imaskm committed Nov 20, 2024
1 parent 45ad288 commit 0d325bc
Show file tree
Hide file tree
Showing 3 changed files with 284 additions and 0 deletions.
2 changes: 2 additions & 0 deletions godo.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type Client struct {
Registry RegistryService
Registries RegistriesService
ReservedIPs ReservedIPsService
ReservedIPV6s ReservedIPV6sService
ReservedIPActions ReservedIPActionsService
Sizes SizesService
Snapshots SnapshotsService
Expand Down Expand Up @@ -295,6 +296,7 @@ func NewClient(httpClient *http.Client) *Client {
c.Registry = &RegistryServiceOp{client: c}
c.Registries = &RegistriesServiceOp{client: c}
c.ReservedIPs = &ReservedIPsServiceOp{client: c}
c.ReservedIPV6s = &ReservedIPV6sServiceOp{client: c}
c.ReservedIPActions = &ReservedIPActionsServiceOp{client: c}
c.Sizes = &SizesServiceOp{client: c}
c.Snapshots = &SnapshotsServiceOp{client: c}
Expand Down
139 changes: 139 additions & 0 deletions reserved_ipv6.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package godo

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

const resourceV6Type = "ReservedIPv6"
const reservedIPV6sBasePath = "v2/reserved_ipv6"

// ReservedIPV6sService is an interface for interfacing with the reserved IPV6s
// endpoints of the Digital Ocean API.
type ReservedIPV6sService interface {
List(context.Context, *ListOptions) ([]ReservedIPV6, *Response, error)
Get(context.Context, string) (*ReservedIPV6, *Response, error)
Reserve(context.Context, *ReservedIPV6ReserveRequest) (*ReservedIPV6, *Response, error)
Release(context.Context, string) (*Response, error)
}

// ReservedIPV6sServiceOp handles communication with the reserved IPs related methods of the
// DigitalOcean API.
type ReservedIPV6sServiceOp struct {
client *Client
}

var _ ReservedIPV6sService = &ReservedIPV6sServiceOp{}

// ReservedIPV6 represents a Digital Ocean reserved IP.
type ReservedIPV6 struct {
RegionSlug string `json:"region_slug"`
IP string `json:"ip"`
ReservedAt time.Time `json:"reserved_at"`
Droplet *Droplet `json:"droplet,omitempty"`
}

func (f ReservedIPV6) String() string {
return Stringify(f)
}

// URN returns the reserved IP in a valid DO API URN form.
func (f ReservedIPV6) URN() string {
return ToURN(resourceV6Type, f.IP)
}

type reservedIPV6sRoot struct {
ReservedIPs []ReservedIPV6 `json:"reserved_ips"`
Links *Links `json:"links"`
Meta *Meta `json:"meta"`
}

type reservedIPV6Root struct {
ReservedIP *ReservedIPV6 `json:"reserved_ip"`
Links *Links `json:"links,omitempty"`
}

// ReservedIPV6ReserveRequest represents a request to reserve a reserved IP.
type ReservedIPV6ReserveRequest struct {
Region string `json:"region_slug,omitempty"`
}

// List all reserved IPV6s.
func (r *ReservedIPV6sServiceOp) List(ctx context.Context, opt *ListOptions) ([]ReservedIPV6, *Response, error) {
path := reservedIPV6sBasePath
path, err := addOptions(path, opt)
if err != nil {
return nil, nil, err
}

req, err := r.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}

root := new(reservedIPV6sRoot)
resp, err := r.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.ReservedIPs, resp, err
}

// Get an individual reserved IPv6.
func (r *ReservedIPV6sServiceOp) Get(ctx context.Context, ip string) (*ReservedIPV6, *Response, error) {
path := fmt.Sprintf("%s/%s", reservedIPV6sBasePath, ip)

req, err := r.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}

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

return root, resp, err
}

// Reserve a new IPv6
func (r *ReservedIPV6sServiceOp) Reserve(ctx context.Context, reserveRequest *ReservedIPV6ReserveRequest) (*ReservedIPV6, *Response, error) {
path := reservedIPV6sBasePath

req, err := r.client.NewRequest(ctx, http.MethodPost, path, reserveRequest)
if err != nil {
return nil, nil, err
}

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

return root, resp, err
}

// Release a reserved IPv6.
func (r *ReservedIPV6sServiceOp) Release(ctx context.Context, ip string) (*Response, error) {
path := fmt.Sprintf("%s/%s", reservedIPV6sBasePath, ip)

req, err := r.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}

resp, err := r.client.Do(ctx, req, nil)

return resp, err
}
143 changes: 143 additions & 0 deletions reserved_ipv6_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package godo

import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"testing"
"time"
)

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

reserveRequest := &ReservedIPV6ReserveRequest{
Region: "nyc3",
}
nowTime := time.Now()

mux.HandleFunc("/v2/reserved_ipv6", func(w http.ResponseWriter, r *http.Request) {
v := new(ReservedIPV6ReserveRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatal(err)
}

testMethod(t, r, http.MethodPost)
if !reflect.DeepEqual(v, reserveRequest) {
t.Errorf("Request body = %+v, expected %+v", v, reserveRequest)
}

fmt.Fprint(w, `{"ip":"2604:a880:800:14::42c3:d000","region_slug":"nyc3","reserved_at":"`+nowTime.Format(time.RFC3339Nano)+`"}`)
})

reservedIP, _, err := client.ReservedIPV6s.Reserve(ctx, reserveRequest)
if err != nil {
t.Errorf("ReservedIPV6s.Create returned error: %v", err)
}

expected := &ReservedIPV6{RegionSlug: "nyc3", IP: "2604:a880:800:14::42c3:d000", ReservedAt: nowTime}

if !equalReserveIPv6Objects(reservedIP, expected) {
t.Errorf("ReservedIPs.Create returned %+v, expected %+v", reservedIP, expected)
}
}

func equalReserveIPv6Objects(a, b *ReservedIPV6) bool {
return a.IP == b.IP &&
a.RegionSlug == b.RegionSlug &&
a.ReservedAt.Equal(b.ReservedAt) &&
reflect.DeepEqual(a.Droplet, b.Droplet)
}

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

mux.HandleFunc("/v2/reserved_ipv6", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
fmt.Fprint(w, `{"reserved_ips": [
{"region_slug":"nyc3","droplet":{"id":1},"ip":"2604:a880:800:14::42c3:d000"},
{"region_slug":"nyc3","droplet":{"id":2},"ip":"2604:a880:800:14::42c3:d001"}
],
"meta":{"total":2}
}`)
})

reservedIPs, resp, err := client.ReservedIPV6s.List(ctx, nil)
if err != nil {
t.Errorf("ReservedIPs.List returned error: %v", err)
}

expectedReservedIPs := []ReservedIPV6{
{RegionSlug: "nyc3", Droplet: &Droplet{ID: 1}, IP: "2604:a880:800:14::42c3:d000"},
{RegionSlug: "nyc3", Droplet: &Droplet{ID: 2}, IP: "2604:a880:800:14::42c3:d001"},
}
if !reflect.DeepEqual(reservedIPs, expectedReservedIPs) {
t.Errorf("ReservedIPV6s.List returned reserved IPs %+v, expected %+v", reservedIPs, expectedReservedIPs)
}

expectedMeta := &Meta{
Total: 2,
}
if !reflect.DeepEqual(resp.Meta, expectedMeta) {
t.Errorf("ReservedIPs.List returned meta %+v, expected %+v", resp.Meta, expectedMeta)
}
}

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

mux.HandleFunc("/v2/reserved_ipv6", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
fmt.Fprint(w, `{"reserved_ips": [
{"region_slug":"nyc3","droplet":{"id":1},"ip":"2604:a880:800:14::42c3:d001"},
{"region":{"slug":"nyc3"},"droplet":{"id":2},"ip":"2604:a880:800:14::42c3:d002"}],
"links":{"pages":{"next":"http://example.com/v2/reserved_ipv6/?page=2"}}}
`)
})

_, resp, err := client.ReservedIPV6s.List(ctx, nil)
if err != nil {
t.Fatal(err)
}

checkCurrentPage(t, resp, 1)
}

func TestReservedIPV6s_Get(t *testing.T) {
setup()
defer teardown()
nowTime := time.Now()
mux.HandleFunc("/v2/reserved_ipv6/2604:a880:800:14::42c3:d001", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
fmt.Fprint(w, `{"region_slug":"nyc3","droplet":{"id":1},"ip":"2604:a880:800:14::42c3:d001", "reserved_at":"`+nowTime.Format(time.RFC3339Nano)+`"}`)
})

reservedIP, _, err := client.ReservedIPV6s.Get(ctx, "2604:a880:800:14::42c3:d001")
if err != nil {
t.Errorf("ReservedIPV6s.Get returned error: %v", err)
}

expected := &ReservedIPV6{RegionSlug: "nyc3", Droplet: &Droplet{ID: 1}, IP: "2604:a880:800:14::42c3:d001", ReservedAt: nowTime}
if !equalReserveIPv6Objects(reservedIP, expected) {
t.Errorf("ReservedIPV6s.Get returned %+v, expected %+v", reservedIP, expected)
}
}

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

mux.HandleFunc("/v2/reserved_ipv6/2604:a880:800:14::42c3:d001", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodDelete)
})

_, err := client.ReservedIPV6s.Release(ctx, "2604:a880:800:14::42c3:d001")
if err != nil {
t.Errorf("ReservedIPV6s.Release returned error: %v", err)
}
}

0 comments on commit 0d325bc

Please sign in to comment.