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
1 change: 1 addition & 0 deletions go/nautobotop/api/v1alpha1/nautobot_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type NautobotSpec struct {
RoleRef []ConfigMapRef `json:"roleRef,omitempty"`
TenantGroupRef []ConfigMapRef `json:"tenantGroupRef,omitempty"`
TenantRef []ConfigMapRef `json:"tenantRef,omitempty"`
DeviceRef []ConfigMapRef `json:"deviceRef,omitempty"`
}

// NautobotStatus defines the observed state of Nautobot.
Expand Down
7 changes: 7 additions & 0 deletions go/nautobotop/api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions go/nautobotop/config/crd/bases/sync.rax.io_nautobots.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,31 @@ spec:
- configMapSelector
type: object
type: array
deviceRef:
items:
description: ConfigMapRef defines a reference to a specific ConfigMap

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit: can we improve the description here? It sounds slightly unintuitive. I can't make sens eof this sentence, 'ie' reference to a specific ConfigMap' of what?

properties:
configMapSelector:
description: The name of the ConfigMap resource being referred

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The description says this selector "is" a name, but then on line 132 we have a separate key field — so it's unclear what the outer description is even referring to. This still looks confusing to me.

to
properties:
key:
description: The key in the ConfigMap data
type: string
name:
description: The name of the ConfigMap
minLength: 1
type: string
namespace:
description: The namespace where the ConfigMap resides
type: string
required:
- name
type: object
required:
- configMapSelector
type: object
type: array
deviceTypeRef:
items:
description: ConfigMapRef defines a reference to a specific ConfigMap
Expand Down
25 changes: 25 additions & 0 deletions go/nautobotop/helm/crds/clients.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,31 @@ spec:
- configMapSelector
type: object
type: array
deviceRef:
items:
description: ConfigMapRef defines a reference to a specific ConfigMap
properties:
configMapSelector:
description: The name of the ConfigMap resource being referred

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same confusion as I described in /config/crd/bases/sync.rax.io_nautobots.yaml

to
properties:
key:
description: The key in the ConfigMap data
type: string
name:
description: The name of the ConfigMap
minLength: 1
type: string
namespace:
description: The namespace where the ConfigMap resides
type: string
required:
- name
type: object
required:
- configMapSelector
type: object
type: array
deviceTypeRef:
items:
description: ConfigMapRef defines a reference to a specific ConfigMap
Expand Down
90 changes: 90 additions & 0 deletions go/nautobotop/internal/controller/dag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package controller

import (
"fmt"
"sort"
)

// ResourceNode defines a sync resource with explicit dependency declarations.
// The topological sort uses DependsOn to determine execution order automatically.
type ResourceNode struct {
Name string
DependsOn []string
}

// topologicalSort using Kahn's algorithm here
// a deterministic execution order that respects all declared dependencies.
//
// Returns an error if:
// - A cycle is detected (not a DAG)
// - A node references a dependency that doesn't exist in the input
// - Duplicate node names are provided
func topologicalSort(nodes []ResourceNode) ([]string, error) {
if len(nodes) == 0 {
return nil, nil
}
nodeSet := make(map[string]struct{}, len(nodes))
for _, node := range nodes {
if _, exists := nodeSet[node.Name]; exists {
return nil, fmt.Errorf("duplicate node name: %q", node.Name)
}
nodeSet[node.Name] = struct{}{}
}

for _, node := range nodes {
for _, dep := range node.DependsOn {
if _, exists := nodeSet[dep]; !exists {
return nil, fmt.Errorf("node %q depends on %q which does not exist", node.Name, dep)
}
}
}

inDegree := make(map[string]int, len(nodes))
dependents := make(map[string][]string, len(nodes))

for _, node := range nodes {
inDegree[node.Name] = len(node.DependsOn)
for _, dep := range node.DependsOn {
dependents[dep] = append(dependents[dep], node.Name)
}
}

var queue []string
for _, node := range nodes {
if inDegree[node.Name] == 0 {
queue = append(queue, node.Name)
}
}
sort.Strings(queue)

result := make([]string, 0, len(nodes))

for len(queue) > 0 {
current := queue[0]
queue = queue[1:]
result = append(result, current)

children := dependents[current]
sort.Strings(children)
for _, child := range children {
inDegree[child]--
if inDegree[child] == 0 {
queue = append(queue, child)
}
}
sort.Strings(queue)
}

if len(result) != len(nodes) {
var cycleNodes []string
for name, degree := range inDegree {
if degree > 0 {
cycleNodes = append(cycleNodes, name)
}
}
sort.Strings(cycleNodes)
return nil, fmt.Errorf("dependency cycle detected involving nodes: %v", cycleNodes)
}

return result, nil
}
Loading
Loading