Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add reserved ipv6 changes as Beta #759

Merged
merged 4 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -83,6 +83,7 @@ type Client struct {
Registry RegistryService
Registries RegistriesService
ReservedIPs ReservedIPsService
ReservedIPV6s ReservedIPV6sService
imaskm marked this conversation as resolved.
Show resolved Hide resolved
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
132 changes: 132 additions & 0 deletions reserved_ipv6.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
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)
Create(context.Context, *ReservedIPV6CreateRequest) (*ReservedIPV6, *Response, error)
Delete(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)(nil)

// 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"`
}

// ReservedIPV6CreateRequest represents a request to reserve a reserved IP.
type ReservedIPV6CreateRequest 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, nil, 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
}

// Create a new IPv6
func (r *ReservedIPV6sServiceOp) Create(ctx context.Context, reserveRequest *ReservedIPV6CreateRequest) (*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
}

// Delete a reserved IPv6.
func (r *ReservedIPV6sServiceOp) Delete(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
}

return r.client.Do(ctx, req, nil)
}
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_Create(t *testing.T) {
setup()
defer teardown()

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

mux.HandleFunc("/v2/reserved_ipv6", func(w http.ResponseWriter, r *http.Request) {
v := new(ReservedIPV6CreateRequest)
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.Create(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_Delete(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.Delete(ctx, "2604:a880:800:14::42c3:d001")
if err != nil {
t.Errorf("ReservedIPV6s.Release returned error: %v", err)
}
}
Loading