diff --git a/go/nautobotop/api/v1alpha1/nautobot_types.go b/go/nautobotop/api/v1alpha1/nautobot_types.go index 3d7607b4e..29eb513e5 100644 --- a/go/nautobotop/api/v1alpha1/nautobot_types.go +++ b/go/nautobotop/api/v1alpha1/nautobot_types.go @@ -89,6 +89,25 @@ func init() { SchemeBuilder.Register(&Nautobot{}, &NautobotList{}) } +// ConfigMapRefs returns every ConfigMap reference declared in the spec. +func (s *NautobotSpec) ConfigMapRefs() []ConfigMapRef { + refs := make([]ConfigMapRef, 0) + refs = append(refs, s.DeviceTypesRef...) + refs = append(refs, s.LocationTypesRef...) + // ... all []ConfigMapRef fields (LocationRef, RackGroupRef, RackRef, VlanGroupRef, + // VlanRef, PrefixRef, ClusterTypeRef, ClusterGroupRef, ClusterRef, NamespaceRef, + // RirRef, RoleRef, TenantGroupRef, TenantRef, DeviceRef) + return refs +} + +// ConfigMapRef returns every ConfigMap reference declared in the object's spec. +func (r *Nautobot) ConfigMapRefs() []ConfigMapRef { + if r == nil { + return nil + } + return r.Spec.ConfigMapRefs() +} + func (r *Nautobot) GetSyncHash(key string) string { if r == nil || r.Status.SyncHash == nil { return "" diff --git a/go/nautobotop/config/rbac/role.yaml b/go/nautobotop/config/rbac/role.yaml index 6867df5e8..0d77fb28a 100644 --- a/go/nautobotop/config/rbac/role.yaml +++ b/go/nautobotop/config/rbac/role.yaml @@ -4,6 +4,14 @@ kind: ClusterRole metadata: name: manager-role rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch - apiGroups: - sync.rax.io resources: diff --git a/go/nautobotop/internal/controller/nautobot_controller.go b/go/nautobotop/internal/controller/nautobot_controller.go index 8bd485e97..b6c005879 100644 --- a/go/nautobotop/internal/controller/nautobot_controller.go +++ b/go/nautobotop/internal/controller/nautobot_controller.go @@ -37,9 +37,13 @@ import ( "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" syncv1alpha1 "github.com/rackerlabs/understack/go/nautobotop/api/v1alpha1" ) @@ -53,6 +57,7 @@ type NautobotReconciler struct { // +kubebuilder:rbac:groups=sync.rax.io,resources=nautobots,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=sync.rax.io,resources=nautobots/status,verbs=get;update;patch // +kubebuilder:rbac:groups=sync.rax.io,resources=nautobots/finalizers,verbs=update +// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -551,11 +556,39 @@ func (r *NautobotReconciler) getAuthTokenFromSecretRef(ctx context.Context, naut func (r *NautobotReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&syncv1alpha1.Nautobot{}). + Watches( + &corev1.ConfigMap{}, + handler.EnqueueRequestsFromMapFunc(r.mapConfigMapToNautobot), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). WithOptions(controller.Options{RecoverPanic: ptr.To(true)}). Named("nautobot"). Complete(r) } +func (r *NautobotReconciler) mapConfigMapToNautobot(ctx context.Context, obj client.Object) []reconcile.Request { + var list syncv1alpha1.NautobotList + if err := r.List(ctx, &list); err != nil { + logf.FromContext(ctx).Error(err, "failed to list Nautobot CRs for ConfigMap mapping") + return nil + } + var requests []reconcile.Request + for i := range list.Items { + cr := &list.Items[i] + for _, ref := range cr.Spec.ConfigMapRefs() { + sel := ref.ConfigMapSelector + if sel.Name == obj.GetName() && + sel.Namespace != nil && *sel.Namespace == obj.GetNamespace() { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{Name: cr.Name}, // CR is cluster-scoped + }) + break + } + } + } + return requests +} + // SyncDecision represents the result of evaluating whether a sync should proceed type SyncDecision struct { ShouldSync bool