Skip to content

feat: implement Gateway API routing class for DevWorkspaceRouting - #1680

Open
btjd wants to merge 4 commits into
devfile:mainfrom
btjd:23675-gateway-api
Open

feat: implement Gateway API routing class for DevWorkspaceRouting#1680
btjd wants to merge 4 commits into
devfile:mainfrom
btjd:23675-gateway-api

Conversation

@btjd

@btjd btjd commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds a new gateway-api routing class to the DevWorkspace Operator that exposes workspace endpoints via Kubernetes Gateway API
HTTPRoutes instead of Ingress/Route resources.

  • Introduces GatewayAPISolver that creates HTTPRoutes attached to a pre-provisioned Gateway referenced in the operator config (config.routing.gatewayRef)
  • Adds GatewayReference type to DevWorkspaceOperatorConfig API to configure the target Gateway name, namespace, and GatewayClass
  • Adds DevWorkspaceRoutingGatewayAPI ("gateway-api") routing class constant
  • Implements HTTPRoute sync lifecycle in the routing controller (create/update/delete with owner references and watches)
  • Fixes a controller readiness stall by requeuing when endpoints are not yet ready
  • Adds unit tests covering HTTPRoute creation, endpoint exposure filtering, discoverable endpoints, and service generation
  • sigs.k8s.io/gateway-api added as a dependency; Gateway API HTTPRoute CRD bundled in deploy/

What issues does this PR fix or reference?

https://issues.redhat.com/browse/CRW-23675

Is it tested? How?

Unit tests added for the GatewayAPISolver and routing controller covering:

  • HTTPRoute creation for exposed endpoints
  • Endpoint exposure filtering (only public endpoints get HTTPRoutes)
  • Discoverable endpoint labeled service generation
  • Service creation parity with the basic solver
  • Owner reference and annotation propagation

Manual verification:

  • Configure DevWorkspaceOperatorConfig with routing.gatewayRef pointing to a pre-provisioned Gateway
  • Create a DevWorkspace with routingClass: gateway-api
  • Verify HTTPRoutes are created and endpoints are reachable through the Gateway
  • Verify cleanup on DevWorkspaceRouting deletion via owner references

PR Checklist

  • E2E tests pass (when PR is ready, comment /test v8-devworkspace-operator-e2e, v8-che-happy-path to trigger)
    • v8-devworkspace-operator-e2e: DevWorkspace e2e test
    • v8-che-happy-path: Happy path for verification integration with Che

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added Gateway API routing support for workspaces.
    • Gateway references can be configured by name and optional namespace.
    • Public endpoints receive HTTPS routes and HTTP-to-HTTPS redirects.
    • Discoverable endpoints are supported through Gateway API HTTPRoutes.
    • Gateway API can be selected as the workspace routing class.
  • Bug Fixes

    • Improved endpoint readiness handling while HTTPRoutes are being provisioned.
    • HTTPRoutes are synchronized automatically as workspace routing changes.

@openshift-ci

openshift-ci Bot commented Jul 28, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: btjd
Once this PR has been reviewed and has the lgtm label, please assign dkwon17 for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds Gateway API routing through configurable Gateway references, generated HTTPRoute resources, controller synchronization, deployment schemas and RBAC, scheme registration, and integration tests.

Changes

Gateway API routing

