Skip to content

Commit 37fd158

Browse files
committed
feat: add location sync to nautobot operator
1 parent 4aa561d commit 37fd158

12 files changed

Lines changed: 523 additions & 50 deletions

File tree

docs/schema/locations.schema.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../schema/locations.schema.json

go/nautobotop/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
bin/*
99
dist/*
1010
Dockerfile.cross
11+
cmd/cmd
1112

1213
# Test binary, built with `go test -c`
1314
*.test
@@ -25,6 +26,7 @@ dist/
2526

2627
# editor and IDE paraphernalia
2728
.idea
29+
.kiro
2830
.vscode
2931
*.swp
3032
*.swo

go/nautobotop/api/v1alpha1/nautobot_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type NautobotSpec struct {
3333
NautobotServiceRef ServiceSelector `json:"nautobotServiceRef,omitempty"`
3434
DeviceTypesRef []ConfigMapRef `json:"deviceTypeRef,omitempty"`
3535
LocationTypesRef []ConfigMapRef `json:"locationTypesRef,omitempty"`
36+
LocationRef []ConfigMapRef `json:"locationRef,omitempty"`
3637
}
3738

3839
// NautobotStatus defines the observed state of Nautobot.

go/nautobotop/api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/nautobotop/config/crd/bases/sync.rax.io_nautobots.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,35 @@ spec:
6868
- name
6969
type: object
7070
type: array
71+
locationRef:
72+
items:
73+
description: ConfigMapRef defines a reference to a specific ConfigMap
74+
properties:
75+
configMapSelector:
76+
description: The name of the ConfigMap resource being referred
77+
to
78+
properties:
79+
key:
80+
description: The key in the ConfigMap data
81+
type: string
82+
name:
83+
description: The name of the ConfigMap
84+
minLength: 1
85+
type: string
86+
namespace:
87+
description: The namespace where the ConfigMap resides
88+
type: string
89+
required:
90+
- name
91+
type: object
92+
name:
93+
description: Name of this config set (logical name)
94+
type: string
95+
required:
96+
- configMapSelector
97+
- name
98+
type: object
99+
type: array
71100
locationTypesRef:
72101
items:
73102
description: ConfigMapRef defines a reference to a specific ConfigMap

go/nautobotop/helm/crds/clients.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,35 @@ spec:
6868
- name
6969
type: object
7070
type: array
71+
locationRef:
72+
items:
73+
description: ConfigMapRef defines a reference to a specific ConfigMap
74+
properties:
75+
configMapSelector:
76+
description: The name of the ConfigMap resource being referred
77+
to
78+
properties:
79+
key:
80+
description: The key in the ConfigMap data
81+
type: string
82+
name:
83+
description: The name of the ConfigMap
84+
minLength: 1
85+
type: string
86+
namespace:
87+
description: The namespace where the ConfigMap resides
88+
type: string
89+
required:
90+
- name
91+
type: object
92+
name:
93+
description: Name of this config set (logical name)
94+
type: string
95+
required:
96+
- configMapSelector
97+
- name
98+
type: object
99+
type: array
71100
locationTypesRef:
72101
items:
73102
description: ConfigMapRef defines a reference to a specific ConfigMap

go/nautobotop/internal/controller/nautobot_controller.go

Lines changed: 72 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -61,70 +61,86 @@ type NautobotReconciler struct {
6161
//
6262
// For more details, check Reconcile and its Result here:
6363
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile
64+
// resourceConfig defines configuration for a single resource type
65+
type resourceConfig struct {
66+
name string
67+
configRefs []syncv1alpha1.ConfigMapRef
68+
syncFunc func(context.Context, *nbClient.NautobotClient, map[string]string) error
69+
}
70+
6471
func (r *NautobotReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
6572
log := logf.FromContext(ctx)
6673
var nautobotCR syncv1alpha1.Nautobot
6774
if err := r.Get(ctx, req.NamespacedName, &nautobotCR); err != nil {
6875
return ctrl.Result{}, client.IgnoreNotFound(err)
6976
}
7077

71-
// Aggregate device type data from all referenced ConfigMaps
72-
locationTypeMap, err := r.aggregateDeviceTypeDataFromConfigMap(ctx, nautobotCR.Spec.LocationTypesRef)
73-
if err != nil {
74-
log.Error(err, "failed to aggregate device type data from ConfigMaps")
75-
return ctrl.Result{}, err
78+
syncInterval := time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second
79+
requeueAfter := time.Duration(nautobotCR.Spec.RequeueAfter) * time.Second
80+
81+
// Define all resources to sync
82+
// Add more resources here: {name: "location", configRefs: nautobotCR.Spec.LocationsRef, syncFunc: r.syncLocations}
83+
resources := []resourceConfig{
84+
{name: "locationType", configRefs: nautobotCR.Spec.LocationTypesRef, syncFunc: r.syncLocationTypes},
85+
{name: "location", configRefs: nautobotCR.Spec.LocationRef, syncFunc: r.syncLocation},
86+
{name: "deviceType", configRefs: nautobotCR.Spec.DeviceTypesRef, syncFunc: r.syncDeviceTypes},
7687
}
7788

78-
// Aggregate device type data from all referenced ConfigMaps
79-
deviceTypeMap, err := r.aggregateDeviceTypeDataFromConfigMap(ctx, nautobotCR.Spec.DeviceTypesRef)
80-
if err != nil {
81-
log.Error(err, "failed to aggregate device type data from ConfigMaps")
82-
return ctrl.Result{}, err
89+
// Aggregate data and check sync decisions for all resources
90+
resourcesToSync := make(map[string]map[string]string)
91+
for _, res := range resources {
92+
dataMap, err := r.aggregateDataFromConfigMap(ctx, res.configRefs)
93+
if err != nil {
94+
log.Error(err, "failed to aggregate data", "resource", res.name)
95+
return ctrl.Result{}, err
96+
}
97+
98+
currentHash := computeHash(dataMap)
99+
previousHash := nautobotCR.GetSyncHash(res.name)
100+
decision := r.shouldSync(nautobotCR.Status.LastSyncedAt, syncInterval, currentHash, previousHash)
101+
102+
if decision.ShouldSync {
103+
log.Info("resource needs sync", "resource", res.name, "reason", decision.Reason)
104+
resourcesToSync[res.name] = dataMap
105+
} else {
106+
log.Info("skipping resource sync", "resource", res.name, "reason", decision.Reason)
107+
}
83108
}
84109

85-
// Check if sync should proceed based on time interval and data changes
86-
syncInterval := time.Duration(nautobotCR.Spec.SyncIntervalSeconds) * time.Second
87-
requestTimeAfter := time.Duration(nautobotCR.Spec.RequeueAfter) * time.Second
88-
currentHash := computeHash(deviceTypeMap)
89-
previousHash := nautobotCR.GetSyncHash("deviceType")
90-
91-
syncDecision := r.shouldSync(nautobotCR.Status.LastSyncedAt, syncInterval, currentHash, previousHash)
92-
if !syncDecision.ShouldSync {
93-
log.Info("skipping sync", "reason", syncDecision.Reason, "hash", currentHash)
94-
nautobotCR.Status.Message = syncDecision.StatusMessage
110+
// If nothing to sync, update status and requeue
111+
if len(resourcesToSync) == 0 {
112+
nautobotCR.Status.Message = "No changes detected"
95113
if err := r.Status().Update(ctx, &nautobotCR); err != nil {
96114
log.Error(err, "failed to update status")
97115
return ctrl.Result{}, err
98116
}
99-
return ctrl.Result{RequeueAfter: requestTimeAfter}, nil
117+
return ctrl.Result{RequeueAfter: requeueAfter}, nil
100118
}
101119

102-
log.Info("proceeding with sync", "reason", syncDecision.Reason, "hash", currentHash)
103-
104-
// Retrieve the Nautobot authentication token from a secret or external source
120+
// Create Nautobot client
105121
username, token, err := r.getAuthTokenFromSecretRef(ctx, nautobotCR)
106122
if err != nil {
107-
log.Error(err, "failed parse find nautobot auth token")
123+
log.Error(err, "failed to get nautobot auth token")
108124
return ctrl.Result{}, err
109125
}
110-
111-
// Create Nautobot client
112126
nautobotURL := fmt.Sprintf("http://%s.%s.svc.cluster.local/api", nautobotCR.Spec.NautobotServiceRef.Name, nautobotCR.Spec.NautobotServiceRef.Namespace)
113127
nautobotClient := nbClient.NewNautobotClient(nautobotURL, username, token)
114128

115-
if err := r.syncLocationTypes(ctx, nautobotClient, locationTypeMap); err != nil {
116-
log.Error(err, "failed to sync device types")
117-
return ctrl.Result{}, err
118-
}
119-
if err := r.syncDeviceTypes(ctx, nautobotClient, deviceTypeMap); err != nil {
120-
log.Error(err, "failed to sync device types")
121-
return ctrl.Result{}, err
129+
// Sync resources that need updating
130+
for _, res := range resources {
131+
if dataMap, ok := resourcesToSync[res.name]; ok {
132+
if err := res.syncFunc(ctx, nautobotClient, dataMap); err != nil {
133+
log.Error(err, "failed to sync resource", "resource", res.name)
134+
return ctrl.Result{}, err
135+
}
136+
nautobotCR.SetSyncHash(res.name, computeHash(dataMap))
137+
}
122138
}
123139

140+
// Update status
124141
nautobotCR.Status.LastSyncedAt = metav1.Now()
125142
nautobotCR.Status.Ready = true
126143
nautobotCR.Status.NautobotStatusReport = nautobotClient.Report
127-
nautobotCR.SetSyncHash("deviceType", currentHash)
128144
if len(nautobotClient.Report) > 0 {
129145
nautobotCR.Status.Message = "sync completed with some errors"
130146
} else {
@@ -134,15 +150,14 @@ func (r *NautobotReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
134150
log.Error(err, "failed to update status")
135151
return ctrl.Result{}, err
136152
}
137-
// Successfully completed reconciliation; requeue after configured sync interval
153+
138154
log.Info("sync completed successfully")
139-
return ctrl.Result{RequeueAfter: requestTimeAfter}, nil
155+
return ctrl.Result{RequeueAfter: requeueAfter}, nil
140156
}
141157

142-
// aggregateDeviceTypeDataFromConfigMap fetches and merges data from all referenced ConfigMaps.
143-
// It returns a map containing all device type configurations.
144-
func (r *NautobotReconciler) aggregateDeviceTypeDataFromConfigMap(ctx context.Context, refs []syncv1alpha1.ConfigMapRef) (map[string]string, error) {
145-
deviceTypeMap := make(map[string]string)
158+
// aggregateDataFromConfigMap fetches and merges data from all referenced ConfigMaps.
159+
func (r *NautobotReconciler) aggregateDataFromConfigMap(ctx context.Context, refs []syncv1alpha1.ConfigMapRef) (map[string]string, error) {
160+
dataMap := make(map[string]string)
146161

147162
for _, ref := range refs {
148163
var configMap corev1.ConfigMap
@@ -156,27 +171,36 @@ func (r *NautobotReconciler) aggregateDeviceTypeDataFromConfigMap(ctx context.Co
156171
namespacedName.Namespace, namespacedName.Name, err)
157172
}
158173

159-
// Merge ConfigMap data into the aggregate map
160-
maps.Copy(deviceTypeMap, configMap.Data)
174+
maps.Copy(dataMap, configMap.Data)
161175
}
162176

163-
return deviceTypeMap, nil
177+
return dataMap, nil
164178
}
165179

166180
// syncDeviceTypes syncs device types to Nautobot.
167-
// The hash comparison is now handled in the Reconcile function.
168181
func (r *NautobotReconciler) syncDeviceTypes(ctx context.Context,
169182
nautobotClient *nbClient.NautobotClient,
170183
deviceTypeMap map[string]string,
171184
) error {
172185
log := logf.FromContext(ctx)
173-
174-
log.Info("syncing device types", "deviceTypeCount", len(deviceTypeMap))
186+
log.Info("syncing device types", "count", len(deviceTypeMap))
175187
syncSvc := sync.NewDeviceTypeSync(nautobotClient)
176188
if err := syncSvc.SyncAll(ctx, deviceTypeMap); err != nil {
177189
return fmt.Errorf("failed to sync device types: %w", err)
178190
}
191+
return nil
192+
}
179193

194+
func (r *NautobotReconciler) syncLocation(ctx context.Context,
195+
nautobotClient *nbClient.NautobotClient,
196+
locationType map[string]string,
197+
) error {
198+
log := logf.FromContext(ctx)
199+
log.Info("syncing location types", "count", len(locationType))
200+
syncSvc := sync.NewLocationSync(nautobotClient)
201+
if err := syncSvc.SyncAll(ctx, locationType); err != nil {
202+
return fmt.Errorf("failed to sync location types: %w", err)
203+
}
180204
return nil
181205
}
182206

@@ -185,13 +209,11 @@ func (r *NautobotReconciler) syncLocationTypes(ctx context.Context,
185209
locationType map[string]string,
186210
) error {
187211
log := logf.FromContext(ctx)
188-
189-
log.Info("syncing location types", "locationTypeCount", len(locationType))
212+
log.Info("syncing location types", "count", len(locationType))
190213
syncSvc := sync.NewLocationTypeSync(nautobotClient)
191214
if err := syncSvc.SyncAll(ctx, locationType); err != nil {
192215
return fmt.Errorf("failed to sync location types: %w", err)
193216
}
194-
195217
return nil
196218
}
197219

0 commit comments

Comments
 (0)