Skip to content

Commit b3440bc

Browse files
committed
add: deviceTypes - manufacturers and powerPortTemplate
1 parent 290981d commit b3440bc

File tree

3 files changed

+433
-0
lines changed

3 files changed

+433
-0
lines changed
Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
package nautobot
2+
3+
import (
4+
"context"
5+
6+
"github.com/charmbracelet/log"
7+
"github.com/samber/lo"
8+
9+
nb "github.com/nautobot/go-nautobot/v2"
10+
"go.yaml.in/yaml/v3"
11+
)
12+
13+
type DeviceTypes struct {
14+
DeviceTypes []DeviceType
15+
}
16+
type DeviceType struct {
17+
Manufacturer string `yaml:"manufacturer"`
18+
PartNumber string `yaml:"part_number"`
19+
Model string `yaml:"model"`
20+
UHeight int `yaml:"u_height"`
21+
IsFullDepth bool `yaml:"is_full_depth"`
22+
Comments string `yaml:"comments"`
23+
ConsolePorts []ConsolePort `yaml:"console-ports"`
24+
PowerPorts []PowerPort `yaml:"power-ports"`
25+
Interfaces []Interface `yaml:"interfaces"`
26+
ModuleBays []ModuleBay `yaml:"module-bays"`
27+
Class string `yaml:"class"`
28+
ResourceClass []ResourceClass `yaml:"resource_class"`
29+
}
30+
31+
type ConsolePort struct {
32+
Name string `yaml:"name"`
33+
Type string `yaml:"type"`
34+
}
35+
36+
type PowerPort struct {
37+
Name string `yaml:"name"`
38+
Type string `yaml:"type"`
39+
MaximumDraw int `yaml:"maximum_draw"`
40+
AllocatedDraw int `yaml:"allocated_draw"`
41+
}
42+
43+
type Interface struct {
44+
Name string `yaml:"name"`
45+
Type string `yaml:"type"`
46+
MgmtOnly bool `yaml:"mgmt_only"`
47+
}
48+
49+
type ModuleBay struct {
50+
Name string `yaml:"name"`
51+
Position string `yaml:"position"`
52+
Label string `yaml:"label,omitempty"`
53+
}
54+
55+
type ResourceClass struct {
56+
Name string `yaml:"name"`
57+
CPU CPU `yaml:"cpu"`
58+
Memory Memory `yaml:"memory"`
59+
Drives []Disk `yaml:"drives"`
60+
NicCount int `yaml:"nic_count"`
61+
}
62+
63+
type CPU struct {
64+
Cores int `yaml:"cores"`
65+
Model string `yaml:"model"`
66+
}
67+
68+
type Memory struct {
69+
Size int `yaml:"size"`
70+
}
71+
72+
type Disk struct {
73+
Size int `yaml:"size"`
74+
}
75+
76+
func (n *NautobotClient) SyncAllDeviceTypes(ctx context.Context, data map[string]string) error {
77+
var deviceTypes DeviceTypes
78+
for _, f := range data {
79+
var yml DeviceType
80+
81+
if err := yaml.Unmarshal([]byte(f), &yml); err != nil {
82+
n.AddReport("yamlFailed", err.Error())
83+
return err
84+
}
85+
deviceTypes.DeviceTypes = append(deviceTypes.DeviceTypes, yml)
86+
87+
manufacturer := n.GetManufacturerByName(context.Background(), yml.Manufacturer)
88+
if manufacturer.Id == "" {
89+
cm, _ := n.CreateNewManufacturer(context.Background(), nb.ManufacturerRequest{
90+
Name: yml.Manufacturer,
91+
Description: nb.PtrString(yml.Manufacturer),
92+
})
93+
manufacturer = *cm
94+
}
95+
96+
deviceType := n.GetDeviceTypeByName(context.Background(), yml.Model)
97+
if deviceType.Id == "" {
98+
dt, _ := n.CreateNewDeviceType(context.Background(), nb.WritableDeviceTypeRequest{
99+
Model: yml.Model,
100+
PartNumber: nb.PtrString(yml.PartNumber),
101+
UHeight: nb.PtrInt32(int32(yml.UHeight)),
102+
IsFullDepth: nb.PtrBool(yml.IsFullDepth),
103+
Comments: nb.PtrString(yml.Comments),
104+
Manufacturer: *buildBulkWritableCableRequestStatus(manufacturer.Id),
105+
})
106+
deviceType = *dt
107+
}
108+
109+
n.syncDeviceTypeInterfaceTemplate(ctx, yml, deviceType)
110+
n.syncDeviceTypeConsolePortTemplate(ctx, yml, deviceType)
111+
n.syncDeviceTypePowerPortTemplate(ctx, yml, deviceType)
112+
n.syncDeviceTypeModuleBayTemplate(ctx, yml, deviceType)
113+
}
114+
115+
desiredDeviceTypes := make(map[string]DeviceType)
116+
for _, deviceType := range deviceTypes.DeviceTypes {
117+
desiredDeviceTypes[deviceType.Model] = deviceType
118+
}
119+
existingDeviceTypes := n.ListAllDeviceTypes(ctx)
120+
existingMap := make(map[string]nb.DeviceType, len(existingDeviceTypes))
121+
for _, template := range existingDeviceTypes {
122+
existingMap[template.Model] = template
123+
}
124+
obsoleteDeviceTypes := lo.OmitByKeys(existingMap, lo.Keys(desiredDeviceTypes))
125+
for _, obsoleteDeviceType := range obsoleteDeviceTypes {
126+
n.DestroyDeviceType(ctx, obsoleteDeviceType.Id)
127+
}
128+
129+
return nil
130+
}
131+
132+
func (n *NautobotClient) syncDeviceTypePowerPortTemplate(ctx context.Context, yml DeviceType, deviceType nb.DeviceType) {
133+
// Build map of desired power ports from YAML configuration
134+
desiredPorts := make(map[string]PowerPort)
135+
for _, port := range yml.PowerPorts {
136+
desiredPorts[port.Name] = port
137+
}
138+
139+
// Build map of existing power port templates from Nautobot
140+
existingTemplates := n.ListAllPowerPortTemplate(ctx, deviceType.Id)
141+
existingMap := make(map[string]nb.PowerPortTemplate)
142+
for _, template := range existingTemplates {
143+
existingMap[template.Name] = template
144+
}
145+
146+
// Process each desired power port: create new or update existing
147+
for portName, desiredPort := range desiredPorts {
148+
powerPortTypeChoice, _ := nb.NewPowerPortTypeChoicesFromValue(desiredPort.Type)
149+
150+
var maximumDraw nb.NullableInt32
151+
if desiredPort.MaximumDraw > 0 {
152+
maximumDraw = *nb.NewNullableInt32(nb.PtrInt32(int32(desiredPort.MaximumDraw)))
153+
}
154+
155+
var allocatedDraw nb.NullableInt32
156+
if desiredPort.AllocatedDraw > 0 {
157+
allocatedDraw = *nb.NewNullableInt32(nb.PtrInt32(int32(desiredPort.AllocatedDraw)))
158+
}
159+
160+
templateRequest := nb.WritablePowerPortTemplateRequest{
161+
Name: desiredPort.Name,
162+
Type: &nb.PatchedWritablePowerPortTemplateRequestType{
163+
PowerPortTypeChoices: powerPortTypeChoice,
164+
},
165+
MaximumDraw: maximumDraw,
166+
AllocatedDraw: allocatedDraw,
167+
DeviceType: buildNullableBulkWritableCircuitRequestTenant(deviceType.Id),
168+
}
169+
170+
if existingTemplate, exists := existingMap[portName]; exists {
171+
n.UpdatePowerPortTemplate(ctx, existingTemplate.Id, templateRequest)
172+
} else {
173+
n.CreateNewPowerPortTemplate(ctx, templateRequest)
174+
}
175+
}
176+
177+
obsoleteTemplates := lo.OmitByKeys(existingMap, lo.Keys(desiredPorts))
178+
for _, obsoleteTemplate := range obsoleteTemplates {
179+
n.DestroyPowerPortTemplate(ctx, obsoleteTemplate.Id)
180+
}
181+
}
182+
183+
func (n *NautobotClient) syncDeviceTypeConsolePortTemplate(ctx context.Context, yml DeviceType, deviceType nb.DeviceType) {
184+
// Build map of desired console ports from YAML configuration
185+
desiredPorts := make(map[string]ConsolePort)
186+
for _, port := range yml.ConsolePorts {
187+
desiredPorts[port.Name] = port
188+
}
189+
190+
existingTemplates := n.ListAllConsolePortTemplateByDeviceType(ctx, deviceType.Id)
191+
existingMap := make(map[string]nb.ConsolePortTemplate)
192+
for _, template := range existingTemplates {
193+
existingMap[template.Name] = template
194+
}
195+
196+
for portName, desiredPort := range desiredPorts {
197+
consolePortTypeChoice, _ := nb.NewConsolePortTypeChoicesFromValue(desiredPort.Type)
198+
templateRequest := nb.WritableConsolePortTemplateRequest{
199+
Name: portName,
200+
Type: &nb.PatchedWritableConsolePortTemplateRequestType{
201+
ConsolePortTypeChoices: consolePortTypeChoice,
202+
},
203+
DeviceType: buildNullableBulkWritableCircuitRequestTenant(deviceType.Id),
204+
}
205+
if existingTemplate, exists := existingMap[portName]; exists {
206+
n.UpdateConsolePortTemplate(ctx, existingTemplate.Id, templateRequest)
207+
} else {
208+
n.CreateNewConsolePortTemplate(ctx, templateRequest)
209+
}
210+
}
211+
obsoleteTemplates := lo.OmitByKeys(existingMap, lo.Keys(desiredPorts))
212+
for _, obsoleteTemplate := range obsoleteTemplates {
213+
n.DestroyConsolePortTemplate(ctx, obsoleteTemplate.Id)
214+
}
215+
}
216+
217+
func (n *NautobotClient) syncDeviceTypeInterfaceTemplate(ctx context.Context, yml DeviceType, deviceType nb.DeviceType) {
218+
// Build map of desired console ports from YAML configuration
219+
desiredInterfaceTemplate := make(map[string]Interface)
220+
for _, interfaceTmpl := range yml.Interfaces {
221+
desiredInterfaceTemplate[interfaceTmpl.Name] = interfaceTmpl
222+
}
223+
224+
existingTemplates := n.ListAllInterfaceTemplateByDeviceType(ctx, deviceType.Id)
225+
existingMap := make(map[string]nb.InterfaceTemplate)
226+
for _, template := range existingTemplates {
227+
existingMap[template.Display] = template
228+
}
229+
for portName, intefaceTmpl := range desiredInterfaceTemplate {
230+
interfaceTemplateChoice, _ := nb.NewInterfaceTypeChoicesFromValue(intefaceTmpl.Type)
231+
232+
templateRequest := nb.WritableInterfaceTemplateRequest{
233+
Name: portName,
234+
Type: *interfaceTemplateChoice,
235+
MgmtOnly: nb.PtrBool(intefaceTmpl.MgmtOnly),
236+
DeviceType: buildNullableBulkWritableCircuitRequestTenant(deviceType.Id),
237+
}
238+
if existingTemplate, exists := existingMap[portName]; exists {
239+
n.UpdateInterfaceTemplate(ctx, existingTemplate.Id, templateRequest)
240+
} else {
241+
n.CreateNewInterfaceTemplate(ctx, templateRequest)
242+
}
243+
}
244+
obsoleteTemplates := lo.OmitByKeys(existingMap, lo.Keys(desiredInterfaceTemplate))
245+
for _, obsoleteTemplate := range obsoleteTemplates {
246+
n.DestroyInterfaceTemplate(ctx, obsoleteTemplate.Id)
247+
}
248+
}
249+
250+
func (n *NautobotClient) syncDeviceTypeModuleBayTemplate(ctx context.Context, yml DeviceType, deviceType nb.DeviceType) {
251+
// Build map of desired power ports from YAML configuration
252+
desiredModuleBays := make(map[string]ModuleBay)
253+
for _, moduleBay := range yml.ModuleBays {
254+
desiredModuleBays[moduleBay.Name] = moduleBay
255+
}
256+
257+
// Build map of existing power port templates from Nautobot
258+
existingTemplates := n.ListAllModuleBayTemplateByDeviceType(ctx, deviceType.Id)
259+
existingMap := make(map[string]nb.ModuleBayTemplate)
260+
for _, template := range existingTemplates {
261+
existingMap[template.Name] = template
262+
}
263+
264+
// Process each desired power port: create new or update existing
265+
for moduleBayName, moduleBay := range desiredModuleBays {
266+
templateRequest := nb.ModuleBayTemplateRequest{
267+
Name: moduleBay.Name,
268+
Label: nb.PtrString(moduleBay.Label),
269+
DeviceType: buildNullableBulkWritableCircuitRequestTenant(deviceType.Id),
270+
}
271+
272+
if existingTemplate, exists := existingMap[moduleBayName]; exists {
273+
n.UpdateModuleBayTemplate(ctx, existingTemplate.Id, templateRequest)
274+
} else {
275+
n.CreateNewModuleBayTemplate(ctx, templateRequest)
276+
}
277+
}
278+
279+
obsoleteTemplates := lo.OmitByKeys(existingMap, lo.Keys(desiredModuleBays))
280+
for _, obsoleteTemplate := range obsoleteTemplates {
281+
n.DestroyModuleBayTemplate(ctx, obsoleteTemplate.Id)
282+
}
283+
}
284+
285+
func (n *NautobotClient) CreateNewDeviceType(ctx context.Context, req nb.WritableDeviceTypeRequest) (*nb.DeviceType, error) {
286+
deviceType, resp, err := n.Client.DcimAPI.DcimDeviceTypesCreate(ctx).WritableDeviceTypeRequest(req).Execute()
287+
if err != nil {
288+
bodyString := readResponseBody(resp)
289+
n.AddReport("createNewDeviceType", "failed to create", "model", req.Model, "error", err.Error(), "response_body", bodyString)
290+
return nil, err
291+
}
292+
log.Printf("Created manufacture: %s", deviceType.Display)
293+
return deviceType, nil
294+
}
295+
296+
func (n *NautobotClient) GetDeviceTypeByName(ctx context.Context, name string) nb.DeviceType {
297+
list, resp, err := n.Client.DcimAPI.DcimDeviceTypesList(ctx).Model([]string{name}).Execute()
298+
if err != nil {
299+
bodyString := readResponseBody(resp)
300+
n.AddReport("GetDeviceTypeByName", "failed to get", "name", name, "error", err.Error(), "response_body", bodyString)
301+
return nb.DeviceType{}
302+
}
303+
if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" {
304+
return nb.DeviceType{}
305+
}
306+
return list.Results[0]
307+
}
308+
309+
func (n *NautobotClient) ListAllDeviceTypes(ctx context.Context) []nb.DeviceType {
310+
list, resp, err := n.Client.DcimAPI.DcimDeviceTypesList(ctx).Depth(10).Execute()
311+
if err != nil {
312+
bodyString := readResponseBody(resp)
313+
n.AddReport("ListAllDeviceTypes", "failed to list", "error", err.Error(), "response_body", bodyString)
314+
return []nb.DeviceType{}
315+
}
316+
if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" {
317+
return []nb.DeviceType{}
318+
}
319+
return list.Results
320+
}
321+
322+
func (n *NautobotClient) DestroyDeviceType(ctx context.Context, id string) error {
323+
resp, err := n.Client.DcimAPI.DcimDeviceTypesDestroy(ctx, id).Execute()
324+
if err != nil {
325+
bodyString := readResponseBody(resp)
326+
n.AddReport("DestroyDeviceType", "failed to destroy", "id", id, "error", err.Error(), "response_body", bodyString)
327+
return err
328+
}
329+
return nil
330+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package nautobot
2+
3+
import (
4+
"context"
5+
"log"
6+
7+
nb "github.com/nautobot/go-nautobot/v2"
8+
)
9+
10+
func (n *NautobotClient) GetManufacturerByName(ctx context.Context, name string) nb.Manufacturer {
11+
list, resp, err := n.Client.DcimAPI.DcimManufacturersList(ctx).Limit(10000).Depth(10).Name([]string{name}).Execute()
12+
if err != nil {
13+
bodyString := readResponseBody(resp)
14+
n.AddReport("GetManufacturerByName", "failed to get manufacturer by name", "name", name, "error", err.Error(), "response_body", bodyString)
15+
return nb.Manufacturer{}
16+
}
17+
if list == nil || len(list.Results) == 0 || list.Results[0].Id == "" {
18+
return nb.Manufacturer{}
19+
}
20+
return list.Results[0]
21+
}
22+
23+
func (n *NautobotClient) CreateNewManufacturer(ctx context.Context, req nb.ManufacturerRequest) (*nb.Manufacturer, error) {
24+
manufacture, resp, err := n.Client.DcimAPI.DcimManufacturersCreate(ctx).ManufacturerRequest(req).Execute()
25+
if err != nil {
26+
bodyString := readResponseBody(resp)
27+
n.AddReport("CreateNewManufacturer", "failed to create manufacturer", "name", req.Name, "error", err.Error(), "response_body", bodyString)
28+
return nil, err
29+
}
30+
log.Printf("Created manufacture: %s", manufacture.Display)
31+
return manufacture, nil
32+
}

0 commit comments

Comments
 (0)