Skip to content

FEATURE: Add ClusterConnection cluster-scoped resource - #549

Open
niyomukiza-mechack wants to merge 4 commits into
mainfrom
CReq/ClusterConnectionCR
Open

FEATURE: Add ClusterConnection cluster-scoped resource#549
niyomukiza-mechack wants to merge 4 commits into
mainfrom
CReq/ClusterConnectionCR

Conversation

@niyomukiza-mechack

@niyomukiza-mechack niyomukiza-mechack commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

What was changed

  • api/v1alpha1/clusterconnection_types.go: Added a new cluster-scoped type reusing
    ConnectionSpec and ConnectionStatus
  • workerdeployment_types.go: added optional Kind field to ConnectionReference
    (default: Connection, backward compatible)
  • worker_controller.go :
    • Added a new function resolveConnection() helper for kind-aware fetch,
      generalized ensureConnectionFinalizer to client.Object, and cluster-wide
      removeConnectionFinalizerIfUnused (cross-namespace list + kind-aware match), and
      findTWDsUsingClusterConnection mapper. Also added ClusterConnection watch + RBAC
    • Migration path: When a WorkerDeployment's connectionRef changes (name or kind), the controller now releases the finalizer from the previously referenced connection if no other WorkerDeployment still uses it.
    • Added ObservedConnectionRef to WorkerDeploymentStatus to detect the change, sameConnectionRef() with kind normalization, and refactored the fetch/release into getConnectionByRef() and releaseConnectionFinalizerIfUnused(). This also closes the pre-existing namespaced Connection name-change leak.

Why?

Context: #456
Today, TWC requires a Connection resource in every namespace. When there's many namespaces, there's a lot of duplication for Connection Resource. ClusterConnection lets you define the connection once, cluster-wide, and have WorkerDeployments in any namespace reference it. The credential Secret still lives per-namespace since a pod can only mount a Secret from its own namespace.

Checklist

  1. Closes

  2. How was this tested:

Unit tests in internal/controller/clusterconnection_test.go

Local Kind cluster:

  • Created 5 tenant namespaces, one ClusterConnection, one Secret per namespace, one WorkerDeployment per namespace all referencing the shared connection
  • All 5 reconciled and connected through the single ClusterConnection
  • Deleted one tenant (4 remaining) and ClusterConnection finalizer correctly kept
  • Confirmed kubectl delete clusterconnection blocks while any WD references it (finalizer protection works)

##Migration path testing (the kind-change scenario):

  • Created WDs pointing at namespaced Connections, then migrated to the ClusterConnection via kubectl patch
  • Confirmed the new ClusterConnection gets the finalizer correctly
  • Confirmed the old namespaced Connection's finalizer is released automatically, so it becomes freely deletable
  • Confirmed the safety case: migrating a WD away from a shared ClusterConnection correctly keeps the finalizer while a WD in another namespace still references it
  1. Any docs updates needed?

@tomba7
tomba7 self-requested a review August 25, 2026 15:52
- apiGroups:
- temporal.io
resources:
- clusterconnections

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure clusterconnections belong here under the Role? Since its cluster scoped it seems like it should be associated only with a ClusterRole?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Should be fixed. clusterconnections was landing in the namespaced Role because hack/sync-rbac-rules.py filtered at block granularity, and controller-gen merged clusterconnections into the same block as the namespaced connections

@jaypipes jaypipes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @niyomukiza-mechack! I've left some thoughts for you inline. I think we want to think about the structure of the ConnectionRef object in a bit of a longer-term view.

Comment thread api/v1alpha1/workerdeployment_types.go Outdated
// +optional
// +kubebuilder:default=Connection
// +kubebuilder:validation:Enum=Connection;ClusterConnection
Kind string `json:"kind,omitempty"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of August, 2026 we only have a single API version (v1alpha1). In the future, however, we will likely have other API versions and potentially other API groups.

Best to think long-term and let's call this field ObjectRef and make it a pointer to a corev1.TypedObjectReference.

TypedObjectReference contains all the type information we might need in the future (for both cluster and namespace-scoped resources).

Another benefit to using TypedObjectReference here is that you can make the Name field optional and put a validation rule in place that says either Name or ObjectRef must be populated. And you can then remove some of the messy conditionals below in releaseConnectionFinalizerIfUnused where you are checking the Name field and trying to determine if the name refers to a Connection or a ClusterConnection...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super helpful and totally makes sense!

ctx context.Context,
workerDeploy *temporaliov1alpha1.WorkerDeployment,
) (temporaliov1alpha1.ConnectionSpec, client.Object, error) {
return r.getConnectionByRef(ctx, workerDeploy.Spec.WorkerOptions.ConnectionRef, workerDeploy.Namespace)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a need for a resolveConnection helper method that is one line that calls the getConnectionByRef helper method? If not, recommend removing this level of indirection.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

Comment thread internal/controller/worker_controller.go Outdated
// finalizer from the previously-referenced connection if no other WD uses it.
// The new connection is already protected by ensureConnectionFinalizer above,
// so the WD is never left unprotected.
current := workerDeploy.Spec.WorkerOptions.ConnectionRef

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that you can avoid the below code with the following guard:

// If the desired state of the resource changed, check that any changes
// to the referenced connection type are handled.
if workerDeploy.Generation != workerDeploy.Status.ObservedGeneration {

}

}
// Preserve conditions that were set during this reconciliation
status.Conditions = workerDeploy.Status.Conditions
status.ObservedConnectionRef = workerDeploy.Spec.WorkerOptions.ConnectionRef.DeepCopy()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return nil
}

// removeConnectionFinalizerIfUnused releases the finalizer from the WD's current

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's a good reason to have this helper method that is just a one-liner that calls another (very similarly-named) helper method. I would prefer to just call releaseConnectionFinalizerIfUnused directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants