Skip to content

Commit

Permalink
add option def endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
elenabushneva committed Nov 25, 2020
1 parent a75e0b3 commit 1138b90
Show file tree
Hide file tree
Showing 3 changed files with 322 additions and 0 deletions.
68 changes: 68 additions & 0 deletions rest/model/dhcp/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,71 @@ type Option struct {

// OptionSet is a convenience type for marshalling an array of options to and from a JSON field.
type OptionSet []Option

type ItemType string

const (
ItemTypeBinary = "binary"
ItemTypeBoolean = "boolean"
ItemTypeEmpty = "empty"
ItemTypeFQDN = "fqdn"
ItemTypeInt16 = "int16"
ItemTypeInt32 = "int32"
ItemTypeInt8 = "int8"
ItemTypeIPv4Address = "ipv4_address"
ItemTypeIPv6Address = "ipv6_address"
ItemTypeIPv6Prefix = "ipv6_prefix"
ItemTypePSID = "psid"
ItemTypeString = "string"
ItemTypeTuple = "tuple"
ItemTypeUint16 = "uint16"
ItemTypeUint32 = "uint32"
ItemTypeUint8 = "uint8"
)

// OptionDefSchemaItems is generated from https://apispec.ns1.com/v1/dhcp/schemas/option-definition-fields.json#/properties/schema/properties/fields/items
type OptionDefSchemaItems struct {
Name string `json:"name"`
Type ItemType `json:"type"`
}

type SchemaType string

const (
SchemaTypeArray SchemaType = "array"
SchemaTypeBinary = "binary"
SchemaTypeBoolean = "boolean"
SchemaTypeEmpty = "empty"
SchemaTypeFQDN = "fqdn"
SchemaTypeInt16 = "int16"
SchemaTypeInt32 = "int32"
SchemaTypeInt8 = "int8"
SchemaTypeIPv4Address = "ipv4_address"
SchemaTypeIPv6Address = "ipv6_address"
SchemaTypeIPv6Prefix = "ipv6_prefix"
SchemaTypePSID = "psid"
SchemaTypeRecord = "record"
SchemaTypeString = "string"
SchemaTypeTuple = "tuple"
SchemaTypeUint16 = "uint16"
SchemaTypeUint32 = "uint32"
SchemaTypeUint8 = "uint8"
)

type OptionDefSchema struct {
Fields []OptionDefSchemaItems `json:"fields"`
Items *string `json:"items,omitempty"`
MultipleFinalValue *bool `json:"multiple_final_value,omitempty"`
Type SchemaType `json:"type"`
}

// OptionDef configures a custom option definition
// https://ftp.isc.org/isc/kea/1.4.0/doc/kea-guide.html#dhcp4-custom-options
type OptionDef struct {
Space string `json:"space,omitempty"`
FriendlyName string `json:"friendly_name"`
Description string `json:"description"`
Code int `json:"code"`
Encapsulate string `json:"encapsulate,omitempty"`
Schema OptionDefSchema `json:"schema"`
}
90 changes: 90 additions & 0 deletions rest/optiondef.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package rest

import (
"errors"
"fmt"
"gopkg.in/ns1/ns1-go.v2/rest/model/dhcp"
"net/http"
)

// OptionDefService handles the 'scope group' endpoints.
type OptionDefService service

// List returns a list of all option definitions.
//
// NS1 API docs: https://ns1.com/api#getlist-dhcp-option-definitions
func (s *OptionDefService) List() ([]dhcp.OptionDef, *http.Response, error) {
req, err := s.client.NewRequest(http.MethodGet, "dhcp/optiondef", nil)
if err != nil {
return nil, nil, err
}

ods := make([]dhcp.OptionDef, 0)
resp, err := s.client.Do(req, &ods)
return ods, resp, err
}

// Get returns the option definition corresponding to the provided ID.
//
// NS1 API docs: https://ns1.com/api#getview-dhcp-option-definition
func (s *OptionDefService) Get(odSpace, odKey string) (*dhcp.OptionDef, *http.Response, error) {
reqPath := fmt.Sprintf("dhcp/optiondef/%s/%s", odSpace, odKey)
req, err := s.client.NewRequest(http.MethodGet, reqPath, nil)
if err != nil {
return nil, nil, err
}

od := &dhcp.OptionDef{}
var resp *http.Response
resp, err = s.client.Do(req, od)
if err != nil {
return nil, resp, err
}

return od, resp, nil
}

// Create creates or updates an option definition.
// The FriendlyName, Description, Code, Schema.Type fields are required.
//
// NS1 API docs: https://ns1.com/api#putcreate-an-custom-dhcp-option-definition
func (s *OptionDefService) Create(od *dhcp.OptionDef, odSpace, odKey string) (*dhcp.OptionDef, *http.Response, error) {
switch {
case od.FriendlyName == "":
return nil, nil, errors.New("the FriendlyName field is required")
case od.Description == "":
return nil, nil, errors.New("the Description field is required")
case od.Code == 0:
return nil, nil, errors.New("the Code field is required")
case od.Schema.Type == "":
return nil, nil, errors.New("the Schema.Type field is required")
}

reqPath := fmt.Sprintf("dhcp/optiondef/%s/%s", odSpace, odKey)
req, err := s.client.NewRequest(http.MethodPut, reqPath, od)
if err != nil {
return nil, nil, err
}

respOd := new(dhcp.OptionDef)
var resp *http.Response
resp, err = s.client.Do(req, respOd)
if err != nil {
return nil, resp, err
}

return respOd, resp, nil
}

// Delete removes a option definition entirely.
//
// NS1 API docs: https://ns1.com/api#deletedelete-a-custom-dhcp-option-definition
func (s *OptionDefService) Delete(odSpace, odKey string) (*http.Response, error) {
reqPath := fmt.Sprintf("dhcp/optiondef/%s/%s", odSpace, odKey)
req, err := s.client.NewRequest(http.MethodDelete, reqPath, nil)
if err != nil {
return nil, err
}

return s.client.Do(req, nil)
}
164 changes: 164 additions & 0 deletions rest/optiondef_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package rest_test

import (
"gopkg.in/ns1/ns1-go.v2/rest/model/dhcp"
"net/http"
"testing"

"gopkg.in/ns1/ns1-go.v2/mockns1"
api "gopkg.in/ns1/ns1-go.v2/rest"
)

func TestDHCPOptionDef(t *testing.T) {
mock, doer, err := mockns1.New(t)
if err != nil {
t.Fatalf("Error creating mock service: %v", err)
}
defer mock.Shutdown()

client := api.NewClient(doer, api.SetEndpoint("https://"+mock.Address+"/v1/"))

t.Run("List", func(t *testing.T) {
defer mock.ClearTestCases()

client.FollowPagination = true
sgs := []dhcp.OptionDef{
{
Code: 1,
Description: "a",
FriendlyName: "a",
Schema: dhcp.OptionDefSchema{
Type: dhcp.SchemaTypeString,
},
},
{
Code: 2,
Description: "b",
FriendlyName: "b",
Schema: dhcp.OptionDefSchema{
Type: dhcp.SchemaTypeString,
},
},
}
err := mock.AddTestCase(http.MethodGet, "/dhcp/optiondef", http.StatusOK, nil, nil, "", sgs)
if err != nil {
t.Fatalf("error adding test case: %v", err)
}

respSgs, _, err := client.OptionDef.List()
if err != nil {
t.Fatalf("error listing DHCP option definitions: %v", err)
}
if len(respSgs) != len(sgs) {
t.Errorf("wrong length: want=%d, got=%d", len(sgs), len(respSgs))
}

for i, sg := range respSgs {
if sg.FriendlyName != sgs[i].FriendlyName || sg.Code != sgs[i].Code ||
sg.Description != sgs[i].Description || sg.Schema.Type != sgs[i].Schema.Type {
t.Errorf("Incorrect data for option definition %d: want=%+v, got=%+v", i, sgs[i], sg)
}
}
})

t.Run("Get", func(t *testing.T) {
defer mock.ClearTestCases()

client.FollowPagination = false
od := dhcp.OptionDef{
Code: 1,
Description: "a",
FriendlyName: "a",
Schema: dhcp.OptionDefSchema{
Type: dhcp.SchemaTypeString,
},
}

err := mock.AddTestCase(http.MethodGet, "/dhcp/optiondef/space/key", http.StatusOK, nil, nil, "", od)
if err != nil {
t.Fatalf("error adding test case: %v", err)
}

respOD, _, err := client.OptionDef.Get("space", "key")
if err != nil {
t.Fatalf("error getting scpe group: %v", err)
}
if od.FriendlyName != respOD.FriendlyName || od.Code != respOD.Code ||
od.Description != respOD.Description || od.Schema.Type != respOD.Schema.Type {
t.Errorf("wrong option def returned, want=%+v, got=%+v", od, respOD)
}
})

t.Run("Create", func(t *testing.T) {
defer mock.ClearTestCases()

t.Run("RequiredParams", func(t *testing.T) {
od := &dhcp.OptionDef{}
_, _, err = client.OptionDef.Create(od, "space", "key")
if err == nil {
t.Errorf("expected a missing code to result in an error")
}

od = &dhcp.OptionDef{
Code: 1,
}
_, _, err = client.OptionDef.Create(od, "space", "key")
if err == nil {
t.Errorf("expected a missing friendly name to result in an error")
}

od = &dhcp.OptionDef{
Code: 1,
Description: "a",
}
_, _, err = client.OptionDef.Create(od, "space", "key")
if err == nil {
t.Errorf("expected a missing description to result in an error")
}

od = &dhcp.OptionDef{
Code: 1,
Description: "a",
FriendlyName: "a",
}
_, _, err = client.OptionDef.Create(od, "space", "key")
if err == nil {
t.Errorf("expected a missing schema type to result in an error")
}
})

od := &dhcp.OptionDef{
Code: 1,
Description: "a",
FriendlyName: "a",
Schema: dhcp.OptionDefSchema{
Type: dhcp.SchemaTypeString,
},
}
err := mock.AddTestCase(http.MethodPut, "/dhcp/optiondef/space/key", http.StatusCreated, nil, nil, od, od)
if err != nil {
t.Fatalf("error adding test case: %v", err)
}
respOD, _, err := client.OptionDef.Create(od, "space", "key")
if err != nil {
t.Fatalf("error creating option definition: %v", err)
}
if od.FriendlyName != respOD.FriendlyName || od.Code != respOD.Code ||
od.Description != respOD.Description || od.Schema.Type != respOD.Schema.Type {
t.Errorf("wrong option def returned, want=%+v, got=%+v", od, respOD)
}
})

t.Run("Delete", func(t *testing.T) {
defer mock.ClearTestCases()

err := mock.AddTestCase(http.MethodDelete, "/dhcp/optiondef/space/key", http.StatusNoContent, nil, nil, "", nil)
if err != nil {
t.Fatalf("error adding test case: %v", err)
}
_, err = client.OptionDef.Delete("space", "key")
if err != nil {
t.Fatalf("error deleting option definition: %v", err)
}
})
}

0 comments on commit 1138b90

Please sign in to comment.