Layer / File(s) Summary
Routing configuration contract
apis/controller/v1alpha1/*, pkg/config/sync.go, deploy/**/crd/*
Adds the gateway-api routing class, Gateway reference configuration, deep-copy support, config merging, and CRD schemas.
Gateway API solver and registration
controllers/controller/devworkspacerouting/solvers/*
Registers GatewayAPISolver, generates HTTP redirect and backend routes, and resolves exposed endpoints.
HTTPRoute reconciliation and diffing
controllers/controller/devworkspacerouting/*, pkg/cache/*, pkg/provision/sync/*
Manages HTTPRoute ownership, synchronization, deletion, readiness requeues, caching, and spec diffing.
Deployment wiring and integration validation
deploy/**/*, main.go, pkg/infrastructure/*, go.mod, controllers/controller/devworkspacerouting/*_test.go, testdata/*
Adds Gateway API scheme registration, discovery, dependency support, HTTPRoute RBAC, envtest CRDs, and integration coverage.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DevWorkspaceRouting
  participant GatewayAPISolver
  participant HTTPRoute
  participant EndpointResolver
  DevWorkspaceRouting->>GatewayAPISolver: Generate routing objects
  GatewayAPISolver->>HTTPRoute: Create redirect and backend routes
  DevWorkspaceRouting->>HTTPRoute: Synchronize routes
  DevWorkspaceRouting->>EndpointResolver: Resolve exposed endpoints
  EndpointResolver-->>DevWorkspaceRouting: Return URLs and readiness
Loading

Suggested labels: ok-to-test

Suggested reviewers: ibuziuk, akurinnoy, dkwon17

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: adding the Gateway API routing class for DevWorkspaceRouting.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@btjd
btjd marked this pull request as draft July 28, 2026 13:50

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (14)
deploy/deployment/openshift/combined.yaml (1)

27952-27957: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Consider scoping httproutes verbs instead of wildcard.

Granting '*' on httproutes is broader than the create/update/delete/watch lifecycle described for HTTPRoutes in this PR. This matches the existing convention for other resources in this ClusterRole (e.g., ingresses, routes), so it's a minor, optional tightening rather than a new problem.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/deployment/openshift/combined.yaml` around lines 27952 - 27957, In the
ClusterRole rule for the gateway.networking.k8s.io httproutes resource, replace
the wildcard verb permission with only the required create, update, delete, and
watch verbs, matching the lifecycle described and the existing ingresses/routes
convention.
controllers/controller/devworkspacerouting/solvers/gateway_api_solver.go (2)

17-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Group third-party imports separately from project-local imports.

metav1 and gwapiv1 (third-party/Kubernetes) are combined with the github.com/devfile/devworkspace-operator/... (project-local) group instead of forming their own group.

As per coding guidelines: "Organize imports into three groups separated by blank lines: standard library, third-party/Kubernetes, and project-local imports."

♻️ Proposed import grouping
 import (
 	"fmt"
 
-	controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
-	"github.com/devfile/devworkspace-operator/pkg/common"
-	"github.com/devfile/devworkspace-operator/pkg/config"
-	"github.com/devfile/devworkspace-operator/pkg/constants"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
+
+	controllerv1alpha1 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
+	"github.com/devfile/devworkspace-operator/pkg/common"
+	"github.com/devfile/devworkspace-operator/pkg/config"
+	"github.com/devfile/devworkspace-operator/pkg/constants"
 )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/controller/devworkspacerouting/solvers/gateway_api_solver.go`
around lines 17 - 26, Reorganize the import block in gateway API solver so
standard-library imports, third-party/Kubernetes imports such as metav1 and
gwapiv1, and project-local devworkspace-operator imports are separated by blank
lines.

Source: Coding guidelines


71-121: 🩺 Stability & Availability | 🔵 Trivial

Handle the Gateway's cross-namespace route allowance.

Both generated HTTPRoutes set ParentRefs[0].Namespace to the Gateway namespace, while the route namespace comes from workspaceMeta.Namespace. That cross-namespace attachment only succeeds if the target Gateway listener’s allowedRoutes.namespaces.from includes the workspace namespace. Confirm the pre-provisioned Gateway is documented to require this configuration; otherwise workspaces outside the Gateway namespace will remain unattached.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/controller/devworkspacerouting/solvers/gateway_api_solver.go`
around lines 71 - 121, The generated routes in getHTTPRoutesForSpec rely on
cross-namespace attachment from workspaceMeta.Namespace to gatewayNamespace.
Confirm and document that the pre-provisioned Gateway listeners set
allowedRoutes.namespaces.from to permit the workspace namespace; if this
configuration is not guaranteed, update the Gateway provisioning/configuration
so both createHTTPRedirectRoute and createHTTPSBackendRoute routes can attach
successfully.
controllers/controller/devworkspacerouting/devworkspacerouting_controller.go (1)

72-72: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Wildcard verbs on the new httproutes RBAC grant. The kubebuilder marker grants verbs=* for gateway.networking.k8s.io/httproutes, which controller-gen materializes into the ClusterRole. The controller's actual HTTPRoute lifecycle (list/get/create/update/patch/delete/watch, per sync_httproutes.go) doesn't need the full wildcard. This mirrors the pre-existing '*' convention already used for ingresses/routes in the same ClusterRole, so it's a consistency-preserving but still-broad grant worth tightening for least privilege.

  • controllers/controller/devworkspacerouting/devworkspacerouting_controller.go#L72-L72: scope the kubebuilder marker to the verbs actually used, e.g. // +kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=httproutes,verbs=get;list;watch;create;update;patch;delete, then run make generate_all to regenerate the RBAC manifests.
  • deploy/deployment/kubernetes/combined.yaml#L27952-L27957: this generated entry will update automatically once the marker above is scoped down and manifests are regenerated.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/controller/devworkspacerouting/devworkspacerouting_controller.go`
at line 72, Restrict the httproutes RBAC marker in
devworkspacerouting_controller.go to get, list, watch, create, update, patch,
and delete, then run make generate_all to regenerate
deploy/deployment/kubernetes/combined.yaml; update the generated ClusterRole
entry at lines 27952-27957 accordingly.
deploy/deployment/kubernetes/objects/devworkspace-controller-role.ClusterRole.yaml (1)

179-184: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Wildcard verb on httproutes. Duplicate of the same generated rule; see the consolidated note.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@deploy/deployment/kubernetes/objects/devworkspace-controller-role.ClusterRole.yaml`
around lines 179 - 184, Remove the duplicate generated RBAC rule granting
wildcard verbs on httproutes in the ClusterRole manifest, while retaining the
consolidated gateway.networking.k8s.io httproutes rule elsewhere.
deploy/deployment/openshift/objects/devworkspace-controller-role.ClusterRole.yaml (1)

179-184: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Wildcard verb on httproutes. Duplicate of the same generated rule; see the consolidated note.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@deploy/deployment/openshift/objects/devworkspace-controller-role.ClusterRole.yaml`
around lines 179 - 184, Remove the duplicate generated RBAC rule granting
wildcard verbs on the httproutes resource in the gateway.networking.k8s.io API
group. Keep the consolidated httproutes rule unchanged and ensure only one
equivalent rule remains.
deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml (1)

259-264: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Wildcard verb on httproutes. Same generated rule as the other three RBAC manifests; see the consolidated note anchored on the template.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml`
around lines 259 - 264, Update the RBAC rule for gateway.networking.k8s.io
httproutes in the devworkspace-operator ClusterServiceVersion manifest to
replace the wildcard verb with only the required explicit verbs, matching the
other generated RBAC manifests and the source template.
controllers/controller/devworkspacerouting/util_test.go (2)

168-180: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the helper tolerant of an absent route, and prefer an explicit name over a boolean flag.

Waiting for existence before deleting turns "route was never created" into a 10s teardown hang plus a misleading failure. Cleanup helpers should be idempotent. Also, deleteHTTPRoute(name, ns, true) at the call sites is opaque; taking the full route name (or exposing a redirectRouteName(endpoint) helper) reads better and removes the duplicated -http-redirect literal.

♻️ Proposed refactor
+func redirectRouteName(endpointName string) string {
+	return common.RouteName(testWorkspaceID, endpointName) + "-http-redirect"
+}
+
 func deleteHTTPRoute(endpointName string, namespace string, isRedirect bool) {
 	createdHTTPRoute := gwapiv1.HTTPRoute{}
 	routeName := common.RouteName(testWorkspaceID, endpointName)
 	if isRedirect {
-		routeName = routeName + "-http-redirect"
+		routeName = redirectRouteName(endpointName)
 	}
 	httpRouteNamespacedName := namespacedName(routeName, namespace)
-	Eventually(func() bool {
-		err := k8sClient.Get(ctx, httpRouteNamespacedName, &createdHTTPRoute)
-		return err == nil
-	}, timeout, interval).Should(BeTrue(), "HTTPRoute should exist in cluster")
-	deleteObject(&createdHTTPRoute)
+	if err := k8sClient.Get(ctx, httpRouteNamespacedName, &createdHTTPRoute); err != nil {
+		Expect(k8sErrors.IsNotFound(err)).Should(BeTrue(), "unexpected error fetching HTTPRoute %s", routeName)
+		return
+	}
+	deleteObject(&createdHTTPRoute)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/controller/devworkspacerouting/util_test.go` around lines 168 -
180, Update deleteHTTPRoute to accept the explicit route name rather than the
isRedirect boolean, with callers using the existing route-name helpers or a
shared redirectRouteName helper instead of duplicating the "-http-redirect"
suffix. Make cleanup idempotent by deleting directly and treating a not-found
HTTPRoute as successful, removing the Eventually wait that requires prior
existence.

168-180: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The -http-redirect suffix is hardcoded in two test locations and (presumably) a third time in the solver. A rename in the solver would leave both tests asserting against a name that no longer exists, and the negative/cleanup paths would fail confusingly rather than pointing at the rename.

  • controllers/controller/devworkspacerouting/util_test.go#L168-L180: introduce a redirectRouteName(endpointName string) string helper (or reuse an exported name-builder from the solver package) and use it here instead of the inline concatenation.
  • controllers/controller/devworkspacerouting/devworkspacerouting_controller_test.go#L541-L547: replace common.RouteName(testWorkspaceID, exposedEndPointName)+"-http-redirect" with that shared helper.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/controller/devworkspacerouting/util_test.go` around lines 168 -
180, The redirect route suffix is duplicated across test helpers and assertions,
so rename changes can leave tests targeting stale names. In
controllers/controller/devworkspacerouting/util_test.go:168-180, add or reuse a
shared redirectRouteName(endpointName string) helper and use it in
deleteHTTPRoute; in
controllers/controller/devworkspacerouting/devworkspacerouting_controller_test.go:541-547,
replace the inline route-name concatenation with that same helper, keeping both
tests aligned with the solver’s naming logic.
controllers/controller/devworkspacerouting/devworkspacerouting_controller_test.go (3)

570-588: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

No assertion on spec.hostnames.

The context configures ClusterHostSuffix: "test-environment-cluster-suffix", but nothing verifies the generated HTTPRoute's hostnames — yet that is exactly what makes the route match traffic for a workspace endpoint. A wrong or empty hostname would pass this whole suite. Consider adding a check that createdHTTPRoute.Spec.Hostnames contains the expected <endpoint-host>.test-environment-cluster-suffix value (and likewise for the redirect route).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@controllers/controller/devworkspacerouting/devworkspacerouting_controller_test.go`
around lines 570 - 588, Extend the HTTPRoute assertions in the discoverable
endpoint test to verify discoverableHTTPRoute.Spec.Hostnames contains the
expected endpoint host combined with ClusterHostSuffix
"test-environment-cluster-suffix". Add equivalent hostname validation for the
redirect route using its expected endpoint host, while preserving the existing
metadata and non-exposed route checks.

366-377: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the exported constant instead of the "gateway-api" literal.

Line 416 already uses controllerv1alpha1.DevWorkspaceRoutingGatewayAPI; keeping a raw literal here means a change to the constant's value would silently leave this config pointing at an unknown routing class.

♻️ Proposed tweak
-					DefaultRoutingClass: "gateway-api",
+					DefaultRoutingClass: string(controllerv1alpha1.DevWorkspaceRoutingGatewayAPI),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@controllers/controller/devworkspacerouting/devworkspacerouting_controller_test.go`
around lines 366 - 377, Replace the hardcoded "gateway-api" value assigned to
RoutingConfig.DefaultRoutingClass in the test setup with the exported
controllerv1alpha1.DevWorkspaceRoutingGatewayAPI constant, matching the existing
usage elsewhere in the test.

455-500: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

This "Creates services" body is a near-verbatim third copy.

Lines 455-500 duplicate the Kubernetes (108-161) and OpenShift (244-295) service assertions almost exactly. Service generation is routing-class independent, so extracting a shared expectConsolidatedServices(createdDWR) helper would keep the three contexts in sync when the service shape changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@controllers/controller/devworkspacerouting/devworkspacerouting_controller_test.go`
around lines 455 - 500, Extract the duplicated service assertions from the
“Creates services” tests into a shared expectConsolidatedServices(createdDWR)
helper, including consolidated and discoverable Service lookup, labels, owner
reference, selectors, ports, and annotations. Replace the near-identical
assertion bodies in the Kubernetes, OpenShift, and routing-class contexts with
calls to this helper while preserving each test’s setup and context-specific
behavior.
deploy/templates/components/rbac/role.yaml (2)

177-182: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Wildcard verb on httproutes (root marker lives in the controller). See the consolidated note.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/templates/components/rbac/role.yaml` around lines 177 - 182, Replace
the wildcard verb in the httproutes rule with only the explicit verbs required
by the controller’s httproute access. Update the rule under apiGroups
gateway.networking.k8s.io and resources httproutes, leaving the controller-owned
root marker unchanged.

177-182: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Tighten the Gateway API RBAC verb wildcard to an explicit list. controllers/controller/devworkspacerouting/devworkspacerouting_controller.go:72 generates the four gateway.networking.k8s.io/httproutes rules, and verbs=* grants deletecollection plus any future Gateway API verbs via both the template and deployed CSV/ClusterRole manifests. Narrow the marker to verbs=get;list;watch;create;update;patch;delete, then regenerate the RBAC manifests; apply the same precedent to the existing ingresses/routes verbs=* rules if the same least-privilege goal applies.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/templates/components/rbac/role.yaml` around lines 177 - 182, The
Gateway API httproutes RBAC rule uses an overly broad verb wildcard. In
deploy/templates/components/rbac/role.yaml:177-182, replace verbs=* with
get;list;watch;create;update;patch;delete, then regenerate the corresponding
manifests in
deploy/deployment/kubernetes/objects/devworkspace-controller-role.ClusterRole.yaml:179-184,
deploy/deployment/openshift/objects/devworkspace-controller-role.ClusterRole.yaml:179-184,
and
deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml:259-264.
Apply the same explicit verb list to existing ingresses/routes wildcard rules
only if they are part of the same least-privilege change.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@controllers/controller/devworkspacerouting/devworkspacerouting_controller_test.go`:
- Around line 430-439: Update the AfterEach cleanup around deleteHTTPRoute so
teardown does not require the discoverable endpoint’s redirect HTTPRoute to
exist; use cleanup behavior that tolerates absent routes while still removing
any routes that were created. Keep deletion for the other resources and verified
route variants unchanged, and align this with the non-strict cleanup approach
used by deleteHTTPRoute/util_test.go.

In
`@controllers/controller/devworkspacerouting/devworkspacerouting_controller.go`:
- Around line 246-262: Call syncHTTPRoutes unconditionally in the HTTPRoute
reconciliation block, including when httpRoutes is empty, so its
deletion-diffing cleanup removes stale cluster HTTPRoutes. Preserve the existing
error handling, requeue/status behavior, and assignment of
clusterRoutingObj.HTTPRoutes for successful synchronization.

In `@controllers/controller/devworkspacerouting/suite_test.go`:
- Around line 115-116: Replace the deprecated gwapiv1.AddToScheme call with
gwapiv1.Install in the scheme setup, matching the existing routev1.Install usage
while preserving the existing error assertion.

In
`@deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml`:
- Around line 66-81: The Gateway reference fields gatewayClassName, name, and
namespace in the CRD schema currently accept unrestricted strings. Add
Kubernetes DNS-1123 resource-name validation, including the appropriate maximum
length, to each field while preserving the existing descriptions and defaults;
ensure name remains required and empty or malformed values are rejected at CRD
validation.

In `@main.go`:
- Line 83: Replace the deprecated gwapiv1.AddToScheme call in the
scheme-registration setup with gwapiv1.Install(scheme), preserving the existing
utilruntime.Must handling and matching the nearby Gateway API registrations.

In `@pkg/provision/sync/diffopts.go`:
- Around line 96-101: Update httpRouteDiffOpts so cmpopts.IgnoreFields for
gwapiv1.ParentReference omits only Group and Kind, keeping Namespace included in
comparisons; leave the other HTTPRoute and reference-field ignore rules
unchanged.

---

Nitpick comments:
In
`@controllers/controller/devworkspacerouting/devworkspacerouting_controller_test.go`:
- Around line 570-588: Extend the HTTPRoute assertions in the discoverable
endpoint test to verify discoverableHTTPRoute.Spec.Hostnames contains the
expected endpoint host combined with ClusterHostSuffix
"test-environment-cluster-suffix". Add equivalent hostname validation for the
redirect route using its expected endpoint host, while preserving the existing
metadata and non-exposed route checks.
- Around line 366-377: Replace the hardcoded "gateway-api" value assigned to
RoutingConfig.DefaultRoutingClass in the test setup with the exported
controllerv1alpha1.DevWorkspaceRoutingGatewayAPI constant, matching the existing
usage elsewhere in the test.
- Around line 455-500: Extract the duplicated service assertions from the
“Creates services” tests into a shared expectConsolidatedServices(createdDWR)
helper, including consolidated and discoverable Service lookup, labels, owner
reference, selectors, ports, and annotations. Replace the near-identical
assertion bodies in the Kubernetes, OpenShift, and routing-class contexts with
calls to this helper while preserving each test’s setup and context-specific
behavior.

In
`@controllers/controller/devworkspacerouting/devworkspacerouting_controller.go`:
- Line 72: Restrict the httproutes RBAC marker in
devworkspacerouting_controller.go to get, list, watch, create, update, patch,
and delete, then run make generate_all to regenerate
deploy/deployment/kubernetes/combined.yaml; update the generated ClusterRole
entry at lines 27952-27957 accordingly.

In `@controllers/controller/devworkspacerouting/solvers/gateway_api_solver.go`:
- Around line 17-26: Reorganize the import block in gateway API solver so
standard-library imports, third-party/Kubernetes imports such as metav1 and
gwapiv1, and project-local devworkspace-operator imports are separated by blank
lines.
- Around line 71-121: The generated routes in getHTTPRoutesForSpec rely on
cross-namespace attachment from workspaceMeta.Namespace to gatewayNamespace.
Confirm and document that the pre-provisioned Gateway listeners set
allowedRoutes.namespaces.from to permit the workspace namespace; if this
configuration is not guaranteed, update the Gateway provisioning/configuration
so both createHTTPRedirectRoute and createHTTPSBackendRoute routes can attach
successfully.

In `@controllers/controller/devworkspacerouting/util_test.go`:
- Around line 168-180: Update deleteHTTPRoute to accept the explicit route name
rather than the isRedirect boolean, with callers using the existing route-name
helpers or a shared redirectRouteName helper instead of duplicating the
"-http-redirect" suffix. Make cleanup idempotent by deleting directly and
treating a not-found HTTPRoute as successful, removing the Eventually wait that
requires prior existence.
- Around line 168-180: The redirect route suffix is duplicated across test
helpers and assertions, so rename changes can leave tests targeting stale names.
In controllers/controller/devworkspacerouting/util_test.go:168-180, add or reuse
a shared redirectRouteName(endpointName string) helper and use it in
deleteHTTPRoute; in
controllers/controller/devworkspacerouting/devworkspacerouting_controller_test.go:541-547,
replace the inline route-name concatenation with that same helper, keeping both
tests aligned with the solver’s naming logic.

In `@deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml`:
- Around line 259-264: Update the RBAC rule for gateway.networking.k8s.io
httproutes in the devworkspace-operator ClusterServiceVersion manifest to
replace the wildcard verb with only the required explicit verbs, matching the
other generated RBAC manifests and the source template.

In
`@deploy/deployment/kubernetes/objects/devworkspace-controller-role.ClusterRole.yaml`:
- Around line 179-184: Remove the duplicate generated RBAC rule granting
wildcard verbs on httproutes in the ClusterRole manifest, while retaining the
consolidated gateway.networking.k8s.io httproutes rule elsewhere.

In `@deploy/deployment/openshift/combined.yaml`:
- Around line 27952-27957: In the ClusterRole rule for the
gateway.networking.k8s.io httproutes resource, replace the wildcard verb
permission with only the required create, update, delete, and watch verbs,
matching the lifecycle described and the existing ingresses/routes convention.

In
`@deploy/deployment/openshift/objects/devworkspace-controller-role.ClusterRole.yaml`:
- Around line 179-184: Remove the duplicate generated RBAC rule granting
wildcard verbs on the httproutes resource in the gateway.networking.k8s.io API
group. Keep the consolidated httproutes rule unchanged and ensure only one
equivalent rule remains.

In `@deploy/templates/components/rbac/role.yaml`:
- Around line 177-182: Replace the wildcard verb in the httproutes rule with
only the explicit verbs required by the controller’s httproute access. Update
the rule under apiGroups gateway.networking.k8s.io and resources httproutes,
leaving the controller-owned root marker unchanged.
- Around line 177-182: The Gateway API httproutes RBAC rule uses an overly broad
verb wildcard. In deploy/templates/components/rbac/role.yaml:177-182, replace
verbs=* with get;list;watch;create;update;patch;delete, then regenerate the
corresponding manifests in
deploy/deployment/kubernetes/objects/devworkspace-controller-role.ClusterRole.yaml:179-184,
deploy/deployment/openshift/objects/devworkspace-controller-role.ClusterRole.yaml:179-184,
and
deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml:259-264.
Apply the same explicit verb list to existing ingresses/routes wildcard rules
only if they are part of the same least-privilege change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: da5d02c0-ae32-40bb-af72-8715f4d81c15

📥 Commits

Reviewing files that changed from the base of the PR and between f91eefe and 00da966.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (26)
  • apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go
  • apis/controller/v1alpha1/devworkspacerouting_types.go
  • apis/controller/v1alpha1/zz_generated.deepcopy.go
  • controllers/controller/devworkspacerouting/devworkspacerouting_controller.go
  • controllers/controller/devworkspacerouting/devworkspacerouting_controller_test.go
  • controllers/controller/devworkspacerouting/solvers/gateway_api_solver.go
  • controllers/controller/devworkspacerouting/solvers/solver.go
  • controllers/controller/devworkspacerouting/suite_test.go
  • controllers/controller/devworkspacerouting/sync_httproutes.go
  • controllers/controller/devworkspacerouting/testdata/gateway.networking.k8s.io_httproutes.yaml
  • controllers/controller/devworkspacerouting/util_test.go
  • deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml
  • deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml
  • deploy/deployment/kubernetes/combined.yaml
  • deploy/deployment/kubernetes/objects/devworkspace-controller-role.ClusterRole.yaml
  • deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml
  • deploy/deployment/openshift/combined.yaml
  • deploy/deployment/openshift/objects/devworkspace-controller-role.ClusterRole.yaml
  • deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml
  • deploy/templates/components/rbac/role.yaml
  • deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml
  • go.mod
  • main.go
  • pkg/config/sync.go
  • pkg/provision/sync/diff.go
  • pkg/provision/sync/diffopts.go

Comment on lines +115 to +116
err = gwapiv1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -t d -p 'gateway-api/apis/v1$' | head
rg -nP '\b(Install|AddToScheme)\b\s*=' --glob '**/gateway-api/apis/v1/*.go'

Repository: devfile/devworkspace-operator

Length of output: 167


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Find suite_test and references:"
fd -a 'suite_test.go$' . | sed 's#^\./##' | rg 'controllers/controller/devworkspacerouting|devworkspacerouting' || true
rg -n 'gwapiv1|routev1\.(Install|AddToScheme)|AddToScheme\(scheme\.Scheme\)' controllers/controller/devworkspacerouting go.mod go.sum 2>/dev/null || true

echo
echo "Go module gateway-api deps:"
python3 - <<'PY'
from pathlib import Path
for p in ['go.mod','go.sum']:
    if Path(p).exists():
        lines=[ln for ln in Path(p).read_text().splitlines() if 'gateway-api' in ln]
        print(f'--- {p}')
        for ln in lines[:40]:
            print(ln)
PY

Repository: devfile/devworkspace-operator

Length of output: 10022


🌐 Web query:

sigs.k8s.io/gateway-api v1.2.1 apis v1 Install AddToScheme deprecated

💡 Result:

In the Gateway API Go packages, the AddToScheme function is deprecated in favor of Install [1][2][3]. This deprecation is a common pattern in Kubernetes-related projects to align with updated conventions for registering API types into a runtime.Scheme. Both AddToScheme and Install typically resolve to the same underlying functionality provided by the SchemeBuilder [1][2][3], but Install is the currently supported and recommended idiom [2][4]. If you encounter code using AddToScheme (such as sigs.k8s.io/gateway-api/apis/v1.AddToScheme), you should update it to use the Install function from the same package (e.g., gwv1.Install) [2][4]. This ensures compatibility with current standards and avoids potential warnings or future removal of the deprecated alias [5][1][3]. It is also important to ensure that this registration happens exactly once (typically during the initialization or client setup phase) to avoid concurrency issues like "concurrent map writes" when multiple controllers or analyzers attempt to register types into the same shared runtime.Scheme simultaneously [2].

Citations:


Replace the deprecated gwapiv1.AddToScheme call with gwapiv1.Install.

This path is flagged by staticcheck SA1019; Install is the non-deprecated gateway-api v1 scheme-builder replacement and already matches this file’s routev1.Install usage.

🐛 Proposed fix
-	err = gwapiv1.AddToScheme(scheme.Scheme)
+	err = gwapiv1.Install(scheme.Scheme)
 	Expect(err).NotTo(HaveOccurred())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
err = gwapiv1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
err = gwapiv1.Install(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 115-115: SA1019: gwapiv1.AddToScheme is deprecated: use Install instead

(staticcheck)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/controller/devworkspacerouting/suite_test.go` around lines 115 -
116, Replace the deprecated gwapiv1.AddToScheme call with gwapiv1.Install in the
scheme setup, matching the existing routev1.Install usage while preserving the
existing error assertion.

Source: Linters/SAST tools

Comment on lines +66 to +81
gatewayClassName:
default: nginx
description: |-
GatewayClassName is the name of the GatewayClass to use.
This is used for validation and informational purposes.
Defaults to "nginx" if not specified.
type: string
name:
description: Name is the name of the Gateway resource
type: string
namespace:
description: |-
Namespace is the namespace of the Gateway resource.
If not specified, HTTPRoutes will reference a Gateway in the same namespace
as the DevWorkspace.
type: string

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate Gateway reference names as Kubernetes resource names.

name, namespace, and gatewayClassName are unrestricted strings; required only checks that name is present and still permits an empty or malformed value. Add DNS-1123/max-length validation so invalid references are rejected at the CRD boundary instead of failing during routing reconciliation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml`
around lines 66 - 81, The Gateway reference fields gatewayClassName, name, and
namespace in the CRD schema currently accept unrestricted strings. Add
Kubernetes DNS-1123 resource-name validation, including the appropriate maximum
length, to each field while preserving the existing descriptions and defaults;
ensure name remains required and empty or malformed values are rejected at CRD
validation.

Comment thread main.go
utilruntime.Must(controllerv1alpha1.AddToScheme(scheme))
utilruntime.Must(dwv1.AddToScheme(scheme))
utilruntime.Must(dwv2.AddToScheme(scheme))
utilruntime.Must(gwapiv1.AddToScheme(scheme))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | sed -n '1,120p'

echo
echo "Search gwapiv1 usage:"
rg -n "gwapiv1|Install\\(|AddToScheme\\(" -S . || true

echo
echo "Go module deps references:"
for f in go.mod go.sum; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    rg -n "gateway|gwapiv1|sigs.k8s.io/gateway-api|AddToScheme|Install" "$f" || true
  fi
done

Repository: devfile/devworkspace-operator

Length of output: 20904


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read main.go around the referenced line without executing repository code.
if [ -f main.go ]; then
  echo "--- main.go outline ---"
  ast-grep outline main.go --view compact || true
  echo "--- main.go lines 60-105 ---"
  sed -n '60,105p' main.go | nl -ba -v60
fi

Repository: devfile/devworkspace-operator

Length of output: 411


🌐 Web query:

gateway-api v1.2.1 apis/v1 AddToScheme deprecated Install

💡 Result:

In the context of the Kubernetes Gateway API, the function AddToScheme has been deprecated in favor of Install [1]. This transition is part of a broader move within the Kubernetes ecosystem to standardize how API types are registered with a runtime scheme. If you are maintaining code that previously used AddToScheme (e.g., v1alpha2.AddToScheme), you should update it to use the corresponding Install function (e.g., v1alpha2.Install) [1]. This pattern applies across the various API versions of the Gateway API (e.g., v1alpha2, v1beta1, v1) [1][2][3]. Regarding Gateway API v1.2.1 specifically, this was a patch release focused on addressing backward compatibility issues related to the SupportedFeatures field in the GatewayClass API [4][5]. It did not introduce the deprecation of AddToScheme itself, as this pattern is a general evolution of the project's Go client libraries [1]. If you are encountering issues related to installing CRDs or upgrading to v1.2 or later, please ensure that any stored versions of deprecated resources (such as v1alpha2 versions of GRPCRoute or ReferenceGrant) are properly migrated and removed from the storedVersions field in your custom resource definitions [4][6].

Citations:


Use gwapiv1.Install(scheme) instead of the deprecated AddToScheme.

AddToScheme is kept only for compatibility in Gateway API Go types; this entry is the only scheme registration still using it here, so switch it to Install to align with the nearby route/template/oauth/security registrations and avoid staticcheck SA1019.

🐛 Proposed fix
-	utilruntime.Must(gwapiv1.AddToScheme(scheme))
+	utilruntime.Must(gwapiv1.Install(scheme))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
utilruntime.Must(gwapiv1.AddToScheme(scheme))
utilruntime.Must(gwapiv1.Install(scheme))
🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 83-83: SA1019: gwapiv1.AddToScheme is deprecated: use Install instead

(staticcheck)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.go` at line 83, Replace the deprecated gwapiv1.AddToScheme call in the
scheme-registration setup with gwapiv1.Install(scheme), preserving the existing
utilruntime.Must handling and matching the nearby Gateway API registrations.

Source: Linters/SAST tools

Comment on lines +96 to +101
var httpRouteDiffOpts = cmp.Options{
cmpopts.IgnoreFields(gwapiv1.HTTPRoute{}, "TypeMeta", "ObjectMeta", "Status"),
cmpopts.IgnoreFields(gwapiv1.BackendRef{}, "Weight"),
cmpopts.IgnoreFields(gwapiv1.BackendObjectReference{}, "Group", "Kind", "Namespace"),
cmpopts.IgnoreFields(gwapiv1.ParentReference{}, "Group", "Kind", "Namespace"),
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -t f 'gateway_api_solver.go' --exec cat -n {}

Repository: devfile/devworkspace-operator

Length of output: 12597


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the diff options file and usages, then inspect the relevant sections.
ast-grep outline pkg/provision/sync/diffopts.go --view expanded || true
printf '\n--- usages ---\n'
rg -n "httpRouteDiffOpts|ParentReference|BackendObjectReference|HTTPRoute" pkg/provision/sync -g '!**/*_test.go'
printf '\n--- diffopts excerpt ---\n'
sed -n '1,180p' pkg/provision/sync/diffopts.go

