Skip to content

Commit 164ca11

Browse files
committed
fix: adding rackGroup go files
1 parent 4942b94 commit 164ca11

3 files changed

Lines changed: 291 additions & 0 deletions

File tree

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package dcim
2+
3+
import (
4+
"context"
5+
6+
"github.com/rackerlabs/understack/go/nautobotop/internal/nautobot/client"
7+
8+
"github.com/charmbracelet/log"
9+
nb "github.com/nautobot/go-nautobot/v3"
10+
"github.com/rackerlabs/understack/go/nautobotop/internal/nautobot/helpers"
11+
)
12+
13+
type RackGroupService struct {
14+
client *client.NautobotClient
15+
}
16+
17+
func NewRackGroupService(nautobotClient *client.NautobotClient) *RackGroupService {
18+
return &RackGroupService{
19+
client: nautobotClient,
20+
}
21+
}
22+
23+
func (s *RackGroupService) Create(ctx context.Context, req nb.RackGroupRequest) (*nb.RackGroup, error) {
24+
rackGroup, resp, err := s.client.APIClient.DcimAPI.DcimRackGroupsCreate(ctx).RackGroupRequest(req).Execute()
25+
if err != nil {
26+
bodyString := helpers.ReadResponseBody(resp)
27+
s.client.AddReport("createNewRackGroup", "failed to create", "model", req.Name, "error", err.Error(), "response_body", bodyString)
28+
return nil, err
29+
}
30+
log.Info("CreateRackGroup", "created rack group", rackGroup.Name)
31+
return rackGroup, nil
32+
}
33+
34+
func (s *RackGroupService) GetByName(ctx context.Context, name string) nb.RackGroup {
35+
list, resp, err := s.client.APIClient.DcimAPI.DcimRackGroupsList(ctx).Depth(2).Name([]string{name}).Execute()
36+
if err != nil {
37+
bodyString := helpers.ReadResponseBody(resp)
38+
s.client.AddReport("GetRackGroupByName", "failed to get", "name", name, "error", err.Error(), "response_body", bodyString)
39+
return nb.RackGroup{}
40+
}
41+
if list == nil || len(list.Results) == 0 {
42+
return nb.RackGroup{}
43+
}
44+
if list.Results[0].Id == nil {
45+
return nb.RackGroup{}
46+
}
47+
return list.Results[0]
48+
}
49+
50+
func (s *RackGroupService) ListAll(ctx context.Context) []nb.RackGroup {
51+
ids := s.client.GetChangeObjectIDS(ctx, "dcim.rackgroup")
52+
list, resp, err := s.client.APIClient.DcimAPI.DcimRackGroupsList(ctx).Id(ids).Depth(2).Execute()
53+
if err != nil {
54+
bodyString := helpers.ReadResponseBody(resp)
55+
s.client.AddReport("ListAllRackGroups", "failed to list", "error", err.Error(), "response_body", bodyString)
56+
return []nb.RackGroup{}
57+
}
58+
if list == nil || len(list.Results) == 0 {
59+
return []nb.RackGroup{}
60+
}
61+
if list.Results[0].Id == nil {
62+
return []nb.RackGroup{}
63+
}
64+
65+
return list.Results
66+
}
67+
68+
func (s *RackGroupService) Update(ctx context.Context, id string, req nb.RackGroupRequest) (*nb.RackGroup, error) {
69+
rackGroup, resp, err := s.client.APIClient.DcimAPI.DcimRackGroupsUpdate(ctx, id).RackGroupRequest(req).Execute()
70+
if err != nil {
71+
bodyString := helpers.ReadResponseBody(resp)
72+
s.client.AddReport("UpdateRackGroup", "failed to update UpdateRackGroup", "id", id, "model", req.Name, "error", err.Error(), "response_body", bodyString)
73+
return nil, err
74+
}
75+
log.Info("successfully updated rack group", "id", id, "model", rackGroup.GetName())
76+
return rackGroup, nil
77+
}
78+
79+
func (s *RackGroupService) Destroy(ctx context.Context, id string) error {
80+
resp, err := s.client.APIClient.DcimAPI.DcimRackGroupsDestroy(ctx, id).Execute()
81+
if err != nil {
82+
bodyString := helpers.ReadResponseBody(resp)
83+
s.client.AddReport("DestroyRackGroup", "failed to destroy", "id", id, "error", err.Error(), "response_body", bodyString)
84+
return err
85+
}
86+
return nil
87+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package models
2+
3+
type RackGroups struct {
4+
RackGroup []RackGroup
5+
}
6+
7+
type RackGroup struct {
8+
Name string `json:"name" yaml:"name"`
9+
Description string `json:"description" yaml:"description"`
10+
Location string `json:"location" yaml:"location"`
11+
Children []RackGroup `json:"children" yaml:"children"`
12+
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package sync
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/charmbracelet/log"
8+
nb "github.com/nautobot/go-nautobot/v3"
9+
"github.com/rackerlabs/understack/go/nautobotop/internal/nautobot/dcim"
10+
"github.com/rackerlabs/understack/go/nautobotop/internal/nautobot/helpers"
11+
"github.com/rackerlabs/understack/go/nautobotop/internal/nautobot/models"
12+
"github.com/samber/lo"
13+
14+
"github.com/rackerlabs/understack/go/nautobotop/internal/nautobot/client"
15+
"go.yaml.in/yaml/v3"
16+
)
17+
18+
type RackGroupSync struct {
19+
client *client.NautobotClient
20+
rackGroupSvc *dcim.RackGroupService
21+
locationSvc *dcim.LocationService
22+
}
23+
24+
func NewRackGroupSync(nautobotClient *client.NautobotClient) *RackGroupSync {
25+
return &RackGroupSync{
26+
client: nautobotClient.GetClient(),
27+
rackGroupSvc: dcim.NewRackGroupService(nautobotClient),
28+
locationSvc: dcim.NewLocationService(nautobotClient.GetClient()),
29+
}
30+
}
31+
32+
func (s *RackGroupSync) SyncAll(ctx context.Context, data map[string]string) error {
33+
var rackGroups models.RackGroups
34+
for key, f := range data {
35+
var yml []models.RackGroup
36+
if err := yaml.Unmarshal([]byte(f), &yml); err != nil {
37+
s.client.AddReport("yamlFailed", "file: "+key+" error: "+err.Error())
38+
return err
39+
}
40+
rackGroups.RackGroup = append(rackGroups.RackGroup, yml...)
41+
}
42+
43+
for _, l := range rackGroups.RackGroup {
44+
if err := s.syncLocationRecursive(ctx, l, nil); err != nil {
45+
return err
46+
}
47+
}
48+
s.deleteObsoleteRackGroup(ctx, rackGroups)
49+
50+
return nil
51+
}
52+
53+
// syncLocationRecursive processes a location and all its children recursively
54+
func (s *RackGroupSync) syncLocationRecursive(ctx context.Context, rackGroup models.RackGroup, parentID *string) error {
55+
currentID, err := s.syncSingleRackGroup(ctx, rackGroup, parentID)
56+
if err != nil {
57+
return err
58+
}
59+
60+
for _, child := range rackGroup.Children {
61+
if err := s.syncLocationRecursive(ctx, child, currentID); err != nil {
62+
return err
63+
}
64+
}
65+
66+
return nil
67+
}
68+
69+
// syncSingleRackGroup handles the create/update logic for a single location
70+
func (s *RackGroupSync) syncSingleRackGroup(ctx context.Context, rackGroup models.RackGroup, parentID *string) (*string, error) {
71+
existingRackGroup := s.rackGroupSvc.GetByName(ctx, rackGroup.Name)
72+
73+
rackGroupRequest := nb.RackGroupRequest{
74+
Name: rackGroup.Name,
75+
Description: nb.PtrString(rackGroup.Description),
76+
Parent: buildParentReference(parentID),
77+
Location: s.buildLocationReference(ctx, rackGroup.Location),
78+
}
79+
80+
if existingRackGroup.Id == nil {
81+
return s.createRackGroup(ctx, rackGroupRequest)
82+
}
83+
84+
if !helpers.CompareJSONFields(existingRackGroup, rackGroupRequest) {
85+
return s.updateRackGroup(ctx, *existingRackGroup.Id, rackGroupRequest)
86+
}
87+
88+
log.Info("location unchanged, skipping update", "name", rackGroupRequest.Name)
89+
return rackGroupRequest.Id, nil
90+
}
91+
92+
// createRackGroup creates a new location in Nautobot
93+
func (s *RackGroupSync) createRackGroup(ctx context.Context, request nb.RackGroupRequest) (*string, error) {
94+
createdRackGroup, err := s.rackGroupSvc.Create(ctx, request)
95+
if err != nil || createdRackGroup == nil {
96+
return nil, fmt.Errorf("failed to create rackgroup %s: %w", request.Name, err)
97+
}
98+
log.Info("location created", "name", request.Name)
99+
return createdRackGroup.Id, nil
100+
}
101+
102+
// updateRackGroup updates an existing location in Nautobot
103+
func (s *RackGroupSync) updateRackGroup(ctx context.Context, id string, request nb.RackGroupRequest) (*string, error) {
104+
updatedRackGroup, err := s.rackGroupSvc.Update(ctx, id, request)
105+
if err != nil || updatedRackGroup == nil {
106+
return nil, fmt.Errorf("failed to update location %s: %w", request.Name, err)
107+
}
108+
log.Info("location updated", "name", request.Name)
109+
return updatedRackGroup.Id, nil
110+
}
111+
112+
// deleteObsoleteRackGroup removes location that are not defined in YAML
113+
func (s *RackGroupSync) deleteObsoleteRackGroup(ctx context.Context, rackGroups models.RackGroups) {
114+
desiredRackGroups := make(map[string]models.RackGroup)
115+
for _, rackGroup := range rackGroups.RackGroup {
116+
s.collectAllRackGroups(rackGroup, desiredRackGroups)
117+
}
118+
119+
existingRackGroup := s.rackGroupSvc.ListAll(ctx)
120+
existingMap := make(map[string]nb.RackGroup, len(existingRackGroup))
121+
for _, rackGroup := range existingRackGroup {
122+
existingMap[rackGroup.Name] = rackGroup
123+
}
124+
125+
obsoleteRackGroup := lo.OmitByKeys(existingMap, lo.Keys(desiredRackGroups))
126+
s.deleteLocationWithDependencies(ctx, obsoleteRackGroup)
127+
}
128+
129+
// collectAllRackGroups recursively collects all location including nested children
130+
func (s *RackGroupSync) collectAllRackGroups(rackGroup models.RackGroup, result map[string]models.RackGroup) {
131+
result[rackGroup.Name] = rackGroup
132+
for _, child := range rackGroup.Children {
133+
s.collectAllRackGroups(child, result)
134+
}
135+
}
136+
137+
// deleteLocationWithDependencies deletes location in correct order
138+
func (s *RackGroupSync) deleteLocationWithDependencies(ctx context.Context, obsoleteRackGroup map[string]nb.RackGroup) {
139+
idToName := make(map[string]string)
140+
for name, rackGroup := range obsoleteRackGroup {
141+
if rackGroup.Id != nil {
142+
idToName[*rackGroup.Id] = name
143+
}
144+
}
145+
146+
childrenMap := make(map[string][]string)
147+
for name, rackGroup := range obsoleteRackGroup {
148+
parentID := s.getParentID(rackGroup)
149+
if parentID != "" {
150+
if parentName, exists := idToName[parentID]; exists {
151+
childrenMap[parentName] = append(childrenMap[parentName], name)
152+
}
153+
}
154+
}
155+
156+
deleted := make(map[string]bool)
157+
for name := range obsoleteRackGroup {
158+
s.deleteRackGroupRecursive(ctx, name, obsoleteRackGroup, childrenMap, deleted)
159+
}
160+
}
161+
162+
// deleteRackGroupRecursive deletes a location and all its children recursively
163+
func (s *RackGroupSync) deleteRackGroupRecursive(ctx context.Context, name string, obsoleteRackGroup map[string]nb.RackGroup, childrenMap map[string][]string, deleted map[string]bool) {
164+
if deleted[name] {
165+
return
166+
}
167+
if children, hasChildren := childrenMap[name]; hasChildren {
168+
for _, childName := range children {
169+
s.deleteRackGroupRecursive(ctx, childName, obsoleteRackGroup, childrenMap, deleted)
170+
}
171+
}
172+
if rackGroup, exists := obsoleteRackGroup[name]; exists && rackGroup.Id != nil {
173+
_ = s.rackGroupSvc.Destroy(ctx, *rackGroup.Id)
174+
deleted[name] = true
175+
}
176+
}
177+
178+
// getParentID extracts the parent ID from a RackGroup
179+
func (s *RackGroupSync) getParentID(rackGroup nb.RackGroup) string {
180+
if rackGroup.Parent.IsSet() {
181+
parent := rackGroup.Parent.Get()
182+
if parent != nil && parent.Id != nil && parent.Id.String != nil {
183+
return *parent.Id.String
184+
}
185+
}
186+
return ""
187+
}
188+
189+
func (s *RackGroupSync) buildLocationReference(ctx context.Context, name string) nb.ApprovalWorkflowStageResponseApprovalWorkflowStage {
190+
rackGroup := s.locationSvc.GetByName(ctx, name)
191+
return helpers.BuildApprovalWorkflowStageResponseApprovalWorkflowStage(*rackGroup.Id)
192+
}

0 commit comments

Comments
 (0)