package controller import ( "context" "fmt" "sync/atomic" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/reconcile" terdutv1alpha1 "git.ryuvia.com/niklas/terdut-operator/api/v1alpha1" "git.ryuvia.com/niklas/terdut-operator/internal/tdclient" ) // Shared fixture values -- a bring-your-own DSN TerdutServer spec, reused // across terdutserver_controller_test.go, terdutteam_controller_test.go and // this file, so there's exactly one definition to keep consistent. const ( testImageRepo = "example.invalid/terdut-server" testImageTag = "test" testDSN = "postgres://terdut@test-postgres:5432/terdut?sslmode=require" // testRefNotFoundName is a name no TerdutServer/TerdutTeam ever gets // created with -- shared by every "...RefNotFound" test across // terdutteam_controller_test.go, terdutescalationrule_controller_test.go, // terdutdeadmanswitch_controller_test.go and // terdutalertsource_controller_test.go, so goconst doesn't flag four // independent copies of the same literal. testRefNotFoundName = "does-not-exist" // testUnreadyDisplayName is the TerdutTeam.spec.displayName every // "...exists but isn't Ready yet" test across the same four files uses // for its deliberately-never-reconciled fixture team, for the same // goconst reason as testRefNotFoundName above. testUnreadyDisplayName = "unready" // testOperatorNamespace is every test file's own namespace for both // the operator's generated/credential objects and the CRs under test // -- always "default" in this suite, so bootstrapReadyTerdutServer // below takes no namespace parameter of its own (golangci-lint's // unparam flagged it once a fourth same-valued caller made that // obvious): there's never a second value to pass. testOperatorNamespace = "default" ) // bootstrapReadyTerdutServer creates a TerdutServer with a bring-your-own // DSN and drives it to Ready against fakeURL, the same three-pass sequence // terdutserver_controller_test.go's own happy-path test exercises directly // -- shared here so TerdutTeam's tests (which need a real, Ready // TerdutServer to resolve against) don't duplicate it. func bootstrapReadyTerdutServer(ctx context.Context, name, fakeURL string) *terdutv1alpha1.TerdutServer { GinkgoHelper() namespace := testOperatorNamespace reconciler := &TerdutServerReconciler{ Client: k8sClient, Scheme: k8sClient.Scheme(), OperatorNamespace: namespace, NewClient: func(string) *tdclient.Client { return tdclient.New(fakeURL) }, } objKey := types.NamespacedName{Name: name, Namespace: namespace} srv := &terdutv1alpha1.TerdutServer{ ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, Spec: terdutv1alpha1.TerdutServerSpec{ Image: terdutv1alpha1.ImageSpec{Repository: testImageRepo, Tag: testImageTag}, Networking: terdutv1alpha1.NetworkingSpec{Hostname: "terdut.example.invalid", ServicePort: 8080}, Database: terdutv1alpha1.DatabaseSpec{DSN: testDSN}, }, } Expect(k8sClient.Create(ctx, srv)).To(Succeed()) _, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: objKey}) // finalizer Expect(err).NotTo(HaveOccurred()) _, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: objKey}) // Deployment/Service Expect(err).NotTo(HaveOccurred()) var deploy appsv1.Deployment Expect(k8sClient.Get(ctx, objKey, &deploy)).To(Succeed()) deploy.Status.ReadyReplicas = 1 deploy.Status.Replicas = 1 Expect(k8sClient.Status().Update(ctx, &deploy)).To(Succeed()) _, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: objKey}) // bootstrap Expect(err).NotTo(HaveOccurred()) Expect(k8sClient.Get(ctx, objKey, srv)).To(Succeed()) Expect(srv.Status.CredentialsSecretRef).NotTo(BeNil(), "test setup: TerdutServer %s/%s did not reach Bootstrapped", namespace, name) return srv } // readyTerdutTeam creates a TerdutTeam under srv (an already-Ready // TerdutServer, e.g. from bootstrapReadyTerdutServer) and drives it to // Ready against fakeURL -- shared by TerdutEscalationRule's and // TerdutDeadmanSwitch's own tests, which both just need a resolvable // teamRef (DESIGN.md ยง5), not TerdutTeam's own behavior. func readyTerdutTeam(ctx context.Context, namespace, name string, srv *terdutv1alpha1.TerdutServer, fakeURL string) *terdutv1alpha1.TerdutTeam { GinkgoHelper() reconciler := &TerdutTeamReconciler{ Client: k8sClient, Scheme: k8sClient.Scheme(), OperatorNamespace: namespace, NewClient: func(string) *tdclient.Client { return tdclient.New(fakeURL) }, } objKey := types.NamespacedName{Name: name, Namespace: namespace} team := &terdutv1alpha1.TerdutTeam{ ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, Spec: terdutv1alpha1.TerdutTeamSpec{ ServerRef: terdutv1alpha1.TerdutServerRef{Name: srv.Name}, DisplayName: name, }, } Expect(k8sClient.Create(ctx, team)).To(Succeed()) _, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: objKey}) // finalizer Expect(err).NotTo(HaveOccurred()) _, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: objKey}) // create+mint+apply Expect(err).NotTo(HaveOccurred()) Expect(k8sClient.Get(ctx, objKey, team)).To(Succeed()) Expect(team.Status.CredentialsSecretRef).NotTo(BeNil(), "test setup: TerdutTeam %s/%s did not reach Ready", namespace, name) return team } // uniqueNameCounter backs uniqueName. GinkgoRandomSeed()/GinkgoParallelProcess() // are constants for the whole suite run, not per-spec -- an earlier version // of this helper used them and collided across every spec that called it // more than once; an atomic counter is genuinely unique per call instead. var uniqueNameCounter atomic.Int64 // uniqueName returns a Kubernetes-object-safe name unique to this call. func uniqueName(prefix string) string { return fmt.Sprintf("%s-%d", prefix, uniqueNameCounter.Add(1)) }