Repository: devfile/devworkspace-operator

Length of output: 5214


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the solver tests around gateway namespace assertions.
rg -n "Namespace|gatewayRef|ParentReference|BackendObjectReference|HTTPRoute" pkg/provision/sync -g '*_test.go'
printf '\n--- solver tests excerpt ---\n'
sed -n '1,240p' pkg/provision/sync/solvers/*gateway*test.go 2>/dev/null || true

Repository: devfile/devworkspace-operator

Length of output: 167


Keep ParentReference.Namespace in the comparison.
Group/Kind can stay ignored, but Namespace comes from GatewayRef.Namespace (or the routing namespace fallback), so ignoring it hides routing.gatewayRef.namespace changes and leaves existing HTTPRoutes attached to the old Gateway namespace.

Proposed fix
 var httpRouteDiffOpts = cmp.Options{
 	cmpopts.IgnoreFields(gwapiv1.HTTPRoute{}, "TypeMeta", "ObjectMeta", "Status"),
 	cmpopts.IgnoreFields(gwapiv1.BackendRef{}, "Weight"),
 	cmpopts.IgnoreFields(gwapiv1.BackendObjectReference{}, "Group", "Kind", "Namespace"),
-	cmpopts.IgnoreFields(gwapiv1.ParentReference{}, "Group", "Kind", "Namespace"),
+	cmpopts.IgnoreFields(gwapiv1.ParentReference{}, "Group", "Kind"),
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var httpRouteDiffOpts = cmp.Options{
cmpopts.IgnoreFields(gwapiv1.HTTPRoute{}, "TypeMeta", "ObjectMeta", "Status"),
cmpopts.IgnoreFields(gwapiv1.BackendRef{}, "Weight"),
cmpopts.IgnoreFields(gwapiv1.BackendObjectReference{}, "Group", "Kind", "Namespace"),
cmpopts.IgnoreFields(gwapiv1.ParentReference{}, "Group", "Kind", "Namespace"),
}
var httpRouteDiffOpts = cmp.Options{
cmpopts.IgnoreFields(gwapiv1.HTTPRoute{}, "TypeMeta", "ObjectMeta", "Status"),
cmpopts.IgnoreFields(gwapiv1.BackendRef{}, "Weight"),
cmpopts.IgnoreFields(gwapiv1.BackendObjectReference{}, "Group", "Kind", "Namespace"),
cmpopts.IgnoreFields(gwapiv1.ParentReference{}, "Group", "Kind"),
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/provision/sync/diffopts.go` around lines 96 - 101, Update
httpRouteDiffOpts so cmpopts.IgnoreFields for gwapiv1.ParentReference omits only
Group and Kind, keeping Namespace included in comparisons; leave the other
HTTPRoute and reference-field ignore rules unchanged.

// that HTTPRoutes should attach to via parentRefs.
type GatewayReference struct {
// Name is the name of the Gateway resource
// +kubebuilder:validation:Required

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Required only checks presence; maybe add length bounds?

Suggested change
// +kubebuilder:validation:Required
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253

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, I will add the length bounds.

// Namespace is the namespace of the Gateway resource.
// If not specified, HTTPRoutes will reference a Gateway in the same namespace
// as the DevWorkspace.
// +kubebuilder:validation:Optional

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit, Perhaps add DNS-1123 label bounds here too

Suggested change
// +kubebuilder:validation:Optional
// +kubebuilder:validation:Optional
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=63

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.

Agreed, adding bounds.

// Defaults to "nginx" if not specified.
// +kubebuilder:validation:Optional
// +kubebuilder:default:="nginx"
GatewayClassName string `json:"gatewayClassName,omitempty"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

+kubebuilder:default always populates this, would omitempty be needed?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

On second thought, I am not sure why this field is needed. It is not referenced in gateway solver. It only consumes Name and Namespace. So generated HTTPRoutes are identical regardless of what this is set to.

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.

You're right, this field isn't used anywhere in the solver. I'll remove it to keep the API surface minimal — we can always add it back if we find a concrete use case.

Owns(&corev1.Service{}).
Owns(&networkingv1.Ingress{})
Owns(&networkingv1.Ingress{}).
Owns(&gwapiv1.HTTPRoute{})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We currently register it unconditionally. Gateway API CRDs aren't installed by default on Kubernetes or OpenShift. Could we gate this the same way the Route watch is gated ?

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 point, this will cause informer failures on clusters without Gateway API CRDs installed. I'll gate this similarly to how Route watches are gated. I'm thinking we can check for the HTTPRoute CRD presence at startup and only register the watch if it exists.

resources:
- httproutes
verbs:
- '*'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit, scope httproutes verbs instead of *

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.

Agreed, will scope to the specific verbs needed: get, list, watch, create, update, patch, delete.

Comment on lines +206 to +207
// 10 hour timeout (matching ingress2gateway output and long-running workspace sessions)
requestTimeout := gwapiv1.Duration("10h")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit, Perhaps introduce these timeouts as constants with links to ingress2gateway spec?

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.

Sure, will extract to a constant.

if len(httpRoute.Spec.Hostnames) > 0 {
hostname := string(httpRoute.Spec.Hostnames[0])
// Use HTTPS scheme
url = fmt.Sprintf("https://%s%s", hostname, endpoint.Path)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we reuse getURLForEndpoint (or mirror its logic) here so protocol, path-joining, and query/fragment handling stay consistent with the other solvers?

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 call, I will refactor to use getURLForEndpoint to get consistent path/query/fragment handling.

routeName := fmt.Sprintf("%s-http-redirect", common.RouteName(workspaceMeta.DevWorkspaceId, endpointName))

httpsScheme := "https"
statusCode := 301

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit, maybe use 308, which always preserves the request method?

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.

301 is the conventional choice for HTTP→HTTPS redirects (matches ingress-nginx, Traefik defaults, and ingress2gateway output). Since workspace endpoints are primarily browser-accessed, 301 is appropriate. Happy to discuss further if there's a specific use case for method-preserving redirects.

) gwapiv1.HTTPRoute {
routeName := fmt.Sprintf("%s-http-redirect", common.RouteName(workspaceMeta.DevWorkspaceId, endpointName))

httpsScheme := "https"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A bit naive question: why are we always redirecting to HTTPS and routing over 443? I haven't checked but how would TLS be handled in this setup? Just want to make sure I understand the intended flow.

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.

TLS termination is handled at the Gateway level, not in the solver. The Gateway is configured with an HTTPS listener (port 443) that has a TLS certificate. The solver creates two HTTPRoutes per endpoint: one on port 80 that redirects to HTTPS, and one on port 443 that routes to the workspace service. Traffic is encrypted between the client and Gateway; traffic from Gateway to the pod is cluster-internal plaintext (same model as Ingress with TLS termination).

httpsPort := gwapiv1.PortNumber(443)
servicePort := gwapiv1.PortNumber(endpoint.TargetPort)

return gwapiv1.HTTPRoute{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit, this builder and the one is createHTTPRedirectRoute share very similar fields. Maybe we can create a shared helper to reduce duplication?

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.

The two builders share ObjectMeta/ParentRefs setup but diverge significantly in their Rules (redirect filter vs backendRef + timeout). I lean toward keeping them separate for readability, but happy to extract the common ObjectMeta/ParentRef setup into a helper if you feel strongly.

@rohanKanojia

Copy link
Copy Markdown
Member

I tested this PR on OpenShift with the built-in OpenShift Gateway API and can confirm the gateway-api routing class works as expected.

Environment: OCP 4.20 (AWS CI), DWO built from this PR.

Steps I followed

1. Enable OpenShift Gateway API

oc apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: openshift-default
spec:
  controllerName: openshift.io/gateway-controller/v1
EOF

oc get gatewayclass openshift-default -o jsonpath='{range .status.conditions[*]}{.type}={.status} {.message}{"\n"}{end}'
oc -n openshift-ingress get pods

Example output:

Accepted=True Handled by Istio controller
ControllerInstalled=True istiod v1.26.8 installed
CRDsReady=True CRDs updated by CIO

NAME                                       READY   STATUS    RESTARTS   AGE
istiod-openshift-gateway-9dbdfcf6c-9gpz4   1/1     Running   0          92s
router-default-67bc5b596d-c4j2q            1/1     Running   0          33m

2. Create a shared Gateway (80 + 443, cross-namespace routes)

oc -n openshift-ingress get secrets | grep tls
DOMAIN=$(oc get ingresses.config.openshift.io cluster -o jsonpath='{.spec.domain}')
echo "$DOMAIN"

oc apply -f - <<'EOF'
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: shared-gateway
  namespace: openshift-ingress
spec:
  gatewayClassName: openshift-default
  listeners:
  - name: http
    protocol: HTTP
    port: 80
    allowedRoutes:
      namespaces:
        from: All
  - name: https
    protocol: HTTPS
    port: 443
    tls:
      mode: Terminate
      certificateRefs:
      - name: router-certs-default
    allowedRoutes:
      namespaces:
        from: All
EOF

oc -n openshift-ingress get gateway shared-gateway
oc -n openshift-ingress get svc | grep shared-gateway

3. Install DWO from this PR and configure DWOC

DOMAIN=$(oc get ingresses.config.openshift.io cluster -o jsonpath='{.spec.domain}')

oc apply -f - <<EOF
apiVersion: controller.devfile.io/v1alpha1
kind: DevWorkspaceOperatorConfig
metadata:
  name: devworkspace-operator-config
  namespace: openshift-operators
config:
  routing:
    clusterHostSuffix: ${DOMAIN}
    defaultRoutingClass: gateway-api
    gatewayRef:
      name: shared-gateway
      namespace: openshift-ingress
      gatewayClassName: openshift-default
EOF

oc -n openshift-operators get dwoc devworkspace-operator-config -o yaml
oc -n openshift-operators get deploy | grep -i workspace

4. Create a test DevWorkspace

Use traefik/whoami on port 8080 (OpenShift blocks non-root bind on :80):

oc new-project gw-api-test 2>/dev/null || oc project gw-api-test

oc apply -f - <<'EOF'
apiVersion: workspace.devfile.io/v1alpha2
kind: DevWorkspace
metadata:
  name: gw-test
  namespace: gw-api-test
spec:
  started: true
  routingClass: gateway-api
  template:
    components:
      - name: tools
        container:
          image: traefik/whoami:v1.10.3
          args: ["--port", "8080"]
          memoryLimit: 128Mi
          endpoints:
            - name: http-server
              targetPort: 8080
              exposure: public
              protocol: http
EOF

oc -n gw-api-test get dw,pod

Example output:

NAMESPACE     NAME      DEVWORKSPACE ID             PHASE     INFO
gw-api-test   gw-test   workspace1f219d6e267842ba   Running   Workspace is running

NAME                                         READY   STATUS    RESTARTS   AGE
workspace1f219d6e267842ba-66bb5d6d8d-2h5wh   1/1     Running   0          13s

5. Verify routing objects

oc -n gw-api-test get dw,dwr,svc,httproute
oc -n gw-api-test get dwr -o jsonpath='{.items[0].status.phase}{"\n"}{.items[0].status.exposedEndpoints.tools[0].url}{"\n"}'
oc -n gw-api-test get httproute -o yaml

Example output:

NAME                                        DEVWORKSPACE ID             PHASE     INFO
devworkspace.workspace.devfile.io/gw-test   workspace1f219d6e267842ba   Running   Workspace is running

NAME                                                                          DEVWORKSPACE ID             PHASE   INFO
devworkspacerouting.controller.devfile.io/routing-workspace1f219d6e267842ba   workspace1f219d6e267842ba   Ready   DevWorkspaceRouting prepared

NAME                                        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/workspace1f219d6e267842ba-service   ClusterIP   172.30.74.209   <none>        8080/TCP   31s

NAME                                                                                      HOSTNAMES                                                                                        AGE
httproute.../workspace1f219d6e267842ba-http-server                 ["workspace1f219d6e267842ba-http-server-8080.apps.ci-ln-….aws-4.ci.openshift.org"]   30s
httproute.../workspace1f219d6e267842ba-http-server-http-redirect   ["workspace1f219d6e267842ba-http-server-8080.apps.ci-ln-….aws-4.ci.openshift.org"]   30s

Ready
https://workspace1f219d6e267842ba-http-server-8080.apps.ci-ln-bipwf72-76ef8.aws-4.ci.openshift.org

HTTPRoute (HTTPS) — key fields:

spec:
  parentRefs:
  - name: shared-gateway
    namespace: openshift-ingress
    port: 443
  rules:
  - backendRefs:
    - name: workspace1f219d6e267842ba-service
      port: 8080
status:
  parents:
  - controllerName: openshift.io/gateway-controller/v1
    conditions:
    - type: Accepted
      status: "True"
    - type: ResolvedRefs
      status: "True"

6. Verify traffic through the Gateway

HOST=$(oc -n gw-api-test get dwr -o jsonpath='{.items[0].status.exposedEndpoints.tools[0].url}' | sed 's|https://||')
GATEWAY_LB=$(oc -n openshift-ingress get svc shared-gateway-openshift-default -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
GW_IP=$(dig +short "$GATEWAY_LB" | head -1)

echo "HOST=$HOST"
echo "GATEWAY_LB=$GATEWAY_LB"
echo "GW_IP=$GW_IP"

curl -vk --resolve "${HOST}:443:${GW_IP}" "https://${HOST}/"

Example output:

HOST=workspace1f219d6e267842ba-http-server-8080.apps.ci-ln-********s-4.ci.openshift.org
GATEWAY_LB=a164c792794794586a2894a79******.us-west-1.elb.amazonaws.com
GW_IP=54.177.216.135

HTTP/2 200
…
Hostname: workspace1f219d6e267842ba-66bb5d6d8d-2h5wh
IP: 10.131.0.25
RemoteAddr: 10.128.2.15:41966
GET / HTTP/1.1
Host: workspace1f219d6e267842ba-http-server-8080.apps.ci-ln-bipwf72-76ef8.aws-4.ci.openshift.org
X-Forwarded-Proto: https
…

7. Cleanup

oc delete dw gw-test -n gw-api-test
oc get httproute -n gw-api-test
oc delete project gw-api-test

Result

  • GatewayClass / Gateway: OpenShift built-in controller, Gateway Programmed=True
  • DWO creates HTTPRoutes with correct parentRefs; controller Accepted=True
  • Traffic through Gateway LoadBalancer with curl --resolveHTTP 200 (whoami)

I can confirm the gateway-api routing class from this PR works as expected
with OpenShift’s built-in Gateway API.

@rohanKanojia

Copy link
Copy Markdown
Member

Also tested this setup on minikube with traefic following these steps https://gist.github.com/btjd/28e07c0f3b07ef0b35fa85ce7bc4f17c

I can confirm these steps also work ✔️

@btjd
btjd marked this pull request as ready for review August 3, 2026 18:53
@btjd
btjd force-pushed the 23675-gateway-api branch from 00da966 to bbd2dd3 Compare August 3, 2026 22:13

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
main.go (1)

218-222: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Exit when conversion webhook registration fails.

If either Complete() call returns an error, this code only logs the error and starts the manager. The operator can then run without a required conversion webhook. Terminate startup after logging either error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.go` around lines 218 - 222, Update both conversion webhook registration
checks around ctrl.NewWebhookManagedBy(...).Complete() to terminate startup
after logging an error, rather than continuing to start the manager. Preserve
the existing error messages and apply the same exit behavior to both
DevWorkspace v1alpha1 and v1alpha2 registrations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@main.go`:
- Around line 218-222: Update both conversion webhook registration checks around
ctrl.NewWebhookManagedBy(...).Complete() to terminate startup after logging an
error, rather than continuing to start the manager. Preserve the existing error
messages and apply the same exit behavior to both DevWorkspace v1alpha1 and
v1alpha2 registrations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 724b099b-9cca-4c51-817e-26d3cee0c6e5

📥 Commits

Reviewing files that changed from the base of the PR and between 00da966 and bbd2dd3.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (26)
  • apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go
  • apis/controller/v1alpha1/devworkspacerouting_types.go
  • apis/controller/v1alpha1/zz_generated.deepcopy.go
  • controllers/controller/devworkspacerouting/devworkspacerouting_controller.go
  • controllers/controller/devworkspacerouting/devworkspacerouting_controller_test.go
  • controllers/controller/devworkspacerouting/solvers/gateway_api_solver.go
  • controllers/controller/devworkspacerouting/solvers/solver.go
  • controllers/controller/devworkspacerouting/suite_test.go
  • controllers/controller/devworkspacerouting/sync_httproutes.go
  • controllers/controller/devworkspacerouting/testdata/gateway.networking.k8s.io_httproutes.yaml
  • controllers/controller/devworkspacerouting/util_test.go
  • deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml
  • deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml
  • deploy/deployment/kubernetes/combined.yaml
  • deploy/deployment/kubernetes/objects/devworkspace-controller-role.ClusterRole.yaml
  • deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml
  • deploy/deployment/openshift/combined.yaml
  • deploy/deployment/openshift/objects/devworkspace-controller-role.ClusterRole.yaml
  • deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml
  • deploy/templates/components/rbac/role.yaml
  • deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml
  • go.mod
  • main.go
  • pkg/config/sync.go
  • pkg/provision/sync/diff.go
  • pkg/provision/sync/diffopts.go
🚧 Files skipped from review as they are similar to previous changes (22)
  • deploy/deployment/kubernetes/objects/devworkspace-controller-role.ClusterRole.yaml
  • deploy/deployment/openshift/objects/devworkspace-controller-role.ClusterRole.yaml
  • deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml
  • controllers/controller/devworkspacerouting/util_test.go
  • deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml
  • pkg/config/sync.go
  • deploy/templates/components/rbac/role.yaml
  • pkg/provision/sync/diff.go
  • apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go
  • pkg/provision/sync/diffopts.go
  • controllers/controller/devworkspacerouting/solvers/solver.go
  • deploy/deployment/openshift/combined.yaml
  • deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml
  • deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml
  • apis/controller/v1alpha1/devworkspacerouting_types.go
  • controllers/controller/devworkspacerouting/testdata/gateway.networking.k8s.io_httproutes.yaml
  • controllers/controller/devworkspacerouting/devworkspacerouting_controller.go
  • deploy/deployment/kubernetes/combined.yaml
  • controllers/controller/devworkspacerouting/solvers/gateway_api_solver.go
  • deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml
  • controllers/controller/devworkspacerouting/sync_httproutes.go
  • apis/controller/v1alpha1/zz_generated.deepcopy.go

btjd added 3 commits August 3, 2026 18:24
Signed-off-by: Badre Tejado-Imam <btejado@redhat.com>
…t and watches

Signed-off-by: Badre Tejado-Imam <btejado@redhat.com>
…s stalls

Signed-off-by: Badre Tejado-Imam <btejado@redhat.com>
@btjd
btjd force-pushed the 23675-gateway-api branch from bbd2dd3 to 9b06672 Compare August 3, 2026 22:24
@tolusha

tolusha commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Hi! I'm che-ai-assistant — I help with your pull requests.

I check for new comments every 10m0s, so there may be a short delay before I respond.

Available commands:

  • /che-ai-assistant generate-che-doc — Generate a documentation PR based on this PR's changes
  • /che-ai-assistant ok-pr-review — Run a comprehensive PR review (summary, code review, deep review, impact analysis)
  • /che-ai-assistant ok-pr-readiness — Ensure PR has validation steps
  • /che-ai-assistant check-pr-test-failures — Analyze failing CI checks, identify root causes, and suggest fixes
  • /che-ai-assistant update-che-e2e-tests — Update Eclipse Che e2e tests
  • /che-ai-assistant claude — Run a free-form instruction on this PR
  • /che-ai-assistant help — Show this help message

@btjd

btjd commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

/retest

- Remove unused GatewayClassName field from GatewayReference struct
- Add MinLength/MaxLength validation markers on GatewayReference fields
- Gate HTTPRoute watch and cache behind Gateway API CRD availability
  using infrastructure detection pattern (IsGatewayAPIInstalled)
- Scope httproutes RBAC from wildcard to explicit verbs
- Make endpoint readiness log/status messages generic
- Extract httpRouteRequestTimeout constant for HTTPRoute timeout
- Reuse getURLForEndpoint for HTTPRoute URL construction

Assisted-by: Claude Opus 4.6

Signed-off-by: Badre Tejado-Imam <btejado@redhat.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Badre Tejado-Imam <btejado@redhat.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
controllers/controller/devworkspacerouting/devworkspacerouting_controller_test.go (3)

454-499: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared service assertions into a helper.

This block repeats the service assertions from the Kubernetes context (Lines 108-161) and the OpenShift context (Lines 244-295) almost verbatim. Move the consolidated-service and discoverable-service checks into a helper in util_test.go and call it from all three contexts. The Gateway API context also omits the "service is not created for non-exposed endpoint" check that the other two contexts perform; a shared helper closes that gap.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@controllers/controller/devworkspacerouting/devworkspacerouting_controller_test.go`
around lines 454 - 499, Extract the consolidated-service, discoverable-service,
and non-exposed-endpoint assertions from the test blocks into a shared helper in
util_test.go, using parameters for the workspace, exposed endpoints, namespace,
and Kubernetes client context as needed. Replace the duplicated checks in the
Kubernetes and OpenShift contexts with calls to this helper, and add the same
call to the Gateway API context so it also verifies that no service is created
for non-exposed endpoints.

519-523: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Assert the optional pointers are non-nil before dereferencing them.

ParentReference.Namespace, ParentReference.Port, BackendObjectReference.Port, HTTPRequestRedirectFilter.Scheme, and HTTPRequestRedirectFilter.StatusCode are all optional pointers in gwapiv1. If the solver omits any of them, the spec panics on a nil dereference instead of reporting the missing field. Add a ShouldNot(BeNil()) assertion before each dereference.

♻️ Proposed hardening for the parent reference assertions
 				parentRef := createdHTTPRoute.Spec.ParentRefs[0]
 				Expect(string(parentRef.Name)).Should(Equal(gatewayName), "HTTPRoute should reference the configured Gateway")
+				Expect(parentRef.Namespace).ShouldNot(BeNil(), "HTTPRoute parent reference should set the Gateway namespace")
 				Expect(*parentRef.Namespace).Should(Equal(gwapiv1.Namespace(gatewayNamespace)), "HTTPRoute should reference Gateway in correct namespace")
+				Expect(parentRef.Port).ShouldNot(BeNil(), "HTTPRoute parent reference should set the Gateway listener port")
 				Expect(*parentRef.Port).Should(Equal(gwapiv1.PortNumber(443)), "HTTPS HTTPRoute should reference port 443")

Apply the same pattern at Line 538, Line 552, and Lines 563-564.

Also applies to: 550-552, 562-564

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@controllers/controller/devworkspacerouting/devworkspacerouting_controller_test.go`
around lines 519 - 523, Harden the Gateway API assertions in the test by adding
ShouldNot(BeNil()) checks before dereferencing optional pointers in
ParentReference.Namespace and Port, BackendObjectReference.Port, and
HTTPRequestRedirectFilter.Scheme and StatusCode. Apply the checks at the
existing assertion blocks around createdHTTPRoute and the corresponding lines
near 538, 550-552, and 562-564, preserving the current value assertions
afterward.

370-370: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the production routing constants in the test expectations.

Replace the "gateway-api" literal with string(controllerv1alpha1.DevWorkspaceRoutingGatewayAPI), and replace the "-http-redirect" route suffix literals with the value generated by the solver. The gateway-api routing class constant exists, but the redirect suffix is currently only produced as `"%s-http-redirect"; export it or reuse the solver helper for the test lookup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@controllers/controller/devworkspacerouting/devworkspacerouting_controller_test.go`
at line 370, Update the test expectations around DefaultRoutingClass to use
string(controllerv1alpha1.DevWorkspaceRoutingGatewayAPI) instead of the
hard-coded "gateway-api" literal, and replace "-http-redirect" suffix literals
with the solver-generated value. Export or reuse the existing solver helper that
produces the "%s-http-redirect" suffix so the test lookup stays aligned with
production behavior.
apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go (1)

122-137: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider adding RFC 1123 pattern validation for Name and Namespace.

Name and Namespace have length bounds but no character-format validation. Kubernetes object names follow the RFC 1123 subdomain pattern (^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$), and namespace names follow the RFC 1123 label pattern (^[a-z0-9]([-a-z0-9]*[a-z0-9])?$). Without a pattern, an invalid Gateway name (for example, one with uppercase characters) passes CRD admission and only fails later, when the solver builds HTTPRoute parent references or when the Gateway API implementation rejects the route. Add a +kubebuilder:validation:Pattern for each field so invalid names are rejected at admission time.

♻️ Suggested pattern additions
 type GatewayReference struct {
 	// Name is the name of the Gateway resource
 	// +kubebuilder:validation:Required
 	// +kubebuilder:validation:MinLength=1
 	// +kubebuilder:validation:MaxLength=253
+	// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`
 	Name string `json:"name"`
 	// Namespace is the namespace of the Gateway resource.
 	// If not specified, HTTPRoutes will reference a Gateway in the same namespace
 	// as the DevWorkspace.
 	// +kubebuilder:validation:Optional
 	// +kubebuilder:validation:MinLength=1
 	// +kubebuilder:validation:MaxLength=63
+	// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`
 	Namespace *string `json:"namespace,omitempty"`
 }

Run make generate_all after this change to regenerate the CRD manifests. As per coding guidelines, "After modifying API types, struct fields in API types, or kubebuilder markers in apis/, run make generate_all."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go` around lines
122 - 137, Add RFC 1123 pattern validation markers to the Name and Namespace
fields of GatewayReference, using the subdomain pattern for Name and the label
pattern for Namespace while preserving their existing length and optionality
markers. Run make generate_all to regenerate the CRD manifests after updating
the API markers.

Source: Coding guidelines

deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml (1)

61-83: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add RFC1123 pattern validation for gatewayRef.name/gatewayRef.namespace (generated in two manifests).

Both generated manifests define the same gatewayRef schema with only maxLength/minLength on name and namespace, no character-set pattern. Other Kubernetes object name/namespace fields in these files (for example pvcName, serviceAccountName) enforce ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$. The root cause is the shared source struct; fix it once there.

  • deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml#L61-L83: regenerate after adding +kubebuilder:validation:Pattern to GatewayReference.Name/GatewayReference.Namespace in apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go.
  • deploy/deployment/openshift/combined.yaml#L61-L83: no separate edit needed; running make generate_all after the struct fix regenerates this file too.

As per coding guidelines: "After modifying API types, struct fields in API types, or kubebuilder markers in apis/, run make generate_all."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml`
around lines 61 - 83, Update GatewayReference.Name and
GatewayReference.Namespace in
apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go with the RFC1123
kubebuilder pattern validation matching other Kubernetes object names, then run
make generate_all. This regenerates the schema in
deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml#L61-L83
and deploy/deployment/openshift/combined.yaml#L61-L83; no separate manifest
edits are needed.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/infrastructure/cluster.go`:
- Around line 46-54: The Initialize flow around detect and isGatewayAPIInstalled
currently enables Gateway API based only on the group name; update discovery to
inspect gateway.networking.k8s.io/v1 resources and set the flag only when the
HTTPRoute resource is served. Preserve the existing error propagation and
unsupported-cluster handling, and use ServerGroupsAndResources or
ServerResourcesForGroupVersion for resource-level discovery.

---

Nitpick comments:
In `@apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go`:
- Around line 122-137: Add RFC 1123 pattern validation markers to the Name and
Namespace fields of GatewayReference, using the subdomain pattern for Name and
the label pattern for Namespace while preserving their existing length and
optionality markers. Run make generate_all to regenerate the CRD manifests after
updating the API markers.

In
`@controllers/controller/devworkspacerouting/devworkspacerouting_controller_test.go`:
- Around line 454-499: Extract the consolidated-service, discoverable-service,
and non-exposed-endpoint assertions from the test blocks into a shared helper in
util_test.go, using parameters for the workspace, exposed endpoints, namespace,
and Kubernetes client context as needed. Replace the duplicated checks in the
Kubernetes and OpenShift contexts with calls to this helper, and add the same
call to the Gateway API context so it also verifies that no service is created
for non-exposed endpoints.
- Around line 519-523: Harden the Gateway API assertions in the test by adding
ShouldNot(BeNil()) checks before dereferencing optional pointers in
ParentReference.Namespace and Port, BackendObjectReference.Port, and
HTTPRequestRedirectFilter.Scheme and StatusCode. Apply the checks at the
existing assertion blocks around createdHTTPRoute and the corresponding lines
near 538, 550-552, and 562-564, preserving the current value assertions
afterward.
- Line 370: Update the test expectations around DefaultRoutingClass to use
string(controllerv1alpha1.DevWorkspaceRoutingGatewayAPI) instead of the
hard-coded "gateway-api" literal, and replace "-http-redirect" suffix literals
with the solver-generated value. Export or reuse the existing solver helper that
produces the "%s-http-redirect" suffix so the test lookup stays aligned with
production behavior.

In
`@deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml`:
- Around line 61-83: Update GatewayReference.Name and GatewayReference.Namespace
in apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go with the RFC1123
kubebuilder pattern validation matching other Kubernetes object names, then run
make generate_all. This regenerates the schema in
deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml#L61-L83
and deploy/deployment/openshift/combined.yaml#L61-L83; no separate manifest
edits are needed.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c451e81f-e1cf-4369-9c56-c2739888369e

📥 Commits

Reviewing files that changed from the base of the PR and between bbd2dd3 and 5c9e528.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (28)
  • apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go
  • apis/controller/v1alpha1/devworkspacerouting_types.go
  • apis/controller/v1alpha1/zz_generated.deepcopy.go
  • controllers/controller/devworkspacerouting/devworkspacerouting_controller.go
  • controllers/controller/devworkspacerouting/devworkspacerouting_controller_test.go
  • controllers/controller/devworkspacerouting/solvers/gateway_api_solver.go
  • controllers/controller/devworkspacerouting/solvers/solver.go
  • controllers/controller/devworkspacerouting/suite_test.go
  • controllers/controller/devworkspacerouting/sync_httproutes.go
  • controllers/controller/devworkspacerouting/testdata/gateway.networking.k8s.io_httproutes.yaml
  • controllers/controller/devworkspacerouting/util_test.go
  • deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml
  • deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml
  • deploy/deployment/kubernetes/combined.yaml
  • deploy/deployment/kubernetes/objects/devworkspace-controller-role.ClusterRole.yaml
  • deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml
  • deploy/deployment/openshift/combined.yaml
  • deploy/deployment/openshift/objects/devworkspace-controller-role.ClusterRole.yaml
  • deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml
  • deploy/templates/components/rbac/role.yaml
  • deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml
  • go.mod
  • main.go
  • pkg/cache/cache.go
  • pkg/config/sync.go
  • pkg/infrastructure/cluster.go
  • pkg/provision/sync/diff.go
  • pkg/provision/sync/diffopts.go
🚧 Files skipped from review as they are similar to previous changes (20)
  • apis/controller/v1alpha1/zz_generated.deepcopy.go
  • pkg/provision/sync/diff.go
  • pkg/config/sync.go
  • deploy/templates/components/rbac/role.yaml
  • deploy/deployment/openshift/objects/devworkspace-controller-role.ClusterRole.yaml
  • main.go
  • controllers/controller/devworkspacerouting/sync_httproutes.go
  • deploy/deployment/kubernetes/objects/devworkspace-controller-role.ClusterRole.yaml
  • apis/controller/v1alpha1/devworkspacerouting_types.go
  • controllers/controller/devworkspacerouting/suite_test.go
  • go.mod
  • deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml
  • deploy/bundle/manifests/devworkspace-operator.clusterserviceversion.yaml
  • controllers/controller/devworkspacerouting/solvers/solver.go
  • controllers/controller/devworkspacerouting/devworkspacerouting_controller.go
  • deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml
  • controllers/controller/devworkspacerouting/solvers/gateway_api_solver.go
  • pkg/provision/sync/diffopts.go
  • controllers/controller/devworkspacerouting/util_test.go
  • controllers/controller/devworkspacerouting/testdata/gateway.networking.k8s.io_httproutes.yaml

Comment on lines +46 to +54
infraType, apiGroups, err := detect()
if err != nil {
return err
}
if current == Unsupported {
if infraType == Unsupported {
return fmt.Errorf("running on unsupported cluster")
}
current = infraType
isGatewayAPIInstalled = findAPIGroup(apiGroups, "gateway.networking.k8s.io") != nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/sh
set -eu

kubectl get --raw /apis/gateway.networking.k8s.io/v1 |
  jq -e '.resources[] | select(.name == "httproutes")'

Repository: devfile/devworkspace-operator

Length of output: 208


🏁 Script executed:

#!/bin/sh
set -eu

printf 'Files:\n'
git ls-files 'pkg/infrastructure/*.go' 'pkg/cache/cache.go' 'pkg/controllers/*' | sed -n '1,200p'

printf '\ncluster.go outline:\n'
ast-grep outline pkg/infrastructure/cluster.go || true

printf '\ncluster.go relevant lines:\n'
cat -n pkg/infrastructure/cluster.go | sed -n '1,140p'

printf '\nRouter/Gateway API source references:\n'
rg -n "HTTPRoute|GatewayRoute|gateway.networking.k8s.io|isGatewayAPIInstalled|GatewayNetwork|Supported|Unsupported|ServerGroups|ServerResources|ServerPreferredResources|findAPIGroup" pkg || true

Repository: devfile/devworkspace-operator

Length of output: 26074


Check the HTTPRoute resource before enabling Gateway API.

detect() only reads API groups via ServerGroups(), and Initialize() sets isGatewayAPIInstalled from the gateway.networking.k8s.io group name alone. Use resource discovery, such as ServerGroupsAndResources() or ServerResourcesForGroupVersion("gateway.networking.k8s.io/v1"), and set the flag only if v1 serves httproutes. This prevents false-positive Gateway API support when a group exists but the required resource is unavailable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/infrastructure/cluster.go` around lines 46 - 54, The Initialize flow
around detect and isGatewayAPIInstalled currently enables Gateway API based only
on the group name; update discovery to inspect gateway.networking.k8s.io/v1
resources and set the flag only when the HTTPRoute resource is served. Preserve
the existing error propagation and unsupported-cluster handling, and use
ServerGroupsAndResources or ServerResourcesForGroupVersion for resource-level
discovery.

@btjd

btjd commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

/retest

1 similar comment
@dkwon17

dkwon17 commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

/retest

@openshift-ci

openshift-ci Bot commented Aug 5, 2026

Copy link
Copy Markdown

@btjd: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/v14-che-happy-path 5c9e528 link true /test v14-che-happy-path

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

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.

4 participants