diff --git a/controllers/atlasmigration_controller.go b/controllers/atlasmigration_controller.go index c2d6ee19..075b7f87 100644 --- a/controllers/atlasmigration_controller.go +++ b/controllers/atlasmigration_controller.go @@ -91,7 +91,7 @@ type ( } ) -func NewAtlasMigrationReconciler(mgr Manager, execPath string) *AtlasMigrationReconciler { +func NewAtlasMigrationReconciler(mgr Manager, execPath string, _ bool) *AtlasMigrationReconciler { return &AtlasMigrationReconciler{ Client: mgr.GetClient(), scheme: mgr.GetScheme(), @@ -219,8 +219,8 @@ func (r *AtlasMigrationReconciler) watchRefs(res *dbv1alpha1.AtlasMigration) { } // Reconcile the given AtlasMigration resource. -func (r *AtlasMigrationReconciler) reconcile(ctx context.Context, dir, envName string) (_ *dbv1alpha1.AtlasMigrationStatus, _ error) { - c, err := atlas.NewClient(dir, r.execPath) +func (r *AtlasMigrationReconciler) reconcile(ctx context.Context, wd, envName string) (_ *dbv1alpha1.AtlasMigrationStatus, _ error) { + c, err := atlas.NewClient(wd, r.execPath) if err != nil { return nil, err } diff --git a/controllers/atlasschema_controller.go b/controllers/atlasschema_controller.go index bc994d17..6638e49c 100644 --- a/controllers/atlasschema_controller.go +++ b/controllers/atlasschema_controller.go @@ -58,7 +58,7 @@ type ( configMapWatcher *watch.ResourceWatcher secretWatcher *watch.ResourceWatcher recorder record.EventRecorder - prewarmDevDB bool + devDB *devDBReconciler } // managedData contains information about the managed database and its desired state. managedData struct { @@ -78,14 +78,15 @@ type ( const sqlLimitSize = 1024 func NewAtlasSchemaReconciler(mgr Manager, execPath string, prewarmDevDB bool) *AtlasSchemaReconciler { + r := mgr.GetEventRecorderFor("atlasschema-controller") return &AtlasSchemaReconciler{ Client: mgr.GetClient(), scheme: mgr.GetScheme(), execPath: execPath, configMapWatcher: watch.New(), secretWatcher: watch.New(), - recorder: mgr.GetEventRecorderFor("atlasschema-controller"), - prewarmDevDB: prewarmDevDB, + recorder: r, + devDB: newDevDB(mgr, r, prewarmDevDB), } } @@ -111,7 +112,7 @@ func (r *AtlasSchemaReconciler) Reconcile(ctx context.Context, req ctrl.Request) r.watchRefs(res) // Clean up any resources created by the controller after the reconciler is successful. if res.IsReady() { - r.cleanUp(ctx, res) + r.devDB.cleanUp(ctx, res) } }() // When the resource is first created, create the "Ready" condition. @@ -145,7 +146,7 @@ func (r *AtlasSchemaReconciler) Reconcile(ctx context.Context, req ctrl.Request) if data.DevURL == "" { // The user has not specified an URL for dev-db, // spin up a dev-db and get the connection string. - data.DevURL, err = r.devURL(ctx, res, *data.URL) + data.DevURL, err = r.devDB.devURL(ctx, res, *data.URL) if err != nil { res.SetNotReady("GettingDevDB", err.Error()) return result(err) diff --git a/controllers/atlasschema_controller_test.go b/controllers/atlasschema_controller_test.go index ae939266..c88096b4 100644 --- a/controllers/atlasschema_controller_test.go +++ b/controllers/atlasschema_controller_test.go @@ -46,7 +46,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/reconcile" ) const ( @@ -111,9 +110,7 @@ func TestReconcile_Reconcile(t *testing.T) { ObjectMeta: meta, Spec: dbv1alpha1.AtlasSchemaSpec{}, } - h, reconcile := newRunner(func(mgr Manager, name string) reconcile.Reconciler { - return NewAtlasSchemaReconciler(mgr, name, true) - }, func(cb *fake.ClientBuilder) { + h, reconcile := newRunner(NewAtlasSchemaReconciler, func(cb *fake.ClientBuilder) { cb.WithObjects(obj) }) assert := func(except ctrl.Result, ready bool, reason, msg string) { @@ -567,6 +564,7 @@ func newTest(t *testing.T) *test { } execPath, err := exec.LookPath("atlas") require.NoError(t, err) + r := record.NewFakeRecorder(100) return &test{ T: t, k8s: m, @@ -576,7 +574,12 @@ func newTest(t *testing.T) *test { execPath: execPath, configMapWatcher: watch.New(), secretWatcher: watch.New(), - recorder: record.NewFakeRecorder(100), + recorder: r, + devDB: &devDBReconciler{ + Client: m, + scheme: scheme, + recorder: r, + }, }, } } diff --git a/controllers/devdb.go b/controllers/devdb.go index 8bda9335..5f551363 100644 --- a/controllers/devdb.go +++ b/controllers/devdb.go @@ -29,12 +29,13 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/client-go/tools/record" + "k8s.io/utils/pointer" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - - dbv1alpha1 "github.com/ariga/atlas-operator/api/v1alpha1" ) const ( @@ -44,12 +45,40 @@ const ( hostReplace = "REPLACE_HOST" ) +type ( + // TODO: Refactor this to a separate controller + devDBReconciler struct { + client.Client + scheme *runtime.Scheme + recorder record.EventRecorder + prewarm bool + } + resourceOwner interface { + metav1.Object + runtime.Object + } +) + +func newDevDB(mgr Manager, r record.EventRecorder, prewarm bool) *devDBReconciler { + if r == nil { + // Only create a new recorder if it is not provided. + // This keep the controller from creating multiple recorders. + r = mgr.GetEventRecorderFor("devdb") + } + return &devDBReconciler{ + Client: mgr.GetClient(), + scheme: mgr.GetScheme(), + recorder: r, + prewarm: prewarm, + } +} + // cleanUp clean up any resources created by the controller -func (r *AtlasSchemaReconciler) cleanUp(ctx context.Context, sc *dbv1alpha1.AtlasSchema) { +func (r *devDBReconciler) cleanUp(ctx context.Context, sc resourceOwner) { // If prewarmDevDB is false, scale down the deployment to 0 - if !r.prewarmDevDB { + if !r.prewarm { deploy := &appsv1.Deployment{} - key := nameDevDB(sc.ObjectMeta) + key := nameDevDB(sc) err := r.Get(ctx, key, deploy) if err != nil { r.recorder.Eventf(sc, corev1.EventTypeWarning, "CleanUpDevDB", "Error getting devDB deployment: %v", err) @@ -61,11 +90,10 @@ func (r *AtlasSchemaReconciler) cleanUp(ctx context.Context, sc *dbv1alpha1.Atla } return } - // delete pods to clean up pods := &corev1.PodList{} err := r.List(ctx, pods, client.MatchingLabels(map[string]string{ - labelInstance: nameDevDB(sc.ObjectMeta).Name, + labelInstance: nameDevDB(sc).Name, })) if err != nil { r.recorder.Eventf(sc, corev1.EventTypeWarning, "CleanUpDevDB", "Error listing devDB pods: %v", err) @@ -79,27 +107,26 @@ func (r *AtlasSchemaReconciler) cleanUp(ctx context.Context, sc *dbv1alpha1.Atla // devURL returns the URL of the dev database for the given target URL. // It creates a dev database if it does not exist. -func (r *AtlasSchemaReconciler) devURL(ctx context.Context, sc *dbv1alpha1.AtlasSchema, targetURL url.URL) (string, error) { +func (r *devDBReconciler) devURL(ctx context.Context, sc resourceOwner, targetURL url.URL) (string, error) { drv := driver(targetURL.Scheme) if drv == "sqlite" { return "sqlite://db?mode=memory", nil } // make sure we have a dev db running - key := nameDevDB(sc.ObjectMeta) + key := nameDevDB(sc) deploy := &appsv1.Deployment{} switch err := r.Get(ctx, key, deploy); { case err == nil: // The dev database already exists, // If it is scaled down, scale it up. if deploy.Spec.Replicas == nil || *deploy.Spec.Replicas == 0 { - *deploy.Spec.Replicas = int32(1) + deploy.Spec.Replicas = pointer.Int32(1) if err := r.Update(ctx, deploy); err != nil { return "", transient(err) } r.recorder.Eventf(sc, corev1.EventTypeNormal, "ScaledUpDevDB", "Scaled up dev database deployment: %s", deploy.Name) return "", transientAfter(errors.New("waiting for dev database to be ready"), 15*time.Second) } - case apierrors.IsNotFound(err): // The dev database does not exist, create it. deploy, err := deploymentDevDB(key, drv, isSchemaBound(drv, &targetURL)) @@ -114,7 +141,6 @@ func (r *AtlasSchemaReconciler) devURL(ctx context.Context, sc *dbv1alpha1.Atlas return "", transientAfter(errors.New("waiting for dev database to be ready"), 15*time.Second) default: // An error occurred while getting the dev database, - sc.SetNotReady("GettingDevDB", err.Error()) return "", err } pods := &corev1.PodList{} @@ -205,10 +231,10 @@ func deploymentDevDB(name types.NamespacedName, drv string, schemaBound bool) (* } // nameDevDB returns the namespaced name of the dev database. -func nameDevDB(owner metav1.ObjectMeta) types.NamespacedName { +func nameDevDB(owner metav1.Object) types.NamespacedName { return types.NamespacedName{ - Name: fmt.Sprintf("%s-atlas-dev-db", owner.Name), - Namespace: owner.Namespace, + Name: fmt.Sprintf("%s-atlas-dev-db", owner.GetName()), + Namespace: owner.GetNamespace(), } } diff --git a/controllers/testhelper.go b/controllers/testhelper.go index cf8ac93e..28e7bef5 100644 --- a/controllers/testhelper.go +++ b/controllers/testhelper.go @@ -46,7 +46,7 @@ type ( ) // newRunner returns a runner that can be used to test a reconcile.Reconciler. -func newRunner[T reconcile.Reconciler](fn func(Manager, string) T, modify func(*fake.ClientBuilder)) (*helper, runner) { +func newRunner[T reconcile.Reconciler](fn func(Manager, string, bool) T, modify func(*fake.ClientBuilder)) (*helper, runner) { execPath, err := exec.LookPath("atlas") if err != nil { panic(err) @@ -64,7 +64,7 @@ func newRunner[T reconcile.Reconciler](fn func(Manager, string) T, modify func(* client: c, recorder: r, scheme: scheme, - }, execPath) + }, execPath, true) h := &helper{client: c, recorder: r} return h, func(obj client.Object, fn check) { fn(a.Reconcile(context.Background(), request(obj))) diff --git a/main.go b/main.go index b8563cd0..4c798345 100644 --- a/main.go +++ b/main.go @@ -104,7 +104,7 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "AtlasSchema") os.Exit(1) } - if err = controllers.NewAtlasMigrationReconciler(mgr, execPath). + if err = controllers.NewAtlasMigrationReconciler(mgr, execPath, prewarmDevDB). SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "AtlasMigration") os.Exit(1)