Refactoring and tests

Break out tenant to its own type
Rearrange structs to better align with memory
This commit is contained in:
Kalle Carlbark 2021-12-19 21:24:29 +01:00
parent af589e9fc0
commit 92eacc150c
No known key found for this signature in database
7 changed files with 1566 additions and 117 deletions

View file

@ -23,8 +23,8 @@ func TestListDevices(t *testing.T) {
apiToken = os.Getenv("NETBOX_TOKEN") apiToken = os.Getenv("NETBOX_TOKEN")
apiURL = os.Getenv("NETBOX_URL") apiURL = os.Getenv("NETBOX_URL")
assert(t, true, apiToken) notempty(t, apiToken)
assert(t, true, apiURL) notempty(t, apiURL)
var nb *Client var nb *Client
nb, err = NewClient(apiURL, nil) nb, err = NewClient(apiURL, nil)
@ -40,6 +40,14 @@ func TestListDevices(t *testing.T) {
} }
} }
func notempty(tb testing.TB, msg string) {
if msg == "" {
_, file, line, _ := runtime.Caller(1)
fmt.Printf("\033[31m%s:%d: string empty\033[39m\n\n", filepath.Base(file), line)
tb.FailNow()
}
}
// ok fails the test if an err is not nil. // ok fails the test if an err is not nil.
func ok(tb testing.TB, err error) { func ok(tb testing.TB, err error) {
if err != nil { if err != nil {

73
devices_test.go Normal file
View file

@ -0,0 +1,73 @@
package netboxgo_test
import (
"bytes"
"context"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"testing"
. "git.kcbark.net/kc/netboxgo"
)
type fakeClient struct {
q url.Values
h *http.Client
}
func newFakeClient() *fakeClient {
return &fakeClient{q: url.Values{}, h: &http.Client{}}
}
func (c *fakeClient) prepare(path string, jsonfile string) {
u, err := url.Parse(path)
if err != nil {
panic(err)
}
b, err := ioutil.ReadFile(jsonfile)
if err != nil {
log.Fatalf("failed to read json file: %q: %+v", jsonfile, err)
}
newFakeHTTPClient := NewTestClient(func(req *http.Request) *http.Response {
return &http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBuffer(b)),
Header: make(http.Header),
}
})
c.q = u.Query()
c.h = newFakeHTTPClient
}
func NewTestClient(fn RoundTripFunc) *http.Client {
return &http.Client{
Transport: RoundTripFunc(fn),
}
}
type RoundTripFunc func(req *http.Request) *http.Response
func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req), nil
}
func TestDevicesList(t *testing.T) {
c := newFakeClient()
c.prepare("/dcim/devices", "./testdata/dcim_devices_list.json")
nb, err := NewClient("tokenloken", c.h)
ok(t, err)
ctx := context.Background()
devices, err := nb.Devices.List(ctx, nil)
ok(t, err)
equals(t, 10, len(devices.Results))
}

44
netbox_test.go Normal file
View file

@ -0,0 +1,44 @@
package netboxgo_test
import (
"fmt"
"path/filepath"
"reflect"
"runtime"
"testing"
)
// func notempty(tb testing.TB, msg string) {
// if msg == "" {
// _, file, line, _ := runtime.Caller(1)
// fmt.Printf("\033[31m%s:%d: string empty\033[39m\n\n", filepath.Base(file), line)
// tb.FailNow()
// }
// }
// ok fails the test if an err is not nil.
func ok(tb testing.TB, err error) {
if err != nil {
_, file, line, _ := runtime.Caller(1)
fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error())
tb.FailNow()
}
}
// assert fails the test if the condition is false.
// func assert(tb testing.TB, condition bool, msg string, v ...interface{}) {
// if !condition {
// _, file, line, _ := runtime.Caller(1)
// fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...)
// tb.FailNow()
// }
// }
// equals fails the test if exp is not equal to act.
func equals(tb testing.TB, exp, act interface{}) {
if !reflect.DeepEqual(exp, act) {
_, file, line, _ := runtime.Caller(1)
fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act)
tb.FailNow()
}
}

View file

