diff --git a/Tiltfile b/Tiltfile index 6605ef886..65ac24991 100644 --- a/Tiltfile +++ b/Tiltfile @@ -21,10 +21,8 @@ docker_build('controller:latest', '.', only=[ local_resource('controller-gen', 'make generate', deps=['api/', 'hack/boilerplate.go.txt']) local_resource('crds', 'make install', deps=['api/']) -provider = os.getenv('PROVIDER', 'openconfig') - manager = kustomize('config/develop') -manager = str(manager).replace('--provider=openconfig', '--provider={}'.format(provider)) +manager = str(manager) k8s_yaml(blob(manager)) k8s_resource('network-operator-controller-manager', resource_deps=['controller-gen']) diff --git a/api/core/v1alpha1/device_types.go b/api/core/v1alpha1/device_types.go index a9a7e6cb6..d4bcd7f17 100644 --- a/api/core/v1alpha1/device_types.go +++ b/api/core/v1alpha1/device_types.go @@ -24,6 +24,11 @@ type DeviceSpec struct { // +required Endpoint Endpoint `json:"endpoint"` + // Provider identifies the network provider plugin used to reconcile this Device and all CRDs that reference it. + // +required + // +immutable + Provider string `json:"provider,omitempty"` + // Provisioning is an optional configuration for the device provisioning process. // It can be used to provide initial configuration templates or scripts that are applied during the device provisioning. // +optional diff --git a/api/core/v1alpha1/groupversion_info.go b/api/core/v1alpha1/groupversion_info.go index 2bac14340..0ada11111 100644 --- a/api/core/v1alpha1/groupversion_info.go +++ b/api/core/v1alpha1/groupversion_info.go @@ -181,6 +181,9 @@ const ( // ErrorReason indicates that an error occurred while reconciling the resource. ErrorReason = "Error" + // ProviderNotFoundReason indicates that the provider specified in the resource's device.spec.provider field was not found. + ProviderNotFoundReason = "ProviderNotFound" + // WaitingForDependenciesReason indicates that the resource is waiting for its dependencies to be ready. WaitingForDependenciesReason = "WaitingForDependencies" diff --git a/charts/network-operator/templates/crd/devices.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/devices.networking.metal.ironcore.dev.yaml index 16aef9365..ccf98b20d 100644 --- a/charts/network-operator/templates/crd/devices.networking.metal.ironcore.dev.yaml +++ b/charts/network-operator/templates/crd/devices.networking.metal.ironcore.dev.yaml @@ -194,6 +194,10 @@ spec: description: Paused can be used to prevent controllers from processing the Device and its associated objects. type: boolean + provider: + description: Provider identifies the network provider plugin used + to reconcile this Device and all CRDs that reference it. + type: string provisioning: description: |- Provisioning is an optional configuration for the device provisioning process. @@ -300,6 +304,7 @@ spec: type: object required: - endpoint + - provider type: object status: description: |- diff --git a/cmd/main.go b/cmd/main.go index 530804326..6fa3e3839 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -50,7 +50,6 @@ import ( evpncontroller "github.com/ironcore-dev/network-operator/internal/controller/evpn" poolcontroller "github.com/ironcore-dev/network-operator/internal/controller/pool" "github.com/ironcore-dev/network-operator/internal/deviceutil" - "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/provisioning" "github.com/ironcore-dev/network-operator/internal/resourcelock" tftpserver "github.com/ironcore-dev/network-operator/internal/tftp" @@ -97,7 +96,6 @@ func main() { //nolint:gocyclo var tlsOpts []func(*tls.Config) var watchNamespace string var watchFilterValue string - var providerName string var requeueInterval time.Duration var heartbeatInterval time.Duration var tftpPort int @@ -123,7 +121,6 @@ func main() { //nolint:gocyclo flag.BoolVar(&enableHTTP2, "enable-http2", false, "If set, HTTP/2 will be enabled for the metrics and webhook servers") flag.StringVar(&watchNamespace, "namespace", "", "Namespace that the controller watches to reconcile api objects. If unspecified, the controller watches for api objects across all namespaces.") flag.StringVar(&watchFilterValue, "watch-filter", "", fmt.Sprintf("Label value that the controller watches to reconcile api objects. Label key is always %q. If unspecified, the controller watches for all api objects.", v1alpha1.WatchLabel)) - flag.StringVar(&providerName, "provider", "openconfig", "The provider to use for the controller. If not specified, the default provider is used. Available providers: "+strings.Join(provider.Providers(), ", ")) flag.DurationVar(&requeueInterval, "requeue-interval", time.Hour, "The interval after which Kubernetes resources should be reconciled again regardless of whether they have changed.") flag.DurationVar(&heartbeatInterval, "heartbeat-interval", 30*time.Second, "The interval after which the controller retries a reachability check on each device.") flag.IntVar(&tftpPort, "tftp-port", 1069, "The port on which the inline TFTP server listens. Set to 0 to disable the TFTP server.") @@ -259,13 +256,6 @@ func main() { //nolint:gocyclo os.Exit(1) } - setupLog.Info("Using provider", "provider", providerName) - prov, err := provider.Get(providerName) - if err != nil { - setupLog.Error(err, "failed to get provider", "provider", providerName) - os.Exit(1) - } - ctx := ctrl.SetupSignalHandler() if lockerNamespace == "" { @@ -313,7 +303,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("device-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, HeartbeatInterval: heartbeatInterval, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Device") @@ -325,7 +314,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("interface-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, RequeueInterval: requeueInterval, }).SetupWithManager(ctx, mgr); err != nil { @@ -338,7 +326,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("banner-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Banner") @@ -350,7 +337,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("user-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "User") @@ -362,7 +348,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("dns-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "DNS") @@ -374,7 +359,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("ntp-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "NTP") @@ -386,7 +370,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("acl-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "AccessControlList") @@ -398,7 +381,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("certificate-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Certificate") @@ -410,7 +392,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("snmp-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "SNMP") @@ -422,7 +403,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("syslog-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Syslog") @@ -434,7 +414,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("managementaccess-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ManagementAccess") @@ -446,7 +425,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("isis-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ISIS") @@ -458,7 +436,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("pim-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "PIM") @@ -470,7 +447,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("bgp-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, RequeueInterval: requeueInterval, }).SetupWithManager(ctx, mgr); err != nil { @@ -483,7 +459,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("bgppeer-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, RequeueInterval: requeueInterval, }).SetupWithManager(ctx, mgr); err != nil { @@ -496,7 +471,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("lldp-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, RequeueInterval: requeueInterval, }).SetupWithManager(ctx, mgr); err != nil { @@ -509,7 +483,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("ospf-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, RequeueInterval: requeueInterval, }).SetupWithManager(ctx, mgr); err != nil { @@ -522,7 +495,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("vlan-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, RequeueInterval: requeueInterval, }).SetupWithManager(ctx, mgr); err != nil { @@ -535,7 +507,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("vrf-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "VRF") @@ -547,7 +518,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("cisco-nx-vpcdomain-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, RequeueInterval: requeueInterval, }).SetupWithManager(ctx, mgr); err != nil { @@ -560,7 +530,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("nve-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, RequeueInterval: requeueInterval, }).SetupWithManager(ctx, mgr); err != nil { @@ -573,7 +542,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("cisco-nx-system-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "System") @@ -585,7 +553,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("evpn-instance-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "EVPNInstance") @@ -597,7 +564,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("aaa-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "AAA") @@ -609,7 +575,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("prefixset-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "PrefixSet") @@ -621,7 +586,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("routingpolicy-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "RoutingPolicy") @@ -633,7 +597,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("cisco-nx-border-gateway-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "BorderGateway") @@ -645,7 +608,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("dhcprelay-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, RequeueInterval: requeueInterval, }).SetupWithManager(ctx, mgr); err != nil { @@ -658,7 +620,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("configbackup-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, }).SetupWithManager(ctx, mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ConfigBackup") @@ -670,7 +631,6 @@ func main() { //nolint:gocyclo Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorder("ethernetsegment-controller"), WatchFilterValue: watchFilterValue, - Provider: prov, Locker: locker, RequeueInterval: requeueInterval, }).SetupWithManager(ctx, mgr); err != nil { @@ -794,14 +754,12 @@ func main() { //nolint:gocyclo // is implemented and the port is set to a non-zero value. // The server is added to the manager so it starts after the cache is synced // and shuts down gracefully when the manager stops. - provisioningProvider, ok := prov().(provider.ProvisioningProvider) - if provisioningHTTPPort != 0 && ok { + if provisioningHTTPPort != 0 { provisioningServer := &provisioning.HTTPServer{ Client: mgr.GetClient(), Logger: ctrl.Log.WithName("provisioning"), Recorder: mgr.GetEventRecorder("provisioning"), ValidateSourceIP: provisioningHTTPValidateSourceIP, - Provider: provisioningProvider, Port: provisioningHTTPPort, } setupLog.Info("Adding provisioning HTTP server to manager", "port", provisioningHTTPPort, "validateSourceIP", provisioningHTTPValidateSourceIP) diff --git a/config/crd/bases/networking.metal.ironcore.dev_devices.yaml b/config/crd/bases/networking.metal.ironcore.dev_devices.yaml index 67379c014..2d10778c2 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_devices.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_devices.yaml @@ -191,6 +191,10 @@ spec: description: Paused can be used to prevent controllers from processing the Device and its associated objects. type: boolean + provider: + description: Provider identifies the network provider plugin used + to reconcile this Device and all CRDs that reference it. + type: string provisioning: description: |- Provisioning is an optional configuration for the device provisioning process. @@ -297,6 +301,7 @@ spec: type: object required: - endpoint + - provider type: object status: description: |- diff --git a/config/develop/manager_patch.yaml b/config/develop/manager_patch.yaml index 4fddb21a4..3736922fb 100644 --- a/config/develop/manager_patch.yaml +++ b/config/develop/manager_patch.yaml @@ -3,7 +3,6 @@ value: - --leader-elect=false - --health-probe-bind-address=:8081 - - --provider=openconfig - --requeue-interval=30s - --max-concurrent-reconciles=5 - --zap-log-level=3 diff --git a/docs/api-reference/index.md b/docs/api-reference/index.md index af84cc064..23b5ef7d4 100644 --- a/docs/api-reference/index.md +++ b/docs/api-reference/index.md @@ -1786,6 +1786,7 @@ _Appears in:_ | --- | --- | --- | --- | | `paused` _boolean_ | Paused can be used to prevent controllers from processing the Device and its associated objects. | false | Optional: \{\}
| | `endpoint` _[Endpoint](#endpoint)_ | Endpoint contains the connection information for the device. | | Required: \{\}
| +| `provider` _string_ | Provider identifies the network provider plugin used to reconcile this Device and all CRDs that reference it. | | Required: \{\}
| | `provisioning` _[Provisioning](#provisioning)_ | Provisioning is an optional configuration for the device provisioning process.
It can be used to provide initial configuration templates or scripts that are applied during the device provisioning. | | Optional: \{\}
| diff --git a/internal/controller/cisco/nx/bordergateway_controller.go b/internal/controller/cisco/nx/bordergateway_controller.go index 1e9b2324f..b77dced69 100644 --- a/internal/controller/cisco/nx/bordergateway_controller.go +++ b/internal/controller/cisco/nx/bordergateway_controller.go @@ -51,9 +51,6 @@ type BorderGatewayReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the bordergateway. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -88,22 +85,39 @@ func (r *BorderGatewayReconciler) Reconcile(ctx context.Context, req ctrl.Reques return ctrl.Result{}, err } - prov, ok := r.Provider().(Provider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + p, err := provider.Get(device.Spec.Provider) + if err != nil { if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.ErrorReason, - Message: "Invalid provider configured for BorderGateway reconciler", + Reason: v1alpha1.ProviderNotFoundReason, + Message: fmt.Sprintf("Provider %q is not registered", device.Spec.Provider), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } + prov, ok := p().(Provider) - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err + if !ok { + if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ + Type: v1alpha1.ReadyCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.NotImplementedReason, + Message: "Provider does not implement provider.BorderGatewayProvider", + }) { + return ctrl.Result{}, r.Status().Update(ctx, obj) + } + return ctrl.Result{}, nil } if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { diff --git a/internal/controller/cisco/nx/bordergateway_controller_test.go b/internal/controller/cisco/nx/bordergateway_controller_test.go index d6a8ec9c5..3fd3b21be 100644 --- a/internal/controller/cisco/nx/bordergateway_controller_test.go +++ b/internal/controller/cisco/nx/bordergateway_controller_test.go @@ -31,6 +31,7 @@ var _ = Describe("BorderGateway Controller", func() { Namespace: metav1.NamespaceDefault, }, Spec: v1alpha1.DeviceSpec{ + Provider: "test-provider", Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, diff --git a/internal/controller/cisco/nx/suite_test.go b/internal/controller/cisco/nx/suite_test.go index 215331851..1020aefb6 100644 --- a/internal/controller/cisco/nx/suite_test.go +++ b/internal/controller/cisco/nx/suite_test.go @@ -116,13 +116,12 @@ var _ = BeforeSuite(func() { _, err = k8sManager.GetCache().GetInformer(ctx, &coordinationv1.Lease{}) Expect(err).NotTo(HaveOccurred()) - prov := func() provider.Provider { return testProvider } + provider.Register("test-provider", func() provider.Provider { return testProvider }) err = (&SystemReconciler{ Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -131,7 +130,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: scheme.Scheme, Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -140,7 +138,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -153,7 +150,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, HeartbeatInterval: 10 * time.Minute, }).SetupWithManager(k8sManager) Expect(err).NotTo(HaveOccurred()) diff --git a/internal/controller/cisco/nx/system_controller.go b/internal/controller/cisco/nx/system_controller.go index 58551fea2..74f60836b 100644 --- a/internal/controller/cisco/nx/system_controller.go +++ b/internal/controller/cisco/nx/system_controller.go @@ -48,9 +48,6 @@ type SystemReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the system. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -85,22 +82,39 @@ func (r *SystemReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c return ctrl.Result{}, err } - prov, ok := r.Provider().(Provider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + p, err := provider.Get(device.Spec.Provider) + if err != nil { if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.ErrorReason, - Message: "Invalid provider configured for System reconciler", + Reason: v1alpha1.ProviderNotFoundReason, + Message: fmt.Sprintf("Provider %q is not registered", device.Spec.Provider), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } + prov, ok := p().(Provider) - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err + if !ok { + if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ + Type: v1alpha1.ReadyCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.NotImplementedReason, + Message: "Provider does not implement provider.SystemProvider", + }) { + return ctrl.Result{}, r.Status().Update(ctx, obj) + } + return ctrl.Result{}, nil } if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { diff --git a/internal/controller/cisco/nx/system_controller_test.go b/internal/controller/cisco/nx/system_controller_test.go index ff04a99a6..553267d5b 100644 --- a/internal/controller/cisco/nx/system_controller_test.go +++ b/internal/controller/cisco/nx/system_controller_test.go @@ -29,6 +29,7 @@ var _ = Describe("System Controller", func() { Namespace: metav1.NamespaceDefault, }, Spec: v1alpha1.DeviceSpec{ + Provider: "test-provider", Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, diff --git a/internal/controller/cisco/nx/vpcdomain_controller.go b/internal/controller/cisco/nx/vpcdomain_controller.go index a6320c5f0..bff820fa6 100644 --- a/internal/controller/cisco/nx/vpcdomain_controller.go +++ b/internal/controller/cisco/nx/vpcdomain_controller.go @@ -51,9 +51,6 @@ type VPCDomainReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the vPC - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker @@ -89,20 +86,39 @@ func (r *VPCDomainReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - prov, ok := r.Provider().(Provider) - if !ok { - meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + p, err := provider.Get(device.Spec.Provider) + if err != nil { + if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Invalid provider configured for VPCDomain reconciler", - }) - return ctrl.Result{}, r.Status().Update(ctx, obj) + Reason: v1alpha1.ProviderNotFoundReason, + Message: fmt.Sprintf("Provider %q is not registered", device.Spec.Provider), + }) { + return ctrl.Result{}, r.Status().Update(ctx, obj) + } + return ctrl.Result{}, nil } + prov, ok := p().(Provider) - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err + if !ok { + if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ + Type: v1alpha1.ReadyCondition, + Status: metav1.ConditionFalse, + Reason: v1alpha1.NotImplementedReason, + Message: "Provider does not implement provider.VPCDomainProvider", + }) { + return ctrl.Result{}, r.Status().Update(ctx, obj) + } + return ctrl.Result{}, nil } if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { diff --git a/internal/controller/cisco/nx/vpcdomain_controller_test.go b/internal/controller/cisco/nx/vpcdomain_controller_test.go index 860a82909..801d7469f 100644 --- a/internal/controller/cisco/nx/vpcdomain_controller_test.go +++ b/internal/controller/cisco/nx/vpcdomain_controller_test.go @@ -31,6 +31,7 @@ var _ = Describe("VPCDomain Controller", func() { Namespace: metav1.NamespaceDefault, }, Spec: corev1.DeviceSpec{ + Provider: "test-provider", Endpoint: corev1.Endpoint{ Address: "192.168.10.2:9339", }, @@ -208,7 +209,7 @@ var _ = Describe("VPCDomain Controller", func() { GenerateName: "vpc-dep-", Namespace: metav1.NamespaceDefault, }, - Spec: corev1.DeviceSpec{Endpoint: corev1.Endpoint{Address: "192.168.10.2:9339"}}, + Spec: corev1.DeviceSpec{Provider: "test-provider", Endpoint: corev1.Endpoint{Address: "192.168.10.2:9339"}}, } Expect(k8sClient.Create(ctx, deviceA)).To(Succeed()) name = deviceA.Name @@ -216,7 +217,7 @@ var _ = Describe("VPCDomain Controller", func() { By("Creating Device B") Expect(k8sClient.Create(ctx, &corev1.Device{ ObjectMeta: metav1.ObjectMeta{Name: name + "-b", Namespace: metav1.NamespaceDefault}, - Spec: corev1.DeviceSpec{Endpoint: corev1.Endpoint{Address: "192.168.10.3:9339"}}, + Spec: corev1.DeviceSpec{Provider: "test-provider", Endpoint: corev1.Endpoint{Address: "192.168.10.3:9339"}}, })).To(Succeed()) By("Creating physical interfaces on Device A and B") diff --git a/internal/controller/core/aaa_controller.go b/internal/controller/core/aaa_controller.go index c38b1061e..6bfec44f5 100644 --- a/internal/controller/core/aaa_controller.go +++ b/internal/controller/core/aaa_controller.go @@ -46,9 +46,6 @@ type AAAReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the AAA configuration. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -84,24 +81,32 @@ func (r *AAAReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.AAAProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.AAAProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.AAAProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{}, err } diff --git a/internal/controller/core/acl_controller.go b/internal/controller/core/acl_controller.go index eba3251ef..1e57a416b 100644 --- a/internal/controller/core/acl_controller.go +++ b/internal/controller/core/acl_controller.go @@ -49,9 +49,6 @@ type AccessControlListReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the accesscontrollist. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -86,24 +83,32 @@ func (r *AccessControlListReconciler) Reconcile(ctx context.Context, req ctrl.Re return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.ACLProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.ACLProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.AccessControlListProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/acl_controller_test.go b/internal/controller/core/acl_controller_test.go index 45718d5af..5e61ca42d 100644 --- a/internal/controller/core/acl_controller_test.go +++ b/internal/controller/core/acl_controller_test.go @@ -33,6 +33,7 @@ var _ = Describe("AccessControlList Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/banner_controller.go b/internal/controller/core/banner_controller.go index 9e474648e..37d31ea39 100644 --- a/internal/controller/core/banner_controller.go +++ b/internal/controller/core/banner_controller.go @@ -51,9 +51,6 @@ type BannerReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the banner. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -90,24 +87,32 @@ func (r *BannerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.BannerProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.BannerProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.BannerProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/banner_controller_test.go b/internal/controller/core/banner_controller_test.go index 411ccf2d4..3513a43d8 100644 --- a/internal/controller/core/banner_controller_test.go +++ b/internal/controller/core/banner_controller_test.go @@ -31,6 +31,7 @@ var _ = Describe("Banner Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/bgp_controller.go b/internal/controller/core/bgp_controller.go index 58e1f6892..066b2be72 100644 --- a/internal/controller/core/bgp_controller.go +++ b/internal/controller/core/bgp_controller.go @@ -56,9 +56,6 @@ type BGPReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the bgp. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker @@ -99,24 +96,32 @@ func (r *BGPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.BGPProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.BGPProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.BGPProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/bgp_controller_test.go b/internal/controller/core/bgp_controller_test.go index 2a70a6c2e..279751e35 100644 --- a/internal/controller/core/bgp_controller_test.go +++ b/internal/controller/core/bgp_controller_test.go @@ -30,6 +30,7 @@ var _ = Describe("BGP Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/bgp_peer_controller.go b/internal/controller/core/bgp_peer_controller.go index 72ec7ee4d..0671024cb 100644 --- a/internal/controller/core/bgp_peer_controller.go +++ b/internal/controller/core/bgp_peer_controller.go @@ -59,9 +59,6 @@ type BGPPeerReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the bgppeer. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker @@ -103,24 +100,32 @@ func (r *BGPPeerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.BGPPeerProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.BGPPeerProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.BGPPeerProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/bgp_peer_controller_test.go b/internal/controller/core/bgp_peer_controller_test.go index 767ad455e..691a427e6 100644 --- a/internal/controller/core/bgp_peer_controller_test.go +++ b/internal/controller/core/bgp_peer_controller_test.go @@ -31,6 +31,7 @@ var _ = Describe("BGPPeer Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/certificate_controller.go b/internal/controller/core/certificate_controller.go index 640a8c75d..276e10a70 100644 --- a/internal/controller/core/certificate_controller.go +++ b/internal/controller/core/certificate_controller.go @@ -51,9 +51,6 @@ type CertificateReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the certificate. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -89,24 +86,32 @@ func (r *CertificateReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.CertificateProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.CertificateProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.CertificateProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/certificate_controller_test.go b/internal/controller/core/certificate_controller_test.go index 501f8f0e5..35d9a3c1a 100644 --- a/internal/controller/core/certificate_controller_test.go +++ b/internal/controller/core/certificate_controller_test.go @@ -42,6 +42,7 @@ var _ = Describe("Certificate Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/configbackup_controller.go b/internal/controller/core/configbackup_controller.go index 74c9ba653..b3ccb897f 100644 --- a/internal/controller/core/configbackup_controller.go +++ b/internal/controller/core/configbackup_controller.go @@ -51,9 +51,6 @@ type ConfigBackupReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the config backup. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -80,24 +77,32 @@ func (r *ConfigBackupReconciler) Reconcile(ctx context.Context, req ctrl.Request return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.ConfigBackupProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.ConfigBackupProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.ConfigBackupProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/configbackup_controller_test.go b/internal/controller/core/configbackup_controller_test.go index a39ff0275..ec0e2be46 100644 --- a/internal/controller/core/configbackup_controller_test.go +++ b/internal/controller/core/configbackup_controller_test.go @@ -32,6 +32,7 @@ var _ = Describe("ConfigBackup Controller", func() { }, Spec: v1alpha1.DeviceSpec{ Endpoint: v1alpha1.Endpoint{Address: "192.168.10.2:9339"}, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/device_controller.go b/internal/controller/core/device_controller.go index 9fa361d08..2617baae4 100644 --- a/internal/controller/core/device_controller.go +++ b/internal/controller/core/device_controller.go @@ -50,7 +50,7 @@ type DeviceReconciler struct { Recorder events.EventRecorder // Provider is the driver that will be used to create & delete the interface. - Provider provider.ProviderFunc + // Provider provider.ProviderFunc // HeartbeatInterval is the duration after which the controller requeues the reconciliation, // regardless of changes. @@ -128,8 +128,7 @@ func (r *DeviceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c obj.Status.Phase = v1alpha1.DevicePhaseRunning return ctrl.Result{}, nil } - - if _, ok := r.Provider().(provider.ProvisioningProvider); !ok { + if _, err := provider.LoadProvider[provider.ProvisioningProvider](obj.Spec.Provider); err != nil { // Skip provisioning if the provider does not support it. log.Info("Provider does not support provisioning, skipping") obj.Status.Phase = v1alpha1.DevicePhaseFailed @@ -193,7 +192,11 @@ func (r *DeviceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c return ctrl.Result{}, nil } log.Info("Device provisioning completed, running post provisioning checks") - prov, _ := r.Provider().(provider.ProvisioningProvider) + prov, err := provider.LoadProvider[provider.ProvisioningProvider](obj.Spec.Provider) + if err != nil { + log.Error(err, "Failed to load provisioning provider") + return ctrl.Result{}, err + } if ok := prov.VerifyProvisioned(ctx, conn, obj); !ok { return ctrl.Result{RequeueAfter: r.HeartbeatInterval}, nil } @@ -203,7 +206,7 @@ func (r *DeviceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c return ctrl.Result{}, nil case v1alpha1.DevicePhaseRunning: - if prov, ok := r.Provider().(provider.DeviceProvider); ok { + if prov, err := provider.LoadProvider[provider.DeviceProvider](obj.Spec.Provider); err == nil { if err := r.reconcile(ctx, obj, prov, conn); err != nil { log.Error(err, "Failed to reconcile resource") return ctrl.Result{}, err @@ -384,7 +387,10 @@ func (r *DeviceReconciler) reconcile(ctx context.Context, device *v1alpha1.Devic } func (r *DeviceReconciler) reconcileMinimal(ctx context.Context, device *v1alpha1.Device, conn *deviceutil.Connection) (reterr error) { - prov := r.Provider() + prov, err := provider.LoadProvider[provider.DeviceProvider](device.Spec.Provider) + if err != nil { + return fmt.Errorf("failed to load device provider: %w", err) + } if err := prov.Connect(ctx, conn); err != nil { conditions.Set(device, metav1.Condition{ Type: v1alpha1.ReachableCondition, @@ -440,7 +446,11 @@ func (r *DeviceReconciler) reconcileMaintenance(ctx context.Context, obj *v1alph v1alpha1.DeviceMaintenanceFactoryReset, v1alpha1.DeviceMaintenanceReprovision: - prov := r.Provider() + prov, err := provider.LoadProvider[provider.DeviceProvider](obj.Spec.Provider) + if err != nil { + return fmt.Errorf("failed to load device provider: %w", err) + } + if err := prov.Connect(ctx, conn); err != nil { return fmt.Errorf("failed to connect to device: %w", err) } diff --git a/internal/controller/core/device_controller_test.go b/internal/controller/core/device_controller_test.go index bb4e5238a..375513a7e 100644 --- a/internal/controller/core/device_controller_test.go +++ b/internal/controller/core/device_controller_test.go @@ -70,6 +70,7 @@ var _ = Describe("Device Controller", func() { Name: name, }, }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -158,6 +159,7 @@ var _ = Describe("Device Controller", func() { Name: name, }, }, + Provider: "test-provider", Provisioning: &v1alpha1.Provisioning{ Image: v1alpha1.Image{ URL: "http://example.com/nxos.bin", @@ -202,6 +204,7 @@ var _ = Describe("Device Controller", func() { Name: name, }, }, + Provider: "test-provider", Provisioning: &v1alpha1.Provisioning{ Image: v1alpha1.Image{ URL: "http://example.com/nxos.bin", @@ -259,6 +262,7 @@ var _ = Describe("Device Controller", func() { Name: name, }, }, + Provider: "test-provider", Provisioning: &v1alpha1.Provisioning{ Image: v1alpha1.Image{ URL: "http://example.com/nxos.bin", @@ -319,6 +323,7 @@ var _ = Describe("Device Controller", func() { Name: name, }, }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -347,6 +352,7 @@ var _ = Describe("Device Controller", func() { Name: name, }, }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -402,6 +408,7 @@ var _ = Describe("Device Controller", func() { Name: name, }, }, + Provider: "test-provider", Provisioning: &v1alpha1.Provisioning{ BootScript: v1alpha1.TemplateSource{ Inline: new("boot nxos.bin"), @@ -489,6 +496,7 @@ var _ = Describe("Device Controller", func() { Name: name, }, }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -537,6 +545,7 @@ var _ = Describe("Device Controller", func() { Name: name, }, }, + Provider: "test-provider", Provisioning: &v1alpha1.Provisioning{ Image: v1alpha1.Image{ URL: "http://example.com/nxos.bin", @@ -590,6 +599,7 @@ var _ = Describe("Device Controller", func() { Name: name, }, }, + Provider: "test-provider", Provisioning: &v1alpha1.Provisioning{ Image: v1alpha1.Image{ URL: "http://example.com/nxos.bin", @@ -656,6 +666,7 @@ var _ = Describe("Device Controller", func() { Name: name, }, }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/dhcprelay_controller.go b/internal/controller/core/dhcprelay_controller.go index ec88c2a47..f4429bb1b 100644 --- a/internal/controller/core/dhcprelay_controller.go +++ b/internal/controller/core/dhcprelay_controller.go @@ -51,9 +51,6 @@ type DHCPRelayReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the dhcp relay configuration. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker @@ -92,24 +89,32 @@ func (r *DHCPRelayReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.DHCPRelayProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.DHCPRelayProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.DHCPRelayProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/dhcprelay_controller_test.go b/internal/controller/core/dhcprelay_controller_test.go index e11406d5c..9f7649b67 100644 --- a/internal/controller/core/dhcprelay_controller_test.go +++ b/internal/controller/core/dhcprelay_controller_test.go @@ -46,6 +46,7 @@ var _ = Describe("DHCPRelay Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.50:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -383,6 +384,7 @@ var _ = Describe("DHCPRelay Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.51:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -469,6 +471,7 @@ var _ = Describe("DHCPRelay Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.52:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -485,6 +488,7 @@ var _ = Describe("DHCPRelay Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.53:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, otherDevice)).To(Succeed()) @@ -627,6 +631,7 @@ var _ = Describe("DHCPRelay Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.57:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -643,6 +648,7 @@ var _ = Describe("DHCPRelay Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.58:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, otherDevice)).To(Succeed()) @@ -811,6 +817,7 @@ var _ = Describe("DHCPRelay Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.54:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -986,6 +993,7 @@ var _ = Describe("DHCPRelay Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.55:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/dns_controller.go b/internal/controller/core/dns_controller.go index 6abe047ad..e7d53fd82 100644 --- a/internal/controller/core/dns_controller.go +++ b/internal/controller/core/dns_controller.go @@ -49,9 +49,6 @@ type DNSReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the dns. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -86,24 +83,32 @@ func (r *DNSReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.DNSProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.DNSProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.DNSProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/dns_controller_test.go b/internal/controller/core/dns_controller_test.go index 4b5c86c94..fac4535bb 100644 --- a/internal/controller/core/dns_controller_test.go +++ b/internal/controller/core/dns_controller_test.go @@ -31,6 +31,7 @@ var _ = Describe("DNS Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/ethernetsegment_controller.go b/internal/controller/core/ethernetsegment_controller.go index 8f0a3ee21..f74bdaa9b 100644 --- a/internal/controller/core/ethernetsegment_controller.go +++ b/internal/controller/core/ethernetsegment_controller.go @@ -49,9 +49,6 @@ type EthernetSegmentReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the ethernetsegment. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker @@ -90,24 +87,32 @@ func (r *EthernetSegmentReconciler) Reconcile(ctx context.Context, req ctrl.Requ return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.EthernetSegmentProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.EthernetSegmentProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.EthernetSegmentProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/ethernetsegment_controller_test.go b/internal/controller/core/ethernetsegment_controller_test.go index bff49709e..4d948b941 100644 --- a/internal/controller/core/ethernetsegment_controller_test.go +++ b/internal/controller/core/ethernetsegment_controller_test.go @@ -32,6 +32,7 @@ var _ = Describe("EthernetSegment Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/evpninstance_controller.go b/internal/controller/core/evpninstance_controller.go index 724c293b4..3c37a8c84 100644 --- a/internal/controller/core/evpninstance_controller.go +++ b/internal/controller/core/evpninstance_controller.go @@ -49,9 +49,6 @@ type EVPNInstanceReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the evpninstance. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -88,24 +85,32 @@ func (r *EVPNInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Request return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.EVPNInstanceProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.EVPNInstanceProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.EVPNInstanceProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/evpninstance_controller_test.go b/internal/controller/core/evpninstance_controller_test.go index 1165bc63d..381445a01 100644 --- a/internal/controller/core/evpninstance_controller_test.go +++ b/internal/controller/core/evpninstance_controller_test.go @@ -32,6 +32,7 @@ var _ = Describe("EVPNInstance Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/interface_controller.go b/internal/controller/core/interface_controller.go index 8e5f7a46d..9ad0e66fd 100644 --- a/internal/controller/core/interface_controller.go +++ b/internal/controller/core/interface_controller.go @@ -54,9 +54,6 @@ type InterfaceReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the interface. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker @@ -98,24 +95,32 @@ func (r *InterfaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.InterfaceProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.InterfaceProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.InterfaceProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/interface_controller_test.go b/internal/controller/core/interface_controller_test.go index ab497fb8a..117e115e3 100644 --- a/internal/controller/core/interface_controller_test.go +++ b/internal/controller/core/interface_controller_test.go @@ -35,6 +35,7 @@ var _ = Describe("Interface Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -1282,6 +1283,7 @@ var _ = Describe("Interface Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.10:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, localDevice)).To(Succeed()) @@ -1295,6 +1297,7 @@ var _ = Describe("Interface Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.11:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, remoteDevice)).To(Succeed()) diff --git a/internal/controller/core/isis_controller.go b/internal/controller/core/isis_controller.go index 00c77033b..5dc391e17 100644 --- a/internal/controller/core/isis_controller.go +++ b/internal/controller/core/isis_controller.go @@ -50,9 +50,6 @@ type ISISReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the isis. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -87,24 +84,32 @@ func (r *ISISReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.ISISProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.ISISProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.ISISProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/isis_controller_test.go b/internal/controller/core/isis_controller_test.go index f6d635ac1..6bacae7ed 100644 --- a/internal/controller/core/isis_controller_test.go +++ b/internal/controller/core/isis_controller_test.go @@ -31,6 +31,7 @@ var _ = Describe("ISIS Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -136,6 +137,7 @@ var _ = Describe("ISIS Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/lldp_controller.go b/internal/controller/core/lldp_controller.go index 4a4471b45..446f4e366 100644 --- a/internal/controller/core/lldp_controller.go +++ b/internal/controller/core/lldp_controller.go @@ -51,9 +51,6 @@ type LLDPReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the LLDP. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker @@ -87,24 +84,32 @@ func (r *LLDPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.LLDPProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.LLDPProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider LLDPProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/lldp_controller_test.go b/internal/controller/core/lldp_controller_test.go index 8b17a1c8e..a0a4a8966 100644 --- a/internal/controller/core/lldp_controller_test.go +++ b/internal/controller/core/lldp_controller_test.go @@ -37,6 +37,7 @@ var _ = Describe("LLDP Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -328,6 +329,7 @@ var _ = Describe("LLDP Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.6:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -452,6 +454,7 @@ var _ = Describe("LLDP Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -599,6 +602,7 @@ var _ = Describe("LLDP Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.3:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -682,6 +686,7 @@ var _ = Describe("LLDP Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.99:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, otherDevice)).To(Succeed()) @@ -833,6 +838,7 @@ var _ = Describe("LLDP Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.4:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -1074,6 +1080,7 @@ var _ = Describe("LLDP Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.7:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -1263,6 +1270,7 @@ var _ = Describe("LLDP Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.8:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/managementaccess_controller.go b/internal/controller/core/managementaccess_controller.go index 1cd63f6d2..d29fe2a36 100644 --- a/internal/controller/core/managementaccess_controller.go +++ b/internal/controller/core/managementaccess_controller.go @@ -49,9 +49,6 @@ type ManagementAccessReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the managementaccess. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -86,24 +83,32 @@ func (r *ManagementAccessReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.ManagementAccessProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.ManagementAccessProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.ManagementAccessProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/managementaccess_controller_test.go b/internal/controller/core/managementaccess_controller_test.go index d5b10b730..224759d1f 100644 --- a/internal/controller/core/managementaccess_controller_test.go +++ b/internal/controller/core/managementaccess_controller_test.go @@ -31,6 +31,7 @@ var _ = Describe("ManagementAccess Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/ntp_controller.go b/internal/controller/core/ntp_controller.go index 806799a50..8f3dfcc3b 100644 --- a/internal/controller/core/ntp_controller.go +++ b/internal/controller/core/ntp_controller.go @@ -49,9 +49,6 @@ type NTPReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the ntp. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -86,24 +83,32 @@ func (r *NTPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.NTPProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.NTPProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.NTPProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/ntp_controller_test.go b/internal/controller/core/ntp_controller_test.go index 972e9c75b..6eb7e3d5a 100644 --- a/internal/controller/core/ntp_controller_test.go +++ b/internal/controller/core/ntp_controller_test.go @@ -31,6 +31,7 @@ var _ = Describe("NTP Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/nve_controller.go b/internal/controller/core/nve_controller.go index bb032f748..7b4d97e35 100644 --- a/internal/controller/core/nve_controller.go +++ b/internal/controller/core/nve_controller.go @@ -51,9 +51,6 @@ type NetworkVirtualizationEdgeReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the dns. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker @@ -87,24 +84,32 @@ func (r *NetworkVirtualizationEdgeReconciler) Reconcile(ctx context.Context, req return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.NVEProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.NVEProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider NVEProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/nve_controller_test.go b/internal/controller/core/nve_controller_test.go index 7accaf843..3e6d920e0 100644 --- a/internal/controller/core/nve_controller_test.go +++ b/internal/controller/core/nve_controller_test.go @@ -37,6 +37,7 @@ var _ = Describe("NVE Controller", func() { }, Spec: v1alpha1.DeviceSpec{ Endpoint: v1alpha1.Endpoint{Address: testEndpointAddr}, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -206,6 +207,7 @@ var _ = Describe("NVE Controller", func() { }, Spec: v1alpha1.DeviceSpec{ Endpoint: v1alpha1.Endpoint{Address: testEndpointAddr}, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -325,6 +327,7 @@ var _ = Describe("NVE Controller", func() { }, Spec: v1alpha1.DeviceSpec{ Endpoint: v1alpha1.Endpoint{Address: testEndpointAddr}, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -399,6 +402,7 @@ var _ = Describe("NVE Controller", func() { }, Spec: v1alpha1.DeviceSpec{ Endpoint: v1alpha1.Endpoint{Address: testEndpointAddr}, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -494,6 +498,7 @@ var _ = Describe("NVE Controller", func() { }, Spec: v1alpha1.DeviceSpec{ Endpoint: v1alpha1.Endpoint{Address: testEndpointAddr}, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -603,6 +608,7 @@ var _ = Describe("NVE Controller", func() { }, Spec: v1alpha1.DeviceSpec{ Endpoint: v1alpha1.Endpoint{Address: testEndpointAddr}, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -703,6 +709,7 @@ var _ = Describe("NVE Controller", func() { }, Spec: v1alpha1.DeviceSpec{ Endpoint: v1alpha1.Endpoint{Address: testEndpointAddr}, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -717,6 +724,7 @@ var _ = Describe("NVE Controller", func() { }, Spec: v1alpha1.DeviceSpec{ Endpoint: v1alpha1.Endpoint{Address: testEndpointAddr}, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device2)).To(Succeed()) @@ -823,6 +831,7 @@ var _ = Describe("NVE Controller", func() { }, Spec: v1alpha1.DeviceSpec{ Endpoint: v1alpha1.Endpoint{Address: testEndpointAddr}, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/ospf_controller.go b/internal/controller/core/ospf_controller.go index 2e69c38fe..5beee572b 100644 --- a/internal/controller/core/ospf_controller.go +++ b/internal/controller/core/ospf_controller.go @@ -52,9 +52,6 @@ type OSPFReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the ospf. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker @@ -93,24 +90,32 @@ func (r *OSPFReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.OSPFProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.OSPFProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.OSPFProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/ospf_controller_test.go b/internal/controller/core/ospf_controller_test.go index e7c7d0a9c..01c0a808b 100644 --- a/internal/controller/core/ospf_controller_test.go +++ b/internal/controller/core/ospf_controller_test.go @@ -32,6 +32,7 @@ var _ = Describe("OSPF Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -135,6 +136,7 @@ var _ = Describe("OSPF Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.3:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/pim_controller.go b/internal/controller/core/pim_controller.go index 5bd8d03ea..cc01596ba 100644 --- a/internal/controller/core/pim_controller.go +++ b/internal/controller/core/pim_controller.go @@ -50,9 +50,6 @@ type PIMReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the pim. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -87,24 +84,32 @@ func (r *PIMReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.PIMProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.PIMProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.PIMProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/pim_controller_test.go b/internal/controller/core/pim_controller_test.go index 427e58d41..e4b8c7afa 100644 --- a/internal/controller/core/pim_controller_test.go +++ b/internal/controller/core/pim_controller_test.go @@ -32,6 +32,7 @@ var _ = Describe("PIM Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) @@ -129,6 +130,7 @@ var _ = Describe("PIM Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/prefixset_controller.go b/internal/controller/core/prefixset_controller.go index 73a220631..7b020087d 100644 --- a/internal/controller/core/prefixset_controller.go +++ b/internal/controller/core/prefixset_controller.go @@ -49,9 +49,6 @@ type PrefixSetReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the prefixset. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -86,24 +83,32 @@ func (r *PrefixSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.PrefixSetProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.PrefixSetProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.PrefixSetProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/prefixset_controller_test.go b/internal/controller/core/prefixset_controller_test.go index 540a886ff..e132c59f6 100644 --- a/internal/controller/core/prefixset_controller_test.go +++ b/internal/controller/core/prefixset_controller_test.go @@ -32,6 +32,7 @@ var _ = Describe("PrefixSet Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/routingpolicy_controller.go b/internal/controller/core/routingpolicy_controller.go index 63acc8c37..0b190ec72 100644 --- a/internal/controller/core/routingpolicy_controller.go +++ b/internal/controller/core/routingpolicy_controller.go @@ -49,9 +49,6 @@ type RoutingPolicyReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the routingpolicy. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -86,24 +83,32 @@ func (r *RoutingPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Reques return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.RoutingPolicyProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.RoutingPolicyProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.RoutingPolicyProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/routingpolicy_controller_test.go b/internal/controller/core/routingpolicy_controller_test.go index d9579f6da..43b6349df 100644 --- a/internal/controller/core/routingpolicy_controller_test.go +++ b/internal/controller/core/routingpolicy_controller_test.go @@ -34,6 +34,7 @@ var _ = Describe("RoutingPolicy Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/snmp_controller.go b/internal/controller/core/snmp_controller.go index 2c2012e62..05ea6599b 100644 --- a/internal/controller/core/snmp_controller.go +++ b/internal/controller/core/snmp_controller.go @@ -49,9 +49,6 @@ type SNMPReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the snmp. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -86,24 +83,32 @@ func (r *SNMPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.SNMPProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.SNMPProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.SNMPProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/snmp_controller_test.go b/internal/controller/core/snmp_controller_test.go index 145dfee47..c57ad388c 100644 --- a/internal/controller/core/snmp_controller_test.go +++ b/internal/controller/core/snmp_controller_test.go @@ -31,6 +31,7 @@ var _ = Describe("SNMP Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/suite_test.go b/internal/controller/core/suite_test.go index 051a89864..e9ac6eebf 100644 --- a/internal/controller/core/suite_test.go +++ b/internal/controller/core/suite_test.go @@ -117,13 +117,12 @@ var _ = BeforeSuite(func() { _, err = k8sManager.GetCache().GetInformer(ctx, &coordinationv1.Lease{}) Expect(err).NotTo(HaveOccurred()) - prov := func() provider.Provider { return testProvider } + provider.Register("test-provider", func() provider.Provider { return testProvider }) err = (&DeviceReconciler{ Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, HeartbeatInterval: time.Second, }).SetupWithManager(k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -132,7 +131,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, RequeueInterval: time.Second, }).SetupWithManager(ctx, k8sManager) @@ -142,7 +140,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -151,7 +148,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -160,7 +156,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -169,7 +164,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -178,7 +172,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -187,7 +180,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -196,7 +188,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -205,7 +196,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -214,7 +204,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -223,7 +212,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -232,7 +220,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -241,7 +228,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -250,7 +236,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, RequeueInterval: time.Second, }).SetupWithManager(ctx, k8sManager) @@ -260,7 +245,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, RequeueInterval: time.Second, }).SetupWithManager(ctx, k8sManager) @@ -270,7 +254,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, RequeueInterval: time.Second, }).SetupWithManager(ctx, k8sManager) @@ -280,7 +263,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, RequeueInterval: time.Second, }).SetupWithManager(ctx, k8sManager) @@ -290,7 +272,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -299,7 +280,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, RequeueInterval: time.Second, }).SetupWithManager(ctx, k8sManager) @@ -309,7 +289,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -318,7 +297,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -327,7 +305,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, RequeueInterval: time.Second, }).SetupWithManager(ctx, k8sManager) @@ -337,7 +314,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, RequeueInterval: time.Second, }).SetupWithManager(ctx, k8sManager) @@ -347,7 +323,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, }).SetupWithManager(ctx, k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -356,7 +331,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: recorder, - Provider: prov, Locker: testLocker, RequeueInterval: time.Second, }).SetupWithManager(ctx, k8sManager) diff --git a/internal/controller/core/syslog_controller.go b/internal/controller/core/syslog_controller.go index 12d8dd364..06a37c161 100644 --- a/internal/controller/core/syslog_controller.go +++ b/internal/controller/core/syslog_controller.go @@ -49,9 +49,6 @@ type SyslogReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the syslog. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -86,24 +83,32 @@ func (r *SyslogReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ c return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.SyslogProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.SyslogProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.SyslogProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/syslog_controller_test.go b/internal/controller/core/syslog_controller_test.go index 8e477ca06..37d40d6f6 100644 --- a/internal/controller/core/syslog_controller_test.go +++ b/internal/controller/core/syslog_controller_test.go @@ -31,6 +31,7 @@ var _ = Describe("Syslog Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/user_controller.go b/internal/controller/core/user_controller.go index ff16800a3..6b5038e79 100644 --- a/internal/controller/core/user_controller.go +++ b/internal/controller/core/user_controller.go @@ -51,9 +51,6 @@ type UserReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the user. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -89,24 +86,32 @@ func (r *UserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.UserProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.UserProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.UserProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/user_controller_test.go b/internal/controller/core/user_controller_test.go index 94cf845e6..f433d9cde 100644 --- a/internal/controller/core/user_controller_test.go +++ b/internal/controller/core/user_controller_test.go @@ -33,6 +33,7 @@ var _ = Describe("User Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/vlan_controller.go b/internal/controller/core/vlan_controller.go index c86866302..9a37a00f6 100644 --- a/internal/controller/core/vlan_controller.go +++ b/internal/controller/core/vlan_controller.go @@ -49,9 +49,6 @@ type VLANReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the vlan. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker @@ -90,24 +87,32 @@ func (r *VLANReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.VLANProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.VLANProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.VLANProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/vlan_controller_test.go b/internal/controller/core/vlan_controller_test.go index edc1b8cd5..e9fb2b95f 100644 --- a/internal/controller/core/vlan_controller_test.go +++ b/internal/controller/core/vlan_controller_test.go @@ -32,6 +32,7 @@ var _ = Describe("VLAN Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/controller/core/vrf_controller.go b/internal/controller/core/vrf_controller.go index 05a1cb30a..1a2592559 100644 --- a/internal/controller/core/vrf_controller.go +++ b/internal/controller/core/vrf_controller.go @@ -49,9 +49,6 @@ type VRFReconciler struct { // More info: https://book.kubebuilder.io/reference/raising-events Recorder events.EventRecorder - // Provider is the driver that will be used to create & delete the isis. - Provider provider.ProviderFunc - // Locker is used to synchronize operations on resources targeting the same device. Locker *resourcelock.ResourceLocker } @@ -88,24 +85,32 @@ func (r *VRFReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl return ctrl.Result{}, err } - prov, ok := r.Provider().(provider.VRFProvider) - if !ok { + device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) + if err != nil { + return ctrl.Result{}, err + } + + if device.Spec.Provider == "" { + return ctrl.Result{}, fmt.Errorf("device %q has no provider set", device.Name) + } + + prov, err := provider.LoadProvider[provider.VRFProvider](device.Spec.Provider) + if err != nil { + reason := v1alpha1.NotImplementedReason + if errors.Is(err, provider.NotFoundError{}) { + reason = v1alpha1.ProviderNotFoundReason + } if meta.SetStatusCondition(&obj.Status.Conditions, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, - Reason: v1alpha1.NotImplementedReason, - Message: "Provider does not implement provider.VRFProvider", + Reason: reason, + Message: err.Error(), }) { return ctrl.Result{}, r.Status().Update(ctx, obj) } return ctrl.Result{}, nil } - device, err := deviceutil.GetDeviceByName(ctx, r, obj.Namespace, obj.Spec.DeviceRef.Name) - if err != nil { - return ctrl.Result{}, err - } - if isPaused, requeue, err := paused.EnsureCondition(ctx, r.Client, device, obj); isPaused || requeue || err != nil { return ctrl.Result{Requeue: requeue}, err } diff --git a/internal/controller/core/vrf_controller_test.go b/internal/controller/core/vrf_controller_test.go index e9d76ff51..3b6c3e4a8 100644 --- a/internal/controller/core/vrf_controller_test.go +++ b/internal/controller/core/vrf_controller_test.go @@ -33,6 +33,7 @@ var _ = Describe("VRF Controller", func() { Endpoint: v1alpha1.Endpoint{ Address: "192.168.10.2:9339", }, + Provider: "test-provider", }, } Expect(k8sClient.Create(ctx, device)).To(Succeed()) diff --git a/internal/provider/cisco/iosxr/provider.go b/internal/provider/cisco/iosxr/provider.go index 39d9aa68a..0d8d246b4 100644 --- a/internal/provider/cisco/iosxr/provider.go +++ b/internal/provider/cisco/iosxr/provider.go @@ -612,5 +612,5 @@ func (p *Provider) DeletePrefixSet(ctx context.Context, req *provider.PrefixSetR } func init() { - provider.Register("cisco-iosxr-gnmi", NewProvider) + provider.Register("iosxr.cisco.networking.metal.ironcore.dev", NewProvider) } diff --git a/internal/provider/cisco/nxos/provider.go b/internal/provider/cisco/nxos/provider.go index 83a54ecd3..02bbf55f0 100644 --- a/internal/provider/cisco/nxos/provider.go +++ b/internal/provider/cisco/nxos/provider.go @@ -4016,5 +4016,5 @@ func (p *Provider) DeleteAAA(ctx context.Context, req *provider.DeleteAAARequest } func init() { - provider.Register("cisco-nxos-gnmi", NewProvider) + provider.Register("nx.cisco.networking.metal.ironcore.dev", NewProvider) } diff --git a/internal/provider/openconfig/provider.go b/internal/provider/openconfig/provider.go index 6ad131ff0..6f7becc2c 100644 --- a/internal/provider/openconfig/provider.go +++ b/internal/provider/openconfig/provider.go @@ -55,5 +55,5 @@ func (p *Provider) Disconnect(_ context.Context, _ *deviceutil.Connection) error } func init() { - provider.Register("openconfig", NewProvider) + provider.Register("openconfig.networking.metal.ironcore.dev", NewProvider) } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 1ad8a2f18..f81847804 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -51,6 +51,8 @@ type MaintenanceProvider interface { // ProvisioningProvider is the interface for the realization of the provisioning-related operations over different providers. type ProvisioningProvider interface { + Provider + // Reprovision prepares the device for reprovisioning by resetting it and reenabling provisioning mechanisms. Reprovision(context.Context, *deviceutil.Connection) error // HashProvisioningPassword takes a plaintext password and returns the hashed password along with the hash type. @@ -899,3 +901,45 @@ type ProviderConfig struct { //nolint:revive // stutter is intentional; Provider func (p ProviderConfig) Into(v any) error { return runtime.DefaultUnstructuredConverter.FromUnstructured(p.obj.Object, v) } + +type NotFoundError struct { + Message string +} + +type NotImplementedError struct { + Message string +} + +func (e NotImplementedError) Error() string { + return e.Message +} + +func (e NotFoundError) Error() string { + return e.Message +} + +func (e NotFoundError) Is(target error) bool { + _, ok := target.(NotFoundError) + return ok +} + +func (e NotImplementedError) Is(target error) bool { + _, ok := target.(NotImplementedError) + return ok +} + +// LoadProvider returns a provider instance cast to the requested interface type T. +// Returns NotFoundError if the provider is not registered, NotImplementedError if it does not implement T. +func LoadProvider[T Provider](providerName string) (T, error) { + prov, err := Get(providerName) + if err != nil { + var zero T + return zero, NotFoundError{Message: fmt.Sprintf("Provider %q is not registered", providerName)} + } + genericProvider, ok := prov().(T) + if !ok { + var zero T + return zero, NotImplementedError{Message: fmt.Sprintf("provider %q does not implement %T", providerName, zero)} + } + return genericProvider, nil +} diff --git a/internal/provisioning/http.go b/internal/provisioning/http.go index a414297b2..318fa66f8 100644 --- a/internal/provisioning/http.go +++ b/internal/provisioning/http.go @@ -91,8 +91,8 @@ type HTTPServer struct { Mux *http.ServeMux Recorder events.EventRecorder ValidateSourceIP bool - Provider provider.ProvisioningProvider - Port int + // Provider provider.ProvisioningProvider + Port int } func (s *HTTPServer) Start(ctx context.Context) error { @@ -306,7 +306,14 @@ func (s *HTTPServer) HandleProvisioningRequest(w http.ResponseWriter, r *http.Re return } - hashedPassword, hashAlgorithm, err := s.Provider.HashProvisioningPassword(string(pass)) + prov, err := provider.LoadProvider[provider.ProvisioningProvider](device.Spec.Provider) + if err != nil { + s.Logger.Error(err, "Failed to get provider", "provider", device.Spec.Provider, "device", device.Name, "error", err) + http.Error(w, "Failed to get provider", http.StatusPreconditionRequired) + return + } + + hashedPassword, hashAlgorithm, err := prov.HashProvisioningPassword(string(pass)) if err != nil { s.Logger.Error(err, "Failed to hash provisioning password", "device", device.Name) http.Error(w, "Failed to hash provisioning password", http.StatusInternalServerError) diff --git a/internal/provisioning/http_test.go b/internal/provisioning/http_test.go index 4b5477552..345e50f55 100644 --- a/internal/provisioning/http_test.go +++ b/internal/provisioning/http_test.go @@ -27,6 +27,7 @@ import ( "github.com/ironcore-dev/network-operator/api/core/v1alpha1" "github.com/ironcore-dev/network-operator/internal/deviceutil" + "github.com/ironcore-dev/network-operator/internal/provider" ) var ( @@ -39,6 +40,7 @@ var ( }, }, Spec: v1alpha1.DeviceSpec{ + Provider: "test-provider", Endpoint: v1alpha1.Endpoint{ Address: "192.168.1.100:22", SecretRef: &v1alpha1.SecretReference{ @@ -79,6 +81,14 @@ type MockProvider struct { mock.Mock } +func (m *MockProvider) Connect(ctx context.Context, conn *deviceutil.Connection) error { + return nil +} + +func (m *MockProvider) Disconnect(ctx context.Context, conn *deviceutil.Connection) error { + return nil +} + func (m *MockProvider) HashProvisioningPassword(password string) (string, string, error) { return "hashedpass", "sha256", nil } @@ -419,6 +429,7 @@ func TestHandleProvisioningRequest(t *testing.T) { }, Spec: v1alpha1.DeviceSpec{ Endpoint: v1alpha1.Endpoint{Address: "192.168.1.200:22"}, + Provider: "test-provider", }, Status: v1alpha1.DeviceStatus{SerialNumber: "ABC123"}, }, @@ -437,6 +448,7 @@ func TestHandleProvisioningRequest(t *testing.T) { Labels: map[string]string{v1alpha1.DeviceSerialLabel: "ABC123"}, }, Spec: v1alpha1.DeviceSpec{ + Provider: "test-provider", Endpoint: v1alpha1.Endpoint{ Address: "192.168.1.100:22", SecretRef: &v1alpha1.SecretReference{ @@ -506,7 +518,6 @@ func TestHandleProvisioningRequest(t *testing.T) { Client: k8sClient, Logger: klog.NewKlogr(), ValidateSourceIP: tt.validateSourceIP, - Provider: new(MockProvider), } rr := httptest.NewRecorder() @@ -986,6 +997,7 @@ func TestGetMTLSClientCA(t *testing.T) { Labels: map[string]string{v1alpha1.DeviceSerialLabel: "ABC123"}, }, Spec: v1alpha1.DeviceSpec{ + Provider: "test-provider", Endpoint: v1alpha1.Endpoint{ Address: "192.168.1.100:22", }, @@ -1010,6 +1022,7 @@ func TestGetMTLSClientCA(t *testing.T) { Labels: map[string]string{v1alpha1.DeviceSerialLabel: "ABC123"}, }, Spec: v1alpha1.DeviceSpec{ + Provider: "test-provider", Endpoint: v1alpha1.Endpoint{ Address: "192.168.1.100:22", TLS: &v1alpha1.TLS{ @@ -1042,6 +1055,7 @@ func TestGetMTLSClientCA(t *testing.T) { Labels: map[string]string{v1alpha1.DeviceSerialLabel: "ABC123"}, }, Spec: v1alpha1.DeviceSpec{ + Provider: "test-provider", Endpoint: v1alpha1.Endpoint{ Address: "192.168.1.100:22", TLS: &v1alpha1.TLS{ @@ -1073,6 +1087,7 @@ func TestGetMTLSClientCA(t *testing.T) { Labels: map[string]string{v1alpha1.DeviceSerialLabel: "ABC123"}, }, Spec: v1alpha1.DeviceSpec{ + Provider: "test-provider", Endpoint: v1alpha1.Endpoint{ Address: "192.168.1.100:22", TLS: &v1alpha1.TLS{ @@ -1152,4 +1167,5 @@ func TestGetMTLSClientCA(t *testing.T) { func init() { utilruntime.Must(v1alpha1.AddToScheme(scheme.Scheme)) + provider.Register("test-provider", func() provider.Provider { return &MockProvider{} }) }