package controller import ( "context" "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" "sigs.k8s.io/controller-runtime/pkg/client" terdutv1alpha1 "git.ryuvia.com/niklas/terdut-operator/api/v1alpha1" "git.ryuvia.com/niklas/terdut-operator/internal/tdclient" ) // childError carries a condition reason/message, the same role teamError // and databaseError play for their own controllers: an expected, // requeue-and-retry outcome, not a reconcile failure. type childError struct { reason string message string } func (e *childError) Error() string { return e.message } // resolveTeamAndClient implements DESIGN.md ยง5's "every child resolves its // own teamRef -> TerdutTeam.status, never chains up to TerdutServer" // rule -- shared by TerdutEscalationRule and TerdutDeadmanSwitch, which // both need exactly this and nothing else to call terdut-server's API. func resolveTeamAndClient( ctx context.Context, c client.Client, operatorNamespace, namespace string, ref terdutv1alpha1.TerdutTeamRef, newClient func(string) *tdclient.Client, ) (*terdutv1alpha1.TerdutTeam, *tdclient.Client, *childError) { var team terdutv1alpha1.TerdutTeam if err := c.Get(ctx, client.ObjectKey{Namespace: namespace, Name: ref.Name}, &team); err != nil { if apierrors.IsNotFound(err) { return nil, nil, &childError{ reason: terdutv1alpha1.ReasonTeamRefNotFound, message: fmt.Sprintf("TerdutTeam %q not found in namespace %q", ref.Name, namespace), } } return nil, nil, &childError{reason: terdutv1alpha1.ReasonTeamRefNotFound, message: err.Error()} } if team.Status.CredentialsSecretRef == nil || team.Status.TeamID == 0 { return nil, nil, &childError{ reason: terdutv1alpha1.ReasonWaitingForTeam, message: fmt.Sprintf("TerdutTeam %q is not Ready yet", ref.Name), } } teamKey, err := readOperatorSecret(ctx, c, operatorNamespace, team.Status.CredentialsSecretRef) if err != nil { return nil, nil, &childError{reason: terdutv1alpha1.ReasonWaitingForTeam, message: err.Error()} } return &team, newClient(team.Status.ServerEndpoint).WithToken(teamKey), nil }