@ -16,89 +16,86 @@ type PrefixesService service
// NewPrefix is used for the return values from Netbox API ipam_prefixes_create // NewPrefix is used for the return values from Netbox API ipam_prefixes_create
type NewPrefix struct { type NewPrefix struct {
CustomFields interface{} `json:"custom_fields"`
Prefix string `json:"prefix"` Prefix string `json:"prefix"`
Description string `json:"description"`
Tags []string `json:"tags"`
Site int `json:"site"` Site int `json:"site"`
Status int `json:"status"`
Role int `json:"role"`
Vrf int `json:"vrf"` Vrf int `json:"vrf"`
Tenant int `json:"tenant"` Tenant int `json:"tenant"`
VLAN int `json:"vlan"` VLAN int `json:"vlan"`
Status int `json:"status"`
Role int `json:"role"`
IsPool bool `json:"is_pool"` IsPool bool `json:"is_pool"`
Description string `json:"description"`
Tags []string `json:"tags"`
CustomFields interface{} `json:"custom_fields"`
} }
// Prefixes is used for the return value from NetBox API ipam_prefixes_list // Prefixes is used for the return value from NetBox API ipam_prefixes_list
type Prefixes struct { type Prefixes struct {
Count int `json:"count"`
Next string `json:"next"` Next string `json:"next"`
Previous string `json:"previous"` Previous string `json:"previous"`
Results []struct { Results []struct {
ID int `json:"id"` LastUpdated time.Time `json:"last_updated"`
Family struct { CustomFields interface{} `json:"custom_fields"`
Label string `json:"label"` VLAN struct {
Value int `json:"value"`
} `json:"family"`
Prefix string `json:"prefix"`
Site struct {
ID int `json:"id"`
URL string `json:"url"`
Name string `json:"name"`
Slug string `json:"slug"`
} `json:"site"`
Vrf struct {
ID int `json:"id"`
URL string `json:"url"` URL string `json:"url"`
Name string `json:"name"` Name string `json:"name"`
Rd string `json:"rd"`
PrefixCount int `json:"prefix_count"`
} `json:"vrf"`
Tenant struct {
ID int `json:"id"`
URL string `json:"url"`
Name string `json:"name"`
Slug string `json:"slug"`
} `json:"tenant"`
VLAN struct {
ID int `json:"id"`
URL string `json:"url"`
Vid int `json:"vid"`
Name string `json:"name"`
DisplayName string `json:"display_name"` DisplayName string `json:"display_name"`
ID int `json:"id"`
Vid int `json:"vid"`
} `json:"vlan"` } `json:"vlan"`
Tenant struct {
URL string `json:"url"`
Name string `json:"name"`
Slug string `json:"slug"`
ID int `json:"id"`
} `json:"tenant"`
Site struct {
URL string `json:"url"`
Name string `json:"name"`
Slug string `json:"slug"`
ID int `json:"id"`
} `json:"site"`
Status struct { Status struct {
Label string `json:"label"` Label string `json:"label"`
Value string `json:"value"` Value string `json:"value"`
} `json:"status"` } `json:"status"`
Role struct { Prefix string `json:"prefix"`
ID int `json:"id"` Description string `json:"description"`
Created string `json:"created"`
Vrf struct {
URL string `json:"url"` URL string `json:"url"`
Name string `json:"name"` Name string `json:"name"`
Slug string `json:"slug"` Rd string `json:"rd"`
ID int `json:"id"`
PrefixCount int `json:"prefix_count"` PrefixCount int `json:"prefix_count"`
VLANCount int `json:"vlan_count"` } `json:"vrf"`
} `json:"role"` Tags []struct {
IsPool bool `json:"is_pool"`
Description string `json:"description"`
Tags []struct {
ID int `json:"id"`
URL string `json:"url"` URL string `json:"url"`
Name string `json:"name"` Name string `json:"name"`
Slug string `json:"slug"` Slug string `json:"slug"`
Color string `json:"color"` Color string `json:"color"`
ID int `json:"id"`
} `json:"tags"` } `json:"tags"`
CustomFields interface{} `json:"custom_fields"` Family struct {
Created string `json:"created"` Label string `json:"label"`
LastUpdated time.Time `json:"last_updated"` Value int `json:"value"`
} `json:"family"`
Role struct {
URL string `json:"url"`
Name string `json:"name"`
Slug string `json:"slug"`
ID int `json:"id"`
PrefixCount int `json:"prefix_count"`
VLANCount int `json:"vlan_count"`
} `json:"role"`
ID int `json:"id"`
IsPool bool `json:"is_pool"`
} `json:"results"` } `json:"results"`
Count int `json:"count"`
} }
// PrefixFilter is used to filter out returned object from Netbox API ipam_prefixes_list // PrefixFilter is used to filter out returned object from Netbox API ipam_prefixes_list
type PrefixFilter struct { type PrefixFilter struct {
Offset int64 `schema:"offset,omitempty"`
Limit int64 `schema:"limit,omitempty"`
// User specific filters // User specific filters
Family string `schema:"family,omitempty"` Family string `schema:"family,omitempty"`
IsPool string `schema:"is_pool,omitempty"` IsPool string `schema:"is_pool,omitempty"`
@ -124,6 +121,9 @@ type PrefixFilter struct {
VLANID string `schema:"vlan_id,omitempty"` VLANID string `schema:"vlan_id,omitempty"`
Role string `schema:"role,omitempty"` Role string `schema:"role,omitempty"`
Status string `schema:"status,omitempty"` Status string `schema:"status,omitempty"`
Offset int `schema:"offset,omitempty"`
Limit int `schema:"limit,omitempty"`
} }
const prefixesPath = ipamPath + "/prefixes" const prefixesPath = ipamPath + "/prefixes"
@ -157,6 +157,35 @@ func (s *PrefixesService) List(ctx context.Context, f *PrefixFilter) (*Prefixes,
return &prefixes, nil return &prefixes, nil
} }
// ListAll prefixes. PrefixFilter is used to list all prefixes based on filters
func (s *PrefixesService) ListAll(ctx context.Context, f *PrefixFilter) (*Prefixes, error) {
var all Prefixes
var r *Prefixes
var num int
var err error
f.Limit = 1
r, err = s.List(ctx, f)
if err != nil {
return &all, fmt.Errorf("unable to count prefixes with filter %+v: %w", f, err)
}
num = r.Count
for count := 0; count < num; count += 1000 {
f.Limit = 1000
f.Offset = count
r, err = s.List(ctx, f)
if err != nil {
return &all, fmt.Errorf("unable to list prefixes with filter %+v: %w", f, err)
}
all.Results = append(all.Results, r.Results...)
}
return &all, nil
}
// Create a prefix // Create a prefix
func (s *PrefixesService) Create(ctx context.Context, c *NewPrefix) error { func (s *PrefixesService) Create(ctx context.Context, c *NewPrefix) error {
var err error var err error

View file

@ -14,53 +14,53 @@ type TenantsService service
// NewTenant is used for the return values from Netbox API tenancy_tenants_create // NewTenant is used for the return values from Netbox API tenancy_tenants_create
type NewTenant struct { type NewTenant struct {
Name string `json:"name"` CustomFields interface{} `json:"custom_fields"`
Slug string `json:"slug"` Slug string `json:"slug"`
Group int `json:"group"`
Description string `json:"description"` Description string `json:"description"`
Comments string `json:"comments"` Comments string `json:"comments"`
Name string `json:"name"`
Tags []string `json:"tags"` Tags []string `json:"tags"`
CustomFields interface{} `json:"custom_fields"` Group int `json:"group"`
} }
// Tenants is used for the return value from NetBox API tenancy_tenants_list // Tenant represents a tenant
type Tenant struct {
LastUpdated time.Time `json:"last_updated"`
CustomFields interface{} `json:"custom_fields"`
VirtualmachineCount interface{} `json:"virtualmachine_count"`
RackCount interface{} `json:"rack_count"`
IpaddressCount interface{} `json:"ipaddress_count"`
CircuitCount interface{} `json:"circuit_count"`
VrfCount interface{} `json:"vrf_count"`
Group struct {
URL string `json:"url"`
Name string `json:"name"`
Slug string `json:"slug"`
ID int `json:"id"`
} `json:"group"`
Created string `json:"created"`
Comments string `json:"comments"`
Description string `json:"description"`
Slug string `json:"slug"`
Name string `json:"name"`
Tags []interface{} `json:"tags"`
DeviceCount int `json:"device_count"`
PrefixCount int `json:"prefix_count"`
SiteCount int `json:"site_count"`
VlanCount int `json:"vlan_count"`
ID int `json:"id"`
}
// Tenants is represents a list of multiple tenants
type Tenants struct { type Tenants struct {
Count int `json:"count"`
Next string `json:"next"`
Previous interface{} `json:"previous"` Previous interface{} `json:"previous"`
Results []struct { Next string `json:"next"`
ID int `json:"id"` Results []Tenant `json:"results"`
Name string `json:"name"` Count int `json:"count"`
Slug string `json:"slug"`
Group struct {
ID int `json:"id"`
URL string `json:"url"`
Name string `json:"name"`
Slug string `json:"slug"`
} `json:"group"`
Description string `json:"description"`
Comments string `json:"comments"`
Tags []interface{} `json:"tags"`
CustomFields interface{} `json:"custom_fields"`
Created string `json:"created"`
LastUpdated time.Time `json:"last_updated"`
CircuitCount interface{} `json:"circuit_count"`
DeviceCount int `json:"device_count"`
IpaddressCount interface{} `json:"ipaddress_count"`
PrefixCount int `json:"prefix_count"`
RackCount interface{} `json:"rack_count"`
SiteCount int `json:"site_count"`
VirtualmachineCount interface{} `json:"virtualmachine_count"`
VlanCount int `json:"vlan_count"`
VrfCount interface{} `json:"vrf_count"`
} `json:"results"`
} }
// TenantFilter is used to filter out returned object from Netbox API tenancy_tenants_list // TenantFilter is used to filter out returned object from Netbox API tenancy_tenants_list
type TenantFilter struct { type TenantFilter struct {
Offset int64 `schema:"offset,omitempty"`
Limit int64 `schema:"limit,omitempty"`
// User specific filters // User specific filters
Name string `schema:"name,omitempty"` Name string `schema:"name,omitempty"`
Slug string `schema:"slug,omitempty"` Slug string `schema:"slug,omitempty"`
@ -69,6 +69,9 @@ type TenantFilter struct {
IDIn string `schema:"id__in,omitempty"` IDIn string `schema:"id__in,omitempty"`
Q string `schema:"q,omitempty"` Q string `schema:"q,omitempty"`
Tag string `schema:"tag,omitempty"` Tag string `schema:"tag,omitempty"`
Offset int `schema:"offset,omitempty"`
Limit int `schema:"limit,omitempty"`
} }
const tenantsPath = tenancyPath + "/tenants" const tenantsPath = tenancyPath + "/tenants"
@ -102,6 +105,35 @@ func (s *TenantsService) List(ctx context.Context, f *TenantFilter) (*Tenants, e
return &tenants, nil return &tenants, nil
} }
// ListAll tenants. TenantFilter is used to list all tenants based on filters
func (s *TenantsService) ListAll(ctx context.Context, f *TenantFilter) (*Tenants, error) {
var all Tenants
var r *Tenants
var num int
var err error
f.Limit = 1
r, err = s.List(ctx, f)
if err != nil {
return &all, fmt.Errorf("unable to count prefixes with filter %+v: %w", f, err)
}
num = r.Count
for count := 0; count < num; count += 1000 {
f.Limit = 1000
f.Offset = count
r, err = s.List(ctx, f)
if err != nil {
return &all, fmt.Errorf("unable to list prefixes with filter %+v: %w", f, err)
}
all.Results = append(all.Results, r.Results...)
}
return &all, nil
}
// Create a tenant // Create a tenant
func (s *TenantsService) Create(ctx context.Context, c *NewTenant) error { func (s *TenantsService) Create(ctx context.Context, c *NewTenant) error {
var err error var err error

1234
testdata/dcim_devices_list.json vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -14,75 +14,72 @@ type VLANsService service
// NewVLAN is used for the return values from Netbox API ipam_vlans_create // NewVLAN is used for the return values from Netbox API ipam_vlans_create
type NewVLAN struct { type NewVLAN struct {
Site int `json:"site"` CustomFields interface{} `json:"custom_fields"`
Group int `json:"group"` Description string `json:"description"`
Vid int `json:"vid"`
Name string `json:"name"` Name string `json:"name"`
Tags []string `json:"tags"`
Site int `json:"site"`
Tenant int `json:"tenant"` Tenant int `json:"tenant"`
Status int `json:"status"` Status int `json:"status"`
Role int `json:"role"` Role int `json:"role"`
Description string `json:"description"` Group int `json:"group"`
Tags []string `json:"tags"` Vid int `json:"vid"`
CustomFields interface{} `json:"custom_fields"`
} }
type VLAN struct { type VLAN struct {
ID int `json:"id"` LastUpdated time.Time `json:"last_updated"`
Site struct { CustomFields interface{} `json:"custom_fields"`
ID int `json:"id"` Site struct {
URL string `json:"url"` URL string `json:"url"`
Name string `json:"name"` Name string `json:"name"`
Slug string `json:"slug"` Slug string `json:"slug"`
ID int `json:"id"`
} `json:"site"` } `json:"site"`
Group struct {
ID int `json:"id"`
URL string `json:"url"`
Name string `json:"name"`
Slug string `json:"slug"`
VLANCount int `json:"vlan_count"`
} `json:"group"`
Vid int `json:"vid"`
Name string `json:"name"`
Tenant struct { Tenant struct {
ID int `json:"id"`
URL string `json:"url"` URL string `json:"url"`
Name string `json:"name"` Name string `json:"name"`
Slug string `json:"slug"` Slug string `json:"slug"`
ID int `json:"id"`
} `json:"tenant"` } `json:"tenant"`
Status struct { Status struct {
Label string `json:"label"` Label string `json:"label"`
Value string `json:"value"` Value string `json:"value"`
} `json:"status"` } `json:"status"`
Description string `json:"description"`
Name string `json:"name"`
Created string `json:"created"`
DisplayName string `json:"display_name"`
Group struct {
URL string `json:"url"`
Name string `json:"name"`
Slug string `json:"slug"`
ID int `json:"id"`
VLANCount int `json:"vlan_count"`
} `json:"group"`
Tags []string `json:"tags"`
Role struct { Role struct {
ID int `json:"id"`
URL string `json:"url"` URL string `json:"url"`
Name string `json:"name"` Name string `json:"name"`
Slug string `json:"slug"` Slug string `json:"slug"`
ID int `json:"id"`
PrefixCount int `json:"prefix_count"` PrefixCount int `json:"prefix_count"`
VLANCount int `json:"vlan_count"` VLANCount int `json:"vlan_count"`
} `json:"role"` } `json:"role"`
Description string `json:"description"` ID int `json:"id"`
Tags []string `json:"tags"` Vid int `json:"vid"`
DisplayName string `json:"display_name"` PrefixCount int `json:"prefix_count"`
CustomFields interface{} `json:"custom_fields"`
Created string `json:"created"`
LastUpdated time.Time `json:"last_updated"`
PrefixCount int `json:"prefix_count"`
} }
// IpamVLANsList is used for the return value from NetBox API ipam_vlans_list // IpamVLANsList is used for the return value from NetBox API ipam_vlans_list
type VLANs struct { type VLANs struct {
Count int `json:"count"`
Next string `json:"next"` Next string `json:"next"`
Previous string `json:"previous"` Previous string `json:"previous"`
Results []VLAN `json:"results"` Results []VLAN `json:"results"`
Count int `json:"count"`
} }
// VLANFilter is used to filter out returned object from Netbox API ipam_vlans_list // VLANFilter is used to filter out returned object from Netbox API ipam_vlans_list
type VLANFilter struct { type VLANFilter struct {
Offset int64 `schema:"offset,omitempty"`
Limit int64 `schema:"limit,omitempty"`
// User specific filters // User specific filters
Vid string `schema:"vid,omitempty"` Vid string `schema:"vid,omitempty"`
Name string `schema:"name,omitempty"` Name string `schema:"name,omitempty"`
@ -100,6 +97,9 @@ type VLANFilter struct {
Role string `schema:"role,omitempty"` Role string `schema:"role,omitempty"`
Status string `schema:"status,omitempty"` Status string `schema:"status,omitempty"`
Tag string `schema:"tag,omitempty"` Tag string `schema:"tag,omitempty"`
Offset int `schema:"offset,omitempty"`
Limit int `schema:"limit,omitempty"`
} }
const vlansPath = ipamPath + "/vlans" const vlansPath = ipamPath + "/vlans"
@ -133,6 +133,35 @@ func (s *VLANsService) List(ctx context.Context, f *VLANFilter) (*VLANs, error)
return &vlans, nil return &vlans, nil
} }
// ListAll vlans. VLANFilter is used to filter list all based on filters
func (s *VLANsService) ListAll(ctx context.Context, f *VLANFilter) (*VLANs, error) {
var all VLANs
var v *VLANs
var numVlans int
var err error
f.Limit = 1
v, err = s.List(ctx, f)
if err != nil {
return &all, fmt.Errorf("unable to count vlans with filter %+v: %w", f, err)
}
numVlans = v.Count
for count := 0; count < numVlans; count += 1000 {
f.Limit = 1000
f.Offset = count
v, err = s.List(ctx, f)
if err != nil {
return &all, fmt.Errorf("unable to list vlans with filter %+v: %w", f, err)
}
all.Results = append(all.Results, v.Results...)
}
return &all, nil
}
// Create a device // Create a device
func (s *VLANsService) Create(ctx context.Context, c *NewDevice) error { func (s *VLANsService) Create(ctx context.Context, c *NewDevice) error {
var err error var err error