Skip to content

Commit

Permalink
Add reserved ipv6 changes as Beta (#759)
Browse files Browse the repository at this point in the history
* Add reserved ipv6 changes

* Resolved review comments

* added reserved ipv6 actions

---------

Co-authored-by: Andrew Starr-Bochicchio <andrewsomething@users.noreply.github.com>
  • Loading branch information
imaskm and andrewsomething authored Nov 22, 2024
1 parent 22a5562 commit 73ff37f
Show file tree
Hide file tree
Showing 5 changed files with 452 additions and 37 deletions.
78 changes: 41 additions & 37 deletions godo.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,43 +54,45 @@ type Client struct {
ratemtx sync.Mutex

// Services used for communicating with the API
Account AccountService
Actions ActionsService
Apps AppsService
Balance BalanceService
BillingHistory BillingHistoryService
CDNs CDNService
Certificates CertificatesService
Databases DatabasesService
Domains DomainsService
Droplets DropletsService
DropletActions DropletActionsService
DropletAutoscale DropletAutoscaleService
Firewalls FirewallsService
FloatingIPs FloatingIPsService
FloatingIPActions FloatingIPActionsService
Functions FunctionsService
Images ImagesService
ImageActions ImageActionsService
Invoices InvoicesService
Keys KeysService
Kubernetes KubernetesService
LoadBalancers LoadBalancersService
Monitoring MonitoringService
OneClick OneClickService
Projects ProjectsService
Regions RegionsService
Registry RegistryService
Registries RegistriesService
ReservedIPs ReservedIPsService
ReservedIPActions ReservedIPActionsService
Sizes SizesService
Snapshots SnapshotsService
Storage StorageService
StorageActions StorageActionsService
Tags TagsService
UptimeChecks UptimeChecksService
VPCs VPCsService
Account AccountService
Actions ActionsService
Apps AppsService
Balance BalanceService
BillingHistory BillingHistoryService
CDNs CDNService
Certificates CertificatesService
Databases DatabasesService
Domains DomainsService
Droplets DropletsService
DropletActions DropletActionsService
DropletAutoscale DropletAutoscaleService
Firewalls FirewallsService
FloatingIPs FloatingIPsService
FloatingIPActions FloatingIPActionsService
Functions FunctionsService
Images ImagesService
ImageActions ImageActionsService
Invoices InvoicesService
Keys KeysService
Kubernetes KubernetesService
LoadBalancers LoadBalancersService
Monitoring MonitoringService
OneClick OneClickService
Projects ProjectsService
Regions RegionsService
Registry RegistryService
Registries RegistriesService
ReservedIPs ReservedIPsService
ReservedIPV6s ReservedIPV6sService
ReservedIPActions ReservedIPActionsService
ReservedIPV6Actions ReservedIPV6ActionsService
Sizes SizesService
Snapshots SnapshotsService
Storage StorageService
StorageActions StorageActionsService
Tags TagsService
UptimeChecks UptimeChecksService
VPCs VPCsService

// Optional function called after every successful request made to the DO APIs
onRequestCompleted RequestCompletionCallback
Expand Down Expand Up @@ -295,7 +297,9 @@ 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.ReservedIPV6Actions = &ReservedIPV6ActionsServiceOp{client: c}
c.Sizes = &SizesServiceOp{client: c}
c.Snapshots = &SnapshotsServiceOp{client: c}
c.Storage = &StorageServiceOp{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)
}
57 changes: 57 additions & 0 deletions reserved_ipv6_actions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package godo

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

// ReservedIPActionsService is an interface for interfacing with the
// reserved IPs actions endpoints of the Digital Ocean API.
// See: https://docs.digitalocean.com/reference/api/api-reference/#tag/Reserved-IP-Actions
type ReservedIPV6ActionsService interface {
Assign(ctx context.Context, ip string, dropletID int) (*Action, *Response, error)
Unassign(ctx context.Context, ip string) (*Action, *Response, error)
}

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

// Assign a reserved IP to a droplet.
func (s *ReservedIPV6ActionsServiceOp) Assign(ctx context.Context, ip string, dropletID int) (*Action, *Response, error) {
request := &ActionRequest{
"type": "assign",
"droplet_id": dropletID,
}
return s.doV6Action(ctx, ip, request)
}

// Unassign a rerserved IP from the droplet it is currently assigned to.
func (s *ReservedIPV6ActionsServiceOp) Unassign(ctx context.Context, ip string) (*Action, *Response, error) {
request := &ActionRequest{"type": "unassign"}
return s.doV6Action(ctx, ip, request)
}

func (s *ReservedIPV6ActionsServiceOp) doV6Action(ctx context.Context, ip string, request *ActionRequest) (*Action, *Response, error) {
path := reservedIPV6ActionPath(ip)

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

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

return root.Event, resp, err
}

func reservedIPV6ActionPath(ip string) string {
return fmt.Sprintf("%s/%s/actions", reservedIPV6sBasePath, ip)
}
79 changes: 79 additions & 0 deletions reserved_ipv6_actions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package godo

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

func TestReservedIPV6sActions_Assign(t *testing.T) {
setup()
defer teardown()
dropletID := 12345
assignRequest := &ActionRequest{
"droplet_id": float64(dropletID),
"type": "assign",
}

mux.HandleFunc("/v2/reserved_ipv6/2604:a880:800:14::42c3:d000/actions", func(w http.ResponseWriter, r *http.Request) {
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}

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

fmt.Fprintf(w, `{"action":{"status":"in-progress","id":1,"type":"assign_ip","resource_type":"reserved_ipv6"}}`)

})

assign, _, err := client.ReservedIPV6Actions.Assign(ctx, "2604:a880:800:14::42c3:d000", 12345)
if err != nil {
t.Errorf("ReservedIPV6sActions.Assign returned error: %v", err)
}

expected := &Action{Status: "in-progress", ID: 1, Type: "assign_ip", ResourceType: "reserved_ipv6"}
if !reflect.DeepEqual(assign, expected) {
t.Errorf("ReservedIPV6sActions.Assign returned %+v, expected %+v", assign, expected)
}
}

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

unassignRequest := &ActionRequest{
"type": "unassign",
}

mux.HandleFunc("/v2/reserved_ipv6/2604:a880:800:14::42c3:d000/actions", func(w http.ResponseWriter, r *http.Request) {
v := new(ActionRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatalf("decode json: %v", err)
}

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

fmt.Fprintf(w, `{"action":{"status":"in-progress","id":1,"type":"unassign_ip","resource_type":"reserved_ipv6"}}`)
})

action, _, err := client.ReservedIPV6Actions.Unassign(ctx, "2604:a880:800:14::42c3:d000")
if err != nil {
t.Errorf("ReservedIPV6sActions.Unassign returned error: %v", err)
}

expected := &Action{Status: "in-progress", ID: 1, Type: "unassign_ip", ResourceType: "reserved_ipv6"}
if !reflect.DeepEqual(action, expected) {
t.Errorf("ReservedIPV6sActions.Unassign returned %+v, expected %+v", action, expected)
}
}
Loading

0 comments on commit 73ff37f

Please sign in to comment.