diff --git a/AUTHORS b/AUTHORS index 30dd8446ea96de4099e613e62712f6e854ca6fa8..df81c2d80358ee4b210d198349daa67a96e9ef23 100644 --- a/AUTHORS +++ b/AUTHORS @@ -11,3 +11,4 @@ Contributors ------------ Allan Liu <aliu@digitalocean.com> Dave Cameron <dcameron@digitalocean.com> +Quan D. Hoang <hdquan2014@gmail.com> diff --git a/netbox/dcim.go b/netbox/dcim.go index 0f955bd01e51c7197122fadd79123d353df3ecf6..28f88d28074682a0f78e80cb39f02fb6819aafb4 100644 --- a/netbox/dcim.go +++ b/netbox/dcim.go @@ -18,6 +18,7 @@ package netbox type DCIMService struct { c *Client InventoryItems *InventoryItemsService + Devices *DevicesService } // NewDCIMService returns a DCIMService initialized with all sub-services. @@ -27,6 +28,9 @@ func NewDCIMService(client *Client) *DCIMService { InventoryItems: &InventoryItemsService{ c: client, }, + Devices: &DevicesService{ + c: client, + }, } } diff --git a/netbox/dcim_devices.go b/netbox/dcim_devices.go new file mode 100644 index 0000000000000000000000000000000000000000..61912044db771ea2ab454d8c0576949374a9a3d1 --- /dev/null +++ b/netbox/dcim_devices.go @@ -0,0 +1,118 @@ +// Copyright 2017 The go-netbox Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by generate_functions.go. DO NOT EDIT. + +package netbox + +import ( + "encoding/json" + "fmt" + "net/http" +) + +// DevicesService is used in a Client to access NetBox's dcim/devices API methods. +type DevicesService struct { + c *Client +} + +// Get retrieves an Device object from NetBox by its ID. +func (s *DevicesService) Get(id int) (*Device, error) { + req, err := s.c.NewRequest( + http.MethodGet, + fmt.Sprintf("api/dcim/devices/%d/", id), + nil, + ) + if err != nil { + return nil, err + } + + t := new(Device) + err = s.c.Do(req, t) + if err != nil { + return nil, err + } + return t, nil +} + +// List returns a Page associated with an NetBox API Endpoint. +func (s *DevicesService) List(options *ListDeviceOptions) *Page { + return NewPage(s.c, "api/dcim/devices/", options) +} + +// Extract retrives a list of Device objects from page. +func (s *DevicesService) Extract(page *Page) ([]*Device, error) { + if err := page.Err(); err != nil { + return nil, err + } + + var groups []*Device + if err := json.Unmarshal(page.data.Results, &groups); err != nil { + return nil, err + } + return groups, nil +} + +// Create creates a new Device object in NetBox and returns the ID of the new object. +func (s *DevicesService) Create(data *Device) (int, error) { + req, err := s.c.NewJSONRequest(http.MethodPost, "api/dcim/devices/", nil, data) + if err != nil { + return 0, err + } + + g := new(writableDevice) + err = s.c.Do(req, g) + if err != nil { + return 0, err + } + return g.ID, nil +} + +// Update changes an existing Device object in NetBox, and returns the ID of the new object. +func (s *DevicesService) Update(data *Device) (int, error) { + req, err := s.c.NewJSONRequest( + http.MethodPatch, + fmt.Sprintf("api/dcim/devices/%d/", data.ID), + nil, + data, + ) + if err != nil { + return 0, err + } + + // g is just used to verify correct api result. + // data is not changed, because the g is not the full representation that one would + // get with Get. But if the response was unmarshaled into writableDevice correctly, + // everything went fine, and we do not need to update data. + g := new(writableDevice) + err = s.c.Do(req, g) + if err != nil { + return 0, err + } + return g.ID, nil +} + +// Delete deletes an existing Device object from NetBox. +func (s *DevicesService) Delete(data *Device) error { + req, err := s.c.NewRequest( + http.MethodDelete, + fmt.Sprintf("api/dcim/devices/%d/", data.ID), + nil, + ) + if err != nil { + return err + } + + return s.c.Do(req, nil) +} diff --git a/netbox/dcim_devices_basic_test.go b/netbox/dcim_devices_basic_test.go new file mode 100644 index 0000000000000000000000000000000000000000..fe7ef3d53ac980ecfd930ae27854bba384ad86c5 --- /dev/null +++ b/netbox/dcim_devices_basic_test.go @@ -0,0 +1,299 @@ +// Copyright 2017 The go-netbox Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by generate_basic_tests.go. DO NOT EDIT. + +package netbox + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "reflect" + "testing" +) + +// Using this to override MarshalJSON +// In all cases when posting data to netbox-API, the Device.MarshalJSON is what you want, +// but not here as a return in testHandler +type serverDataDevice Device + +func convertToServerDataDevice(data []*Device) []*serverDataDevice { + dataWant := make([]*serverDataDevice, len(data)) + for i := range data { + tmp := serverDataDevice(*data[i]) + dataWant[i] = &tmp + } + return dataWant +} + +func TestBasicDeviceGet(t *testing.T) { + var tests = []struct { + desc string + want *Device + }{ + { + desc: "Simple Device", + want: testDevice(1), + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) { + serverData := serverDataDevice(*tt.want) + + c, done := testClient(t, testHandler(t, http.MethodGet, "/api/dcim/devices/1/", &serverData)) + defer done() + + res, err := c.DCIM.Devices.Get(1) + if err != nil { + t.Fatalf("unexpected error from Client.DCIM.Devices.Get: %v", err) + } + + if want, got := tt.want, res; !reflect.DeepEqual(want, got) { + t.Fatalf("unexpected Device:\n- want: %v\n- got: %v", want, got) + } + }) + } +} + +func TestBasicDeviceGet404(t *testing.T) { + c, done := testClient(t, testStatusHandler(t, http.MethodGet, "/api/dcim/devices/1/", &struct { + Detail string `json:"detail"` + }{ + Detail: "Not found.", + }, + http.StatusNotFound)) + defer done() + + res, err := c.DCIM.Devices.Get(1) + errstr := "404 - Not found." + if want, got := errors.New(errstr), err; !reflect.DeepEqual(want, got) { + t.Fatalf("unexpected error from Client.DCIM.Devices.Get:\n- want: %v\n- got: %v", want, got) + } + + if res != nil { + t.Fatalf("unexpected result:\n- want: %v\n- got: %v", nil, res) + } +} + +func TestBasicListExtractDevice(t *testing.T) { + want := []*Device{ + testDevice(1), + testDevice(2), + } + serverWant := convertToServerDataDevice(want) + serverData, _ := json.Marshal(serverWant) + c, done := testClient(t, testHandler(t, http.MethodGet, "/api/dcim/devices/", &pageData{ + Count: 2, + NextURL: "", + PreviousURL: "", + Results: serverData, + })) + defer done() + + page := c.DCIM.Devices.List(nil) + + if page == nil { + t.Fatalf("unexpexted result from c.DCIM.Devices.List.") + } + + got := []*Device{} + counter := 0 + for page.Next() { + var err error + got, err = c.DCIM.Devices.Extract(page) + if err != nil { + t.Fatalf("unexpected error from c.DCIM.Devices.Extract: %v", err) + } + counter = counter + 1 + if counter > 2 { // Safe guard + break + } + } + if counter != 1 { + t.Fatalf("unexpected page count:\n- want: 1\n- got: %d", counter) + } + + if !reflect.DeepEqual(want, got) { + t.Fatalf("unexpected result:\n- want: %v\n- got: %v", want, got) + } + + if page.Err() != nil { + t.Fatalf("unexpected error from page:\n- want: %v\n- got: %v", want, got) + } +} + +func TestBasicCreateDevice(t *testing.T) { + var tests = []struct { + desc string + data *Device + want int + serverData interface{} + status int + errstr string + }{ + { + desc: "Create with ID 0", + data: testDeviceCreate(1), + want: 1, + status: 0, + errstr: "", + serverData: testDevice(1), + }, + { + desc: "Create duplicate", + data: testDeviceCreate(1), + want: 0, + status: http.StatusBadRequest, + errstr: "400 - {\"name\":[\"DevicesService with this name already exists.\"]}\n", + serverData: &struct { + Name []string `json:"name"` + }{ + Name: []string{"DevicesService with this name already exists."}, + }, + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) { + c, done := testClient(t, testStatusHandler(t, http.MethodPost, "/api/dcim/devices/", tt.serverData, tt.status)) + defer done() + + var terr error + if tt.errstr != "" { + terr = errors.New(tt.errstr) // Using errstr and initialize real err here, to satisfy golint + } + + res, err := c.DCIM.Devices.Create(tt.data) + if want, got := terr, err; !reflect.DeepEqual(want, got) { + t.Fatalf("unexpected error:\n- want: %v\n- got: %v", want, got) + } + if want, got := tt.want, res; !reflect.DeepEqual(want, got) { + t.Fatalf("unexpected Device:\n- want: %v\n- got: %v", want, got) + } + }) + } +} + +func TestBasicUpdateDevice(t *testing.T) { + var tests = []struct { + desc string + data *Device + want int + serverData interface{} + status int + errstr string + }{ + { + desc: "Update with ID 1", + data: testDevice(1), + want: 1, + serverData: testDevice(1), + status: 0, + errstr: "", + }, + { + desc: "Update not found", + data: testDevice(1), + want: 0, + serverData: &struct { + Detail string + }{ + Detail: "Not found.", + }, + status: http.StatusNotFound, + errstr: "404 - Not found.", + }, + { + desc: "Update to duplicate", + data: testDevice(1), + want: 0, + serverData: &struct { + Name []string `json:"name"` + }{ + Name: []string{"DevicesService with this name already exists."}, + }, + status: http.StatusBadRequest, + errstr: "400 - {\"name\":[\"DevicesService with this name already exists.\"]}\n", + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) { + c, done := testClient(t, testStatusHandler(t, http.MethodPatch, "/api/dcim/devices/1/", tt.serverData, tt.status)) + defer done() + + var terr error + if tt.errstr != "" { + terr = errors.New(tt.errstr) // Using errstr and initialize real err here, to satisfy golint + } + + res, err := c.DCIM.Devices.Update(tt.data) + if want, got := terr, err; !reflect.DeepEqual(want, got) { + t.Fatalf("unexpected error:\n- want: %v\n- got: %v", want, got) + } + if want, got := tt.want, res; !reflect.DeepEqual(want, got) { + t.Fatalf("unexpected Device:\n- want: %v\n- got: %v", want, got) + } + }) + } +} + +func TestBasicDeleteDevice(t *testing.T) { + var tests = []struct { + desc string + data *Device + serverData interface{} + status int + errstr string + }{ + { + desc: "Delete ID 1", + data: testDevice(1), + serverData: testDevice(1), + status: 0, + errstr: "", + }, + { + desc: "Delete not Found", + data: testDevice(1), + serverData: &struct { + Detail string `json:"detail"` + }{ + Detail: "Not found.", + }, + status: http.StatusNotFound, + errstr: "404 - Not found.", + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("[%d] %s", i, tt.desc), func(t *testing.T) { + c, done := testClient(t, testStatusHandler(t, http.MethodDelete, "/api/dcim/devices/1/", tt.serverData, tt.status)) + defer done() + + var terr error + if tt.errstr != "" { + terr = errors.New(tt.errstr) // Using errstr and initialize real err here, to satisfy golint + } + + err := c.DCIM.Devices.Delete(tt.data) + if want, got := terr, err; !reflect.DeepEqual(want, got) { + t.Fatalf("unexpected error:\n- want: %v\n- got: %v", want, got) + } + }) + } +} diff --git a/netbox/dcim_devices_test.go b/netbox/dcim_devices_test.go new file mode 100644 index 0000000000000000000000000000000000000000..bbf809ea7ea2d083696bae793b5764659d98e3af --- /dev/null +++ b/netbox/dcim_devices_test.go @@ -0,0 +1,298 @@ +// Copyright 2017 The go-netbox Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package netbox + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + _ "net/url" + "reflect" + "testing" +) + +func TestDeviceGet(t *testing.T) { + var tests = []struct { + desc string + want *Device + }{ + { + desc: "Device with Platform", + want: testDevice(1), + }, + } + + for idx, tt := range tests { + t.Run(fmt.Sprintf("[%d] %s", idx, tt.desc), func(t *testing.T) { + serverData := serverDataDevice(*tt.want) + + c, done := testClient(t, testHandler(t, http.MethodGet, "/api/dcim/devices/1/", &serverData)) + defer done() + + res, err := c.DCIM.Devices.Get(1) + if err != nil { + t.Fatalf("unexpected error from c.DCIM.Devices.Get: %v", err) + } + + if want, got := tt.want, res; !reflect.DeepEqual(want, got) { + t.Fatalf("unexpected Device\n- want: %v\n- got: %v", want, got) + } + }) + } +} + +func TestDeviceUnmarshalJSON(t *testing.T) { + var tests = []struct { + desc string + data []byte + want *Device + }{ + { + desc: "Minimum device", + data: []byte(`{"id": 1, "name": "Device 1", "display_name": "Device 1", "device_type": { "id": 11, "url": "http://localhost/api/dcim/device-types/11/", "manufacturer": { "id": 21, "url": "http://localhost/api/dcim/manufacturers/21/", "name": "Manufacturer Name", "slug": "mfg-name"}, "model": "Device Type Model", "slug": "device-type-model"}, "device_role": { "id": 31, "url": "http://localhost/api/dcim/device-roles/31/", "name": "Device Role Name", "slug": "device-role-name"}, "site": { "id": 61, "url": "http://localhost/api/dcim/sites/61/", "name": "Site Name", "slug": "site-name" }, "status": { "value": 1, "label": "Active" } }`), + want: &Device{ + ID: 1, + Name: "Device 1", + DisplayName: "Device 1", + DeviceType: &NestedDeviceType{ID: 11, + URL: "http://localhost/api/dcim/device-types/11/", + Manufacturer: &NestedManufacturer{ + ID: 21, URL: "http://localhost/api/dcim/manufacturers/21/", + Name: "Manufacturer Name", + Slug: "mfg-name", + }, + Model: "Device Type Model", + Slug: "device-type-model", + }, + DeviceRole: &NestedDeviceRole{ + ID: 31, + URL: "http://localhost/api/dcim/device-roles/31/", + Name: "Device Role Name", + Slug: "device-role-name", + }, + Site: &NestedSite{ + ID: 61, + URL: "http://localhost/api/dcim/sites/61/", + Name: "Site Name", + Slug: "site-name", + }, + Status: &StatusType{ + Value: 1, + Label: "Active", + }, + }, + }, + { + desc: "Maximum device", + data: []byte(`{"id": 1, "name": "Device 1", "display_name": "Device 1", "device_type": { "id": 11, "url": "http://localhost/api/dcim/device-types/11/", "manufacturer": { "id": 21, "url": "http://localhost/api/dcim/manufacturers/21/", "name": "Manufacturer Name", "slug": "mfg-name"}, "model": "Device Type Model", "slug": "device-type-model"}, "device_role": { "id": 31, "url": "http://localhost/api/dcim/device-roles/31/", "name": "Device Role Name", "slug": "device-role-name"}, "tenant": { "id": 41, "url": "http://localhost/api/tenancy/tenants/41/", "name": "Tenant Name", "slug": "tenant-name" }, "platform": { "id": 51, "url": "http://localhost/api/dcim/platforms/51", "name": "Platform Name", "slug": "platform-name" }, "serial": "Serial", "asset_tag": "Tag", "site": { "id": 61, "url": "http://localhost/api/dcim/sites/61/", "name": "Site Name", "slug": "site-name" }, "rack": { "id": 71, "url": "http://localhost/api/dcim/racks/71/", "name": "Rack Name", "display_name": "Rack Name" }, "position": 81, "face": { "value": 0, "label": "Front" }, "status": { "value": 1, "label": "Active" } }`), + want: &Device{ + ID: 1, + Name: "Device 1", + DisplayName: "Device 1", + DeviceType: &NestedDeviceType{ + ID: 11, + URL: "http://localhost/api/dcim/device-types/11/", + Manufacturer: &NestedManufacturer{ + ID: 21, + URL: "http://localhost/api/dcim/manufacturers/21/", + Name: "Manufacturer Name", + Slug: "mfg-name", + }, + Model: "Device Type Model", + Slug: "device-type-model", + }, + DeviceRole: &NestedDeviceRole{ + ID: 31, + URL: "http://localhost/api/dcim/device-roles/31/", + Name: "Device Role Name", + Slug: "device-role-name", + }, + Tenant: &NestedTenant{ + ID: 41, + URL: "http://localhost/api/tenancy/tenants/41/", + Name: "Tenant Name", + Slug: "tenant-name", + }, + Platform: &NestedPlatform{ + ID: 51, + URL: "http://localhost/api/dcim/platforms/51", + Name: "Platform Name", + Slug: "platform-name", + }, + Serial: "Serial", + AssetTag: "Tag", + Site: &NestedSite{ + ID: 61, + URL: "http://localhost/api/dcim/sites/61/", + Name: "Site Name", + Slug: "site-name", + }, + Rack: &NestedRack{ + ID: 71, + URL: "http://localhost/api/dcim/racks/71/", + Name: "Rack Name", + DisplayName: "Rack Name", + }, + Position: 81, + Face: &FaceType{ + Value: 0, + Label: "Front", + }, + Status: &StatusType{ + Value: 1, + Label: "Active", + }, + }, + }, + } + + for idx, tt := range tests { + t.Run(fmt.Sprintf("[%d] %s", idx, tt.desc), func(t *testing.T) { + result := new(Device) + err := json.Unmarshal(tt.data, result) + if err != nil { + t.Fatalf("unexpected error from Device.UnmarshalJSON: %v", err) + } + + if want, got := tt.want, result; !reflect.DeepEqual(want, got) { + t.Fatalf("unexpected Device:\n- want: %v\n- got: %v", want, got) + } + }) + } +} + +func TestDeviceMarshalJSON(t *testing.T) { + var tests = []struct { + desc string + data *Device + want []byte + }{ + { + desc: "Sample Device", + data: testDevice(1), + want: []byte(`{"id":1,"name":"Device 1","display_name":"Device 1","device_type":2001,"device_role":1001,"tenant":3001,"platform":4001,"site":5001,"rack":6001}`), + }, + } + + for idx, tt := range tests { + t.Run(fmt.Sprintf("[%d] %s", idx, tt.desc), func(t *testing.T) { + result, err := json.Marshal(tt.data) + if err != nil { + t.Fatalf("unexpected error from writableDevice.MarshalJSON: %v", err) + } + if want, got := tt.want, result; bytes.Compare(want, got) != 0 { + t.Fatalf("unexpected JSON:\n- want: %v\n- got: %v", string(want), string(got)) + } + }) + } +} + +func testNestedDeviceRole(id int) *NestedDeviceRole { + return &NestedDeviceRole{ + ID: id, + URL: fmt.Sprintf("http://localhost/api/dcim/device-roles/%d/", id), + Name: fmt.Sprintf("Device Role %d", id), + Slug: fmt.Sprintf("device-role-%d", id), + } +} + +func testNestedPlatform(id int) *NestedPlatform { + return &NestedPlatform{ + ID: id, + URL: fmt.Sprintf("http://localhost/api/dcim/platforms/%d/", id), + Name: fmt.Sprintf("Platform %d", id), + Slug: fmt.Sprintf("platform-%d", id), + } +} + +func testNestedTenant(id int) *NestedTenant { + return &NestedTenant{ + ID: id, + URL: fmt.Sprintf("http://localhost/api/tenancy/tenants/%d/", id), + Name: fmt.Sprintf("Tenant %d", id), + Slug: fmt.Sprintf("tenant-%d", id), + } +} + +func testNestedSite(id int) *NestedSite { + return &NestedSite{ + ID: id, + URL: fmt.Sprintf("http://localhost/api/dcim/sites/%d/", id), + Name: fmt.Sprintf("Site %d", id), + Slug: fmt.Sprintf("site-%d", id), + } +} + +func testNestedRack(id int) *NestedRack { + return &NestedRack{ + ID: id, + URL: fmt.Sprintf("http://localhost/api/dcim/racks/%d/", id), + Name: fmt.Sprintf("Rack %d", id), + DisplayName: fmt.Sprintf("Rack %d", id), + } +} + +func testNestedDeviceType(id int) *NestedDeviceType { + manufacturerID := id + 1000 + return &NestedDeviceType{ + ID: id, + URL: fmt.Sprintf("http://localhost/api/dcim/device-types/%d/", id), + Manufacturer: testNestedManufacturer(manufacturerID), + Model: fmt.Sprintf("Device Type Model of %d", id), + Slug: fmt.Sprintf("test-device-type-model-%d", id), + } +} + +func testDevice(id int) *Device { + roleID := id + 1000 + typeID := id + 2000 + tenantID := id + 3000 + platformID := id + 4000 + siteID := id + 5000 + rackID := id + 6000 + + return testDeviceHelper(id, false, testNestedDeviceType(typeID), testNestedDeviceRole(roleID), testNestedTenant(tenantID), testNestedPlatform(platformID), testNestedSite(siteID), testNestedRack(rackID)) +} + +func testDeviceCreate(id int) *Device { + roleID := id + 1000 + typeID := id + 2000 + tenantID := id + 3000 + platformID := id + 4000 + siteID := id + 5000 + rackID := id + 6000 + return testDeviceHelper(id, true, testNestedDeviceType(typeID), testNestedDeviceRole(roleID), testNestedTenant(tenantID), testNestedPlatform(platformID), testNestedSite(siteID), testNestedRack(rackID)) +} + +func testDeviceHelper(id int, create bool, devicetype *NestedDeviceType, role *NestedDeviceRole, tenant *NestedTenant, platform *NestedPlatform, site *NestedSite, rack *NestedRack) *Device { + deviceID := id + + if create { + deviceID = 0 + } + + return &Device{ + ID: deviceID, + Name: fmt.Sprintf("Device %d", id), + DisplayName: fmt.Sprintf("Device %d", id), + DeviceType: devicetype, + DeviceRole: role, + Tenant: tenant, + Platform: platform, + Site: site, + Rack: rack, + } +} diff --git a/netbox/dcim_devices_types.go b/netbox/dcim_devices_types.go new file mode 100644 index 0000000000000000000000000000000000000000..d130921c1ed8943f73776a47930dfddb10476190 --- /dev/null +++ b/netbox/dcim_devices_types.go @@ -0,0 +1,383 @@ +// Copyright 2017 The go-netbox Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package netbox + +import ( + "encoding/json" + "net/url" + "strconv" +) + +type nestedBase struct { + ID int `json:"id"` + URL string `json:"url"` + Name string `json:"name"` + Slug string `json:"slug"` +} + +type simpleValueLabel struct { + Value int `json:"value"` + Label string `json:"label"` +} + +// ShortDeviceBay represent the short form of a Device Bay +// when a Device Bay appears in a parent device of +// another object. +type ShortDeviceBay struct { + ID int `json:"id"` + Name string `json:"name"` +} + +// ShortDevice corresponds to the simple form of +// a Device, when a Device appears as a +// parent device of another object +type ShortDevice struct { + DeviceBay *ShortDeviceBay `json:"device_bay"` + ID int `json:"id"` + Name string `json:"name"` +} + +// FaceType represent the face of a Device in a Rack (Front/Rear) +type FaceType simpleValueLabel + +// StatusType represent status of a Device in Netbox API. +type StatusType simpleValueLabel + +// A NestedDeviceRole corresponds to the Netbox API's +// nested serializer, when a DeviceRole appears as a +// parent of another object +type NestedDeviceRole nestedBase + +// A NestedPlatform corresponds to the Netbox API's +// nested serializer, when a Platform appears as a +// parent of another object +type NestedPlatform nestedBase + +// A NestedTenant corresponds to the Netbox API's +// nested serializer, when a Tenant appears as a +// parent of another object +type NestedTenant nestedBase + +// A NestedSite corresponds to the Netbox API's +// nested serializer, when a Site appears as a +// parent of another object +type NestedSite nestedBase + +// A NestedDeviceType corresponds to the Netbox API's +// nested serializer, when a DeviceType appears as a +// parent of another object +type NestedDeviceType struct { + ID int `json:"id"` + URL string `json:"url"` + Manufacturer *NestedManufacturer `json:"manufacturer"` + Model string `json:"model"` + Slug string `json:"slug"` +} + +// A NestedRack correspondes to the Netbox API's +// nested serializer, when a Rack appears as a +// parent of another object +type NestedRack struct { + ID int `json:"id"` + URL string `json:"url"` + Name string `json:"name"` + DisplayName string `json:"display_name"` +} + +// A NestedIP will be used as nested serializer for +// IPv4, IPv6 ip address +type NestedIP struct { + ID int `json:"id"` + URL string `json:"url"` + Family int `json:"family"` + Address string `json:"address"` +} + +// A Device corresponds to the Netbox API's +// base serializer for Device +type Device struct { + ID int `json:"id,omitempty"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + DeviceType *NestedDeviceType `json:"device_type"` + DeviceRole *NestedDeviceRole `json:"device_role"` + Tenant *NestedTenant `json:"tenant,omitempty"` + Platform *NestedPlatform `json:"platform,omitempty"` + Serial string `json:"serial,omitempty"` + AssetTag string `json:"asset_tag,omitempty"` + Site *NestedSite `json:"site"` + Rack *NestedRack `json:"rack,omitempty"` + Position int `json:"position,omitempty"` + Face *FaceType `json:"face,omitempty"` + Parent *ShortDevice `json:"parent_device,omitempty"` + Status *StatusType `json:"status"` + PrimaryIP *NestedIP `json:"primary_ip,omitempty"` + PrimaryIPv4 *NestedIP `json:"primary_ip4,omitempty"` + PrimaryIPv6 *NestedIP `json:"primary_ip6,omitempty"` + Comments string `json:"comments,omitempty"` +} + +// A writableDevice corresponds to the Netbox API's +// writable serializer for a Device. It is used transparently +// when Devices are serialize into JSON. +type writableDevice struct { + ID int `json:"id,omitempty"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + DeviceType int `json:"device_type"` + DeviceRole int `json:"device_role"` + Tenant int `json:"tenant,omitempty"` + Platform int `json:"platform,omitempty"` + Serial string `json:"serial,omitempty"` + AssetTag string `json:"asset_tag,omitempty"` + Site int `json:"site"` + Rack int `json:"rack,omitempty"` + Position int `json:"position,omitempty"` + Face string `json:"face,omitempty"` + Parent int `json:"parent_device,omitempty"` + Status string `json:"status,omitempty"` + PrimaryIP int `json:"primary_ip,omitempty"` + PrimaryIPv4 int `json:"primary_ip4,omitempty"` + PrimaryIPv6 int `json:"primary_ip6,omitempty"` + Comments string `json:"comments,omitempty"` +} + +// MarshalJSON marshals a Device into JSON bytes, +// and is used by the standard json package. +func (d *Device) MarshalJSON() ([]byte, error) { + var typeID, roleID, tenantID, platformID, siteID, rackID, parentID, primaryIPID, primaryIPv4ID, primaryIPv6ID int + var status, face string + + if d.DeviceType != nil { + typeID = d.DeviceType.ID + } + + if d.DeviceRole != nil { + roleID = d.DeviceRole.ID + } + + if d.Tenant != nil { + tenantID = d.Tenant.ID + } + + if d.Platform != nil { + platformID = d.Platform.ID + } + + if d.Site != nil { + siteID = d.Site.ID + } + + if d.Rack != nil { + rackID = d.Rack.ID + } + + if d.PrimaryIP != nil { + primaryIPID = d.PrimaryIP.ID + } + + if d.PrimaryIPv4 != nil { + primaryIPv4ID = d.PrimaryIPv4.ID + } + + if d.PrimaryIPv6 != nil { + primaryIPv6ID = d.PrimaryIPv6.ID + } + + if d.Face != nil { + face = d.Face.Label + } + + if d.Parent != nil { + parentID = d.Parent.ID + } + + if d.Status != nil { + status = d.Status.Label + } + + return json.Marshal(writableDevice{ + ID: d.ID, + Name: d.Name, + DisplayName: d.DisplayName, + DeviceType: typeID, + DeviceRole: roleID, + Tenant: tenantID, + Platform: platformID, + Serial: d.Serial, + AssetTag: d.AssetTag, + Site: siteID, + Rack: rackID, + Position: d.Position, + Face: face, + Parent: parentID, + Status: status, + PrimaryIP: primaryIPID, + PrimaryIPv4: primaryIPv4ID, + PrimaryIPv6: primaryIPv6ID, + Comments: d.Comments, + }) + +} + +// ListDeviceOptions is used as an argument for Client.DCIM.Devices.List. +// Integer fileds with an *ID suffix are preferred over their string +// counterparts, and if both are set, only the *ID filed will be used. +type ListDeviceOptions struct { + Name string + Serial string + AssetTag string + MacAddress string + IDIn int + SiteID int + Site string + RackGroupID int + RackID int + RoleID int + Role string + TenantID int + Tenant string + DeviceTypeID int + ManufacturerID int + Manufacturer string + DeviceModel string + PlatformID int + Platform string + Status string + IsConsoleServer *bool + IsPDU *bool + IsNetworkDevice *bool + HasPrimaryIP *bool + + Query string +} + +// Values generates a url.Values map from the data in ListDeviceOptions. +func (o *ListDeviceOptions) Values() (url.Values, error) { + if o == nil { + return nil, nil + } + + v := url.Values{} + + status := map[string]int{ + "Offline": 0, + "Active": 1, + "Planned": 2, + "Staged": 3, + "Failed": 4, + "Inventory": 5, + } + + switch { + case o.SiteID != 0: + v.Set("site_id", strconv.Itoa(o.SiteID)) + case o.Site != "": + v.Set("site", o.Site) + } + + switch { + case o.TenantID != 0: + v.Set("tenant_id", strconv.Itoa(o.TenantID)) + case o.Tenant != "": + v.Set("tenant", o.Tenant) + } + + switch { + case o.ManufacturerID != 0: + v.Set("manufacturer_id", strconv.Itoa(o.ManufacturerID)) + case o.Manufacturer != "": + v.Set("manufacturer", o.Manufacturer) + } + + switch { + case o.PlatformID != 0: + v.Set("platform_id", strconv.Itoa(o.PlatformID)) + case o.Platform != "": + v.Set("platform", o.Platform) + } + + switch { + case o.RoleID != 0: + v.Set("role_id", strconv.Itoa(o.RoleID)) + case o.Role != "": + v.Set("role", o.Role) + } + + if o.Name != "" { + v.Set("name", o.Name) + } + + if o.Serial != "" { + v.Set("serial", o.Serial) + } + + if o.AssetTag != "" { + v.Set("asset_tag", o.AssetTag) + } + + if o.MacAddress != "" { + v.Set("mac_address", o.MacAddress) + } + + if o.IDIn != 0 { + v.Set("id__in", strconv.Itoa(o.IDIn)) + } + + if o.RackGroupID != 0 { + v.Set("rack_group_id", strconv.Itoa(o.RackGroupID)) + } + + if o.RackID != 0 { + v.Set("rack_id", strconv.Itoa(o.RackID)) + } + + if o.DeviceTypeID != 0 { + v.Set("device_type_id", strconv.Itoa(o.DeviceTypeID)) + } + + if o.DeviceModel != "" { + v.Set("model", o.DeviceModel) + } + + if o.Status != "" { + v.Set("status", strconv.Itoa(status[o.Status])) + } + + if o.IsConsoleServer != nil { + v.Set("is_console_server", strconv.FormatBool(*o.IsConsoleServer)) + } + + if o.IsPDU != nil { + v.Set("is_pdu", strconv.FormatBool(*o.IsPDU)) + } + + if o.IsNetworkDevice != nil { + v.Set("is_network_device", strconv.FormatBool(*o.IsNetworkDevice)) + } + + if o.HasPrimaryIP != nil { + v.Set("has_primary_ip", strconv.FormatBool(*o.HasPrimaryIP)) + } + + if o.Query != "" { + v.Set("q", o.Query) + } + + return v, nil +} + +//go:generate go run generate_functions.go -endpoint dcim -service devices -service-name DevicesService -type-name Device -update-type-name writableDevice +//go:generate go run generate_basic_tests.go -client-endpoint DCIM -client-service Devices -endpoint dcim -service devices -service-name DevicesService -type-name Device