From 92f98acf71bdfcbe537e0d7b07cbf2ce072a48a8 Mon Sep 17 00:00:00 2001 From: Chris Morse <120681681+christhemorse@users.noreply.github.com> Date: Fri, 18 Aug 2023 13:21:26 -0400 Subject: [PATCH] Add VPC2 nodes endpoints (#263) * Add VPC2 nodes endpoints * Fix linting issues * Fix linter issues part 2 * Fix linting issue part 3 * Fix linting issue part 4 * Fix the last of the linting issues * Add tests for new VPC2 endpoints --- instance.go | 1 + network.go | 2 +- vpc.go | 2 +- vpc2.go | 76 +++++++++++++++++++++++++++++++++++- vpc2_test.go | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 185 insertions(+), 3 deletions(-) diff --git a/instance.go b/instance.go index acaadf3..fe2f744 100644 --- a/instance.go +++ b/instance.go @@ -175,6 +175,7 @@ type VPC2Info struct { IPAddress string `json:"ip_address"` } +// AttachVPC2Req parameters for attaching a VPC 2.0 network type AttachVPC2Req struct { VPCID string `json:"vpc_id,omitempty"` IPAddress *string `json:"ip_address,omitempty"` diff --git a/network.go b/network.go index cb6284a..1b9a146 100644 --- a/network.go +++ b/network.go @@ -13,7 +13,7 @@ const netPath = "/v2/private-networks" // NetworkService is the interface to interact with the network endpoints on the Vultr API // Link : https://www.vultr.com/api/#tag/private-Networks // Deprecated: NetworkService should no longer be used. Instead, use VPCService. -type NetworkService interface { //nolint:dupl +type NetworkService interface { // Deprecated: NetworkService Create should no longer be used. Instead, use VPCService Create. Create(ctx context.Context, createReq *NetworkReq) (*Network, *http.Response, error) // Deprecated: NetworkService Get should no longer be used. Instead, use VPCService Get. diff --git a/vpc.go b/vpc.go index 83852cc..1de69f8 100644 --- a/vpc.go +++ b/vpc.go @@ -12,7 +12,7 @@ const vpcPath = "/v2/vpcs" // VPCService is the interface to interact with the VPC endpoints on the Vultr API // Link : https://www.vultr.com/api/#tag/vpcs -type VPCService interface { //nolint:dupl +type VPCService interface { Create(ctx context.Context, createReq *VPCReq) (*VPC, *http.Response, error) Get(ctx context.Context, vpcID string) (*VPC, *http.Response, error) Update(ctx context.Context, vpcID string, description string) error diff --git a/vpc2.go b/vpc2.go index 7b9a862..2528c1c 100644 --- a/vpc2.go +++ b/vpc2.go @@ -12,12 +12,15 @@ const vpc2Path = "/v2/vpc2" // VPC2Service is the interface to interact with the VPC 2.0 endpoints on the Vultr API // Link : https://www.vultr.com/api/#tag/vpc2 -type VPC2Service interface { //nolint:dupl +type VPC2Service interface { Create(ctx context.Context, createReq *VPC2Req) (*VPC2, *http.Response, error) Get(ctx context.Context, vpcID string) (*VPC2, *http.Response, error) Update(ctx context.Context, vpcID string, description string) error Delete(ctx context.Context, vpcID string) error List(ctx context.Context, options *ListOptions) ([]VPC2, *Meta, *http.Response, error) + ListNodes(ctx context.Context, vpc2ID string, options *ListOptions) ([]VPC2Node, *Meta, *http.Response, error) + Attach(ctx context.Context, vpcID string, attachReq *VPC2AttachDetachReq) error + Detach(ctx context.Context, vpcID string, detachReq *VPC2AttachDetachReq) error } // VPC2ServiceHandler handles interaction with the VPC 2.0 methods for the Vultr API @@ -35,6 +38,16 @@ type VPC2 struct { DateCreated string `json:"date_created"` } +// VPC2Node represents a node attached to a VPC 2.0 network +type VPC2Node struct { + ID string `json:"id"` + IPAddress string `json:"ip_address"` + MACAddress int `json:"mac_address"` + Description string `json:"description"` + Type string `json:"type"` + NodeStatus string `json:"node_status"` +} + // VPC2Req represents parameters to create or update a VPC 2.0 resource type VPC2Req struct { Region string `json:"region"` @@ -44,6 +57,11 @@ type VPC2Req struct { PrefixLength int `json:"prefix_length"` } +// VPC2AttachDetachReq represents parameters to mass attach or detach nodes from VPC 2.0 networks +type VPC2AttachDetachReq struct { + Nodes []string `json:"nodes"` +} + type vpcs2Base struct { VPCs []VPC2 `json:"vpcs"` Meta *Meta `json:"meta"` @@ -53,6 +71,11 @@ type vpc2Base struct { VPC *VPC2 `json:"vpc"` } +type vpc2NodesBase struct { + Nodes []VPC2Node `json:"nodes"` + Meta *Meta `json:"meta"` +} + // Create creates a new VPC 2.0. A VPC 2.0 can only be used at the location for which it was created. func (n *VPC2ServiceHandler) Create(ctx context.Context, createReq *VPC2Req) (*VPC2, *http.Response, error) { req, err := n.client.NewRequest(ctx, http.MethodPost, vpc2Path, createReq) @@ -133,3 +156,54 @@ func (n *VPC2ServiceHandler) List(ctx context.Context, options *ListOptions) ([] return vpcs.VPCs, vpcs.Meta, resp, nil } + +// ListNodes lists all nodes attached to a VPC 2.0 network +func (n *VPC2ServiceHandler) ListNodes(ctx context.Context, vpc2ID string, options *ListOptions) ([]VPC2Node, *Meta, *http.Response, error) { //nolint:dupl,lll + uri := fmt.Sprintf("%s/%s/nodes", vpc2Path, vpc2ID) + + req, err := n.client.NewRequest(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, nil, nil, err + } + + newValues, err := query.Values(options) + if err != nil { + return nil, nil, nil, err + } + + req.URL.RawQuery = newValues.Encode() + + nodes := new(vpc2NodesBase) + resp, err := n.client.DoWithContext(ctx, req, nodes) + if err != nil { + return nil, nil, resp, err + } + + return nodes.Nodes, nodes.Meta, resp, nil +} + +// Attach attaches nodes to a VPC 2.0 network +func (n *VPC2ServiceHandler) Attach(ctx context.Context, vpcID string, attachReq *VPC2AttachDetachReq) error { + uri := fmt.Sprintf("%s/%s/nodes/attach", vpc2Path, vpcID) + + req, err := n.client.NewRequest(ctx, http.MethodPost, uri, attachReq) + if err != nil { + return err + } + + _, err = n.client.DoWithContext(ctx, req, nil) + return err +} + +// Detach detaches nodes from a VPC 2.0 network +func (n *VPC2ServiceHandler) Detach(ctx context.Context, vpcID string, detachReq *VPC2AttachDetachReq) error { + uri := fmt.Sprintf("%s/%s/nodes/detach", vpc2Path, vpcID) + + req, err := n.client.NewRequest(ctx, http.MethodPost, uri, detachReq) + if err != nil { + return err + } + + _, err = n.client.DoWithContext(ctx, req, nil) + return err +} diff --git a/vpc2_test.go b/vpc2_test.go index 6e31511..494c06c 100644 --- a/vpc2_test.go +++ b/vpc2_test.go @@ -152,3 +152,110 @@ func TestVPC2ServiceHandler_Get(t *testing.T) { t.Errorf("VPC2.Get returned %+v, expected %+v", vpc, expected) } } + +func TestVPC2ServiceHandler_ListNodes(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v2/vpc2/84fee086-6691-417a-b2db-e2a71061fa17/nodes", func(writer http.ResponseWriter, request *http.Request) { + response := ` + { + "nodes": [ + { + "id": "35dbcffe-58bf-46fe-bd68-964d95488dd8", + "ip_address": "10.1.112.5", + "mac_address": 98956121034033, + "description": "bbbbbb-8ac448299844", + "type": "vps", + "node_status": "active" + }, + { + "id": "1f5d784a-1011-430c-a2e2-39ba045abe3c", + "ip_address": "10.1.112.6", + "mac_address": 98956121034034, + "description": "bbbbbb-c76d8fc029d6", + "type": "vps", + "node_status": "active" + } + ], + "meta": { + "total": 2, + "links": { + "next": "", + "prev": "" + } + } + } + ` + fmt.Fprint(writer, response) + }) + + nodes, _, _, err := client.VPC2.ListNodes(ctx, "84fee086-6691-417a-b2db-e2a71061fa17", nil) + + if err != nil { + t.Errorf("VPC2.ListNodes returned error: %v", err) + } + + expected := []VPC2Node{ + { + ID: "35dbcffe-58bf-46fe-bd68-964d95488dd8", + IPAddress: "10.1.112.5", + MACAddress: 98956121034033, + Description: "bbbbbb-8ac448299844", + Type: "vps", + NodeStatus: "active", + }, + { + ID: "1f5d784a-1011-430c-a2e2-39ba045abe3c", + IPAddress: "10.1.112.6", + MACAddress: 98956121034034, + Description: "bbbbbb-c76d8fc029d6", + Type: "vps", + NodeStatus: "active", + }, + } + + if !reflect.DeepEqual(nodes, expected) { + t.Errorf("VPC2.ListNode returned %+v, expected %+v", nodes, expected) + } +} + +func TestVPC2ServiceHandler_Attach(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v2/vpc2/84fee086-6691-417a-b2db-e2a71061fa17/nodes/attach", func(writer http.ResponseWriter, request *http.Request) { + fmt.Fprint(writer) + }) + + nodes := []string{"ce44b37a-bbe7-4e30-bfae-695c2e633bff", "45b794b7-4dd1-48b1-beb7-0b7bf3a16941"} + options := &VPC2AttachDetachReq{ + Nodes: nodes, + } + + err := client.VPC2.Attach(ctx, "84fee086-6691-417a-b2db-e2a71061fa17", options) + + if err != nil { + t.Errorf("VPC2.Attach returned %+v, expected %+v", err, nil) + } +} + +func TestVPC2ServiceHandler_Detach(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v2/vpc2/84fee086-6691-417a-b2db-e2a71061fa17/nodes/detach", func(writer http.ResponseWriter, request *http.Request) { + fmt.Fprint(writer) + }) + + nodes := []string{"ce44b37a-bbe7-4e30-bfae-695c2e633bff", "45b794b7-4dd1-48b1-beb7-0b7bf3a16941"} + options := &VPC2AttachDetachReq{ + Nodes: nodes, + } + + err := client.VPC2.Detach(ctx, "84fee086-6691-417a-b2db-e2a71061fa17", options) + + if err != nil { + t.Errorf("VPC2.Detach returned %+v, expected %+v", err, nil) + } +}