Repository navigation
fix: identical endpoint name conflicts #1521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
akurinnoy
wants to merge
3
commits into
devfile:main
Choose a base branch
from
akurinnoy:identical-endpoint-name
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # Index discoverable endpoints for admission conflict checks | ||
|
|
||
| **Status**: Accepted | ||
| **Date**: 2026-10-07 | ||
| **Deciders**: Not recorded | ||
|
|
||
| ## Context | ||
|
|
||
| Admission checks for a workspace with discoverable endpoints currently list every DevWorkspace in its namespace, then scan the returned specs for colliding Service names. This copies and examines unrelated workspaces. The existing `discoverableEndpointNames` helper already extracts normalized, unique endpoint names when the discoverable attribute is boolean or string `true` and exposure is not `none`. | ||
|
|
||
| ## Decision | ||
|
|
||
| Register a controller-runtime field index on DevWorkspace objects under `controller.devfile.io/discoverable-endpoint`. Its extractor reuses `discoverableEndpointNames`. Register the index before webhook handlers are registered and before the manager starts. | ||
|
|
||
| For each incoming normalized discoverable endpoint name, admission performs one namespace-scoped List using that exact indexed name. Keep the existing update gating, cached-client copy behavior, self-UID exclusion, error handling, and defensive endpoint scan of returned candidates. The scan remains the final admission check for a matching candidate. | ||
|
|
||
| The index covers endpoints visible in `.spec.template`; contributed endpoints remain protected by the controller's Service synchronization ownership check. Admission remains eventually consistent, so concurrent requests can both pass; the controller's fresh Service ownership check remains authoritative. No API, RBAC, dependency, or code-generation changes are required. | ||
|
|
||
| ## Considered Alternatives | ||
|
|
||
| ### Alternative 1: Keep listing and scanning the whole namespace | ||
|
|
||
| This is simpler, but continues copying every workspace and scanning unrelated specs for each admission check. | ||
|
|
||
| **Rejected because**: The field index targets the matching workspaces through the existing cache. | ||
|
|
||
| ### Alternative 2: Disable cache object copying | ||
|
|
||
| This would avoid copies for all listed workspaces but exposes shared cached objects to mutation and races. | ||
|
|
||
| **Rejected because**: Limiting query results with the index avoids copying unrelated workspaces while retaining the client's normal safety behavior. | ||
|
|
||
| ### Alternative 3: Add a custom cache or external endpoint index owner | ||
|
|
||
| This would add another mechanism and lifecycle to maintain. | ||
|
|
||
| **Rejected because**: A native controller-runtime field index provides the needed lookup within the existing manager and cache. | ||
|
|
||
| ## Consequences | ||
|
|
||
| ### Positive | ||
|
|
||
| Only workspaces matching each incoming discoverable endpoint name are copied and scanned. | ||
|
|
||
| ### Negative | ||
|
|
||
| The cache uses memory for the index and CPU to extract and update indexed names. Requests with multiple incoming names issue one List per name. | ||
|
|
||
| ### Neutral | ||
|
|
||
| The lookup remains a best-effort admission check over cached state. Existing update gating, conflict responses, and the controller's Service conflict guard retain their roles. | ||
|
|
||
| ## References | ||
|
|
||
| - [Endpoint validation](../webhook/workspace/handler/validate.go) | ||
| - [Webhook configuration](../webhook/workspace/config.go) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| # Mount per-service certificates for discoverable endpoints | ||
|
|
||
| **Status**: Accepted | ||
| **Date**: 2026-10-08 | ||
| **Deciders**: Not recorded | ||
|
|
||
| ## Context | ||
|
|
||
| With TLS enabled, cluster routing creates an aggregate workspace Service and one Service per discoverable endpoint. OpenShift issues a serving certificate for each annotated Service. Mounting all certificates at `/var/serving-cert/` overlaps mounts; clients need a distinct certificate location for each discoverable Service. | ||
|
|
||
| ## Decision | ||
|
|
||
| Keep the aggregate Service certificate mounted at `/var/serving-cert/`. Mount each discoverable Service's certificate at `/var/serving-cert/<service-name>/`. The solver annotates each Service with its own name, and the Secret reference continues to use that actual Service name. | ||
|
|
||
| Keep `devworkspace-serving-cert-<service-name>` when the full generated volume name fits within 63 characters; otherwise use `devworkspace-cert-` plus the hex encoding of the first 10 SHA-256 bytes of the Service name. Distinct prefixes prevent a digest name from deterministically colliding with one generated for a short Service name. Hashing avoids truncation collisions; the 80-bit digest can still collide and is not guaranteed unique. Service and Secret names remain unchanged. | ||
|
|
||
| ## Considered Alternatives | ||
|
|
||
| ### Alternative 1: Share the aggregate certificate mount | ||
|
|
||
| Expose the aggregate certificate to workloads and use it for discoverable Service connections. | ||
|
|
||
| **Rejected because**: OpenShift binds each serving certificate to its Service's internal DNS name. Reusing the aggregate certificate would leave the discoverable Service DNS names uncovered. [OpenShift certificate documentation](https://docs.redhat.com/en/documentation/openshift_container_platform/4.21/html/security_and_compliance/configuring-certificates) | ||
|
|
||
| ### Alternative 2: Truncate long volume names or reject long Service names | ||
|
|
||
| Shorten generated names to fit Kubernetes' 63-character volume-name limit, or disallow endpoint names that produce longer values. | ||
|
|
||
| **Rejected because**: Truncation can merge names from distinct Services, while rejecting them would disallow otherwise valid Service names. | ||
|
|
||
| ### Alternative 3: Hash every volume name | ||
|
|
||
| Use a fixed-length digest-based name for all Services. | ||
|
|
||
| **Rejected because**: It removes readable names for ordinary cases without avoiding any additional limit. | ||
|
|
||
| ## Consequences | ||
|
|
||
| ### Positive | ||
|
|
||
| - Each discoverable Service has a distinct certificate mount path alongside the unchanged aggregate mount. | ||
| - Long valid Service names can be represented by Kubernetes-compatible volume names without truncation. | ||
|
|
||
| ### Negative | ||
|
|
||
| - Each discoverable Service adds a pod volume and mount. | ||
| - The 80-bit digest has a theoretical collision risk for distinct long Service names. | ||
|
|
||
| ### Neutral | ||
|
|
||
| - Workloads use the actual Service name to identify the corresponding Secret; only the generated volume name may differ for long names. | ||
| - The reserved-name guard prevents a discoverable Service from taking the aggregate Service name, and duplicate normalized Service names are rejected. | ||
|
|
||
| ## References | ||
|
|
||
| - [Cluster solver](../controllers/controller/devworkspacerouting/solvers/cluster_solver.go) | ||
| - [Cluster solver certificate mount tests](../controllers/controller/devworkspacerouting/solvers/cluster_solver_test.go) | ||
| - [Serving certificate volume naming](../pkg/common/naming.go) | ||
| - [Discoverable Service name guard](../controllers/controller/devworkspacerouting/solvers/common.go) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| # Guard routing Service ownership during synchronization | ||
|
|
||
| **Status**: Accepted | ||
| **Date**: 2026-10-08 | ||
| **Deciders**: Not recorded | ||
|
|
||
| ## Context | ||
|
|
||
| DevWorkspaceRouting Services share namespace names, including discoverable endpoints. Admission reads cached workspace state, so concurrent requests can both pass; the cache can also hide a Service missing its workspace ID label. Treating that miss as proof a name is free risks overwriting another routing's Service. | ||
|
|
||
| ## Decision | ||
|
|
||
| Identify routing Services by the discoverable annotation or DevWorkspaceRouting controller owner. When configured, read them through the non-caching client (controller setup supplies it) and use this fresh result for ownership. If the desired Service has a controller owner, its UID must match the existing controller owner's UID; that UID remains authoritative when the workspace ID label is stale or missing, allowing synchronization to repair the label. Without a desired controller owner, require a nonempty workspace ID label matching the existing Service. Reject a foreign same-name Service as a permanent conflict. | ||
|
|
||
| If Service creation returns AlreadyExists, retry instead of using the generic update fallback. Updates carry the existing UID, resourceVersion, and ClusterIP; cleanup deletes only a routing-controlled Service with UID and resourceVersion preconditions. These guards reject writes against a changed or replaced Service; update conflicts and missing objects trigger another reconcile. The read and ownership decision are not atomic, and this does not reserve names across reconcilers. | ||
|
|
||
| ## Considered Alternatives | ||
|
|
||
| ### Alternative 1: Trust admission conflict checks | ||
|
|
||
| Use cached admission as the sole protection for discoverable endpoint names. | ||
|
|
||
| **Rejected because**: Stale cache state lets concurrent requests both pass before a Service exists. | ||
|
|
||
| ### Alternative 2: Treat workspace ID labels as ownership proof | ||
|
|
||
| Require the existing Service label to match the desired workspace ID. | ||
|
|
||
| **Rejected because**: A label match does not prove matching controller ownership, and labels can drift. A valid routing owner can repair a stale label without losing its Service. | ||
|
|
||
| ### Alternative 3: Keep the generic AlreadyExists update fallback | ||
|
|
||
| Use generic update after Service creation reports AlreadyExists. | ||
|
|
||
| **Rejected because**: The fallback lacks a fresh object for ownership validation and identity preservation. | ||
|
|
||
| ## Consequences | ||
|
|
||
| ### Positive | ||
|
|
||
| - The fresh check protects foreign Services hidden by cache filtering. | ||
| - Owned Services with stale labels can be repaired while preserving identity and ClusterIP. | ||
|
|
||
| ### Negative | ||
|
|
||
| - Fresh reads add API calls and latency; changed Services can make guarded writes conflict and require another reconcile. | ||
|
|
||
| ### Neutral | ||
|
|
||
| - Admission remains advisory; the controller check is authoritative. Concurrent creates rely on Kubernetes name uniqueness and retry. | ||
|
|
||
| ## References | ||
|
|
||
| - [Service synchronization](../pkg/provision/sync/sync.go) | ||
| - [Service update fields](../pkg/provision/sync/update.go) | ||
| - [Service conflict error](../pkg/provision/sync/service.go) | ||
| - [Routing Service cleanup](../controllers/controller/devworkspacerouting/sync_services.go) | ||
| - [Routing controller client setup](../controllers/controller/devworkspacerouting/devworkspacerouting_controller.go) | ||
| - [Ownership and cache-miss tests](../pkg/provision/sync/service_test.go) | ||
| - [Routing cleanup and conflict tests](../controllers/controller/devworkspacerouting/sync_services_test.go) | ||
| - [Label repair tests](../controllers/controller/devworkspacerouting/workspace_name_test.go) | ||
| - [Admission index decision](2026-10-07-discoverable-endpoint-index.md) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.