diff --git a/pkg/operator/controller/callbacks.go b/pkg/operator/controller/callbacks.go index 5df5251215..685f29b45b 100644 --- a/pkg/operator/controller/callbacks.go +++ b/pkg/operator/controller/callbacks.go @@ -52,7 +52,6 @@ import ( func addReconcileCallbacks(r *ReconcileCDI) { r.reconciler.AddCallback(&appsv1.Deployment{}, reconcileDeleteControllerDeployment) r.reconciler.AddCallback(&corev1.ServiceAccount{}, reconcileSCC) - r.reconciler.AddCallback(&appsv1.Deployment{}, reconcileCreateRoute) r.reconciler.AddCallback(&appsv1.Deployment{}, reconcileCreatePrometheusInfra) r.reconciler.AddCallback(&appsv1.Deployment{}, reconcileRemainingRelationshipLabels) r.reconciler.AddCallback(&appsv1.Deployment{}, reconcileDeleteDeprecatedResources) @@ -60,6 +59,9 @@ func addReconcileCallbacks(r *ReconcileCDI) { r.reconciler.AddCallback(&appsv1.Deployment{}, reconcilePvcMutatingWebhook) r.reconciler.AddCallback(&extv1.CustomResourceDefinition{}, reconcileSetConfigAuthority) r.reconciler.AddCallback(&extv1.CustomResourceDefinition{}, reconcileHandleOldVersion) + if r.haveRoutes { + r.reconciler.AddCallback(&appsv1.Deployment{}, reconcileRoute) + } } func isControllerDeployment(d *appsv1.Deployment) bool { @@ -109,7 +111,7 @@ func reconcileDeleteControllerDeployment(args *callbacks.ReconcileCallbackArgs) return nil } -func reconcileCreateRoute(args *callbacks.ReconcileCallbackArgs) error { +func reconcileRoute(args *callbacks.ReconcileCallbackArgs) error { if args.State != callbacks.ReconcileStatePostRead { return nil } @@ -126,6 +128,10 @@ func reconcileCreateRoute(args *callbacks.ReconcileCallbackArgs) error { } args.Recorder.Event(cr, corev1.EventTypeNormal, createResourceSuccess, "Successfully ensured upload proxy route exists") + if err := updateUserRoutes(context.TODO(), args.Logger, args.Client, args.Recorder); err != nil { + return err + } + return nil } diff --git a/pkg/operator/controller/controller.go b/pkg/operator/controller/controller.go index 49e993f9ab..2c0eeb7a1f 100644 --- a/pkg/operator/controller/controller.go +++ b/pkg/operator/controller/controller.go @@ -69,6 +69,9 @@ const ( deleteResourceFailed = "DeleteResourceFailed" deleteResourceSuccess = "DeleteResourceSuccess" dumpInstallStrategyKey = "DUMP_INSTALL_STRATEGY" + + annInjectUploadProxyCert = "operator.cdi.kubevirt.io/injectUploadProxyCert" + updateUserRouteSuccess = "UploadProxyRouteInjectSuccess" ) var ( @@ -139,6 +142,11 @@ func newReconciler(mgr manager.Manager) (*ReconcileCDI, error) { recorder := mgr.GetEventRecorderFor("operator-controller") + haveRoutes, err := haveRoutes(uncachedClient) + if err != nil { + return nil, err + } + r := &ReconcileCDI{ client: restClient, uncachedClient: uncachedClient, @@ -149,6 +157,7 @@ func newReconciler(mgr manager.Manager) (*ReconcileCDI, error) { clusterArgs: clusterArgs, namespacedArgs: &namespacedArgs, dumpInstallStrategy: dumpInstallStrategy, + haveRoutes: haveRoutes, } callbackDispatcher := callbacks.NewCallbackDispatcher(log, restClient, uncachedClient, scheme, namespace) r.reconciler = sdkr.NewReconciler(r, log, restClient, callbackDispatcher, scheme, mgr.GetCache, createVersionLabel, updateVersionLabel, LastAppliedConfigAnnotation, certPollInterval, finalizerName, false, recorder) @@ -181,6 +190,7 @@ type ReconcileCDI struct { certManager CertManager reconciler *sdkr.Reconciler dumpInstallStrategy bool + haveRoutes bool } // SetController sets the controller dependency diff --git a/pkg/operator/controller/controller_test.go b/pkg/operator/controller/controller_test.go index e5e3bc2a19..4ece2d18a2 100644 --- a/pkg/operator/controller/controller_test.go +++ b/pkg/operator/controller/controller_test.go @@ -282,6 +282,35 @@ var _ = Describe("Controller", func() { validateEvents(args.reconciler, createReadyEventValidationMap()) }) + It("should update existing route", func() { + route := &routev1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: "user-route", + Namespace: cdiNamespace, + Annotations: map[string]string{ + "operator.cdi.kubevirt.io/injectUploadProxyCert": "true", + }, + }, + Spec: routev1.RouteSpec{ + TLS: &routev1.TLSConfig{}, + }, + } + + args := createArgs(route) + doReconcile(args) + Expect(setDeploymentsReady(args)).To(BeTrue()) + + obj, err := getObject(args.client, route) + Expect(err).ToNot(HaveOccurred()) + route = obj.(*routev1.Route) + + Expect(route.Spec.TLS.DestinationCACertificate).Should(Equal(testCertData)) + + eventMap := createReadyEventValidationMap() + eventMap["Normal UploadProxyRouteInjectSuccess Successfully updated Route destination CA certificate"] = false + validateEvents(args.reconciler, eventMap) + }) + It("should have CDIOperatorDown", func() { args := createArgs() doReconcile(args) @@ -1635,9 +1664,10 @@ func createFromArgs(version string) *args { } } -func createArgs() *args { +func createArgs(objs ...client.Object) *args { cdi := createCDI("cdi", "good uid") - client := createClient(cdi) + objs = append(objs, cdi) + client := createClient(objs...) reconciler := createReconciler(client) return &args{ @@ -1753,6 +1783,7 @@ func createReconciler(client client.Client) *ReconcileCDI { clusterArgs: clusterArgs, namespacedArgs: namespacedArgs, certManager: newFakeCertManager(client, namespace), + haveRoutes: true, } callbackDispatcher := callbacks.NewCallbackDispatcher(log, client, client, scheme.Scheme, namespace) getCache := func() cache.Cache { diff --git a/pkg/operator/controller/route.go b/pkg/operator/controller/route.go index f8c0c3e2a0..1a18fd8cdc 100644 --- a/pkg/operator/controller/route.go +++ b/pkg/operator/controller/route.go @@ -28,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -49,21 +50,15 @@ func ensureUploadProxyRouteExists(ctx context.Context, logger logr.Logger, c cli return fmt.Errorf("cluster scoped owner not supported") } - cm := &corev1.ConfigMap{} - key := client.ObjectKey{Namespace: namespace, Name: uploadProxyCABundle} - if err := c.Get(ctx, key, cm); err != nil { + cert, err := getUploadProxyCABundle(ctx, c) + if err != nil { if errors.IsNotFound(err) { - logger.V(3).Info("upload proxy ca cert doesn't exist yet") + logger.V(3).Info("ensureUploadProxyRouteExists() upload proxy ca cert doesn't exist") return nil } return err } - cert, exists := cm.Data["ca-bundle.crt"] - if !exists { - return fmt.Errorf("unexpected ConfigMap format, 'ca-bundle.crt' key missing") - } - cr, err := cc.GetActiveCDI(ctx, c) if err != nil { return err @@ -100,7 +95,7 @@ func ensureUploadProxyRouteExists(ctx context.Context, logger logr.Logger, c cli util.SetRecommendedLabels(desiredRoute, installerLabels, "cdi-operator") currentRoute := &routev1.Route{} - key = client.ObjectKey{Namespace: namespace, Name: uploadProxyRouteName} + key := client.ObjectKey{Namespace: namespace, Name: uploadProxyRouteName} err = c.Get(ctx, key, currentRoute) if err == nil { if currentRoute.Spec.To.Kind != desiredRoute.Spec.To.Kind || @@ -115,12 +110,6 @@ func ensureUploadProxyRouteExists(ctx context.Context, logger logr.Logger, c cli return nil } - if meta.IsNoMatchError(err) { - // not in openshift - logger.V(3).Info("No match error for Route, must not be in openshift") - return nil - } - if !errors.IsNotFound(err) { return err } @@ -132,18 +121,75 @@ func ensureUploadProxyRouteExists(ctx context.Context, logger logr.Logger, c cli return c.Create(ctx, desiredRoute) } -func (r *ReconcileCDI) watchRoutes() error { - err := r.uncachedClient.List(context.TODO(), &routev1.RouteList{}, &client.ListOptions{ +func updateUserRoutes(ctx context.Context, logger logr.Logger, c client.Client, recorder record.EventRecorder) error { + cert, err := getUploadProxyCABundle(ctx, c) + if err != nil { + if errors.IsNotFound(err) { + logger.V(3).Info("updateUserRoutes() upload proxy ca cert doesn't exist") + return nil + } + return err + } + + routes := &routev1.RouteList{} + err = c.List(ctx, routes, &client.ListOptions{ Namespace: util.GetNamespace(), - Limit: 1, }) - if err == nil { - return r.controller.Watch(source.Kind(r.getCache(), &routev1.Route{}), enqueueCDI(r.client)) + if err != nil { + return err } - if meta.IsNoMatchError(err) { + + for _, r := range routes.Items { + route := r.DeepCopy() + if route.Annotations[annInjectUploadProxyCert] != "true" { + continue + } + + if route.Spec.TLS == nil { + logger.V(1).Info("Route has no TLS config, skipping", "route", route.Name) + continue + } + + if route.Spec.TLS.DestinationCACertificate != cert { + logger.V(1).Info("Updating route with new CA cert", "route", route.Name) + route.Spec.TLS.DestinationCACertificate = cert + if err := c.Update(ctx, route); err != nil { + return err + } + recorder.Event(route, corev1.EventTypeNormal, updateUserRouteSuccess, "Successfully updated Route destination CA certificate") + } + } + + return nil +} + +func getUploadProxyCABundle(ctx context.Context, c client.Client) (string, error) { + cm := &corev1.ConfigMap{} + key := client.ObjectKey{Namespace: util.GetNamespace(), Name: uploadProxyCABundle} + if err := c.Get(ctx, key, cm); err != nil { + return "", err + } + return cm.Data["ca-bundle.crt"], nil +} + +func (r *ReconcileCDI) watchRoutes() error { + if !r.haveRoutes { log.Info("Not watching Routes") return nil } + return r.controller.Watch(source.Kind(r.getCache(), &routev1.Route{}), enqueueCDI(r.client)) +} - return err +func haveRoutes(c client.Client) (bool, error) { + err := c.List(context.TODO(), &routev1.RouteList{}, &client.ListOptions{ + Namespace: util.GetNamespace(), + Limit: 1, + }) + if err != nil { + if meta.IsNoMatchError(err) { + return false, nil + } + return false, err + } + return true, nil }