Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 2 additions & 73 deletions go/nautobotop/internal/nautobot/client/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package client
import (
"context"
"io"
"net/http"

"k8s.io/apimachinery/pkg/util/json"

Expand All @@ -15,12 +14,8 @@ type GraphQL struct {
}

type ObjectChanges struct {
ID string `json:"id"`
UserName string `json:"user_name"`
Action string `json:"action"`
RequestID string `json:"request_id"`
ChangedObjectID string `json:"changed_object_id"`
RelatedObjectID string `json:"related_object_id"`
UserName string `json:"user_name"`
Action string `json:"action"`
}

type GraphQLData struct {
Expand All @@ -37,10 +32,8 @@ func (n *NautobotClient) IsCreatedByUser(ctx context.Context, objectID string) (
user_name: $userName
action__ic: ["CREATE"]
) {
id
user_name
action
changed_object_id
}
}`,
Variables: map[string]any{
Expand Down Expand Up @@ -74,67 +67,3 @@ func (n *NautobotClient) IsCreatedByUser(ctx context.Context, objectID string) (
}
return false, nil
}

func (n *NautobotClient) GetCreateChangeList(ctx context.Context, objectType string) ([]ObjectChanges, *http.Response, error) {
var allObjectChanges []ObjectChanges
var lastResp *http.Response
offset := 0
limit := 100

for {
req := nb.GraphQLAPIRequest{
Query: `
query GetObjectChanges($changedObjectType: String, $userName: [String], $action: [String], $limit: Int, $offset: Int) {
object_changes(
changed_object_type: $changedObjectType
user_name: $userName
action: $action
limit: $limit
offset: $offset
) {
id
user_name
action
request_id
changed_object_id
related_object_id
}
}`,
Variables: map[string]any{
"changedObjectType": objectType,
"limit": limit,
"offset": offset,
"userName": []string{n.Username},
"action": []string{"create", "delete"},
},
}

_, resp, err := n.APIClient.GraphqlAPI.GraphqlCreate(ctx).
GraphQLAPIRequest(req).
Execute()
if err != nil {
return allObjectChanges, resp, err
}

lastResp = resp

bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return allObjectChanges, resp, err
}
resp.Body.Close() //nolint:errcheck

var graphQL GraphQL
if err := json.Unmarshal(bodyBytes, &graphQL); err != nil {
return allObjectChanges, resp, err
}
if len(graphQL.Data.ObjectChanges) == 0 {
break
}

allObjectChanges = append(allObjectChanges, graphQL.Data.ObjectChanges...)
offset += limit
}

return allObjectChanges, lastResp, nil
}
36 changes: 24 additions & 12 deletions go/nautobotop/internal/nautobot/dcim/devicetype.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,31 @@ func (s *DeviceTypeService) GetByName(ctx context.Context, name string) nb.Devic
return list.Results[0]
}

func (s *DeviceTypeService) GetByID(ctx context.Context, id string) nb.DeviceType {
if id == "" {
return nb.DeviceType{}
}
if deviceType, ok := cache.FindByID(s.client.Cache, "devicetypes", id, func(dt nb.DeviceType) *string {
return dt.Id
}); ok {
return deviceType
}

list, resp, err := s.client.APIClient.DcimAPI.DcimDeviceTypesList(ctx).Depth(10).Id([]string{id}).Execute()
if err != nil {
bodyString := helpers.ReadResponseBody(resp)
s.client.AddReport("GetDeviceTypeByID", "failed to get", "id", id, "error", err.Error(), "response_body", bodyString)
return nb.DeviceType{}
}
if list == nil || len(list.Results) == 0 || list.Results[0].Id == nil {
return nb.DeviceType{}
}

return list.Results[0]
}

func (s *DeviceTypeService) ListAll(ctx context.Context) []nb.DeviceType {
ids := s.client.GetChangeObjectIDS(ctx, "dcim.devicetype")
list, resp, err := s.client.APIClient.DcimAPI.DcimDeviceTypesList(ctx).Id(ids).Depth(10).Execute()
list, resp, err := s.client.APIClient.DcimAPI.DcimDeviceTypesList(ctx).Limit(10000).Depth(10).Execute()
if err != nil {
bodyString := helpers.ReadResponseBody(resp)
s.client.AddReport("ListAllDeviceTypes", "failed to list", "error", err.Error(), "response_body", bodyString)
Expand All @@ -76,16 +98,6 @@ func (s *DeviceTypeService) ListAll(ctx context.Context) []nb.DeviceType {
}

func (s *DeviceTypeService) Update(ctx context.Context, id string, req nb.WritableDeviceTypeRequest) (*nb.DeviceType, error) {
owned, err := s.client.IsCreatedByUser(ctx, id)
if err != nil {
s.client.AddReport("UpdateDeviceType", "failed to check ownership", "id", id, "error", err.Error())
return nil, err
}
if !owned {
log.Warn("skipping update, object not created by user", "id", id, "user", s.client.Username)
return nil, nil
}

deviceType, resp, err := s.client.APIClient.DcimAPI.DcimDeviceTypesUpdate(ctx, id).WritableDeviceTypeRequest(req).Execute()
if err != nil {
bodyString := helpers.ReadResponseBody(resp)
Expand Down
36 changes: 24 additions & 12 deletions go/nautobotop/internal/nautobot/dcim/location.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,31 @@ func (s *LocationService) GetByName(ctx context.Context, name string) nb.Locatio
return list.Results[0]
}

func (s *LocationService) GetByID(ctx context.Context, id string) nb.Location {
if id == "" {
return nb.Location{}
}
if location, ok := cache.FindByID(s.client.Cache, "locations", id, func(l nb.Location) *string {
return l.Id
}); ok {
return location
}

list, resp, err := s.client.APIClient.DcimAPI.DcimLocationsList(ctx).Depth(10).Id([]string{id}).Execute()
if err != nil {
bodyString := helpers.ReadResponseBody(resp)
s.client.AddReport("GetLocationByID", "failed to get", "id", id, "error", err.Error(), "response_body", bodyString)
return nb.Location{}
}
if list == nil || len(list.Results) == 0 || list.Results[0].Id == nil {
return nb.Location{}
}

return list.Results[0]
Copy link
Copy Markdown
Contributor

@nidzrai nidzrai May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so here if found in cache, no API call needed otherwise and it call API. bt where would it update cache ? how does it write ?

}

func (s *LocationService) ListAll(ctx context.Context) []nb.Location {
ids := s.client.GetChangeObjectIDS(ctx, "dcim.location")
list, resp, err := s.client.APIClient.DcimAPI.DcimLocationsList(ctx).Id(ids).Depth(10).Execute()
list, resp, err := s.client.APIClient.DcimAPI.DcimLocationsList(ctx).Limit(10000).Depth(10).Execute()
if err != nil {
bodyString := helpers.ReadResponseBody(resp)
s.client.AddReport("ListAllLocations", "failed to list", "error", err.Error(), "response_body", bodyString)
Expand All @@ -77,16 +99,6 @@ func (s *LocationService) ListAll(ctx context.Context) []nb.Location {
}

func (s *LocationService) Update(ctx context.Context, id string, req nb.LocationRequest) (*nb.Location, error) {
owned, err := s.client.IsCreatedByUser(ctx, id)
if err != nil {
s.client.AddReport("UpdateLocation", "failed to check ownership", "id", id, "error", err.Error())
return nil, err
}
if !owned {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if !owned , this check is gone from Update but still exists in destroy . without this guard one can accidentally puts a UUID of a human-created or externally-managed Nautobot object in their YAML. the operator will overwrite it without any warnin I see .

log.Warn("skipping update, object not created by user", "id", id, "user", s.client.Username)
return nil, nil
}

location, resp, err := s.client.APIClient.DcimAPI.DcimLocationsUpdate(ctx, id).LocationRequest(req).Execute()
if err != nil {
bodyString := helpers.ReadResponseBody(resp)
Expand Down
36 changes: 24 additions & 12 deletions go/nautobotop/internal/nautobot/dcim/locationtype.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,31 @@ func (s *LocationTypeService) GetByName(ctx context.Context, name string) nb.Loc
return list.Results[0]
}

func (s *LocationTypeService) GetByID(ctx context.Context, id string) nb.LocationType {
if id == "" {
return nb.LocationType{}
}
if locationType, ok := cache.FindByID(s.client.Cache, "locationtypes", id, func(lt nb.LocationType) *string {
return lt.Id
}); ok {
return locationType
}

list, resp, err := s.client.APIClient.DcimAPI.DcimLocationTypesList(ctx).Depth(10).Id([]string{id}).Execute()
if err != nil {
bodyString := helpers.ReadResponseBody(resp)
s.client.AddReport("GetLocationTypeByID", "failed to get", "id", id, "error", err.Error(), "response_body", bodyString)
return nb.LocationType{}
}
if list == nil || len(list.Results) == 0 || list.Results[0].Id == nil {
return nb.LocationType{}
}

return list.Results[0]
}

func (s *LocationTypeService) ListAll(ctx context.Context) []nb.LocationType {
ids := s.client.GetChangeObjectIDS(ctx, "dcim.locationtype")
list, resp, err := s.client.APIClient.DcimAPI.DcimLocationTypesList(ctx).Id(ids).Depth(10).Execute()
list, resp, err := s.client.APIClient.DcimAPI.DcimLocationTypesList(ctx).Limit(10000).Depth(10).Execute()
if err != nil {
bodyString := helpers.ReadResponseBody(resp)
s.client.AddReport("ListAllLocationTypes", "failed to list", "error", err.Error(), "response_body", bodyString)
Expand All @@ -76,16 +98,6 @@ func (s *LocationTypeService) ListAll(ctx context.Context) []nb.LocationType {
}

func (s *LocationTypeService) Update(ctx context.Context, id string, req nb.LocationTypeRequest) (*nb.LocationType, error) {
owned, err := s.client.IsCreatedByUser(ctx, id)
if err != nil {
s.client.AddReport("UpdateLocationType", "failed to check ownership", "id", id, "error", err.Error())
return nil, err
}
if !owned {
log.Warn("skipping update, object not created by user", "id", id, "user", s.client.Username)
return nil, nil
}

locationType, resp, err := s.client.APIClient.DcimAPI.DcimLocationTypesUpdate(ctx, id).LocationTypeRequest(req).Execute()
if err != nil {
bodyString := helpers.ReadResponseBody(resp)
Expand Down
33 changes: 12 additions & 21 deletions go/nautobotop/internal/nautobot/dcim/manufacturer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package dcim

import (
"context"
"net/http"

"github.com/charmbracelet/log"
"github.com/rackerlabs/understack/go/nautobotop/internal/nautobot/cache"
Expand All @@ -23,28 +22,20 @@ func NewManufacturerService(nautobotClient *client.NautobotClient) *Manufacturer
}

func (s *ManufacturerService) ListAll(ctx context.Context) []nb.Manufacturer {
ids := s.client.GetChangeObjectIDS(ctx, "dcim.manufacturer")

// Define the API call function for this specific endpoint
apiCall := func(ctx context.Context, batchIds []string) ([]nb.Manufacturer, *http.Response, error) {
list, resp, err := s.client.APIClient.DcimAPI.DcimManufacturersList(ctx).Id(batchIds).Depth(10).Execute()
if err != nil {
return nil, resp, err
}
if list == nil {
return []nb.Manufacturer{}, resp, nil
}
return list.Results, resp, nil
list, resp, err := s.client.APIClient.DcimAPI.DcimManufacturersList(ctx).Limit(10000).Depth(10).Execute()
if err != nil {
bodyString := helpers.ReadResponseBody(resp)
s.client.AddReport("ListAllManufacturers", "failed to list", "error", err.Error(), "response_body", bodyString)
return []nb.Manufacturer{}
}
if list == nil || len(list.Results) == 0 {
return []nb.Manufacturer{}
}
if list.Results[0].Id == nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In ListAll, if Results[0].Id == nil, the entire result set is discarded and an empty slice is returned. I m not sure if it is intentional . but let's say there are 10 items , it would check just 1st or the 0th position and would discard all . right ?

return []nb.Manufacturer{}
}

// Use the helper function for pagination
return helpers.PaginatedListWithIDs(
ctx,
ids,
apiCall,
s.client.AddReport,
"ListAllManufacturers",
)
return list.Results
}

func (s *ManufacturerService) GetByName(ctx context.Context, name string) nb.Manufacturer {
Expand Down
36 changes: 24 additions & 12 deletions go/nautobotop/internal/nautobot/dcim/rack.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,31 @@ func (s *RackService) GetByName(ctx context.Context, name string) nb.Rack {
return list.Results[0]
}

func (s *RackService) GetByID(ctx context.Context, id string) nb.Rack {
if id == "" {
return nb.Rack{}
}
if rack, ok := cache.FindByID(s.client.Cache, "racks", id, func(r nb.Rack) *string {
return r.Id
}); ok {
return rack
}

list, resp, err := s.client.APIClient.DcimAPI.DcimRacksList(ctx).Depth(2).Id([]string{id}).Execute()
if err != nil {
bodyString := helpers.ReadResponseBody(resp)
s.client.AddReport("GetRackByID", "failed to get", "id", id, "error", err.Error(), "response_body", bodyString)
return nb.Rack{}
}
if list == nil || len(list.Results) == 0 || list.Results[0].Id == nil {
return nb.Rack{}
}

return list.Results[0]
}

func (s *RackService) ListAll(ctx context.Context) []nb.Rack {
ids := s.client.GetChangeObjectIDS(ctx, "dcim.rack")
list, resp, err := s.client.APIClient.DcimAPI.DcimRacksList(ctx).Id(ids).Depth(2).Execute()
list, resp, err := s.client.APIClient.DcimAPI.DcimRacksList(ctx).Limit(10000).Depth(2).Execute()
if err != nil {
bodyString := helpers.ReadResponseBody(resp)
s.client.AddReport("ListAllRacks", "failed to list", "error", err.Error(), "response_body", bodyString)
Expand All @@ -75,16 +97,6 @@ func (s *RackService) ListAll(ctx context.Context) []nb.Rack {
}

func (s *RackService) Update(ctx context.Context, id string, req nb.WritableRackRequest) (*nb.Rack, error) {
owned, err := s.client.IsCreatedByUser(ctx, id)
if err != nil {
s.client.AddReport("UpdateRack", "failed to check ownership", "id", id, "error", err.Error())
return nil, err
}
if !owned {
log.Warn("skipping update, object not created by user", "id", id, "user", s.client.Username)
return nil, nil
}

rack, resp, err := s.client.APIClient.DcimAPI.DcimRacksUpdate(ctx, id).WritableRackRequest(req).Execute()
if err != nil {
bodyString := helpers.ReadResponseBody(resp)
Expand Down
Loading
Loading