Skip to content

Implement LLMProviderTemplate, LLMProvider, LLMProxy, MCPProxy, APIKey, SubscriptionPlan, Subscription and ManagedSecret CRDs support in Gateway Operator#1813

Open
CrowleyRajapakse wants to merge 1 commit intowso2:mainfrom
CrowleyRajapakse:integration-tests-actions
Open

Implement LLMProviderTemplate, LLMProvider, LLMProxy, MCPProxy, APIKey, SubscriptionPlan, Subscription and ManagedSecret CRDs support in Gateway Operator#1813
CrowleyRajapakse wants to merge 1 commit intowso2:mainfrom
CrowleyRajapakse:integration-tests-actions

Conversation

@CrowleyRajapakse
Copy link
Copy Markdown
Contributor

Purpose

Implement LLMProviderTemplate, LLMProvider, LLMProxy, MCPProxy, APIKey, SubscriptionPlan, Subscription and ManagedSecret CRDs support in Gateway Operator

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

📝 Walkthrough

Implementation of Management API Resource Support in Gateway Operator

This PR extends the Gateway Operator to support reconciliation of eight additional Custom Resource Definitions (CRDs) against the management API, enabling unified gateway control for LLM providers, MCP proxies, API key management, subscriptions, and certificate handling alongside existing REST API support.

Core API Types Added

New v1alpha1 API types introduced:

  • LlmProviderTemplate: Defines token extraction patterns and model identification for LLM APIs
  • LlmProvider: Manages LLM backend providers with access control and upstream authentication
  • LlmProxy: Routes requests to LLM providers with policy support
  • Mcp: Model Context Protocol proxy configuration with prompts, resources, and tools
  • ManagedSecret: Platform-managed secrets with optional Kubernetes Secret references
  • Certificate: X.509 certificate management with PEM data sourcing
  • ApiKey: API key lifecycle management with expiration and parent resource references (RestApi, LlmProvider, LlmProxy)
  • SubscriptionPlan: Subscription tier definitions with throttling and quota controls
  • Subscription: API subscriptions binding users to plans with token-based authentication

All types support controller-managed status tracking with conditions, gateway-issued UUIDs, and update timestamps.

Controller Framework

Generic reconciliation engine (GenericReconciler) provides shared lifecycle management:

  • In-memory resource tracker for deployment coordination across reconciles
  • Retry logic with exponential backoff for transient failures
  • Status condition propagation (Accepted/Programmed lifecycle)
  • Finalizer-based deletion with best-effort cleanup
  • UUID persistence for idempotent gateway operations

Per-resource controllers implemented:

  • ApiKeyReconciler: Supports parent-path nesting for RestApi, LlmProvider, and LlmProxy parents
  • CertificateReconciler: UUID-keyed certificate lifecycle with optional key-only updates
  • LlmProviderReconciler, LlmProviderTemplateReconciler, LlmProxyReconciler: LLM resource orchestration with template/provider dependency watching
  • McpReconciler: MCP proxy deployment with upstream auth resolution
  • ManagedSecretReconciler: Secret value resolution with Kubernetes Secret integration
  • SubscriptionReconciler: UUID-keyed subscription create-once semantics with immutability enforcement via fingerprinting
  • SubscriptionPlanReconciler: Plan create/update/delete operations with optional quota configuration

Gateway Client Extensions

New management API client primitives:

  • Resource path builders: Endpoints for LLM providers/templates, LLM proxies, MCP proxies, secrets, certificates, subscriptions, and subscription plans
  • Generic CRUD operations: ResourceExists, DeployResource, CreateResource, UpdateResource, DeleteResource with content-type negotiation (YAML/JSON)
  • Type-specific clients: Certificate upload, API key nested-path operations, subscription CRUD with fingerprinting, subscription plan management
  • Envelope YAML builder: Standardized Kubernetes-style manifest generation from Go structs
  • Upstream auth resolution: Secret reference resolution for LLM provider and MCP upstream authentication

Configuration & Authorization

RBAC updates: Extended ClusterRole permissions for:

  • CRUD operations on all new resource types
  • /status subresource access for conditions and controller fields
  • /finalizers update for graceful deletion

CRD definitions: Complete OpenAPI v3 schemas with:

  • Required/optional field validation
  • Enum constraints (HTTP methods, duration units, status values)
  • CEL validation rules for mutual exclusivity (e.g., expiration modes, secret value sources)
  • Print columns for operational visibility (status IDs, plan names, API IDs)

Example Manifests & Documentation

Comprehensive examples added:

  • API key management across parent resources with expiration policies
  • LLM provider/template/proxy setup with upstream authentication
  • MCP backend deployment with session-based invocation
  • Certificate provisioning with Kubernetes Secret sourcing
  • Subscription plan and subscription resource lifecycle
  • Managed secret configuration with optional encryption key references

Documentation additions:

  • Feature specification document outlining implementation scope and runtime behavior
  • GitHub discussion proposal with reconciler architecture and status/finalizer semantics
  • Integration guide with end-to-end curl examples and verification steps
  • Maintainer notes linking management and Gateway API implementation contexts

Integration Testing

Extended operator integration test workflow:

  • Management prerequisite setup (Secrets, ManagedSecrets)
  • LLM mock deployment with HTTPS termination
  • MCP backend integration with tool invocation validation
  • API key enforcement verification across parent types
  • Subscription and plan lifecycle testing
  • End-to-end gateway runtime validation with multiple authentication flows
  • Comprehensive cleanup and diagnostics on failure

Secret Source Handling

New secretsource package provides unified resolution:

  • Inline plaintext values via spec.*.value
  • Kubernetes Secret key references via spec.*.valueFrom with optional selectors
  • Error classification (missing secrets, ambiguous config, unconfigured sources)
  • Consistent error propagation for reconciler retry/failure logic

All changes maintain backward compatibility with existing RestApi reconciliation while establishing patterns for future management API resource types.

Walkthrough

Adds management-API-backed functionality to the gateway-operator: new v1alpha1 CRD types (ApiKey, Certificate, LlmProvider, LlmProviderTemplate, LlmProxy, Mcp, ManagedSecret, SubscriptionPlan, Subscription), generated deepcopy and CRD manifests, per-kind controllers wired into main, a shared GenericReconciler with an in-memory ResourceTracker, gatewayclient resource helpers (paths, CRUD, envelope YAML), secret-resolution utilities, RBAC/Helm updates, example/demo manifests, and unit/integration tests.

Sequence Diagram(s)

sequenceDiagram
    participant K8s as Kubernetes API
    participant Controller as GenericReconciler
    participant Adapter as ResourceAdapter
    participant Tracker as ResourceTracker
    participant Secret as Kubernetes Secret
    participant GWClient as gatewayclient
    participant GWAPI as Gateway Management API

    K8s->>Controller: CR create/update
    Controller->>Tracker: Get or create tracking entry
    Tracker-->>Controller: tracking state

    alt needs deploy
        Controller->>Adapter: Prepare deploy (resolve spec)
        Adapter->>Secret: Resolve SecretValueSource (if present)
        Secret-->>Adapter: secret value / error
        Adapter-->>Controller: deploy payload + metadata

        Controller->>GWClient: ResourceExists probe
        GWClient->>GWAPI: GET /{resource}/{handle}
        GWAPI-->>GWClient: 200 / 404
        GWClient-->>Controller: exists?

        Controller->>GWClient: DeployResource (POST or PUT)
        GWClient->>GWAPI: POST/PUT /{resource}[/{handle}]
        GWAPI-->>GWClient: 200/201 (+body)
        GWClient-->>Controller: result (Id)

        Controller->>Adapter: Persist Id (if returned)
        Adapter-->>Controller: updated status fields
        Controller->>K8s: Patch CR.status (Programmed=true / status.id)
    else already programmed
        Controller->>Tracker: synchronize state (no-op)
    end

    K8s->>Controller: CR deletion
    Controller->>Adapter: Delete (uses status.id or handle)
    Adapter->>GWClient: DeleteResource
    GWClient->>GWAPI: DELETE /{resource}/{id}
    GWAPI-->>GWClient: 200/204/404
    GWClient-->>Adapter: deletion result
    Controller->>K8s: remove finalizer / update status
Loading
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (21)
kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go-40-43 (1)

40-43: ⚠️ Potential issue | 🟠 Major

Add CRD validation for spec.status enum.

The field mirrors the management-API SubscriptionPlanCreateRequest.Status enum (ACTIVE, INACTIVE), but currently accepts any string. Add the +kubebuilder:validation:Enum=ACTIVE,INACTIVE marker to line 40 to enforce validation at CRD admission time rather than failing at reconciliation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go` around
lines 40 - 43, The Status field in the SubscriptionPlan spec currently allows
any string; add a kubebuilder validation marker to enforce the allowed enum
values. Insert the comment marker +kubebuilder:validation:Enum=ACTIVE,INACTIVE
immediately above the Status field definition for the Status *string
`json:"status,omitempty"` in subscriptionplan_types.go so the generated CRD
enforces the ACTIVE/INACTIVE values at admission time.
kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yaml-187-254 (1)

187-254: ⚠️ Potential issue | 🟠 Major

Harden upstream validation to reject empty backend config.

upstream is required, but the schema currently allows an empty object. This can admit invalid resources and defer failure to reconcile time.

Proposed CRD schema fix
               upstream:
                 description: Upstream is the MCP backend.
+                anyOf:
+                - required:
+                  - url
+                - required:
+                  - ref
                 properties:
                   auth:
                     description: Auth configures upstream credentials.
@@
                   url:
                     description: Url is the direct backend URL.
+                    format: uri
+                    minLength: 1
                     type: string
                 type: object
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yaml`
around lines 187 - 254, The upstream object currently allows an empty object;
make the CRD reject empty backend configs by adding validation requiring at
least one backend field. Modify the upstream schema (the upstream: properties
block) to include either a minProperties: 1 entry or an anyOf/oneOf that
requires one of the concrete fields (e.g., auth, hostRewrite, ref, url) to be
present; update the upstream object under the CRD so that upstream cannot be an
empty object and ensure the chosen constraint is applied alongside the existing
properties and types.
kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go-53-57 (1)

53-57: ⚠️ Potential issue | 🟠 Major

Validate gateway response contains a non-empty certificate ID.

UploadCertificate should fail fast if id is missing, otherwise later update/delete operations can fail with unclear errors.

Suggested code fix
 	var out CertificateCreateResponse
 	if err := json.Unmarshal(respBody, &out); err != nil {
 		return nil, fmt.Errorf("decode certificate response: %w", err)
 	}
+	if out.Id == "" {
+		return nil, fmt.Errorf("certificate response missing id")
+	}
 	return &out, nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go`
around lines 53 - 57, In UploadCertificate, after unmarshalling into
CertificateCreateResponse (variable out), validate that out.ID (or the struct's
certificate id field) is non-empty and return a clear error (e.g.,
fmt.Errorf("empty certificate id in gateway response")) if it is missing; this
ensures UploadCertificate fails fast and prevents downstream update/delete
errors when out.ID is empty.
kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yaml-79-115 (1)

79-115: ⚠️ Potential issue | 🟠 Major

Tighten subscriptionToken validation to require a token source and complete valueFrom references.

subscriptionToken is required at the spec level, but the schema allows an empty object {} and an incomplete valueFrom (missing the mandatory name field to reference a Secret). Add name to the required fields in valueFrom and use oneOf to enforce exactly one token source.

Suggested schema fix
               subscriptionToken:
                 properties:
                   valueFrom:
                     required:
+                    - name
                     - key
                     type: object
                     x-kubernetes-map-type: atomic
+                oneOf:
+                - required: ["value"]
+                - required: ["valueFrom"]
                 type: object
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yaml`
around lines 79 - 115, The SubscriptionToken schema lets an empty object or
incomplete valueFrom pass validation; update the CRD by marking valueFrom.name
as required and enforce exactly one token source by replacing the current
optional properties with a oneOf that lists an object with required "value" and
an object with required "valueFrom" (where valueFrom itself requires "name" and
"key"), updating the SubscriptionToken definition (properties: value, valueFrom)
so either inline value OR valueFrom (but not both/none) is mandatory and
valueFrom.name cannot be omitted.
kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml-173-208 (1)

173-208: ⚠️ Potential issue | 🟠 Major

Add oneOf validation to enforce exactly one credential source in value object.

The schema description requires exactly one of value or valueFrom, but the current schema allows both to be omitted or both to be present. Kubernetes admission will accept an empty object {}, violating the documented constraint. Use oneOf to enforce mutual exclusivity at validation time.

Suggested schema fix
                        type: object
                        properties:
                          value:
                            description: |-
                              Value is the inline plaintext value. Avoid for production use; prefer
                              ValueFrom so the secret is stored in a Kubernetes Secret.
                            type: string
                          valueFrom:
                            description: |-
                              ValueFrom selects a key from a Kubernetes Secret in the same namespace
                              as the owning CR.
                            properties:
                              key:
                                description: The key of the secret to select from.  Must
                                  be a valid secret key.
                                type: string
                              name:
                                default: ""
                                description: |-
                                  Name of the referent.
                                  This field is effectively required, but due to backwards compatibility is
                                  allowed to be empty. Instances of this type with an empty value here are
                                  almost certainly wrong.
                                  More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
                                type: string
                              optional:
                                description: Specify whether the Secret or its key
                                  must be defined
                                type: boolean
                            required:
                            - key
                            type: object
                            x-kubernetes-map-type: atomic
+                        oneOf:
+                        - required: ["value"]
+                        - required: ["valueFrom"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml`
around lines 173 - 208, The schema for the credential object currently defines
properties "value" and "valueFrom" but does not enforce mutual exclusivity; add
a top-level oneOf with two subschemas—one requiring "value" and the other
requiring "valueFrom"—so exactly one of the properties is present; keep the
existing properties (including the nested "valueFrom" shape, its required "key",
and x-kubernetes-map-type: atomic) and the surrounding type: object, and add the
oneOf block alongside properties to validate the constraint.
kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml-91-119 (1)

91-119: ⚠️ Potential issue | 🟠 Major

Enforce expiresAt vs expiresIn mutual exclusivity in schema.

The CRD documents these fields as mutually exclusive, but admission validation currently allows both. Add schema validation to reject dual specification and keep behavior deterministic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml`
around lines 91 - 119, The schema currently allows both expiresAt and expiresIn;
add a validation rule at the parent object (the object that defines properties
expiresAt and expiresIn) to forbid both being provided by adding a "not" clause
that has "required": ["expiresAt","expiresIn"] so any object with both fields
fails validation; place this alongside the existing properties (expiresAt,
expiresIn) and keep the existing expiresIn sub-schema (duration, unit)
unchanged.
kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yaml-133-172 (1)

133-172: ⚠️ Potential issue | 🟠 Major

Enforce exactly one credential source in auth.value.

The schema text says exactly one of value or valueFrom must be set, but this is not validated. Add structural validation so invalid combinations are rejected at admission time.

Suggested schema fix
                       value:
                         description: |-
                           Value sources the credential. Exactly one of value or valueFrom must
                           be set.
                         properties:
                           value:
                             description: |-
                               Value is the inline plaintext value. Avoid for production use; prefer
                               ValueFrom so the secret is stored in a Kubernetes Secret.
                             type: string
                           valueFrom:
                             description: |-
                               ValueFrom selects a key from a Kubernetes Secret in the same namespace
                               as the owning CR.
                             properties:
                               key:
                                 description: The key of the secret to select from.  Must
                                   be a valid secret key.
                                 type: string
                               name:
                                 default: ""
                                 description: |-
                                   Name of the referent.
                                   This field is effectively required, but due to backwards compatibility is
                                   allowed to be empty. Instances of this type with an empty value here are
                                   almost certainly wrong.
                                   More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
                                 type: string
                               optional:
                                 description: Specify whether the Secret or its key
                                   must be defined
                                 type: boolean
                             required:
                             - key
                             type: object
                             x-kubernetes-map-type: atomic
+                        oneOf:
+                        - required: ["value"]
+                        - required: ["valueFrom"]
                         type: object
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yaml`
around lines 133 - 172, The CRD allows both or neither of the credential fields
because the schema for the object containing the properties "value" and
"valueFrom" does not enforce exclusivity; update that object schema (the block
that defines properties value and valueFrom) to use oneOf with two
alternatives—one that requires "value" and disallows "valueFrom", and one that
requires "valueFrom" and disallows "value"—so admission rejects payloads that
set both or neither; also remove any standalone required: ["value"] on that
object so the oneOf governs requiredness.
kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml-53-67 (1)

53-67: ⚠️ Potential issue | 🟠 Major

Move HTTP method enum validation to array items

methods is typed as an array, but the enum is currently applied to the array node with scalar values. This makes validation behavior incorrect for method elements. Put the enum under items so each method value is validated.

Suggested CRD schema adjustment
 methods:
   description: Methods is the list of HTTP methods covered by this exception.
-  enum:
-  - GET
-  - POST
-  - PUT
-  - PATCH
-  - DELETE
-  - HEAD
-  - OPTIONS
-  - '*'
   items:
+    enum:
+    - GET
+    - POST
+    - PUT
+    - PATCH
+    - DELETE
+    - HEAD
+    - OPTIONS
+    - '*'
     type: string
   type: array

Also applies to: 116-129

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml`
around lines 53 - 67, The CRD currently places the enum on the array node for
the methods field which validates the array itself instead of each element;
update the gateway.api-platform.wso2.com_llmproviders CRD so that the enum is
moved under the items block for the methods array (i.e., ensure methods.type:
array remains, and add enum: [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, '*']
inside methods.items) and apply the same change to the other methods array block
referenced around lines 116-129 so each HTTP method element is validated
individually.
kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yaml-76-90 (1)

76-90: ⚠️ Potential issue | 🟠 Major

Fix methods enum placement to validate each item

The enum is attached to the array field rather than array items. Move enum constraints under items to correctly validate each HTTP method entry.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yaml`
around lines 76 - 90, The `methods` array schema currently places the enum on
the array itself, so change the schema for the `methods` field to move the enum
under `items` (i.e., keep `type: array` on `methods`, ensure `items` has `type:
string` and add the enum list inside `items`) so each entry is validated against
the allowed HTTP methods; remove the top-level `enum` that is currently sibling
to `items` so the array-level enum is not used.
kubernetes/gateway-operator/api/v1alpha1/mcp_types.go-207-208 (1)

207-208: ⚠️ Potential issue | 🟠 Major

Require spec at the CR root

spec is optional due to omitempty, which can allow admission of incomplete objects. Make spec required to enforce the intended contract at validation time.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/api/v1alpha1/mcp_types.go` around lines 207 -
208, The Spec field is currently optional due to `json:"spec,omitempty"`; make
it required by removing `omitempty` from the tag and adding a kubebuilder
validation marker so the CRD enforces presence. Specifically, update the struct
field `Spec MCPProxyConfigData` to use `json:"spec"` (no omitempty) and add `//
+kubebuilder:validation:Required` immediately above the `Spec` field declaration
in the same type definition so CRD generation will mark `spec` as required (then
regenerate CRDs).
kubernetes/gateway-operator/api/v1alpha1/mcp_types.go-41-49 (1)

41-49: ⚠️ Potential issue | 🟠 Major

Validate upstream target selection (url xor ref)

MCPUpstream currently permits both fields unset (or both set). Add schema validation to require exactly one upstream target source.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/api/v1alpha1/mcp_types.go` around lines 41 - 49,
MCPUpstream must enforce that exactly one of Url or Ref is provided; implement
validation by adding webhook validator methods on the MCPUpstream type
(implement ValidateCreate and ValidateUpdate) that check the XOR condition (one
set XOR the other set) and return a field.Invalid/field.Required error pointing
to the offending fields when the condition fails; also add or update the
kubebuilder webhook marker (e.g., +kubebuilder:webhook) for MCPUpstream if
missing so the CRD webhook is generated and registered.
kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml-56-60 (1)

56-60: ⚠️ Potential issue | 🟠 Major

Enforce minimum API key length in schema

The description states inline keys must be at least 36 characters, but the schema does not enforce it. Add minLength: 36 for spec.apiKey.value.

Suggested CRD schema update
 value:
   description: |-
     Value is the inline plaintext value. Avoid for production use; prefer
     ValueFrom so the secret is stored in a Kubernetes Secret.
+  minLength: 36
   type: string
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml`
around lines 56 - 60, The schema for the inline API key lacks the length
constraint described in the docs; update the CRD so that the property at
spec.apiKey.value includes minLength: 36 to enforce the 36-character minimum
(i.e., add minLength: 36 alongside type: string under the spec.apiKey.value
schema).
kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go-78-79 (1)

78-79: ⚠️ Potential issue | 🟠 Major

Make top-level spec required

Using omitempty on Spec allows CR creation without spec, which bypasses the intended required fields under the spec schema. Make spec mandatory at the root type.

Suggested type change
-	Spec   LLMProxyConfigData `json:"spec,omitempty"`
+	// +kubebuilder:validation:Required
+	Spec   LLMProxyConfigData `json:"spec"`
 	Status ResourceStatus     `json:"status,omitempty"`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go` around lines 78 -
79, The top-level Spec field is marked `json:"spec,omitempty"` which allows CRs
without a spec; update the struct so Spec is required by removing `omitempty`
and annotating it with a kubebuilder validation marker; specifically change the
field declaration for `Spec LLMProxyConfigData` in the top-level type (the one
that currently reads `Spec   LLMProxyConfigData `json:"spec,omitempty"` Status
ResourceStatus`) to `Spec LLMProxyConfigData `json:"spec"` and add
`+kubebuilder:validation:Required` above the Spec field so the generated CRD
enforces a required spec.
kubernetes/gateway-operator/api/v1alpha1/common_types.go-24-39 (1)

24-39: ⚠️ Potential issue | 🟠 Major

Enforce SecretValueSource exclusivity in the API schema

The type documents “exactly one of value or valueFrom”, but this is not enforced by schema validation. Add schema-level validation so invalid specs are rejected early.

Suggested validation marker
 type SecretValueSource struct {
+	// +kubebuilder:validation:XValidation:rule="has(self.value) != has(self.valueFrom)",message="exactly one of value or valueFrom must be set"
 	// Value is the inline plaintext value. Avoid for production use; prefer
 	// ValueFrom so the secret is stored in a Kubernetes Secret.
 	// +optional
 	Value *string `json:"value,omitempty"`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/api/v1alpha1/common_types.go` around lines 24 -
39, Add a kubebuilder CEL XValidation marker above the SecretValueSource type to
enforce mutual exclusivity: add a line like `//
+kubebuilder:validation:XValidation:rule="has(self.value) !=
has(self.valueFrom)",message="exactly one of value or valueFrom must be set"`
immediately above `type SecretValueSource struct { ... }` so schema validation
rejects CRs that set neither or both of Value and ValueFrom.
kubernetes/gateway-operator/internal/controller/subscription_controller.go-99-126 (1)

99-126: ⚠️ Potential issue | 🟠 Major

Post-create spec changes are silently ignored.

Once .status.id is set, the reconciler only forwards spec.status. Changes to apiId, applicationId, subscriptionPlanId, billing IDs, or subscriptionToken never reach the gateway and there is no validation or status signal explaining that those fields are effectively create-only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/internal/controller/subscription_controller.go`
around lines 99 - 126, The reconciler currently only sends spec.status changes
after creation (when cr.Status.Id is set), ignoring updates to apiId,
applicationId, subscriptionPlanId, billing IDs, and subscriptionToken; update
the logic in the reconcile path that uses cr.Status.Id (around the
CreateSubscription/UpdateSubscription flow) to: 1) detect changes to create-only
fields (compare current cr.Spec.* to last-applied values stored in status or an
annotation) and either reject/emit a NonRetryableError or set a status condition
explaining those fields are immutable, or 2) if the gateway supports updates for
those fields, include them in an expanded
gatewayclient.SubscriptionUpdatePayload and call
gatewayclient.UpdateSubscription with that payload (ensure to resolve
secretsource.Resolve for SubscriptionToken before sending); also ensure
DeployResult/Status.Id is preserved and update cr.Status with any validation
message or error so callers see that create-only fields were not applied.
kubernetes/gateway-operator/internal/controller/apikey_controller.go-60-69 (1)

60-69: ⚠️ Potential issue | 🟠 Major

Secret-backed keys can get stuck until the ApiKey changes.

ResolveOptional failures are marked non-retryable, but this controller only watches ApiKey and APIGateway. If the referenced Secret is created or fixed later, nothing here re-enqueues the resource automatically.

Also applies to: 99-102

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/internal/controller/apikey_controller.go` around
lines 60 - 69, The controller only watches ApiKey and APIGateway, so Secret
changes (e.g., resolved by ResolveOptional) won't re-enqueue ApiKeys; update
SetupWithManager to also watch corev1.Secret and enqueue related ApiKeys when a
Secret changes. Add a Watches(...) call for &corev1.Secret{} using
handler.EnqueueRequestsFromMapFunc with the existing enqueueAllOfKind helper
(e.g., enqueueAllOfKind(r.Client, &apiv1.ApiKeyList{})) and an appropriate
predicate to filter only Secrets referenced by ApiKey (or a generic
name/namespace match), so ApiKeyReconciler will be retriggered when a backing
Secret is created/updated.
kubernetes/gateway-operator/internal/controller/apikey_controller.go-114-123 (1)

114-123: ⚠️ Potential issue | 🟠 Major

Honor expiresAt precedence when both expiry fields are set.

The spec says expiresAt wins, but this code forwards both ExpiresIn and ExpiresAt. That leaves the outcome to the management API instead of enforcing the CR contract here.

Proposed fix
 	if cr.Spec.ExpiresIn != nil {
 		payload.ExpiresIn = &gatewayclient.APIKeyExpiry{
 			Duration: cr.Spec.ExpiresIn.Duration,
 			Unit:     cr.Spec.ExpiresIn.Unit,
 		}
 	}
 	if cr.Spec.ExpiresAt != nil {
 		t := cr.Spec.ExpiresAt.Time
-		payload.ExpiresAt = (*time.Time)(&t)
+		payload.ExpiresIn = nil
+		payload.ExpiresAt = &t
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/internal/controller/apikey_controller.go` around
lines 114 - 123, The current logic sets both payload.ExpiresIn and
payload.ExpiresAt when both cr.Spec.ExpiresIn and cr.Spec.ExpiresAt are present,
but the CR contract says ExpiresAt should take precedence; change the assignment
order and guards so that if cr.Spec.ExpiresAt != nil you set payload.ExpiresAt
(using t := cr.Spec.ExpiresAt.Time; payload.ExpiresAt = (*time.Time)(&t)) and do
NOT set payload.ExpiresIn; only when cr.Spec.ExpiresAt is nil should you
populate payload.ExpiresIn from cr.Spec.ExpiresIn.Duration/Unit. Ensure you
update the code paths around cr.Spec.ExpiresIn, cr.Spec.ExpiresAt,
payload.ExpiresIn and payload.ExpiresAt accordingly.
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05-management-resources.yaml-10-20 (1)

10-20: ⚠️ Potential issue | 🟠 Major

Replace the placeholder certificate with a parseable PEM.

demo-certificate-apim resolves cert.pem verbatim from this Secret. The current block is clearly placeholder content, so the Certificate example will fail once that resource is reconciled.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05-management-resources.yaml`
around lines 10 - 20, The cert.pem in the Secret used by demo-certificate-apim
contains placeholder text and must be replaced with a valid, parseable PEM
certificate (i.e., real base64 DER data between -----BEGIN CERTIFICATE----- and
-----END CERTIFICATE----- with no "DEMOONLY" or other filler); update the
cert.pem block in the 05-management-resources.yaml Secret for
demo-certificate-apim to include a proper PEM certificate (or a real certificate
chain if required) so the controller can successfully parse and reconcile the
Certificate.
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05-management-resources.yaml-33-37 (1)

33-37: ⚠️ Potential issue | 🟠 Major

Use the SecretValueSource.valueFrom shape that the controller actually resolves.

These blocks nest secretKeyRef under valueFrom, but the resolver in this PR consumes valueFrom itself as the selector. With the current shape, these secret-backed fields will not resolve correctly.

Suggested fix
   valueFrom:
-    secretKeyRef:
-      name: demo-management-secrets-apim
-      key: managed-secret-value
+    name: demo-management-secrets-apim
+    key: managed-secret-value

Apply the same flattening to the other valueFrom blocks in this file.

Also applies to: 76-80, 133-136, 179-183

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05-management-resources.yaml`
around lines 33 - 37, The YAML nests secretKeyRef under a `value` block (value
-> valueFrom -> secretKeyRef) but the controller expects the `valueFrom`
selector shape directly; update each affected block (the one using secretKeyRef
with name `demo-management-secrets-apim` and key `managed-secret-value`, plus
the other occurrences around the referenced ranges) by removing the extra
`value:` wrapper so `valueFrom:` is the top-level field containing
`secretKeyRef:` (i.e., flatten the structure so `valueFrom: secretKeyRef: name:
... key: ...`), and apply the same flattening to the other `valueFrom` blocks
noted in the comment.
kubernetes/gateway-operator/internal/secretsource/resolver.go-125-140 (1)

125-140: ⚠️ Potential issue | 🟠 Major

Honor optional: true when the Secret object is missing.

SecretKeySelector.Optional allows the referenced Secret or key to be absent. This code only suppresses the missing-key case, so optional refs still fail when the Secret itself does not exist.

Suggested fix
 	if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: sel.Name}, secret); err != nil {
 		if apierrors.IsNotFound(err) {
+			if sel.Optional != nil && *sel.Optional {
+				return "", nil
+			}
 			return "", &ErrSecretMissing{Field: fieldPath, Namespace: ns, Name: sel.Name, Cause: err}
 		}
 		return "", &ErrSecretMissing{Field: fieldPath, Namespace: ns, Name: sel.Name, Cause: err}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/internal/secretsource/resolver.go` around lines
125 - 140, The code incorrectly returns ErrSecretMissing when the referenced
Secret is absent even if sel.Optional is set; update the error handling around
k8sClient.Get (the block that currently returns &ErrSecretMissing) to check
sel.Optional (nil-safe) and if true return "", nil instead of an error,
otherwise preserve the existing ErrSecretMissing behavior; keep the later
key-existence checks (secret.Data, secret.StringData) and the existing
ErrKeyMissing for absent keys unchanged.
kubernetes/gateway-operator/internal/gatewayclient/resource_client.go-140-145 (1)

140-145: ⚠️ Potential issue | 🟠 Major

Preserve retry classification for delete failures.

DeleteResource returns plain errors for non-success statuses, unlike the other gatewayclient helpers. That drops the retryable/non-retryable signal and makes delete handling inconsistent with deploy/create/update paths.

Suggested fix
 	switch resp.StatusCode {
 	case http.StatusOK, http.StatusNoContent, http.StatusAccepted, http.StatusNotFound:
 		return nil
 	default:
 		body, _ := io.ReadAll(resp.Body)
-		return fmt.Errorf("gateway returned status %d: %s", resp.StatusCode, string(body))
+		if IsRetryableStatusCode(resp.StatusCode) {
+			return &RetryableError{
+				Err:        fmt.Errorf("gateway returned status %d: %s", resp.StatusCode, string(body)),
+				StatusCode: resp.StatusCode,
+			}
+		}
+		return &NonRetryableError{
+			Err:        fmt.Errorf("gateway returned status %d: %s", resp.StatusCode, string(body)),
+			StatusCode: resp.StatusCode,
+		}
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/internal/gatewayclient/resource_client.go` around
lines 140 - 145, DeleteResource currently returns a plain fmt.Errorf for
non-success HTTP statuses (checking resp.StatusCode against
http.StatusOK/NoContent/Accepted/NotFound) which loses the
retryable/non-retryable classification; modify DeleteResource to
construct/return the same gateway error type used by the other gatewayclient
helpers (the helper used by deploy/create/update that encapsulates status, body
and a retryable flag) instead of fmt.Errorf so delete failures preserve retry
semantics—read resp.Body into body and pass status/body into that existing error
constructor or classifier used elsewhere in the package.
🟡 Minor comments (6)
kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_websubapis.yaml-42-43 (1)

42-43: ⚠️ Potential issue | 🟡 Minor

Fix type description mismatch in CRD schema.

The spec description says WebhookAPIData, but this CRD is WebSubApi. Please update the description to avoid API documentation drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_websubapis.yaml`
around lines 42 - 43, Update the CRD schema's spec.description to reference the
correct resource type: replace the incorrect "WebhookAPIData mirrors the
management-API WebhookAPIData payload." text with a description that mentions
WebSubApi (e.g., "WebSubApi mirrors the management-API WebSubApi payload." or an
accurate description of the WebSubApi spec) by editing the spec.description
field for the WebSubApi CRD so API docs no longer reference WebhookAPIData.
kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md-17-17 (1)

17-17: ⚠️ Potential issue | 🟡 Minor

Align resource naming with ManagedSecret throughout the plan.

The plan currently mixes Secret terminology with the implemented CRD naming. Please standardize to ManagedSecret where referring to the operator CRD to avoid ambiguity.

Also applies to: 65-66, 119-120, 155-156

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md` at
line 17, Update the documentation to consistently use the operator CRD name
ManagedSecret instead of Secret: replace the token "Secret" with "ManagedSecret"
in the resource list containing `LlmProviderTemplate`, `LlmProvider`,
`LlmProxy`, `Mcp`, `WebSubApi`, `Secret`, `Certificate`, `ApiKey`,
`SubscriptionPlan`, `Subscription` and in the other referenced occurrences (the
blocks around the same resource lists at the other sections). Ensure all
mentions of the operator CRD use the exact identifier ManagedSecret so the docs
match the implemented CRD naming.
kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml-53-60 (1)

53-60: ⚠️ Potential issue | 🟡 Minor

Add minLength for inline API key value.

The description requires at least 36 characters, but this is not validated in the schema. Add minLength: 36 for spec.apiKey.value to align validation with contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml`
around lines 53 - 60, Add a JSON Schema validation for the inline API key by
adding minLength: 36 under the spec.apiKey.value property in the CRD so the
"Value is the inline plaintext value" string field is validated to be at least
36 characters; update the ApiKey/value property block (the "value" property
under properties for ApiKey) to include minLength: 36 alongside type: string to
enforce the contract.
kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md-59-59 (1)

59-59: ⚠️ Potential issue | 🟡 Minor

Fix the malformed table row.

The formatting around RestApi is not closed, so this row does not render as a valid Markdown table entry.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md`
at line 59, The table row is malformed because the inline code backticks around
RestApi aren't closed and the pipe separators are misaligned; update the row so
code spans are balanced and the table cells are correct—e.g., ensure `RestApi`
is wrapped with matching backticks and the rest of the cell uses `ApiKey` and
`spec.parentRef` (`kind`, `name`) properly, keeping the pipe delimiters so the
row renders as: | Issue an API key for a **`RestApi` or LLM resource** |
**`ApiKey`** with **`spec.parentRef`** (`kind`, `name`); REST path nests under
parent. |
kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md-18-18 (1)

18-18: ⚠️ Potential issue | 🟡 Minor

Use the shipped CRD name consistently.

This doc introduces a custom Secret kind, but the PR scope is ManagedSecret. Keeping Secret here is easy to confuse with the core Kubernetes kind and does not match the resource users are being asked to adopt.

Also applies to: 58-58

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md`
at line 18, The doc currently refers to the custom CRD as `Secret`, which
conflicts with core Kubernetes `Secret` and the PR's actual CRD name; update all
occurrences (e.g., the line describing platform secrets and any other mentions
such as at lines noted) to use the shipped CRD name `ManagedSecret` (including
backticks and any headings), and ensure the text still conveys the same meaning
(platform secrets stored by gateway-controller, optional `valueFrom` to read
from core Secrets when populating payloads) but consistently references
`ManagedSecret`.
kubernetes/gateway-operator/api/v1alpha1/apikey_types.go-36-38 (1)

36-38: ⚠️ Potential issue | 🟡 Minor

Reject empty parentRef.name values at admission.

Required only guarantees the field is present. An empty string still passes schema validation here and would produce an invalid nested API-key path during reconciliation.

Proposed fix
 	// Name is the parent resource handle (metadata.name).
 	// +kubebuilder:validation:Required
+	// +kubebuilder:validation:MinLength=1
 	Name string `json:"name"`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/api/v1alpha1/apikey_types.go` around lines 36 -
38, The ParentRef.Name field currently only has
`+kubebuilder:validation:Required` which allows empty strings; add a
schema-level non-empty constraint so admission rejects empty values by updating
the ParentRef (Name) field in apikey_types.go to include a minimum-length
validation (e.g. add `// +kubebuilder:validation:MinLength=1` above the Name
string field) so the generated CRD/admission will reject empty parentRef.name
values; ensure the annotation is applied to the Name field in the ParentRef
struct (the `Name string `json:"name"`` declaration).
🧹 Nitpick comments (7)
kubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml (1)

9-9: Commented annotation references ConfigMap that exists.

The annotation gateway.api-platform.wso2.com/helm-values-configmap is commented out but references the ConfigMap custom-gateway-values defined in 01-gateway-values-configmap.yaml. This appears intentional for demo purposes, allowing users to uncomment the annotation to enable custom Helm values.

Ensure documentation or inline comments clarify when and how users should enable this feature.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml` at line
9, The commented annotation gateway.api-platform.wso2.com/helm-values-configmap
currently references the ConfigMap named custom-gateway-values but lacks
guidance; update the 02-gateway.yaml around the commented line to add a brief
inline comment explaining that users can uncomment
gateway.api-platform.wso2.com/helm-values-configmap: custom-gateway-values to
enable custom Helm values from the ConfigMap (custom-gateway-values) and
describe any prerequisites (e.g., ensure the ConfigMap is applied or describe
the related 01-gateway-values-configmap resource), so operators know when and
how to enable it.
kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml (1)

10-13: Consider environment compatibility for LoadBalancer service type.

The service type is set to LoadBalancer, which requires cloud provider integration. In local development environments (kind, minikube, Docker Desktop without metallb), this service type may remain in a pending state indefinitely.

Consider documenting environment requirements or providing an alternative demo configuration using NodePort or ClusterIP with port-forwarding instructions for local setups.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml`
around lines 10 - 13, The manifest sets gateway.gatewayRuntime.service.type to
LoadBalancer which can hang in local clusters; update the demo to either
document environment requirements for cloud LB providers or provide an
alternative config (e.g., gateway.gatewayRuntime.service.type: NodePort with a
specified nodePort or ClusterIP plus port-forward instructions) and include
brief instructions in the same chart/readme explaining when to use LoadBalancer
vs NodePort/ClusterIP for local dev (mention kind/minikube/MetalLB). Ensure
references to gateway.gatewayRuntime.service.type and the demo values file are
updated so users can easily switch modes.
kubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yaml (1)

7-21: Consider adding an explicit demo-only note above stringData.

A short comment clarifying these values are non-production placeholders would reduce accidental reuse risk in copied manifests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yaml`
around lines 7 - 21, Add an explicit demo-only comment above the stringData
block to warn these values are non-production placeholders and must not be
reused; reference the stringData block and the keys provider-token,
managed-secret-value, cert.pem, and subscription-token so the maintainer can
locate it quickly and include a clear "DEMO / DO NOT USE IN PRODUCTION" note and
brief guidance to replace with real credentials when deploying.
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yaml (1)

7-21: Optional clarity improvement: mark embedded values as demo-only.

Adding a short comment near stringData would help prevent accidental reuse of placeholder credentials outside demo environments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yaml`
around lines 7 - 21, Add a clear inline comment above the Kubernetes secret data
indicating these embedded values are demo-only and must not be used in
production; update the block containing stringData (and keys provider-token,
managed-secret-value, cert.pem, subscription-token) to include a short comment
like "DEMO ONLY — placeholder credentials/cert for testing" so future readers
cannot accidentally reuse these values outside demo environments.
kubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.go (1)

45-70: Strengthen assertions for fields already set in the fixture.

The test sets metadata annotations and auth header, but does not verify them in output. Adding those assertions will improve regression coverage.

Proposed test enhancement
 	require.Equal(t, "LlmProvider", got["kind"])
+	require.Equal(t, apiv1.GroupVersion.String(), got["apiVersion"])

 	metadata, ok := got["metadata"].(map[string]interface{})
 	require.True(t, ok)
 	require.Equal(t, "openai-provider", metadata["name"])
+	annotations, ok := metadata["annotations"].(map[string]interface{})
+	require.True(t, ok)
+	require.Equal(t, "platform", annotations["team"])

@@
 	auth, ok := upstream["auth"].(map[string]interface{})
 	require.True(t, ok)
 	require.Equal(t, "api-key", auth["type"])
+	require.Equal(t, "Authorization", auth["header"])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.go`
around lines 45 - 70, Add assertions that verify metadata annotations and the
auth header already set in the test fixture: after extracting metadata from got
(variable metadata) assert metadata["annotations"] is a map[string]interface{}
and that it contains the expected annotation key(s)/value(s) from the fixture;
similarly, after extracting auth and value (variables auth and value) assert the
auth header entry exists and matches the expected header name/value (e.g.,
ensure headers/header key in value is present and equals the fixture value). Use
the same require.True/require.Equal pattern as other checks for consistency when
checking these keys.
kubernetes/gateway-operator/internal/controller/generic_reconciler.go (2)

599-616: Error handling in cleanupAllGateways silently returns nil.

This function logs delete errors but always returns nil. The caller at line 584-587 checks the returned error, but that check is effectively dead code since cleanupAllGateways never returns an error.

This best-effort cleanup approach is reasonable to avoid blocking finalizer removal indefinitely, but consider:

  1. Documenting this behavior explicitly in a comment.
  2. Alternatively, aggregating errors and returning them so the caller can decide whether to proceed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/internal/controller/generic_reconciler.go` around
lines 599 - 616, cleanupAllGateways currently logs per-gateway delete failures
but always returns nil, making the caller's error check ineffective; update
cleanupAllGateways to collect individual errors from Adapter.Delete (called via
r.Adapter.Delete in the loop using authFn produced by r.buildAuthFn) into a
slice and after the loop return an aggregated error (use errors.Join or
k8s.io/apimachinery/pkg/util/errors.NewAggregate) when non-empty, or if you
prefer keep the best-effort behavior, add a concise comment above
cleanupAllGateways documenting that failures are logged but not propagated so
finalizer removal won't be blocked.

440-441: Consider renaming ResourceStatusDeployed usage for failed terminal states.

Setting ResourceStatusDeployed when max retries are exceeded or after non-retryable errors (also at line 485) is semantically misleading. The resource isn't actually deployed—reconciliation has simply been abandoned.

Consider introducing a distinct status like ResourceStatusFailed or ResourceStatusTerminal for these cases to improve clarity for future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/internal/controller/generic_reconciler.go` around
lines 440 - 441, The code currently sets entry.Status = ResourceStatusDeployed
when reconciliation is abandoned (e.g., max retries exceeded or non-retryable
errors); introduce a distinct status constant (e.g., ResourceStatusFailed or
ResourceStatusTerminal) in the same status enum/type and replace uses where
reconciliation is being abandoned (the assignments to entry.Status before
calling r.Tracker.Set(trackingKey, entry) at the spots in generic_reconciler.go)
so the Tracker.Set call records the new terminal/failed status instead of
ResourceStatusDeployed; update any switch/handling logic and tests that expect
the old status to use the new status constant.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 00fcd98c-5b45-4668-af09-5c2fb3369276

📥 Commits

Reviewing files that changed from the base of the PR and between 87f8c56 and 3893e19.

📒 Files selected for processing (86)
  • gateway/examples/api-key.yaml
  • gateway/examples/certificate.yaml
  • gateway/examples/managed-secret.yaml
  • gateway/examples/subscription-plan.yaml
  • gateway/examples/subscription.yaml
  • gateway/examples/websub-api.yaml
  • kubernetes/gateway-operator/api/v1alpha1/apikey_types.go
  • kubernetes/gateway-operator/api/v1alpha1/certificate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/common_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovider_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
  • kubernetes/gateway-operator/api/v1alpha1/mcp_types.go
  • kubernetes/gateway-operator/api/v1alpha1/secret_types.go
  • kubernetes/gateway-operator/api/v1alpha1/subscription_types.go
  • kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go
  • kubernetes/gateway-operator/api/v1alpha1/websubapi_types.go
  • kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go
  • kubernetes/gateway-operator/cmd/main.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_websubapis.yaml
  • kubernetes/gateway-operator/config/rbac/role.yaml
  • kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.md
  • kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/internal/controller/apikey_controller.go
  • kubernetes/gateway-operator/internal/controller/certificate_controller.go
  • kubernetes/gateway-operator/internal/controller/generic_reconciler.go
  • kubernetes/gateway-operator/internal/controller/llmprovider_controller.go
  • kubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.go
  • kubernetes/gateway-operator/internal/controller/llmproxy_controller.go
  • kubernetes/gateway-operator/internal/controller/managedsecret_controller.go
  • kubernetes/gateway-operator/internal/controller/management_resources_helpers.go
  • kubernetes/gateway-operator/internal/controller/mcp_controller.go
  • kubernetes/gateway-operator/internal/controller/subscription_controller.go
  • kubernetes/gateway-operator/internal/controller/subscriptionplan_controller.go
  • kubernetes/gateway-operator/internal/controller/websubapi_controller.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/paths.go
  • kubernetes/gateway-operator/internal/gatewayclient/resource_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.go
  • kubernetes/gateway-operator/internal/secretsource/resolver.go
  • kubernetes/gateway-operator/internal/secretsource/resolver_test.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_websubapis.yaml
  • kubernetes/helm/operator-helm-chart/templates/_helpers.tpl
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05-management-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05d-websub-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05-management-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05d-websub-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.md
  • kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml
  • kubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml

@CrowleyRajapakse CrowleyRajapakse force-pushed the integration-tests-actions branch from 3893e19 to d5195e9 Compare April 29, 2026 09:59
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 16

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (6)
kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md-18-21 (1)

18-21: ⚠️ Potential issue | 🟡 Minor

Use ManagedSecret terminology consistently with the implemented CRD.

Lines 18 and 58 describe a Secret CR, but this PR introduces ManagedSecret as the CRD/controller surface. Aligning this wording will avoid operator confusion.

Also applies to: 58-58

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md`
around lines 18 - 21, The doc currently refers to a `Secret` CR but the
implemented CRD/controller uses `ManagedSecret`; update all occurrences (e.g.,
the bullet listing that currently says **`Secret`** and any other mentions
around lines referenced) to use **`ManagedSecret`** and its correct behaviour
(including the note about optional `valueFrom` reading core Secrets) so the
documentation matches the actual CRD/controller surface (`ManagedSecret`,
`Certificate`, `ApiKey`, `SubscriptionPlan`, `Subscription`). Ensure the term
`ManagedSecret` is used consistently wherever the CR was previously named
`Secret`.
kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go-79-85 (1)

79-85: ⚠️ Potential issue | 🟡 Minor

Validate keyHandle for key-specific operations.

For APIKeyExists and DeleteAPIKey, an empty key handle should be rejected early to avoid ambiguous collection-path behavior and to surface clearer caller errors.

Proposed fix
 func APIKeyExists(ctx context.Context, gatewayEndpoint, parentKind, parentHandle, keyHandle string, auth AuthHeaderFunc) (bool, error) {
+	if keyHandle == "" {
+		return false, fmt.Errorf("apikey handle is required")
+	}
 	rp, err := apiKeyResourcePathForExists(parentKind, parentHandle)
@@
 func DeleteAPIKey(ctx context.Context, gatewayEndpoint, parentKind, parentHandle, keyHandle string, auth AuthHeaderFunc) error {
+	if keyHandle == "" {
+		return fmt.Errorf("apikey handle is required")
+	}
 	rp, err := apiKeyResourcePathForExists(parentKind, parentHandle)

Also applies to: 102-108

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go` around
lines 79 - 85, Add explicit validation to reject an empty keyHandle at the start
of APIKeyExists and DeleteAPIKey: if keyHandle == "" return a clear error (e.g.,
fmt.Errorf("empty keyHandle")) instead of letting ResourceExists/ResourceDelete
operate on a collection path; keep the existing
apiKeyResourcePathForExists/apiKeyResourcePathForDelete calls for parent path
resolution after this check so callers get an early, unambiguous error for
key-specific operations.
kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md-59-60 (1)

59-60: ⚠️ Potential issue | 🟡 Minor

Fix broken Markdown in the API key use-case row.

Line 59 has an unclosed/misaligned formatted cell (**\RestApi` or LLM resource`) which breaks table rendering. Please correct the cell so the row renders consistently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md`
around lines 59 - 60, The table row has misnested/unclosed Markdown formatting
around the RestApi/LLM cell; fix the cell so the backticks and bold markers are
balanced and the pipe separators remain intact — for example ensure the cell
reads something like: Issue an API key for a **`RestApi` or LLM resource`** |
**`ApiKey`** with **`spec.parentRef`** (`kind`, `name`); REST path nests under
parent., making sure the inline code markers around `RestApi` and the bold
markup around the entire cell are properly closed and the table pipes align so
the `ApiKey` / `spec.parentRef` cell renders correctly.
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yaml-29-34 (1)

29-34: ⚠️ Potential issue | 🟡 Minor

Remove the unsupported outputSchema field.

MCPTool currently supports only name, description, inputSchema, and optional title, so outputSchema will be pruned from this resource. If this metadata is needed, add it to the type and regenerate the CRD first.

Suggested fix
   tools:
     - name: lookupOrder
       title: Lookup order
       description: Retrieve order details by id
       inputSchema: '{"type":"object","properties":{"orderId":{"type":"string"}},"required":["orderId"]}'
-      outputSchema: '{"type":"object","properties":{"order":{"type":"object"}}}'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yaml`
around lines 29 - 34, Remove the unsupported outputSchema field from the tool
definition: locate the tools entry with name "lookupOrder" (and its title
"Lookup order") and delete the outputSchema property so only supported fields
remain (name, description, inputSchema, and optional title); if you actually
need output metadata, add it to the MCPTool type and regenerate the CRD before
reintroducing it.
kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml-100-103 (1)

100-103: ⚠️ Potential issue | 🟡 Minor

Align expiresAt/expiresIn description with validation behavior

Line 102 says expiresAt wins when both are set, but Lines 157-159 reject objects that set both fields. Please update one side so behavior and documentation are consistent.

Also applies to: 157-159

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml`
around lines 100 - 103, The description for the expiresAt/expiresIn fields is
inconsistent with the CRD's validation; update the property description for
expiresAt/expiresIn to state that the two fields are mutually exclusive and that
objects specifying both fields will be rejected by validation (instead of saying
expiresAt "wins"), and ensure any other similar descriptions are changed to
match the existing validation rule that forbids setting both fields.
kubernetes/gateway-operator/api/v1alpha1/certificate_types.go-28-30 (1)

28-30: ⚠️ Potential issue | 🟡 Minor

Update comment to match certificate lifecycle behavior

Lines 28-30 mention PUT/DELETE on /certificates/{status.id}, but controller behavior is create-once (no PUT) and delete-by-id. Please adjust the comment to avoid drift from runtime behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/api/v1alpha1/certificate_types.go` around lines
28 - 30, Update the comment in certificate_types.go to reflect the actual
lifecycle: state that the gateway-controller assigns a UUID on first upload and
persists it to .status.id (create-once, no subsequent PUT), and that subsequent
reconcile operations use /certificates/{status.id} only for DELETE; replace the
current "PUT/DELETE" phrasing with wording that specifies create-once and
delete-by-id, referencing .status.id and the controller reconcile behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@kubernetes/gateway-operator/api/v1alpha1/mcp_types.go`:
- Around line 41-49: The MCPUpstream CRD type is too permissive: it defines both
Url and Ref and allows them to be omitted, but platform-api (mcp.go
Create/Update) and management-openapi (MCPProxyConfigData) require a URL-only
upstream; update MCPUpstream by removing the Ref field and making Url required
(or add CRD validation to forbid Ref and require Url) so that
req.Upstream.Main.Url is always present and platform validators align with the
CRD contract.

In
`@kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml`:
- Around line 53-67: The "methods" array in the CRD currently has the enum on
the array itself which prevents validating individual entries; move the enum
block under the items schema (so items.type: string also has enum: [GET, POST,
PUT, PATCH, DELETE, HEAD, OPTIONS, '*']) for both occurrences of the "methods"
field shown in the diff (the array definition at lines ~53-67 and the other
occurrence around lines 116-129), then regenerate the CRD from the Go source
types to persist the change.

In
`@kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_websubapis.yaml`:
- Around line 202-213: The CRD currently marks vhosts.main as required which
prevents partial vhost overrides; remove the required constraint so both
vhosts.main and vhosts.sandbox are optional (aligning with the APIVhost
contract). Update the gateway.api-platform.wso2.com_websubapis.yaml resource
definition by deleting the required: - main entry under the vhosts schema (the
vhosts properties block containing main and sandbox) so callers can
independently override either vhost.

In `@kubernetes/gateway-operator/internal/controller/apikey_controller.go`:
- Around line 72-74: The current Secret watch uses enqueueAllOfKind and
apiKeySecretWatchPredicate which causes every ApiKey to requeue on any Secret
change; replace this with a targeted mapper that only enqueues ApiKeys that
actually reference the changed Secret. Implement a mapper function (e.g.,
secretToApiKeyMapper or enqueueApiKeysReferencingSecret) that takes the Secret
object, lists ApiKey resources (apiv1.ApiKeyList) in the Secret's namespace
using r.Client, filters by the ApiKey's secret reference field (the
spec.secretRef or equivalent used by ApiKey), and returns only those
reconcile.Requests; then use
handler.EnqueueRequestsFromMapFunc(secretToApiKeyMapper) in the Watch call
instead of enqueueAllOfKind/apiKeySecretWatchPredicate. Ensure the mapper
handles nil/empty namespace and RBAC/list errors gracefully.
- Around line 163-166: When resolving the API key via
secretsource.ResolveOptional in apikey_controller.go you currently wrap every
error as gatewayclient.NonRetryableError; instead inspect the error using k8s
API error helpers (k8s.io/apimachinery/pkg/api/errors) after the ResolveOptional
call and only return a NonRetryableError for terminal errors such as
apierrors.IsNotFound(err), apierrors.IsForbidden(err) or
apierrors.IsInvalid(err); for other errors (transient read/connection errors)
return the raw err (or a retryable error) so reconciliation can retry. Keep the
existing symbols: secretsource.ResolveOptional, k8sClient, DeployResult{}, and
gatewayclient.NonRetryableError when implementing the branching.
- Around line 119-121: The current code suppresses Secret-triggered
reconciliation by returning false when c.List(...) fails; instead fail-open so
the Secret change isn't dropped: in the method containing the c.List call in
apikey_controller.go, replace the `return false` on list error with a fail-open
behavior (e.g., return `true` so reconciliation proceeds) or return the error to
force a requeue; ensure the error is at least logged via the controller's logger
before returning so failures are visible.

In `@kubernetes/gateway-operator/internal/controller/certificate_controller.go`:
- Around line 98-101: The current code in certificate_controller.go wraps every
error returned by secretsource.Resolve into a NonRetryableError; instead,
inspect the returned error from secretsource.Resolve(ctx, k8sClient,
"spec.certificate", cr.Spec.Certificate, cr.Namespace) and only convert known
terminal errors (e.g., secret not found or invalid secret data) into
&gatewayclient.NonRetryableError{Err: err}; for transient API/read errors (e.g.,
k8s api-server timeouts, network errors or errors that satisfy transient
conditions) return the original error (not wrapped) so the controller will
retry; use errors.Is/IsNotFound/IsForbidden from
k8s.io/apimachinery/pkg/api/errors or sentinel errors exported by secretsource
to distinguish terminal vs transient failure when handling the pem resolution
result.

In `@kubernetes/gateway-operator/internal/controller/generic_reconciler.go`:
- Around line 256-260: The code sets entry.Status = ResourceStatusDeployed
before calling updateProgrammed, which means if the subsequent updateProgrammed
(or its persistence) fails the reconciler later hits the early-return branch
that logs "deployment completed but status not yet propagated" and never
progresses; fix by only marking entry.Status as ResourceStatusDeployed after
updateProgrammed has successfully completed (or by retrying/persisting the
programmed update before changing the status). Concretely, move the
updateProgrammed call to occur before assigning ResourceStatusDeployed (or make
the code persist/update the entry status only after updateProgrammed returns
nil), and ensure the branch that checks for ResourceStatusDeployed in the
reconcile loop (the logic surrounding ResourceStatusDeployed and the call sites
that call updateProgrammed) no longer short-circuits reconciliation when the
programmed state hasn’t been confirmed.

In `@kubernetes/gateway-operator/internal/controller/llmproxy_controller.go`:
- Around line 98-101: The current code wraps every error from
resolveLLMProviderUpstreamAuth as gatewayclient.NonRetryableError; change it to
distinguish configuration errors from transient secret-read errors by having
resolveLLMProviderUpstreamAuth return or wrap errors with identifiable types
(e.g., a sentinel ErrTransientSecretRead or a typed error). In
llmproxy_controller.go where resolved, err :=
resolveLLMProviderUpstreamAuth(...), inspect the error: if it indicates a
configuration/validation problem return DeployResult{} with
&gatewayclient.NonRetryableError{Err: err}; if it indicates a transient
secret-read failure return DeployResult{} with the original error or wrap it as
a retryable error (e.g., gatewayclient.RetryableError) so the controller will
retry. Ensure resolveLLMProviderUpstreamAuth, and the error checks here, use the
same sentinel/type names to reliably classify errors.

In `@kubernetes/gateway-operator/internal/gatewayclient/resource_client.go`:
- Around line 123-137: The DeleteResource function currently returns plain
errors when creating the HTTP request (http.NewRequestWithContext) and when
sending the request (client.Do), which is inconsistent with other helpers that
return RetryableError or NonRetryableError; update DeleteResource to wrap the
request-creation error and network/transport errors into the appropriate retry
classification (use NonRetryableError for request construction failures and
RetryableError for transient transport/do failures) and preserve the original
error as the cause so retry/backoff logic can act consistently; adjust the error
returns where auth(ctx, req) is called only if it needs classification as well,
and use the same RetryableError/NonRetryableError constructors used elsewhere in
this package to keep behavior uniform.

In `@kubernetes/gateway-operator/internal/secretsource/resolver.go`:
- Around line 125-133: The current k8sClient.Get error handling maps all errors
to ErrSecretMissing (in the resolver function that calls k8sClient.Get with
types.NamespacedName{Namespace: ns, Name: sel.Name}), which hides transient/read
errors; change the logic so only apierrors.IsNotFound(err) returns
ErrSecretMissing (respecting sel.Optional and returning nil when optional),
while any other non-not-found error is returned unchanged (i.e., return err) so
callers can retry; keep existing references to sel.Optional, fieldPath, ns,
sel.Name and secret when implementing this conditional.

In
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml`:
- Around line 50-156: The CRD is missing the required spec.scope field from
ApiKeySpec causing schema drift; update the ApiKey CRD schema (the ApiKey CRD
definition in this file) to add a spec.scope property matching ApiKeySpec (e.g.,
type and allowed values or string as in your model), place it under the
spec.properties section alongside parentRef/apiKey/etc., and include "scope" in
the spec.required array so user-provided spec.scope values are preserved during
validation and reconciliation.

In
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_websubapis.yaml`:
- Around line 211-213: The CRD currently forces vhosts.main to be present by
listing "main" under the nested required for the vhosts object; remove that
nested required so vhosts can be a partial override (allowing only sandbox or
only main), i.e., delete the required: [main] block under the vhosts schema and
ensure the APIVhost schema (vhosts, main, sandbox) matches the platform-api's
openapi.yaml behavior where both main and sandbox are optional.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05-management-resources.yaml`:
- Line 186: The manifest currently sets subscriptionPlanId to the CR name
("demo-plan-apim") which is incorrect; change subscriptionPlanId to use the
deployed plan's UUID (SubscriptionPlan.status.id) instead of the CR name and
ensure you populate this field only after SubscriptionPlan.status.id is
available (e.g., read the status.id from the SubscriptionPlan resource and
inject that value into subscriptionPlanId rather than using the literal
"demo-plan-apim"); update any template or sync logic that writes
subscriptionPlanId so it resolves at runtime from SubscriptionPlan.status.id.
- Around line 43-46: Remove the extra secretKeyRef nesting under each valueFrom:
block so that valueFrom maps directly to the SecretKeySelector fields (name,
key, optional); specifically update each occurrence where you currently have
valueFrom: secretKeyRef: name: ... key: ... (lines shown) to instead have
valueFrom with name: ..., key: ..., optional: ... directly (this applies to the
four occurrences referencing demo-management-secrets-apim in this file),
ensuring the structure matches corev1.SecretKeySelector used by Kubernetes.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml`:
- Around line 39-40: The subscription manifest is binding to the plan CR name
instead of the plan's deployed id; change the value of subscriptionPlanId to the
reconciled SubscriptionPlan.status.id (the numeric/string id produced by the
controller) rather than the CR name "demo-plan-apim". Locate the Subscription
resource that contains apiId: hello-normal-api-apim and subscriptionPlanId and
replace the plan-name literal with the plan's status.id value (or wire in the
template/lookup that injects SubscriptionPlan.status.id) so the controller can
match the subscription to the actual deployed plan.

---

Minor comments:
In `@kubernetes/gateway-operator/api/v1alpha1/certificate_types.go`:
- Around line 28-30: Update the comment in certificate_types.go to reflect the
actual lifecycle: state that the gateway-controller assigns a UUID on first
upload and persists it to .status.id (create-once, no subsequent PUT), and that
subsequent reconcile operations use /certificates/{status.id} only for DELETE;
replace the current "PUT/DELETE" phrasing with wording that specifies
create-once and delete-by-id, referencing .status.id and the controller
reconcile behavior.

In
`@kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md`:
- Around line 18-21: The doc currently refers to a `Secret` CR but the
implemented CRD/controller uses `ManagedSecret`; update all occurrences (e.g.,
the bullet listing that currently says **`Secret`** and any other mentions
around lines referenced) to use **`ManagedSecret`** and its correct behaviour
(including the note about optional `valueFrom` reading core Secrets) so the
documentation matches the actual CRD/controller surface (`ManagedSecret`,
`Certificate`, `ApiKey`, `SubscriptionPlan`, `Subscription`). Ensure the term
`ManagedSecret` is used consistently wherever the CR was previously named
`Secret`.
- Around line 59-60: The table row has misnested/unclosed Markdown formatting
around the RestApi/LLM cell; fix the cell so the backticks and bold markers are
balanced and the pipe separators remain intact — for example ensure the cell
reads something like: Issue an API key for a **`RestApi` or LLM resource`** |
**`ApiKey`** with **`spec.parentRef`** (`kind`, `name`); REST path nests under
parent., making sure the inline code markers around `RestApi` and the bold
markup around the entire cell are properly closed and the table pipes align so
the `ApiKey` / `spec.parentRef` cell renders correctly.

In `@kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go`:
- Around line 79-85: Add explicit validation to reject an empty keyHandle at the
start of APIKeyExists and DeleteAPIKey: if keyHandle == "" return a clear error
(e.g., fmt.Errorf("empty keyHandle")) instead of letting
ResourceExists/ResourceDelete operate on a collection path; keep the existing
apiKeyResourcePathForExists/apiKeyResourcePathForDelete calls for parent path
resolution after this check so callers get an early, unambiguous error for
key-specific operations.

In
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml`:
- Around line 100-103: The description for the expiresAt/expiresIn fields is
inconsistent with the CRD's validation; update the property description for
expiresAt/expiresIn to state that the two fields are mutually exclusive and that
objects specifying both fields will be rejected by validation (instead of saying
expiresAt "wins"), and ensure any other similar descriptions are changed to
match the existing validation rule that forbids setting both fields.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yaml`:
- Around line 29-34: Remove the unsupported outputSchema field from the tool
definition: locate the tools entry with name "lookupOrder" (and its title
"Lookup order") and delete the outputSchema property so only supported fields
remain (name, description, inputSchema, and optional title); if you actually
need output metadata, add it to the MCPTool type and regenerate the CRD before
reintroducing it.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c06859af-84a1-4880-a405-e4e1c987668c

📥 Commits

Reviewing files that changed from the base of the PR and between 3893e19 and d5195e9.

📒 Files selected for processing (86)
  • gateway/examples/api-key.yaml
  • gateway/examples/certificate.yaml
  • gateway/examples/managed-secret.yaml
  • gateway/examples/subscription-plan.yaml
  • gateway/examples/subscription.yaml
  • gateway/examples/websub-api.yaml
  • kubernetes/gateway-operator/api/v1alpha1/apikey_types.go
  • kubernetes/gateway-operator/api/v1alpha1/certificate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/common_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovider_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
  • kubernetes/gateway-operator/api/v1alpha1/mcp_types.go
  • kubernetes/gateway-operator/api/v1alpha1/secret_types.go
  • kubernetes/gateway-operator/api/v1alpha1/subscription_types.go
  • kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go
  • kubernetes/gateway-operator/api/v1alpha1/websubapi_types.go
  • kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go
  • kubernetes/gateway-operator/cmd/main.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_websubapis.yaml
  • kubernetes/gateway-operator/config/rbac/role.yaml
  • kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.md
  • kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/internal/controller/apikey_controller.go
  • kubernetes/gateway-operator/internal/controller/certificate_controller.go
  • kubernetes/gateway-operator/internal/controller/generic_reconciler.go
  • kubernetes/gateway-operator/internal/controller/llmprovider_controller.go
  • kubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.go
  • kubernetes/gateway-operator/internal/controller/llmproxy_controller.go
  • kubernetes/gateway-operator/internal/controller/managedsecret_controller.go
  • kubernetes/gateway-operator/internal/controller/management_resources_helpers.go
  • kubernetes/gateway-operator/internal/controller/mcp_controller.go
  • kubernetes/gateway-operator/internal/controller/subscription_controller.go
  • kubernetes/gateway-operator/internal/controller/subscriptionplan_controller.go
  • kubernetes/gateway-operator/internal/controller/websubapi_controller.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/paths.go
  • kubernetes/gateway-operator/internal/gatewayclient/resource_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.go
  • kubernetes/gateway-operator/internal/secretsource/resolver.go
  • kubernetes/gateway-operator/internal/secretsource/resolver_test.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_websubapis.yaml
  • kubernetes/helm/operator-helm-chart/templates/_helpers.tpl
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05-management-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05d-websub-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05-management-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05d-websub-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.md
  • kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml
  • kubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml
✅ Files skipped from review due to trivial changes (25)
  • kubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05d-websub-resources.yaml
  • gateway/examples/subscription-plan.yaml
  • gateway/examples/certificate.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • gateway/examples/managed-secret.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • gateway/examples/subscription.yaml
  • gateway/examples/api-key.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yaml
  • gateway/examples/websub-api.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.md
  • kubernetes/gateway-operator/api/v1alpha1/apikey_types.go
  • kubernetes/helm/operator-helm-chart/templates/_helpers.tpl
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml
🚧 Files skipped from review as they are similar to previous changes (21)
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md
  • kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05-management-resources.yaml
  • kubernetes/gateway-operator/config/rbac/role.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.md
  • kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
  • kubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/gateway-operator/api/v1alpha1/subscription_types.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/gateway-operator/internal/controller/management_resources_helpers.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/gateway-operator/internal/controller/managedsecret_controller.go
  • kubernetes/gateway-operator/api/v1alpha1/secret_types.go
  • kubernetes/gateway-operator/internal/controller/llmprovider_controller.go
  • kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/api/v1alpha1/llmprovider_types.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go

Comment thread kubernetes/gateway-operator/api/v1alpha1/mcp_types.go
Comment thread kubernetes/gateway-operator/internal/controller/apikey_controller.go Outdated
Comment thread kubernetes/gateway-operator/internal/controller/apikey_controller.go Outdated
@CrowleyRajapakse CrowleyRajapakse force-pushed the integration-tests-actions branch from d5195e9 to 60a12c4 Compare May 4, 2026 11:32
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (1)
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05-management-resources.yaml (1)

183-183: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

subscriptionPlanId should reference the deployed plan UUID, not the CR name.

The field currently uses demo-plan-apim, which is the Kubernetes resource name. This field expects the gateway-assigned UUID from SubscriptionPlan.status.id. Using the CR name may cause subscription creation to fail.

Ensure the subscriptionPlanId is populated with the value from SubscriptionPlan.status.id after the plan is deployed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05-management-resources.yaml`
at line 183, subscriptionPlanId currently contains the CR name
("demo-plan-apim") but must be set to the gateway-assigned UUID from
SubscriptionPlan.status.id; update the manifest or templating logic that
produces subscriptionPlanId so it reads the deployed SubscriptionPlan's
status.id (e.g., query or template the SubscriptionPlan resource and inject
SubscriptionPlan.status.id into subscriptionPlanId) instead of using the CR
name, and ensure this substitution happens after the plan is created/deployed so
the real UUID is available.
🧹 Nitpick comments (7)
kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml (1)

13-13: 💤 Low value

LoadBalancer requires a cloud provider or bare-metal LB controller.

In local clusters (kind, minikube without tunneling, etc.) the service will remain in Pending state indefinitely. Since the annotation in 02-gateway.yaml is currently commented out, this is inert by default and is acceptable for a demo. Consider adding a brief comment noting the cluster prerequisite so users know what to expect when they opt in.

📝 Suggested comment addition
     gateway:
       gatewayRuntime:
         service:
+          # Requires a cloud LoadBalancer or a bare-metal LB controller (e.g. MetalLB).
           type: LoadBalancer
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml`
at line 13, The Service currently sets type: LoadBalancer which requires a cloud
provider or a bare-metal LB controller and will stay in Pending on local
clusters (e.g., kind, minikube) — add a short comment next to the type:
LoadBalancer line in 01-gateway-values-configmap.yaml explaining this
prerequisite and advising users that the related annotation in 02-gateway.yaml
is commented out by default for demos; mention alternatives
(NodePort/port-forward) for local testing so users know what to expect when
enabling LoadBalancer.
kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.go (1)

29-54: ⚡ Quick win

All specified branches are tested; consider adding a plain-error passthrough case.

The four test cases cover every branch in rewriteLLMDeployDependencyErrors. One minor gap: there is no test verifying the early-return path when the input is not a *gatewayclient.NonRetryableError (e.g., a plain errors.New(...)). Adding it would complete branch coverage.

✅ Suggested additional test case
 require.Equal(t, other400, rewriteLLMDeployDependencyErrors(other400))
+
+// Non-NonRetryableError input must pass through unchanged.
+plainErr := errors.New("unexpected error")
+require.Equal(t, plainErr, rewriteLLMDeployDependencyErrors(plainErr))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.go`
around lines 29 - 54, Add a test case to TestRewriteLLMDeployDependencyErrors
that passes a plain error (e.g., err := errors.New("some plain error")) into
rewriteLLMDeployDependencyErrors and assert the function returns the same error
unchanged (require.Equal(t, err, rewriteLLMDeployDependencyErrors(err))), so the
early-return passthrough path for non-*gatewayclient.NonRetryableError inputs is
covered.
kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml (1)

197-248: 💤 Low value

Consider adding security context for container hardening.

While acceptable for demo purposes, adding securityContext with allowPrivilegeEscalation: false and runAsNonRoot: true would align with Kubernetes security best practices. This is optional given the demo scope.

Also applies to: 265-312

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`
around lines 197 - 248, Add a container and/or pod securityContext to harden the
mock-openapi Deployment: under the container "prism" in the Deployment spec (and
optionally at pod spec level) set securityContext to include runAsNonRoot: true,
allowPrivilegeEscalation: false (and consider setting runAsUser to a non-root
UID and fsGroup if needed) so the "prism" container cannot escalate privileges
and does not run as root; update both the mock-openapi Deployment resource and
the other similar block referenced (lines ~265-312) to keep them consistent.
kubernetes/gateway-operator/api/v1alpha1/llmprovider_types.go (1)

69-86: 💤 Low value

Consider adding mutual exclusivity validation for url and ref.

Both Url and Ref are marked optional. If the intent is that exactly one must be provided, consider adding a CEL validation rule similar to SecretValueSource to enforce this constraint at admission time.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/api/v1alpha1/llmprovider_types.go` around lines
69 - 86, Add a CEL XValidation marker on LLMProviderUpstream to enforce that
exactly one of Url or Ref is provided: add a +kubebuilder:validation:XValidation
rule on the LLMProviderUpstream type (referencing fields Url and Ref) with a CEL
expression like "(self.url == null && self.ref != null) || (self.url != null &&
self.ref == null)" and a descriptive message such as "exactly one of url or ref
must be set" so the CRD admission will reject objects that omit both or supply
both.
kubernetes/gateway-operator/internal/controller/management_llm_enqueue.go (1)

39-41: 💤 Low value

Silent error handling may mask operational issues.

When c.List fails (lines 39-41 and 64-66), returning nil silently drops the error. Consider logging the error at debug/warn level to aid troubleshooting when enqueue mappers fail unexpectedly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@kubernetes/gateway-operator/internal/controller/management_llm_enqueue.go`
around lines 39 - 41, The two silent error returns around the client.List calls
should log the error so failures aren't dropped; update the enqueue mapper
functions that call c.List to log the returned err (at debug/warn level) with
contextual information (e.g., namespace/template name) before returning nil so
you keep existing behavior but surface operational issues—specifically
instrument the client.List calls (the c.List(...) invocations) in the
management_llm enqueue mapper functions to call the existing logger (or klog)
with the error and context.
kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml (1)

67-72: Add enum constraint to spec.status with the three valid subscription lifecycle states.

The Subscription CRD's spec.status field (lines 67-72) lacks an enum constraint, but the management-API defines three valid states for SubscriptionCreateRequest.Status: ACTIVE, INACTIVE, and REVOKED. Adding an enum constraint would catch invalid values at admission time. Note: this differs from SubscriptionPlan.spec.status, which only allows ACTIVE and INACTIVE (two values).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml`
around lines 67 - 72, The CRD is missing an enum constraint for the Subscription
CRD's spec.status field; update the Subscription CRD definition for spec.status
to include an enum with the three allowed values "ACTIVE", "INACTIVE", and
"REVOKED" so admission validation rejects other values (locate the spec.status
property in the Subscription CRD manifest and add an enum array with these three
strings).
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05-management-resources.yaml (1)

43-45: ⚡ Quick win

Standardize indentation to 2 spaces.

The name and key fields under valueFrom use 4-space indentation instead of the standard 2 spaces. Consistent 2-space indentation improves readability and aligns with common YAML conventions.

📝 Proposed fix
   value:
     valueFrom:
-        name: demo-management-secrets-apim
-        key: managed-secret-value
+      name: demo-management-secrets-apim
+      key: managed-secret-value

Apply the same fix to lines 140-142 (Certificate) and 186-188 (Subscription subscriptionToken).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05-management-resources.yaml`
around lines 43 - 45, Indentation under the valueFrom block is inconsistent;
change the child keys 'name' and 'key' to use 2-space indentation (align them
directly under valueFrom) in the valueFrom block and do the same adjustment for
the Certificate block (fields under its valueFrom) and the Subscription's
subscriptionToken valueFrom block so all valueFrom -> name/key entries use
2-space indentation for consistent YAML formatting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@kubernetes/gateway-operator/api/v1alpha1/apikey_types.go`:
- Around line 76-79: Update the ExpiresIn field comment to accurately state that
the CR is rejected if both ExpiresIn and ExpiresAt are set (rather than saying
"ExpiresAt wins"); reference the XValidation constraint "!has(self.expiresAt) ||
!has(self.expiresIn)" which enforces mutual exclusivity, and mention that the
controller fallback ordering in apikey_controller (the defensive else-if) only
applies if validation is bypassed.

In `@kubernetes/gateway-operator/internal/controller/managedsecret_controller.go`:
- Around line 104-105: Remove the unused local variable specCopy (created from
cr.Spec) and its assignment; instead, when building the flat struct, set the
Value field directly using apiv1.SecretValueSource{Value: &val} (i.e., use
cr.Spec for other fields and assign Value inline into the flat construction).
Update references in managedsecret_controller.go so no specCopy is created or
modified and the flat struct is built from cr.Spec with Value set to &val.

In
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05-management-resources.yaml`:
- Around line 160-188: The Subscription is referencing the SubscriptionPlan by
Kubernetes name (subscriptionPlanId: demo-plan) but the CRD requires the
gateway-assigned UUID from SubscriptionPlan.status.id; update the manifest to
either (a) add a clear comment next to Subscription.plan reference explaining
the two-step workflow: create SubscriptionPlan (kind: SubscriptionPlan name:
demo-plan), wait until SubscriptionPlan.status.id is populated, then set
Subscription.spec.subscriptionPlanId to that UUID (SubscriptionPlan.status.id)
and apply the Subscription, or (b) use a placeholder value (e.g.,
"<PLAN_STATUS_ID_HERE>") in Subscription.spec.subscriptionPlanId and document
that it must be replaced with SubscriptionPlan.status.id after reconciliation;
reference the SubscriptionPlan and Subscription resources and the
SubscriptionPlan.status.id field when making the change.

In
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml`:
- Around line 18-34: Add a pod/container securityContext for the
mcp-server-backend container to prevent running as root and disallow privilege
escalation: in the container spec for name mcp-server-backend, set
securityContext with runAsNonRoot: true (and optionally runAsUser: 1000), set
allowPrivilegeEscalation: false, drop all capabilities (capabilities: drop:
["ALL"]) and set readOnlyRootFilesystem: true; this ensures the container cannot
escalate privileges or run as root while keeping the rest of the container spec
unchanged.

In `@kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md`:
- Around line 212-217: The README curl example uses the wrong API key value;
update the X-API-Key header to match the ApiKey CR's sourced secret key. Replace
the current demo-subscription-token-1234567890-abcdef with the actual apikey
value demo-apikey-value-1234567890-abcdef (the ApiKey CR pulls from the
demo-management-secrets secret key apikey-value as defined in
05f0-apikey-resources.yaml), so the curl command uses the correct header value.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml`:
- Around line 17-34: Add a securityContext to the container named
"mcp-server-backend": set runAsNonRoot: true and a non-root runAsUser (e.g.,
1000), disallow privilege escalation (allowPrivilegeEscalation: false), drop all
Linux capabilities (capabilities.drop: ["ALL"]), enable a readOnlyRootFilesystem
where feasible, and set seccompProfile to "RuntimeDefault" (or
"runtime/default") to enforce a baseline sandbox; place this securityContext
directly under the container spec for mcp-server-backend so the probes and ports
remain unchanged.

---

Duplicate comments:
In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05-management-resources.yaml`:
- Line 183: subscriptionPlanId currently contains the CR name ("demo-plan-apim")
but must be set to the gateway-assigned UUID from SubscriptionPlan.status.id;
update the manifest or templating logic that produces subscriptionPlanId so it
reads the deployed SubscriptionPlan's status.id (e.g., query or template the
SubscriptionPlan resource and inject SubscriptionPlan.status.id into
subscriptionPlanId) instead of using the CR name, and ensure this substitution
happens after the plan is created/deployed so the real UUID is available.

---

Nitpick comments:
In `@kubernetes/gateway-operator/api/v1alpha1/llmprovider_types.go`:
- Around line 69-86: Add a CEL XValidation marker on LLMProviderUpstream to
enforce that exactly one of Url or Ref is provided: add a
+kubebuilder:validation:XValidation rule on the LLMProviderUpstream type
(referencing fields Url and Ref) with a CEL expression like "(self.url == null
&& self.ref != null) || (self.url != null && self.ref == null)" and a
descriptive message such as "exactly one of url or ref must be set" so the CRD
admission will reject objects that omit both or supply both.

In `@kubernetes/gateway-operator/internal/controller/management_llm_enqueue.go`:
- Around line 39-41: The two silent error returns around the client.List calls
should log the error so failures aren't dropped; update the enqueue mapper
functions that call c.List to log the returned err (at debug/warn level) with
contextual information (e.g., namespace/template name) before returning nil so
you keep existing behavior but surface operational issues—specifically
instrument the client.List calls (the c.List(...) invocations) in the
management_llm enqueue mapper functions to call the existing logger (or klog)
with the error and context.

In
`@kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.go`:
- Around line 29-54: Add a test case to TestRewriteLLMDeployDependencyErrors
that passes a plain error (e.g., err := errors.New("some plain error")) into
rewriteLLMDeployDependencyErrors and assert the function returns the same error
unchanged (require.Equal(t, err, rewriteLLMDeployDependencyErrors(err))), so the
early-return passthrough path for non-*gatewayclient.NonRetryableError inputs is
covered.

In
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml`:
- Around line 67-72: The CRD is missing an enum constraint for the Subscription
CRD's spec.status field; update the Subscription CRD definition for spec.status
to include an enum with the three allowed values "ACTIVE", "INACTIVE", and
"REVOKED" so admission validation rejects other values (locate the spec.status
property in the Subscription CRD manifest and add an enum array with these three
strings).

In
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`:
- Around line 197-248: Add a container and/or pod securityContext to harden the
mock-openapi Deployment: under the container "prism" in the Deployment spec (and
optionally at pod spec level) set securityContext to include runAsNonRoot: true,
allowPrivilegeEscalation: false (and consider setting runAsUser to a non-root
UID and fsGroup if needed) so the "prism" container cannot escalate privileges
and does not run as root; update both the mock-openapi Deployment resource and
the other similar block referenced (lines ~265-312) to keep them consistent.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05-management-resources.yaml`:
- Around line 43-45: Indentation under the valueFrom block is inconsistent;
change the child keys 'name' and 'key' to use 2-space indentation (align them
directly under valueFrom) in the valueFrom block and do the same adjustment for
the Certificate block (fields under its valueFrom) and the Subscription's
subscriptionToken valueFrom block so all valueFrom -> name/key entries use
2-space indentation for consistent YAML formatting.

In
`@kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml`:
- Line 13: The Service currently sets type: LoadBalancer which requires a cloud
provider or a bare-metal LB controller and will stay in Pending on local
clusters (e.g., kind, minikube) — add a short comment next to the type:
LoadBalancer line in 01-gateway-values-configmap.yaml explaining this
prerequisite and advising users that the related annotation in 02-gateway.yaml
is commented out by default for demos; mention alternatives
(NodePort/port-forward) for local testing so users know what to expect when
enabling LoadBalancer.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 84bc4076-ffaf-4bf6-9c35-dc1cbc19a78e

📥 Commits

Reviewing files that changed from the base of the PR and between d5195e9 and 60a12c4.

📒 Files selected for processing (98)
  • gateway/examples/api-key.yaml
  • gateway/examples/certificate.yaml
  • gateway/examples/managed-secret.yaml
  • gateway/examples/subscription-plan.yaml
  • gateway/examples/subscription.yaml
  • kubernetes/gateway-operator/api/v1alpha1/apikey_types.go
  • kubernetes/gateway-operator/api/v1alpha1/certificate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/common_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovider_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
  • kubernetes/gateway-operator/api/v1alpha1/mcp_types.go
  • kubernetes/gateway-operator/api/v1alpha1/secret_types.go
  • kubernetes/gateway-operator/api/v1alpha1/subscription_types.go
  • kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go
  • kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go
  • kubernetes/gateway-operator/cmd/main.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/gateway-operator/config/rbac/role.yaml
  • kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.md
  • kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/internal/controller/apikey_controller.go
  • kubernetes/gateway-operator/internal/controller/certificate_controller.go
  • kubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.go
  • kubernetes/gateway-operator/internal/controller/generic_reconciler.go
  • kubernetes/gateway-operator/internal/controller/httproute_enqueue_test.go
  • kubernetes/gateway-operator/internal/controller/k8s_gateway_controller.go
  • kubernetes/gateway-operator/internal/controller/llmprovider_controller.go
  • kubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.go
  • kubernetes/gateway-operator/internal/controller/llmproxy_controller.go
  • kubernetes/gateway-operator/internal/controller/managedsecret_controller.go
  • kubernetes/gateway-operator/internal/controller/management_llm_enqueue.go
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.go
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.go
  • kubernetes/gateway-operator/internal/controller/management_resources_helpers.go
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload.go
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload_test.go
  • kubernetes/gateway-operator/internal/controller/mcp_controller.go
  • kubernetes/gateway-operator/internal/controller/payload_metadata_test.go
  • kubernetes/gateway-operator/internal/controller/subscription_controller.go
  • kubernetes/gateway-operator/internal/controller/subscriptionplan_controller.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/paths.go
  • kubernetes/gateway-operator/internal/gatewayclient/resource_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.go
  • kubernetes/gateway-operator/internal/secretsource/resolver.go
  • kubernetes/gateway-operator/internal/secretsource/resolver_test.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/helm/operator-helm-chart/templates/_helpers.tpl
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-02-restapi.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-03-restapi.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05-management-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f0-apikey-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/llm-mock-openapi-it/Dockerfile
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05-management-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f0-apikey-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.md
  • kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml
  • kubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml
✅ Files skipped from review due to trivial changes (30)
  • kubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • gateway/examples/subscription.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/gateway-operator/internal/controller/payload_metadata_test.go
  • kubernetes/gateway-operator/api/v1alpha1/common_types.go
  • gateway/examples/api-key.yaml
  • kubernetes/gateway-operator/internal/controller/k8s_gateway_controller.go
  • kubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.go
  • kubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.md
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/certificate_types.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/internal/secretsource/resolver_test.go
  • kubernetes/gateway-operator/internal/controller/httproute_enqueue_test.go
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.md
  • kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md
  • gateway/examples/certificate.yaml
  • kubernetes/gateway-operator/api/v1alpha1/secret_types.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.go
  • kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go
  • kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go
🚧 Files skipped from review as they are similar to previous changes (26)
  • kubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.go
  • gateway/examples/subscription-plan.yaml
  • kubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_client.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/gateway-operator/api/v1alpha1/mcp_types.go
  • kubernetes/gateway-operator/internal/controller/certificate_controller.go
  • kubernetes/helm/operator-helm-chart/templates/_helpers.tpl
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/gateway-operator/config/rbac/role.yaml
  • kubernetes/gateway-operator/internal/controller/llmproxy_controller.go
  • kubernetes/gateway-operator/internal/gatewayclient/resource_client.go
  • kubernetes/gateway-operator/internal/controller/mcp_controller.go
  • kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
  • kubernetes/gateway-operator/internal/controller/apikey_controller.go
  • kubernetes/gateway-operator/internal/controller/subscription_controller.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/gateway-operator/api/v1alpha1/subscription_types.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/gateway-operator/internal/controller/subscriptionplan_controller.go

Comment thread kubernetes/gateway-operator/api/v1alpha1/apikey_types.go
Comment thread kubernetes/gateway-operator/internal/controller/managedsecret_controller.go Outdated
@CrowleyRajapakse CrowleyRajapakse force-pushed the integration-tests-actions branch from 60a12c4 to 1da6e05 Compare May 5, 2026 04:31
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (3)
kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml (1)

53-67: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Move methods.enum under items in both array schemas.

Line 56 and Line 118 attach enum to the array field instead of array elements. This should be defined under items for per-method validation.

Proposed schema fix
                         methods:
                           description: Methods is the list of HTTP methods covered
                             by this exception.
-                          enum:
-                          - GET
-                          - POST
-                          - PUT
-                          - PATCH
-                          - DELETE
-                          - HEAD
-                          - OPTIONS
-                          - '*'
                           items:
+                            enum:
+                            - GET
+                            - POST
+                            - PUT
+                            - PATCH
+                            - DELETE
+                            - HEAD
+                            - OPTIONS
+                            - '*'
                             type: string
                           type: array
@@
                           methods:
                             description: Methods is the list of HTTP methods.
-                            enum:
-                            - GET
-                            - POST
-                            - PUT
-                            - PATCH
-                            - DELETE
-                            - HEAD
-                            - OPTIONS
-                            - '*'
                             items:
+                              enum:
+                              - GET
+                              - POST
+                              - PUT
+                              - PATCH
+                              - DELETE
+                              - HEAD
+                              - OPTIONS
+                              - '*'
                               type: string
                             type: array
#!/bin/bash
# Verify both `methods` blocks place enum under `items` (not at array level).
rg -n -A14 -B2 '^\s+methods:\s*$' kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml

Also applies to: 116-129

🤖 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
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml`
around lines 53 - 67, The YAML schema places the enum at the array level for the
"methods" field; move the "enum" definition into the "items" object for both
occurrences so each array element is validated (i.e., change the schema that
currently has "methods: type: array" with top-level "enum:" to instead have
"methods: type: array" and under its "items: type: string" add the "enum: [GET,
POST, PUT, PATCH, DELETE, HEAD, OPTIONS, '*']"); update both "methods" blocks
found in the CRD so the enum is defined under the corresponding "items" key.
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml (1)

22-29: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

subscriptionPlanId should reference the reconciled plan's status.id.

subscriptionPlanId is expected to carry the gateway-issued id from SubscriptionPlan.status.id, not the CR name demo-plan-apim. With the current value the controller will not bind this subscription to the deployed plan.

🤖 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
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml`
around lines 22 - 29, Replace the literal CR name in subscriptionPlanId with a
reference to the reconciled plan's gateway-issued id: set subscriptionPlanId to
read SubscriptionPlan.status.id (not "demo-plan-apim"). Update the subscription
resource so subscriptionPlanId.valueFrom points to the SubscriptionPlan
resource's status.id (for the plan CR named demo-plan-apim) instead of the CR
name, ensuring the controller binds the subscription to the deployed plan.
kubernetes/gateway-operator/internal/controller/certificate_controller.go (1)

96-108: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Skip secret resolution when the certificate is already deployed.

Line 98 resolves the certificate source before checking whether status.id is already set. Since this controller treats certificates as immutable, this can fail reconciles unnecessarily after successful upload. Check cr.Status.Id first and return early.

Suggested fix
 func (a *certificateAdapter) Deploy(ctx context.Context, k8sClient client.Client, gatewayEndpoint string, obj client.Object, authFn gatewayclient.AuthHeaderFunc) (DeployResult, error) {
 	cr := obj.(*apiv1.Certificate)
-	pem, err := secretsource.Resolve(ctx, k8sClient, "spec.certificate", cr.Spec.Certificate, cr.Namespace)
-	if err != nil {
-		return DeployResult{}, classifySecretSourceResolveError(err)
-	}
 
 	// Certificates are immutable on the management API: when status.id is
 	// already set we treat the CR as deployed and skip re-uploading. This
 	// matches the OpenAPI contract (no PUT /certificates/{id}).
 	if cr.Status.Id != "" {
 		return DeployResult{Id: cr.Status.Id}, nil
 	}
+	pem, err := secretsource.Resolve(ctx, k8sClient, "spec.certificate", cr.Spec.Certificate, cr.Namespace)
+	if err != nil {
+		return DeployResult{}, classifySecretSourceResolveError(err)
+	}
🤖 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 `@kubernetes/gateway-operator/internal/controller/certificate_controller.go`
around lines 96 - 108, In certificateAdapter.Deploy, avoid resolving the
certificate secret when the CR is already marked deployed: check cr.Status.Id
first and return DeployResult{Id: cr.Status.Id}, nil before calling
secretsource.Resolve; move the existing if cr.Status.Id != "" early in the
function (before the secretsource.Resolve call) so Resolve is only invoked for
new/undeployed Certificate objects.
🧹 Nitpick comments (15)
kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yaml (2)

186-256: ⚡ Quick win

spec.upstream has no minimum-content validation: both url and ref are optional with no XOR constraint.

An Mcp resource with upstream: {} (or upstream containing only auth) passes CRD validation but has no backend, which will only surface as a controller reconcile error. A CEL validation rule on upstream can enforce that exactly one of url/ref is present, consistent with the value/valueFrom pattern used elsewhere:

              type: object
+             x-kubernetes-validations:
+             - message: exactly one of url or ref must be set
+               rule: has(self.url) != has(self.ref)

The corresponding marker in mcp_types.go would be:

// +kubebuilder:validation:XValidation:rule="has(self.url) != has(self.ref)",message="exactly one of url or ref must be set"
🤖 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
`@kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yaml`
around lines 186 - 256, spec.upstream allows empty/invalid configs because
neither url nor ref is required; add an XOR validation so exactly one is
present. Update the CRD/CEL validation for the upstream schema to require
has(self.url) != has(self.ref) (message: "exactly one of url or ref must be
set") — or add the equivalent kubebuilder marker on the MCP Go type (e.g., on
the Upstream struct in mcp_types.go:
+kubebuilder:validation:XValidation:rule="has(self.url) !=
has(self.ref)",message="exactly one of url or ref must be set") so the generated
gateway.api-platform.wso2.com_mcps.yaml includes the rule for spec.upstream.

278-336: ⚡ Quick win

status.conditions is missing x-kubernetes-list-type: map / x-kubernetes-list-map-keys.

Same issue as the Certificate CRD: without these annotations, server-side apply treats conditions as atomic, and any PATCH with partial conditions clobbers the rest. Apply the same fix in the Go API types (mcp_types.go):

// +listType=map
// +listMapKey=type
Conditions []metav1.Condition `json:"conditions,omitempty"`
🤖 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
`@kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yaml`
around lines 278 - 336, Add list metadata to make status.conditions a map-backed
list: in the CRD under the status.conditions schema block add the annotations
x-kubernetes-list-type: map and x-kubernetes-list-map-keys: ["type"] so
server-side apply merges by condition type instead of replacing the whole array.
Also update the Go API type for the Conditions field in mcp_types.go by adding
the k8s markers above the field: // +listType=map and // +listMapKey=type on the
Conditions []metav1.Condition `json:"conditions,omitempty"` declaration so the
generated CRD includes the same list semantics.
kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yaml (1)

109-167: ⚡ Quick win

status.conditions is missing x-kubernetes-list-type: map / x-kubernetes-list-map-keys.

Without these annotations, server-side apply treats status.conditions as an atomic list. A PATCH that includes only a subset of conditions will silently drop the others. The fix belongs in the Go API type's Conditions field, not the YAML directly (this file is regenerated by controller-gen).

Add the following markers to the Conditions []metav1.Condition field in certificate_types.go:

// +listType=map
// +listMapKey=type
Conditions []metav1.Condition `json:"conditions,omitempty"`

This causes controller-gen to emit:

              type: array
+             x-kubernetes-list-map-keys:
+             - type
+             x-kubernetes-list-type: map
🤖 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
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yaml`
around lines 109 - 167, Update the Go API type (not the generated YAML): in
certificate_types.go locate the Conditions field on the Certificate status (the
Conditions []metav1.Condition field) and add controller-gen markers to make it a
map list by adding the +listType=map and +listMapKey=type markers directly above
the Conditions field (ensure the field retains its json:"conditions,omitempty"
tag); regenerate the CRD so controller-gen emits x-kubernetes-list-type: map and
x-kubernetes-list-map-keys for status.conditions.
kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go (2)

59-75: 💤 Low value

Optionally validate payload.PlanName before POST.

SubscriptionPlanCreatePayload.PlanName is a required field on the gateway side, but CreateSubscriptionPlan does not enforce non-empty here. A local check would fail fast with a clearer error than a 400 from the gateway. Low priority.

🤖 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
`@kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go`
around lines 59 - 75, CreateSubscriptionPlan currently POSTs without validating
required fields; add a preflight check in CreateSubscriptionPlan to ensure
SubscriptionPlanCreatePayload.PlanName is non-empty (trim whitespace) and return
a clear error (e.g., "plan name required") before marshalling/POSTing; reference
the CreateSubscriptionPlan function and SubscriptionPlanCreatePayload.PlanName
so the check runs at the top of the function and fails fast with a descriptive
error rather than relying on the gateway 400.

62-95: ⚡ Quick win

Marshal/unmarshal failures should be classified as non-retryable.

json.Marshal/json.Unmarshal errors on Lines 64, 72, 85, and 93 are returned as plain fmt.Errorf. The GenericReconciler.handleDeploymentError default branch wraps unclassified errors as RetryableError, which will keep retrying a deterministic encoding/decoding failure until MaxRetryAttempts is reached. Wrapping these in gatewayclient.NonRetryableError short-circuits the retry loop and surfaces the failure faster.

Proposed change
 	body, err := json.Marshal(payload)
 	if err != nil {
-		return nil, fmt.Errorf("marshal subscription plan: %w", err)
+		return nil, &NonRetryableError{Err: fmt.Errorf("marshal subscription plan: %w", err)}
 	}
@@
 	if err := json.Unmarshal(respBody, &out); err != nil {
-		return nil, fmt.Errorf("decode subscription plan response: %w", err)
+		return nil, &NonRetryableError{Err: fmt.Errorf("decode subscription plan response: %w", err)}
 	}

(Apply the same to UpdateSubscriptionPlan.)

🤖 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
`@kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go`
around lines 62 - 95, The json.Marshal/json.Unmarshal errors in
CreateSubscriptionPlan and UpdateSubscriptionPlan should be classified as
non-retryable: replace plain fmt.Errorf returns for marshal/unmarshal failures
(the errors produced when marshalling payload and when unmarshalling respBody
into SubscriptionPlanResponse) with gatewayclient.NonRetryableError wrapping the
original error (e.g. return nil,
gatewayclient.NonRetryableError(fmt.Errorf("marshal subscription plan: %w",
err))) so the reconciler will not treat deterministic encoding/decoding failures
as retryable; apply the same pattern to both the "marshal subscription plan",
"decode subscription plan response", "marshal subscription plan update", and
corresponding decode error paths in UpdateSubscriptionPlan.
kubernetes/gateway-operator/internal/controller/generic_reconciler.go (5)

605-625: 💤 Low value

Helper for tracker key from object would simplify reconcileDeletion.

Constructing a synthetic ctrl.Request on Line 617 just to reuse trackingKey is awkward. A small r.trackingKeyForObj(obj client.Object) string helper would clarify intent and eliminate the dummy Request.

🤖 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 `@kubernetes/gateway-operator/internal/controller/generic_reconciler.go` around
lines 605 - 625, Create a small helper on GenericReconciler (e.g.,
trackingKeyForObj(obj client.Object) string) that builds the same key currently
produced by r.trackingKey(ctrl.Request{...}) from an object, then use it in
reconcileDeletion to call r.Tracker.Delete with that key instead of constructing
a synthetic ctrl.Request; update reconcileDeletion to remove the ctrl.Request
construction and call r.trackingKeyForObj(obj) when deleting the tracker entry,
keeping all existing FinalizerName(), cleanupAllGateways, logging, and Update
calls unchanged.

627-644: 🏗️ Heavy lift

Cleanup only targets currently-matching gateways.

cleanupAllGateways resolves gateways via FindMatchingGateways at deletion time using the current CR labels. If the CR's gateway selector or a gateway's labels changed since deployment, previously-targeted gateways are no longer in matched and their copies will not be removed before the finalizer is dropped, leaking resources on those gateways.

Tracking deployed-to gateway keys in the ResourceTrackingEntry (or recording them in status) would let cleanup target the actual deployment set. This may be out of scope for this PR but is worth tracking as a follow-up.

🤖 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 `@kubernetes/gateway-operator/internal/controller/generic_reconciler.go` around
lines 627 - 644, cleanupAllGateways currently computes target gateways at
deletion time using GatewaySelectionKey + registry.FindMatchingGateways, which
misses gateways that were previously targeted; change cleanupAllGateways to read
the recorded deployment set (e.g. ResourceTrackingEntry or a status field) for
the object and iterate that list instead of the current matched slice so all
previously-deployed gateways are cleaned up; use the same buildAuthFn(gateway)
and Adapter.Delete(ctx, gateway.GetGatewayServiceEndpoint(), obj, authFn) calls
for each recorded gateway and fall back to FindMatchingGateways only if no
tracking entry exists.

368-387: ⚡ Quick win

buildAuthFn errors propagate as unclassified plain errors.

auth.GetAuthSettingsForRegistryGateway failures on Line 372 are returned as plain fmt.Errorf. Any ResourceAdapter.Deploy call that surfaces this error eventually reaches handleDeploymentError, where the default branch wraps it as RetryableError. Auth/credential resolution errors are typically deterministic (missing secret/key), so this leads to backoff loops until MaxRetryAttempts is reached.

Consider returning &gatewayclient.NonRetryableError{Err: ...} here so the reconciler stops retrying configuration errors quickly.

🤖 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 `@kubernetes/gateway-operator/internal/controller/generic_reconciler.go` around
lines 368 - 387, The error returned from auth.GetAuthSettingsForRegistryGateway
inside buildAuthFn should be converted into a NonRetryableError so the
reconciler stops futile retries; replace the plain fmt.Errorf return with
returning &gatewayclient.NonRetryableError{Err: fmt.Errorf("retrieve auth config
for gateway %q: %w", gateway.Name, err)} (and add the gatewayclient import if
missing), leaving the rest of buildAuthFn unchanged so downstream
ResourceAdapter.Deploy -> handleDeploymentError treats credential resolution
failures as non-retryable.

457-475: ⚖️ Poor tradeoff

ResourceStatusDeployed is set on terminal failure paths, which is semantically misleading.

In handleRetryableError (Line 472, after max retries) and handleNonRetryableError (Line 528), the tracker entry is moved to ResourceStatusDeployed even though the resource was not actually deployed. Subsequent reconciles route through the Deployed branch in decideAndProcess, which works only because the failed updateProgrammed writes ObservedGeneration == entry.Generation and Status=False.

This coupling is fragile. Consider introducing a ResourceStatusFailed/ResourceStatusTerminal state to model "no further action needed" without overloading Deployed, or document the invariant clearly so future changes do not accidentally regress the early-skip logic.

Also applies to: 504-531

🤖 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 `@kubernetes/gateway-operator/internal/controller/generic_reconciler.go` around
lines 457 - 475, The code incorrectly marks failed resources as
ResourceStatusDeployed in handleRetryableError and handleNonRetryableError
(where you call r.Tracker.Set after updateProgrammed), which conflates "terminal
failure" with "deployed" and breaks decideAndProcess invariants; add a new
status constant (e.g., ResourceStatusFailed or ResourceStatusTerminal), update
both handleRetryableError and handleNonRetryableError to set entry.Status =
ResourceStatusFailed and entry.GatewayDeploySucceeded = false before calling
r.Tracker.Set, and update any switch/branching in decideAndProcess (or document
the invariant) so that the new failed state is treated as a
terminal/no-further-action state instead of using ResourceStatusDeployed.

246-291: 💤 Low value

Redundant generation check inside Deployed branch.

The outer block at Line 246 already enforces crGeneration > statusObservedGen, so the inner if crGeneration > statusObservedGen on Line 268 is always true. The else branch on Line 274 is unreachable. Consider removing the inner condition:

Proposed simplification
-				case ResourceStatusDeployed:
-					if crGeneration > statusObservedGen {
-						log.Info("retrying programmed status (tracker deployed, status not yet propagated)",
-							zap.String("name", obj.GetName()),
-							zap.Int64("generation", crGeneration))
-						return r.handleDeploymentSuccess(ctx, obj, trackingKey, entry, entry.GatewayKey)
-					}
-					log.Debug("deployment and status in sync with tracker",
-						zap.String("name", obj.GetName()),
-						zap.Int64("generation", crGeneration))
-					return ctrl.Result{}, nil
+				case ResourceStatusDeployed:
+					log.Info("retrying programmed status (tracker deployed, status not yet propagated)",
+						zap.String("name", obj.GetName()),
+						zap.Int64("generation", crGeneration))
+					return r.handleDeploymentSuccess(ctx, obj, trackingKey, entry, entry.GatewayKey)
🤖 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 `@kubernetes/gateway-operator/internal/controller/generic_reconciler.go` around
lines 246 - 291, The inner redundant check "if crGeneration > statusObservedGen"
inside the ResourceStatusDeployed branch is always true because the outer scope
already checks that; remove that inner if/else and the unreachable else branch,
and directly call r.handleDeploymentSuccess(ctx, obj, trackingKey, entry,
entry.GatewayKey) (keeping the existing log.Info) when entry.Generation ==
crGeneration in the ResourceStatusDeployed case; update the
ResourceStatusDeployed case in generic_reconciler.go so it no longer contains
the nested crGeneration check but preserves the original logging and return
behavior (use r.handleDeploymentSuccess and ctrl.Result{} as before).
kubernetes/gateway-operator/internal/gatewayclient/resource_client.go (2)

41-77: 💤 Low value

Request-construction error classification is inconsistent across helpers.

ResourceExists (Line 45) and sendBodyForResponse (Line 162) classify http.NewRequestWithContext failures as RetryableError, while DeleteResource (Line 124) classifies the same failure as NonRetryableError. A malformed URL/method is typically permanent and warrants a non-retryable classification consistently across all helpers.

Proposed alignment
@@ ResourceExists
-		return false, &RetryableError{Err: fmt.Errorf("create HTTP request: %w", err)}
+		return false, &NonRetryableError{Err: fmt.Errorf("create HTTP request: %w", err)}
@@ sendBodyForResponse
-		return nil, &RetryableError{Err: fmt.Errorf("create HTTP request: %w", err)}
+		return nil, &NonRetryableError{Err: fmt.Errorf("create HTTP request: %w", err)}
🤖 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 `@kubernetes/gateway-operator/internal/gatewayclient/resource_client.go` around
lines 41 - 77, The request-construction failure handling is inconsistent: change
the error returned when http.NewRequestWithContext fails in ResourceExists (and
similarly in sendBodyForResponse) to a NonRetryableError instead of
RetryableError so malformed URL/methods are treated as permanent; update the
constructor calls that wrap fmt.Errorf("create HTTP request: %w", err) to return
&NonRetryableError{Err: fmt.Errorf(...)} (and include StatusCode only where
appropriate) to match DeleteResource's semantics.

47-51: ⚡ Quick win

auth() errors bypass the retryable/non-retryable classification.

When the auth callback fails on Line 49, Line 128, and Line 170, the raw error is returned to the caller. Downstream handleDeploymentError then falls into the default branch and wraps the error as retryable, which may not be the intended semantics for credential-resolution failures. Consider wrapping these in an explicit NonRetryableError (or a dedicated classification) so retry/backoff treats auth misconfiguration deterministically.

Also applies to: 126-130, 168-172

🤖 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 `@kubernetes/gateway-operator/internal/gatewayclient/resource_client.go` around
lines 47 - 51, The auth callback errors are returned raw from the auth(ctx, req)
calls (occurring in the resource client where auth is invoked), which causes
handleDeploymentError to treat them as retryable; update each place that
currently does "if err := auth(ctx, req); err != nil { return ..., err }" to
wrap the returned error in an explicit non-retryable classification (e.g.,
return ..., NewNonRetryableError(err) or wrap with a NonRetryableError type) so
credential/authorization failures are deterministically non-retryable; reference
the auth callback invocation sites and ensure handleDeploymentError’s
expectations align with the new NonRetryableError type.
kubernetes/gateway-operator/internal/controller/management_resources_helpers.go (2)

92-180: ⚖️ Poor tradeoff

Reduce duplication in newObjectListSameType / extractItems.

Both helpers must be updated whenever a new CRD kind is added, and extractItems repeats the same for i := range v.Items { out = append(out, &v.Items[i]) } block nine times. A reflection-based implementation would centralize this:

Sketch using reflection
func newObjectListSameType(template client.ObjectList) client.ObjectList {
    v := reflect.ValueOf(template)
    if v.Kind() != reflect.Ptr || v.IsNil() {
        return nil
    }
    out, ok := reflect.New(v.Type().Elem()).Interface().(client.ObjectList)
    if !ok {
        return nil
    }
    return out
}

func extractItems(list client.ObjectList) []client.Object {
    v := reflect.ValueOf(list).Elem().FieldByName("Items")
    if !v.IsValid() || v.Kind() != reflect.Slice {
        return nil
    }
    out := make([]client.Object, 0, v.Len())
    for i := 0; i < v.Len(); i++ {
        if obj, ok := v.Index(i).Addr().Interface().(client.Object); ok {
            out = append(out, obj)
        }
    }
    return out
}

The explicit switch is clear, but if the controller surface grows further the reflection version removes a maintenance hazard.

🤖 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
`@kubernetes/gateway-operator/internal/controller/management_resources_helpers.go`
around lines 92 - 180, newObjectListSameType and extractItems are duplicated
across CRD kinds; replace their big type switches with reflection-based
implementations: in newObjectListSameType (currently matching on template types)
use reflect.ValueOf(template) to ensure it's a non-nil pointer, create a new
value with reflect.New(v.Type().Elem()), then assert it to client.ObjectList and
return or nil if the assertion fails; in extractItems (currently switching on
concrete List types) use reflect.ValueOf(list).Elem().FieldByName("Items"),
validate it's a slice, iterate i from 0 to v.Len(), get
v.Index(i).Addr().Interface(), assert to client.Object and append to out; ensure
you guard against nil pointers and invalid reflection values so both functions
return nil on unexpected inputs.

207-215: 💤 Low value

Remove the unused crSetupOptions struct and its silencer pattern.

The struct is defined but never used elsewhere in the codebase, and the var _ = crSetupOptions{} silencer on line 215 indicates incomplete scaffolding. Either complete the integration into per-kind SetupWithManager calls as originally intended, or remove both the struct and the silencer to eliminate dead code.

🤖 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
`@kubernetes/gateway-operator/internal/controller/management_resources_helpers.go`
around lines 207 - 215, The crSetupOptions type and its unused silencer (the
crSetupOptions struct and the var _ = crSetupOptions{} line) are dead
code—remove both declarations and any related comments from
management_resources_helpers.go; if you intended to use crSetupOptions in
per-kind SetupWithManager calls instead, implement its usage by passing the
crSetupOptions value into those controllers' SetupWithManager functions (e.g.,
where controllers call SetupWithManager or similar manager registration
functions) and remove the silencer only after the type is actually referenced.
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yaml (1)

1-36: 💤 Low value

LGTM — demo prerequisites are cohesive.

The Secret keys (provider-token, subscription-token, managed-secret-value, cert.pem) line up with the consumers in 05c/05e/05f manifests, and the ManagedSecret CR correctly references the Secret via valueFrom. Demo placeholder values are acceptable here.

Optional: the indentation of name/key under valueFrom (Lines 34-36) uses an extra step relative to its parent. Not a YAML correctness issue, but consider 2-space steps for consistency with the rest of the demo manifests.

🤖 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
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yaml`
around lines 1 - 36, The ManagedSecret spec currently nests name/key under
spec.value.valueFrom with an extra indent level; fix the indentation for
spec.value.valueFrom so that the "name" and "key" keys align with "valueFrom"
using consistent 2-space steps (adjust the block under the ManagedSecret
resource, specifically the spec -> value -> valueFrom -> name and key entries)
to match the rest of the demo manifests.
🤖 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
`@kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml`:
- Around line 319-396: The CRD status docs are copied from a shared
ResourceStatus comment and mention other kinds (Subscription, SubscriptionPlan,
Certificate), which is misleading for LlmProviderTemplate; update the shared
comment on the ResourceStatus type in common_types.go to be kind-agnostic
(remove specific kind names) or add/override a specific doc comment for
LlmProviderTemplateStatus (the LlmProviderTemplateStatus struct) so the
generated CRD no longer references unrelated kinds; search for the
ResourceStatus type and LlmProviderTemplateStatus symbol to locate where to edit
the comment and regenerate the CRD.
- Around line 328-386: The conditions array is missing Kubernetes list markers;
update the Go definition for ResourceStatus.Conditions to include the CRD
markers by adding the struct tags comments // +listType=map and //
+listMapKey=type immediately above the Conditions field
(ResourceStatus.Conditions) in the Go type, then regenerate the CRD so the YAML
includes x-kubernetes-list-type: map and x-kubernetes-list-map-keys: [type];
this ensures condition items are merged by the "type" key rather than treated as
an atomic list.

In `@kubernetes/gateway-operator/internal/controller/subscription_controller.go`:
- Around line 107-110: When resolving the subscription token with
secretsource.Resolve (token, err := secretsource.Resolve(...)), don't
unconditionally wrap every error as gatewayclient.NonRetryableError; instead
inspect the returned error and only convert permanent/missing-secret cases into
a NonRetryableError while returning other errors as-is (so they remain
retryable). Implement this by checking the error type or helper (e.g.,
secretsource.IsNotFound/IsPermanent or errors.Is against a
secretsource.ErrSecretMissing constant) and wrap only those permanent errors
with &gatewayclient.NonRetryableError{Err: err}; for all other errors return
DeployResult{}, err so reconciliation can retry.

In `@kubernetes/helm/operator-helm-chart/values.yaml`:
- Around line 30-31: The values.yaml currently sets tag: "0.7.0-SNAPSHOT" with
pullPolicy: IfNotPresent which causes stale cached images on main; update the
default in values.yaml by replacing the mutable SNAPSHOT tag with a pinned
stable tag (e.g., change tag from "0.7.0-SNAPSHOT" to "0.7.0") and set
pullPolicy to Always (or IfNotPresent if you intentionally pin the tag) so
Kubernetes will pull the correct image for CI/prod; alternatively keep the
SNAPSHOT + IfNotPresent combination only on a dev/feature branch and do not
merge that into main.

In `@kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md`:
- Around line 103-114: The README currently shows two conflicting upstream URLs
for the LLM mock: "https://mock-openapi-https:9449/..." (in the
05b0-mock-openapi-https.yaml description) and
"http://mock-openapi:4010/openai/v1" (in the 05b-llm-resources.yaml paragraph).
Update the README to pick a single authoritative in-cluster URL and clearly
state when the alternate URL is used (e.g., compose IT vs in-cluster demo):
reference the demo manifest 05b0-mock-openapi-https.yaml and the
LlmProvider/LlmProviderTemplate named openai-test (and LlmProxy entries) and
change the second paragraph to match the chosen URL or add a brief parenthetical
explaining that 4010/http is used by the compose IT mock while 9449/https is
used in-cluster with the mock-openapi-https Service.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`:
- Around line 29-83: This manifest embeds a private TLS key under
stringData.tls.key (and tls.crt) — remove the embedded certificate and private
key from this tracked YAML and instead reference a pre-created Kubernetes Secret
(for example named mock-openapi-https-tls) created at deploy-time or via
cert-manager; update the resource so it no longer contains
stringData.tls.key/tls.crt but points to the external secret
(secretName/mock-openapi-https-tls or secretRef as used by your
ingress/operator) and ensure you apply the same change to the mirrored file and
any code that expects this secret name.

---

Duplicate comments:
In `@kubernetes/gateway-operator/internal/controller/certificate_controller.go`:
- Around line 96-108: In certificateAdapter.Deploy, avoid resolving the
certificate secret when the CR is already marked deployed: check cr.Status.Id
first and return DeployResult{Id: cr.Status.Id}, nil before calling
secretsource.Resolve; move the existing if cr.Status.Id != "" early in the
function (before the secretsource.Resolve call) so Resolve is only invoked for
new/undeployed Certificate objects.

In
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml`:
- Around line 53-67: The YAML schema places the enum at the array level for the
"methods" field; move the "enum" definition into the "items" object for both
occurrences so each array element is validated (i.e., change the schema that
currently has "methods: type: array" with top-level "enum:" to instead have
"methods: type: array" and under its "items: type: string" add the "enum: [GET,
POST, PUT, PATCH, DELETE, HEAD, OPTIONS, '*']"); update both "methods" blocks
found in the CRD so the enum is defined under the corresponding "items" key.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml`:
- Around line 22-29: Replace the literal CR name in subscriptionPlanId with a
reference to the reconciled plan's gateway-issued id: set subscriptionPlanId to
read SubscriptionPlan.status.id (not "demo-plan-apim"). Update the subscription
resource so subscriptionPlanId.valueFrom points to the SubscriptionPlan
resource's status.id (for the plan CR named demo-plan-apim) instead of the CR
name, ensuring the controller binds the subscription to the deployed plan.

---

Nitpick comments:
In
`@kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yaml`:
- Around line 186-256: spec.upstream allows empty/invalid configs because
neither url nor ref is required; add an XOR validation so exactly one is
present. Update the CRD/CEL validation for the upstream schema to require
has(self.url) != has(self.ref) (message: "exactly one of url or ref must be
set") — or add the equivalent kubebuilder marker on the MCP Go type (e.g., on
the Upstream struct in mcp_types.go:
+kubebuilder:validation:XValidation:rule="has(self.url) !=
has(self.ref)",message="exactly one of url or ref must be set") so the generated
gateway.api-platform.wso2.com_mcps.yaml includes the rule for spec.upstream.
- Around line 278-336: Add list metadata to make status.conditions a map-backed
list: in the CRD under the status.conditions schema block add the annotations
x-kubernetes-list-type: map and x-kubernetes-list-map-keys: ["type"] so
server-side apply merges by condition type instead of replacing the whole array.
Also update the Go API type for the Conditions field in mcp_types.go by adding
the k8s markers above the field: // +listType=map and // +listMapKey=type on the
Conditions []metav1.Condition `json:"conditions,omitempty"` declaration so the
generated CRD includes the same list semantics.

In `@kubernetes/gateway-operator/internal/controller/generic_reconciler.go`:
- Around line 605-625: Create a small helper on GenericReconciler (e.g.,
trackingKeyForObj(obj client.Object) string) that builds the same key currently
produced by r.trackingKey(ctrl.Request{...}) from an object, then use it in
reconcileDeletion to call r.Tracker.Delete with that key instead of constructing
a synthetic ctrl.Request; update reconcileDeletion to remove the ctrl.Request
construction and call r.trackingKeyForObj(obj) when deleting the tracker entry,
keeping all existing FinalizerName(), cleanupAllGateways, logging, and Update
calls unchanged.
- Around line 627-644: cleanupAllGateways currently computes target gateways at
deletion time using GatewaySelectionKey + registry.FindMatchingGateways, which
misses gateways that were previously targeted; change cleanupAllGateways to read
the recorded deployment set (e.g. ResourceTrackingEntry or a status field) for
the object and iterate that list instead of the current matched slice so all
previously-deployed gateways are cleaned up; use the same buildAuthFn(gateway)
and Adapter.Delete(ctx, gateway.GetGatewayServiceEndpoint(), obj, authFn) calls
for each recorded gateway and fall back to FindMatchingGateways only if no
tracking entry exists.
- Around line 368-387: The error returned from
auth.GetAuthSettingsForRegistryGateway inside buildAuthFn should be converted
into a NonRetryableError so the reconciler stops futile retries; replace the
plain fmt.Errorf return with returning &gatewayclient.NonRetryableError{Err:
fmt.Errorf("retrieve auth config for gateway %q: %w", gateway.Name, err)} (and
add the gatewayclient import if missing), leaving the rest of buildAuthFn
unchanged so downstream ResourceAdapter.Deploy -> handleDeploymentError treats
credential resolution failures as non-retryable.
- Around line 457-475: The code incorrectly marks failed resources as
ResourceStatusDeployed in handleRetryableError and handleNonRetryableError
(where you call r.Tracker.Set after updateProgrammed), which conflates "terminal
failure" with "deployed" and breaks decideAndProcess invariants; add a new
status constant (e.g., ResourceStatusFailed or ResourceStatusTerminal), update
both handleRetryableError and handleNonRetryableError to set entry.Status =
ResourceStatusFailed and entry.GatewayDeploySucceeded = false before calling
r.Tracker.Set, and update any switch/branching in decideAndProcess (or document
the invariant) so that the new failed state is treated as a
terminal/no-further-action state instead of using ResourceStatusDeployed.
- Around line 246-291: The inner redundant check "if crGeneration >
statusObservedGen" inside the ResourceStatusDeployed branch is always true
because the outer scope already checks that; remove that inner if/else and the
unreachable else branch, and directly call r.handleDeploymentSuccess(ctx, obj,
trackingKey, entry, entry.GatewayKey) (keeping the existing log.Info) when
entry.Generation == crGeneration in the ResourceStatusDeployed case; update the
ResourceStatusDeployed case in generic_reconciler.go so it no longer contains
the nested crGeneration check but preserves the original logging and return
behavior (use r.handleDeploymentSuccess and ctrl.Result{} as before).

In
`@kubernetes/gateway-operator/internal/controller/management_resources_helpers.go`:
- Around line 92-180: newObjectListSameType and extractItems are duplicated
across CRD kinds; replace their big type switches with reflection-based
implementations: in newObjectListSameType (currently matching on template types)
use reflect.ValueOf(template) to ensure it's a non-nil pointer, create a new
value with reflect.New(v.Type().Elem()), then assert it to client.ObjectList and
return or nil if the assertion fails; in extractItems (currently switching on
concrete List types) use reflect.ValueOf(list).Elem().FieldByName("Items"),
validate it's a slice, iterate i from 0 to v.Len(), get
v.Index(i).Addr().Interface(), assert to client.Object and append to out; ensure
you guard against nil pointers and invalid reflection values so both functions
return nil on unexpected inputs.
- Around line 207-215: The crSetupOptions type and its unused silencer (the
crSetupOptions struct and the var _ = crSetupOptions{} line) are dead
code—remove both declarations and any related comments from
management_resources_helpers.go; if you intended to use crSetupOptions in
per-kind SetupWithManager calls instead, implement its usage by passing the
crSetupOptions value into those controllers' SetupWithManager functions (e.g.,
where controllers call SetupWithManager or similar manager registration
functions) and remove the silencer only after the type is actually referenced.

In `@kubernetes/gateway-operator/internal/gatewayclient/resource_client.go`:
- Around line 41-77: The request-construction failure handling is inconsistent:
change the error returned when http.NewRequestWithContext fails in
ResourceExists (and similarly in sendBodyForResponse) to a NonRetryableError
instead of RetryableError so malformed URL/methods are treated as permanent;
update the constructor calls that wrap fmt.Errorf("create HTTP request: %w",
err) to return &NonRetryableError{Err: fmt.Errorf(...)} (and include StatusCode
only where appropriate) to match DeleteResource's semantics.
- Around line 47-51: The auth callback errors are returned raw from the
auth(ctx, req) calls (occurring in the resource client where auth is invoked),
which causes handleDeploymentError to treat them as retryable; update each place
that currently does "if err := auth(ctx, req); err != nil { return ..., err }"
to wrap the returned error in an explicit non-retryable classification (e.g.,
return ..., NewNonRetryableError(err) or wrap with a NonRetryableError type) so
credential/authorization failures are deterministically non-retryable; reference
the auth callback invocation sites and ensure handleDeploymentError’s
expectations align with the new NonRetryableError type.

In
`@kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go`:
- Around line 59-75: CreateSubscriptionPlan currently POSTs without validating
required fields; add a preflight check in CreateSubscriptionPlan to ensure
SubscriptionPlanCreatePayload.PlanName is non-empty (trim whitespace) and return
a clear error (e.g., "plan name required") before marshalling/POSTing; reference
the CreateSubscriptionPlan function and SubscriptionPlanCreatePayload.PlanName
so the check runs at the top of the function and fails fast with a descriptive
error rather than relying on the gateway 400.
- Around line 62-95: The json.Marshal/json.Unmarshal errors in
CreateSubscriptionPlan and UpdateSubscriptionPlan should be classified as
non-retryable: replace plain fmt.Errorf returns for marshal/unmarshal failures
(the errors produced when marshalling payload and when unmarshalling respBody
into SubscriptionPlanResponse) with gatewayclient.NonRetryableError wrapping the
original error (e.g. return nil,
gatewayclient.NonRetryableError(fmt.Errorf("marshal subscription plan: %w",
err))) so the reconciler will not treat deterministic encoding/decoding failures
as retryable; apply the same pattern to both the "marshal subscription plan",
"decode subscription plan response", "marshal subscription plan update", and
corresponding decode error paths in UpdateSubscriptionPlan.

In
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yaml`:
- Around line 109-167: Update the Go API type (not the generated YAML): in
certificate_types.go locate the Conditions field on the Certificate status (the
Conditions []metav1.Condition field) and add controller-gen markers to make it a
map list by adding the +listType=map and +listMapKey=type markers directly above
the Conditions field (ensure the field retains its json:"conditions,omitempty"
tag); regenerate the CRD so controller-gen emits x-kubernetes-list-type: map and
x-kubernetes-list-map-keys for status.conditions.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yaml`:
- Around line 1-36: The ManagedSecret spec currently nests name/key under
spec.value.valueFrom with an extra indent level; fix the indentation for
spec.value.valueFrom so that the "name" and "key" keys align with "valueFrom"
using consistent 2-space steps (adjust the block under the ManagedSecret
resource, specifically the spec -> value -> valueFrom -> name and key entries)
to match the rest of the demo manifests.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 695f7bdd-e544-4d68-8551-054f24a99e69

📥 Commits

Reviewing files that changed from the base of the PR and between 60a12c4 and 1da6e05.

📒 Files selected for processing (98)
  • gateway/examples/api-key.yaml
  • gateway/examples/certificate.yaml
  • gateway/examples/managed-secret.yaml
  • gateway/examples/subscription-plan.yaml
  • gateway/examples/subscription.yaml
  • kubernetes/gateway-operator/api/v1alpha1/apikey_types.go
  • kubernetes/gateway-operator/api/v1alpha1/certificate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/common_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovider_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
  • kubernetes/gateway-operator/api/v1alpha1/mcp_types.go
  • kubernetes/gateway-operator/api/v1alpha1/secret_types.go
  • kubernetes/gateway-operator/api/v1alpha1/subscription_types.go
  • kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go
  • kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go
  • kubernetes/gateway-operator/cmd/main.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/gateway-operator/config/rbac/role.yaml
  • kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.md
  • kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/internal/controller/apikey_controller.go
  • kubernetes/gateway-operator/internal/controller/certificate_controller.go
  • kubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.go
  • kubernetes/gateway-operator/internal/controller/generic_reconciler.go
  • kubernetes/gateway-operator/internal/controller/httproute_enqueue_test.go
  • kubernetes/gateway-operator/internal/controller/k8s_gateway_controller.go
  • kubernetes/gateway-operator/internal/controller/llmprovider_controller.go
  • kubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.go
  • kubernetes/gateway-operator/internal/controller/llmproxy_controller.go
  • kubernetes/gateway-operator/internal/controller/managedsecret_controller.go
  • kubernetes/gateway-operator/internal/controller/management_llm_enqueue.go
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.go
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.go
  • kubernetes/gateway-operator/internal/controller/management_resources_helpers.go
  • kubernetes/gateway-operator/internal/controller/management_secretsource_resolve_error.go
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload.go
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload_test.go
  • kubernetes/gateway-operator/internal/controller/mcp_controller.go
  • kubernetes/gateway-operator/internal/controller/payload_metadata_test.go
  • kubernetes/gateway-operator/internal/controller/subscription_controller.go
  • kubernetes/gateway-operator/internal/controller/subscriptionplan_controller.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/paths.go
  • kubernetes/gateway-operator/internal/gatewayclient/resource_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.go
  • kubernetes/gateway-operator/internal/secretsource/resolver.go
  • kubernetes/gateway-operator/internal/secretsource/resolver_test.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/helm/operator-helm-chart/templates/_helpers.tpl
  • kubernetes/helm/operator-helm-chart/values.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-02-restapi.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-03-restapi.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f0-apikey-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/llm-mock-openapi-it/Dockerfile
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f0-apikey-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.md
  • kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml
  • kubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml
✅ Files skipped from review due to trivial changes (31)
  • kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/llm-mock-openapi-it/Dockerfile
  • kubernetes/gateway-operator/internal/controller/payload_metadata_test.go
  • kubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/gateway-operator/internal/controller/httproute_enqueue_test.go
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload_test.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/certificate_types.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_client.go
  • gateway/examples/certificate.yaml
  • kubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.md
  • gateway/examples/subscription.yaml
  • gateway/examples/subscription-plan.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/gateway-operator/internal/secretsource/resolver_test.go
  • kubernetes/gateway-operator/internal/controller/k8s_gateway_controller.go
  • gateway/examples/managed-secret.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.go
  • kubernetes/gateway-operator/api/v1alpha1/apikey_types.go
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.md
  • kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go
🚧 Files skipped from review as they are similar to previous changes (36)
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.go
  • kubernetes/gateway-operator/internal/controller/management_llm_enqueue.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f0-apikey-resources.yaml
  • gateway/examples/api-key.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/gateway-operator/api/v1alpha1/llmprovider_types.go
  • kubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go
  • kubernetes/gateway-operator/api/v1alpha1/common_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
  • kubernetes/gateway-operator/api/v1alpha1/secret_types.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-03-restapi.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-02-restapi.yaml
  • kubernetes/gateway-operator/internal/controller/apikey_controller.go
  • kubernetes/gateway-operator/cmd/main.go
  • kubernetes/gateway-operator/internal/secretsource/resolver.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/gateway-operator/api/v1alpha1/mcp_types.go
  • kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go
  • kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/internal/controller/managedsecret_controller.go
  • kubernetes/gateway-operator/internal/controller/llmprovider_controller.go
  • kubernetes/gateway-operator/internal/gatewayclient/paths.go
  • kubernetes/gateway-operator/api/v1alpha1/subscription_types.go
  • kubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.go
  • kubernetes/gateway-operator/config/rbac/role.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/helm/operator-helm-chart/templates/_helpers.tpl

Comment thread kubernetes/helm/operator-helm-chart/values.yaml Outdated
@CrowleyRajapakse CrowleyRajapakse force-pushed the integration-tests-actions branch from 1da6e05 to a2fab84 Compare May 5, 2026 05:34
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

♻️ Duplicate comments (3)
kubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml (1)

18-34: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a container securityContext for mcp-server-backend.

Line 19 defines the container without hardening settings, so it may run with permissive defaults. Please set runAsNonRoot and allowPrivilegeEscalation: false (and ideally drop capabilities/read-only root FS) to align with safer defaults.

Suggested patch
       containers:
         - name: mcp-server-backend
           image: rakhitharr/mcp-everything:v3
           imagePullPolicy: IfNotPresent
+          securityContext:
+            runAsNonRoot: true
+            allowPrivilegeEscalation: false
+            capabilities:
+              drop: ["ALL"]
+            readOnlyRootFilesystem: true
           ports:
🤖 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
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml`
around lines 18 - 34, Add a container-level securityContext to the
mcp-server-backend container to enforce non-root execution and prevent privilege
escalation: under the container with name mcp-server-backend add securityContext
keys including runAsNonRoot: true, allowPrivilegeEscalation: false, and consider
adding capabilities.drop: ["ALL"] and readOnlyRootFilesystem: true to further
harden the container.
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml (1)

23-25: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use deployed plan ID for subscriptionPlanId, not the plan CR name.

Line 24 binds the subscription to demo-plan-apim (name). This field should reference the reconciled plan identifier (SubscriptionPlan.status.id), otherwise the subscription may not attach to the intended plan.

🤖 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
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml`
around lines 23 - 25, The subscription resource uses the Plan CR name in
subscriptionPlanId; replace that with the reconciled plan identifier from the
Plan status (SubscriptionPlan.status.id) so the subscription binds to the
deployed plan. Update the subscription manifest field subscriptionPlanId
(currently set to "demo-plan-apim") to reference the actual plan ID produced by
the operator (SubscriptionPlan.status.id) or render that status value into the
manifest/template so the reconciled numeric/UUID plan id is used rather than the
CR name.
kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md (1)

103-114: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Resolve conflicting in-cluster upstream URL guidance in the LLM mock section.

Line 103 says in-cluster LlmProvider should use https://mock-openapi-https:9449/..., while Line 114 states 05b-llm-resources.yaml uses http://mock-openapi:4010/openai/v1. Please make one value authoritative for this demo path and explicitly label the other as compose-only if needed.

🤖 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 `@kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md` around
lines 103 - 114, The README contains conflicting upstream URLs for the demo LLM
mock; pick the in-cluster address as authoritative
(https://mock-openapi-https:9449/...) and mark the alternative
(http://mock-openapi:4010/openai/v1) explicitly as "docker-compose / IT-only".
Update the paragraphs that reference 05b0-mock-openapi-https.yaml,
05b-llm-resources.yaml, and the LlmProvider/LlmProviderTemplate (template:
openai-test) / LlmProxy examples to state the in-cluster URL is used by the
operator reconcilers, and add a short parenthetical note that the 4010 HTTP URL
is only for compose-based local testing.
🧹 Nitpick comments (9)
kubernetes/gateway-operator/api/v1alpha1/common_types.go (1)

52-53: ⚡ Quick win

Add +listType=map and +listMapKey=type markers to Conditions.

Without these kubebuilder markers, controller-gen omits x-kubernetes-list-type: map from the generated CRD, causing Server-Side Apply to treat the conditions array as atomic. Concurrent writers (e.g., multiple controllers or kubectl apply) can inadvertently wipe conditions set by others. This affects every CRD that embeds ResourceStatus.

♻️ Proposed fix
+	// +listMapKey=type
+	// +listType=map
 	// +optional
 	Conditions []metav1.Condition `json:"conditions,omitempty"`
🤖 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 `@kubernetes/gateway-operator/api/v1alpha1/common_types.go` around lines 52 -
53, Add the kubebuilder list markers to the Conditions slice so controller-gen
emits x-kubernetes-list-type: map and x-kubernetes-list-map-keys: ["type"];
specifically, above the Conditions []metav1.Condition field (in the
ResourceStatus/common_types.go definition) add the comments
+kubebuilder:validation:Optional (if needed) and the markers +listType=map and
+listMapKey=type so the generated CRD treats conditions as a map keyed by type
and avoids atomic-array SSA overwrites.
.github/workflows/operator-integration-test.yml (4)

1631-1647: ⚡ Quick win

Cleanup ordering does not wait for finalizers between dependent CRs.

kubectl delete ... --ignore-not-found returns immediately without waiting for finalizers. If Subscription finalizer logic depends on SubscriptionPlan still existing on the gateway, deleting the plan one line later (1633) before the subscription's finalizer has fully reconciled could leave the gateway in an inconsistent state, even though the namespace deletion at 1642 will eventually force-clear everything. For a deterministic teardown, add kubectl wait --for=delete ... between the dependent kinds (apikey → restapi, subscription → subscriptionplan, llmproxy → llmprovider → llmprovidertemplate, etc.).

🤖 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 @.github/workflows/operator-integration-test.yml around lines 1631 - 1647,
The cleanup deletes dependent CRs without waiting for finalizers; update the
teardown to explicitly wait for each dependent resource to be fully removed
before deleting the next one (for example, after deleting
apikey/demo-restapi-apikey wait for its deletion with kubectl wait --for=delete
apikey/demo-restapi-apikey -n apigateway-demo --timeout=... before deleting
restapi/hello-apikey-api; similarly wait for subscription/demo-subscription
deletion before removing subscriptionplan/demo-plan, and chain waits for
llmproxy/demo-llm-proxy → llmprovider/demo-llm-provider →
llmprovidertemplate/openai-test); use appropriate --timeout values and keep
--ignore-not-found on the initial delete to avoid failing on missing resources.

1076-1080: 💤 Low value

pkill followed by a fixed sleep is racy across runners.

pkill ... || true plus sleep 2 then a new port-forward is a common source of flakes on slower runners — the previous forwarder may still hold port 8080 when the new one starts. Consider waiting for the port to become free (e.g., a short loop that probes localhost:8080) or using wait on the captured PID from the previous step, where available. Same pattern repeats at line 1190.

🤖 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 @.github/workflows/operator-integration-test.yml around lines 1076 - 1080,
The current workflow uses `pkill -f "kubectl port-forward.*8080:8080" || true`
followed by `sleep 2` then starting `kubectl port-forward` and saving its PID to
`PF_PID`, which is racy; replace the fixed sleep with an explicit wait loop that
probes localhost:8080 (e.g., curl -sS or netcat) until the port is free before
starting the new `kubectl port-forward`, or capture and `wait` on the previous
port-forward PID when available; apply the same change to the identical pattern
handling port 8080 near line 1190 so the script reliably ensures the old
forwarder has released the port before launching the new one.

1106-1115: ⚡ Quick win

Capture session-id failure with a clearer message.

[ -n "${SESSION_ID}" ] under set -euo pipefail will exit non-zero if the header is missing, but without any indication of what went wrong. If the MCP initialize response shape changes, the job will fail on a bare bracket test. Suggest emitting the response headers/body before the assertion so CI logs surface the cause.

♻️ Suggested change
   SESSION_ID=$(awk 'BEGIN{IGNORECASE=1} /^mcp-session-id:/ {print $2}' /tmp/mcp-init-headers.txt | tr -d '\r')
   echo "SESSION_ID=${SESSION_ID}"
-  [ -n "${SESSION_ID}" ]
+  if [ -z "${SESSION_ID}" ]; then
+    echo "MCP initialize did not return mcp-session-id; dumping response for diagnosis:"
+    cat /tmp/mcp-init-headers.txt || true
+    cat /tmp/mcp-init-body.txt || true
+    exit 1
+  fi
🤖 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 @.github/workflows/operator-integration-test.yml around lines 1106 - 1115,
The current check `[ -n "${SESSION_ID}" ]` can fail silently under set -euo
pipefail; update the script around SESSION_ID (extracted from
/tmp/mcp-init-headers.txt) to first output the /tmp/mcp-init-headers.txt and
/tmp/mcp-init-body.txt (or echo SESSION_ID) for debugging, then replace the bare
bracket test with an explicit conditional that prints a clear error message
including the captured headers/body and exits non-zero if SESSION_ID is empty;
reference the SESSION_ID extraction and the files /tmp/mcp-init-headers.txt and
/tmp/mcp-init-body.txt so logs show the failing response before failing the job.

1062-1071: 💤 Low value

Sequential kubectl wait calls can mask which CR actually failed.

Each wait has a 300s timeout and runs serially, so a single hung resource consumes the full budget while later kinds never start. On failure, the diagnostic step at the bottom of the job dumps generic operator logs but no per-CR status. Consider adding a brief kubectl get <kind> -n apigateway-demo -o yaml (or kubectl describe) on failure for each waited resource, or wrapping the waits in a helper that prints status when the wait fails. Not blocking the green path, just speeds up triage.

🤖 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 @.github/workflows/operator-integration-test.yml around lines 1062 - 1071,
Replace the current sequential kubectl wait calls (the lines waiting for
managedsecret/demo-managed-secret, llmprovidertemplate/openai-test,
llmprovider/demo-llm-provider, llmproxy/demo-llm-proxy, mcp/everything-mcp-v1.0,
restapi/hello-sub-api, restapi/hello-apikey-api, subscriptionplan/demo-plan,
subscription/demo-subscription, apikey/demo-restapi-apikey) with a small
helper/wrapper that on failure runs a diagnostic kubectl get <kind> -n
apigateway-demo -o yaml (or kubectl describe) for that specific resource and
outputs it before failing; implement the wrapper so each wait runs the same
kubectl wait invocation but catches non-zero exit, prints the resource-specific
yaml/describe, and then exits non-zero to preserve existing behavior.
kubernetes/gateway-operator/internal/controller/generic_reconciler.go (2)

462-475: ⚖️ Poor tradeoff

Terminal failure state is labeled ResourceStatusDeployed.

After max retries are exceeded (line 472) and on non-retryable errors (line 528), the tracker is marked ResourceStatusDeployed even though deployment failed. This conflates "successfully deployed" with "gave up", complicates the decideAndProcess invariants, and makes log/debug interpretation harder. Consider introducing a distinct terminal status (e.g., ResourceStatusFailed) and updating the switch in decideAndProcess accordingly.

🤖 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 `@kubernetes/gateway-operator/internal/controller/generic_reconciler.go` around
lines 462 - 475, The terminal failure branch is incorrectly marking resources as
ResourceStatusDeployed; introduce a distinct terminal state (e.g.,
ResourceStatusFailed) and use it in the failure paths: change the status set in
the max-retries and non-retryable error branches (the block calling
r.updateProgrammed and then setting entry.Status = ResourceStatusDeployed and
entry.GatewayDeploySucceeded = false) to set entry.Status =
ResourceStatusFailed, keep GatewayDeploySucceeded = false, and call
r.Tracker.Set(trackingKey, entry); then update the decision logic in
decideAndProcess to handle ResourceStatusFailed as a terminal/failure state
(instead of treating it like ResourceStatusDeployed) so downstream behavior and
invariants reflect a real failure.

246-291: 💤 Low value

Outer if crGeneration > statusObservedGen is redundant.

Line 238 already returns when crGeneration == statusObservedGen and Programmed=True, but execution continues for the remaining == cases (e.g., Programmed=False, missing condition with statusObservedGen=0 and crGeneration=0). Reaching line 246 with crGeneration < statusObservedGen would also fall through to the no action needed log without entering this block, which is fine, but the explicit guard adds nesting without changing behavior on the realistic paths. Collapsing the branches (or asserting the invariant explicitly) would simplify the control flow; not blocking.

🤖 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 `@kubernetes/gateway-operator/internal/controller/generic_reconciler.go` around
lines 246 - 291, The outer guard "if crGeneration > statusObservedGen" around
the big switch is redundant and adds nesting; remove that outer if and let the
existing inner comparisons and cases (checks of entry.Generation,
ResourceStatusProcessing/Retrying/Deployed branches, and the fallback
entry.Generation < crGeneration path) drive control flow, or alternatively
replace the outer guard with an explicit assertion/early-return that documents
the invariant (e.g., return when crGeneration == statusObservedGen and
Programmed is true) so the rest of the code (calls to r.processDeployment and
r.handleDeploymentSuccess, and use of fields like entry.Generation,
entry.GatewayKey, entry.RetryCount) executes without extra nesting; ensure no
behavior change for the existing branches (keep log messages and return values
intact).
kubernetes/gateway-operator/cmd/main.go (1)

240-245: 💤 Low value

Constructors run before any setup call; an early-construction failure mode should be considered.

All nine reconcilers are constructed eagerly when the slice literal is built (line 230–238), then setup is invoked in the loop. If a future constructor performs side effects (e.g., registering with a singleton, opening a client) and fails for one kind, the others have already executed those side effects without a paired teardown. Today the constructors look pure, so this is a minor maintainability note: consider storing constructor functions in the slice and invoking them inside the loop, mirroring the setup pattern.

🤖 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 `@kubernetes/gateway-operator/cmd/main.go` around lines 240 - 245, The current
code builds concrete reconciler instances eagerly in the managementControllers
slice before calling mc.setup, which can cause partial side-effect leaks on
constructor failure; change managementControllers to be a slice of constructor
functions (e.g., type controllerCtor func() (controllerType, error)) and
populate it with functions that create each reconciler (defer actual creation
until needed), then inside the loop call ctor() to obtain the controller, handle
constructor errors immediately, and then call controller.setup(mgr) (using the
same error handling and teardown pathway if setup or ctor fails); update
references from mc to the created controller instance and ensure any
per-controller cleanup method (if present) is invoked when a subsequent failure
requires rolling back previously created controllers.
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yaml (1)

48-52: ⚡ Quick win

Use valueFrom instead of an inline credential value, even for demo manifests.

The CRD schema's own documentation warns: "Avoid for production use; prefer ValueFrom so the secret is stored in a Kubernetes Secret." Keeping an inline value in a tracked demo file encourages the same pattern to be copied into production configurations.

♻️ Proposed alternative using `valueFrom`

Create a separate Secret (outside this manifest or in a secrets/ directory that is .gitignored):

apiVersion: v1
kind: Secret
metadata:
  name: demo-llm-provider-apim-key
  namespace: apigateway-demo-apim
stringData:
  api-key: "Bearer sk-test-key"

Then reference it in the LlmProvider:

     auth:
       type: api-key
       header: Authorization
       value:
-        value: Bearer sk-test-key
+        valueFrom:
+          name: demo-llm-provider-apim-key
+          key: api-key
🤖 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
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yaml`
around lines 48 - 52, Replace the inline credential in the LlmProvider auth
block (currently using auth.type, auth.header, auth.value.value) with a secret
reference: create a Kubernetes Secret (e.g., name demo-llm-provider-apim-key in
the same namespace, containing stringData key "api-key" with the token) and
update the auth.value to use valueFrom.secretKeyRef referencing that Secret and
key "api-key" instead of storing "Bearer sk-test-key" inline; ensure the Secret
name and key match the valueFrom.secretKeyRef used by the LlmProvider.
🤖 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 `@kubernetes/gateway-operator/api/v1alpha1/subscription_types.go`:
- Around line 72-73: The Subscription CRD should require a top-level spec:
update the Subscription struct so the Spec field uses `json:"spec"` (remove
`omitempty`) to make it required (e.g., the Subscription type where Spec is
declared as SubscriptionSpec), leave Status as-is if optional, then regenerate
the CRDs so the OpenAPI schema reflects the required spec field and re-run any
codegen.

In
`@kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yaml`:
- Around line 109-167: The CRD is missing Server-Side Apply list annotations
because the ResourceStatus.Conditions field in common_types.go lacks the
kubebuilder list markers; add the list markers directly above the Conditions
field in common_types.go (e.g. // +listType=map and // +listMapKey=type or the
equivalent kubebuilder markers your generator expects) on the
ResourceStatus.Conditions declaration so that controller-gen regenerates the CRD
with x-kubernetes-list-type: map and x-kubernetes-list-map-keys: [type], then
re-run CRD generation to update gateway.api-platform.wso2.com_certificates.yaml.

In
`@kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yaml`:
- Around line 75-88: The CRD places the enum at the array level so per-item HTTP
method validation fails; fix by changing the Go type: define a named string type
HTTPMethod with the kubebuilder Enum marker (e.g. //
+kubebuilder:validation:Enum=GET;POST;PUT;PATCH;DELETE;HEAD;OPTIONS;"*") and
change the Methods field from []string to []HTTPMethod (Methods []HTTPMethod
`json:"methods"`), then regenerate the CRD so controller-gen emits enum under
items; if you cannot modify Go, alternatively move the enum block inside items
in the CRD YAML so each string is validated.

In `@kubernetes/gateway-operator/internal/controller/generic_reconciler.go`:
- Around line 238-244: The code path that short-circuits for already-deployed
CRs overwrites the tracker entry with a minimal ResourceTrackingEntry, dropping
persisted fields like GatewayKey and Id; change the branch that calls
r.Tracker.Set(trackingKey, &ResourceTrackingEntry{Generation: crGeneration,
Status: ResourceStatusDeployed}) to preserve existing fields: fetch the prior
entry via r.Tracker.Get(trackingKey) and copy GatewayKey (and other non-empty
fields) into the new entry, and if Id is missing on the prior entry populate Id
from r.Adapter.GetStatus(obj).Id before calling r.Tracker.Set so the tracker
retains GatewayKey/Id when skipping deployment in the if (crGeneration ==
statusObservedGen && progCond != nil && progCond.Status == metav1.ConditionTrue)
branch.
- Around line 627-644: The cleanupAllGateways function currently logs
per-gateway Delete errors but always returns nil; change it to aggregate
failures and return a non-nil error so reconcileDeletion will retry instead of
removing finalizers prematurely. Inside cleanupAllGateways, collect errors from
r.Adapter.Delete (using r.buildAuthFn(gateway) and
gateway.GetGatewayServiceEndpoint() as called) into a slice and at the end
return a combined error (e.g., errors.Join or fmt.Errorf with wrapped errors) if
any failed; keep logging as-is for each gateway but ensure the final returned
error is non-nil when any Delete failed (or, if you intend best-effort, add a
clear comment at the top of cleanupAllGateways documenting that behavior).
- Around line 567-603: The updateProgrammed function mutates latestStatus.Id
before comparing it to desiredStatus.Id, causing the early-return check
(existing vs cond and latestStatus.Id == desiredStatus.Id) to always treat Id as
unchanged and potentially skip persisting a new Id; fix by capturing the
original latestId (e.g., prevLatestId := latestStatus.Id) or by moving the
assignment of latestStatus.Id = desiredStatus.Id until after the
equality/early-return check, and ensure that if prevLatestId != desiredStatus.Id
you do not return early and instead call r.Status().Update (keep references to
updateProgrammed, latestStatus.Id, desiredStatus.Id, existing,
meta.SetStatusCondition, and r.Status().Update).

In
`@kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.go`:
- Line 53: The test currently uses require.Equal to check
rewriteLLMDeployDependencyErrors(other400) which only checks deep equality;
change the assertion to require.Same(t, other400,
rewriteLLMDeployDependencyErrors(other400)) so the test verifies pointer
identity (the exact same *NonRetryableError instance) rather than a new
equivalent value—update the assertion in management_llm_gateway_retry_test.go
where other400 and rewriteLLMDeployDependencyErrors are used.

In `@kubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.go`:
- Around line 39-50: BuildEnvelopeYAML currently allows empty apiVersion or kind
which creates invalid envelopes; add an early guard at the top of
BuildEnvelopeYAML to validate apiVersion and kind and return a descriptive local
error if either is empty (e.g., "missing apiVersion" or "missing kind") before
proceeding to construct cleanPayload so callers get fast, actionable
diagnostics.

In
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml`:
- Around line 99-102: Update the human-readable description for the expiresAt
and expiresIn properties to match the CEL validation that forbids setting both:
change the text around expiresAt/expiresIn (the properties block describing
ExpiresIn/ExpiresAt) so it states they are mutually exclusive and that
attempting to set both is rejected by validation, or alternatively adjust the
CEL rule that enforces mutual exclusion if you prefer precedence behavior;
reference the expiresAt and expiresIn property descriptions and the CRD CEL
validation rule that currently rejects the combination when making the change.

In
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`:
- Around line 214-245: Add a restrictive securityContext to the container specs
for the prism and nginx containers: in the containers array for the prism
container (name: prism) and the nginx container (name: nginx) add
securityContext with runAsNonRoot: true, allowPrivilegeEscalation: false,
capabilities: { drop: ["ALL"] } and readOnlyRootFilesystem: true where the
image/volume usage allows it; ensure any required writable paths are mounted as
explicit writable emptyDir volumes and adjust readOnlyRootFilesystem
accordingly.
- Around line 29-83: The manifest currently embeds sensitive TLS material in the
stringData block (tls.crt and tls.key) — remove the private key content from the
apigateway-restapi-operator-demo Secret and change the chart to reference an
externally provisioned secret or secret controller instead; specifically replace
the in-repo stringData tls.key/tls.crt entries with a reference pattern (e.g.,
use values-driven filePath or annotations for
cert-manager/ExternalSecret/SealedSecret) and update the Secret creation logic
in the Helm template that generates this resource so it reads the certificate
from an external source at deploy time rather than hardcoding tls.key/tls.crt in
this YAML. Ensure the template keys (stringData, tls.crt, tls.key) are removed
and the deployment/values now accept an externalSecretRef or use cert-manager
annotations to provision the cert.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`:
- Around line 197-249: Add a container-level securityContext to both the "prism"
and "nginx" containers in the Deployment manifests: set
allowPrivilegeEscalation: false and runAsNonRoot: true (and optionally set
runAsUser to a non-root UID such as 1000) on each container spec to prevent root
execution and privilege escalation; ensure you update the prism container block
(container name "prism") and the nginx container block (container name "nginx")
accordingly, and if nginx requires writable paths validate or adjust file
ownership/paths before enabling these constraints.

---

Duplicate comments:
In
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml`:
- Around line 18-34: Add a container-level securityContext to the
mcp-server-backend container to enforce non-root execution and prevent privilege
escalation: under the container with name mcp-server-backend add securityContext
keys including runAsNonRoot: true, allowPrivilegeEscalation: false, and consider
adding capabilities.drop: ["ALL"] and readOnlyRootFilesystem: true to further
harden the container.

In `@kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md`:
- Around line 103-114: The README contains conflicting upstream URLs for the
demo LLM mock; pick the in-cluster address as authoritative
(https://mock-openapi-https:9449/...) and mark the alternative
(http://mock-openapi:4010/openai/v1) explicitly as "docker-compose / IT-only".
Update the paragraphs that reference 05b0-mock-openapi-https.yaml,
05b-llm-resources.yaml, and the LlmProvider/LlmProviderTemplate (template:
openai-test) / LlmProxy examples to state the in-cluster URL is used by the
operator reconcilers, and add a short parenthetical note that the 4010 HTTP URL
is only for compose-based local testing.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml`:
- Around line 23-25: The subscription resource uses the Plan CR name in
subscriptionPlanId; replace that with the reconciled plan identifier from the
Plan status (SubscriptionPlan.status.id) so the subscription binds to the
deployed plan. Update the subscription manifest field subscriptionPlanId
(currently set to "demo-plan-apim") to reference the actual plan ID produced by
the operator (SubscriptionPlan.status.id) or render that status value into the
manifest/template so the reconciled numeric/UUID plan id is used rather than the
CR name.

---

Nitpick comments:
In @.github/workflows/operator-integration-test.yml:
- Around line 1631-1647: The cleanup deletes dependent CRs without waiting for
finalizers; update the teardown to explicitly wait for each dependent resource
to be fully removed before deleting the next one (for example, after deleting
apikey/demo-restapi-apikey wait for its deletion with kubectl wait --for=delete
apikey/demo-restapi-apikey -n apigateway-demo --timeout=... before deleting
restapi/hello-apikey-api; similarly wait for subscription/demo-subscription
deletion before removing subscriptionplan/demo-plan, and chain waits for
llmproxy/demo-llm-proxy → llmprovider/demo-llm-provider →
llmprovidertemplate/openai-test); use appropriate --timeout values and keep
--ignore-not-found on the initial delete to avoid failing on missing resources.
- Around line 1076-1080: The current workflow uses `pkill -f "kubectl
port-forward.*8080:8080" || true` followed by `sleep 2` then starting `kubectl
port-forward` and saving its PID to `PF_PID`, which is racy; replace the fixed
sleep with an explicit wait loop that probes localhost:8080 (e.g., curl -sS or
netcat) until the port is free before starting the new `kubectl port-forward`,
or capture and `wait` on the previous port-forward PID when available; apply the
same change to the identical pattern handling port 8080 near line 1190 so the
script reliably ensures the old forwarder has released the port before launching
the new one.
- Around line 1106-1115: The current check `[ -n "${SESSION_ID}" ]` can fail
silently under set -euo pipefail; update the script around SESSION_ID (extracted
from /tmp/mcp-init-headers.txt) to first output the /tmp/mcp-init-headers.txt
and /tmp/mcp-init-body.txt (or echo SESSION_ID) for debugging, then replace the
bare bracket test with an explicit conditional that prints a clear error message
including the captured headers/body and exits non-zero if SESSION_ID is empty;
reference the SESSION_ID extraction and the files /tmp/mcp-init-headers.txt and
/tmp/mcp-init-body.txt so logs show the failing response before failing the job.
- Around line 1062-1071: Replace the current sequential kubectl wait calls (the
lines waiting for managedsecret/demo-managed-secret,
llmprovidertemplate/openai-test, llmprovider/demo-llm-provider,
llmproxy/demo-llm-proxy, mcp/everything-mcp-v1.0, restapi/hello-sub-api,
restapi/hello-apikey-api, subscriptionplan/demo-plan,
subscription/demo-subscription, apikey/demo-restapi-apikey) with a small
helper/wrapper that on failure runs a diagnostic kubectl get <kind> -n
apigateway-demo -o yaml (or kubectl describe) for that specific resource and
outputs it before failing; implement the wrapper so each wait runs the same
kubectl wait invocation but catches non-zero exit, prints the resource-specific
yaml/describe, and then exits non-zero to preserve existing behavior.

In `@kubernetes/gateway-operator/api/v1alpha1/common_types.go`:
- Around line 52-53: Add the kubebuilder list markers to the Conditions slice so
controller-gen emits x-kubernetes-list-type: map and x-kubernetes-list-map-keys:
["type"]; specifically, above the Conditions []metav1.Condition field (in the
ResourceStatus/common_types.go definition) add the comments
+kubebuilder:validation:Optional (if needed) and the markers +listType=map and
+listMapKey=type so the generated CRD treats conditions as a map keyed by type
and avoids atomic-array SSA overwrites.

In `@kubernetes/gateway-operator/cmd/main.go`:
- Around line 240-245: The current code builds concrete reconciler instances
eagerly in the managementControllers slice before calling mc.setup, which can
cause partial side-effect leaks on constructor failure; change
managementControllers to be a slice of constructor functions (e.g., type
controllerCtor func() (controllerType, error)) and populate it with functions
that create each reconciler (defer actual creation until needed), then inside
the loop call ctor() to obtain the controller, handle constructor errors
immediately, and then call controller.setup(mgr) (using the same error handling
and teardown pathway if setup or ctor fails); update references from mc to the
created controller instance and ensure any per-controller cleanup method (if
present) is invoked when a subsequent failure requires rolling back previously
created controllers.

In `@kubernetes/gateway-operator/internal/controller/generic_reconciler.go`:
- Around line 462-475: The terminal failure branch is incorrectly marking
resources as ResourceStatusDeployed; introduce a distinct terminal state (e.g.,
ResourceStatusFailed) and use it in the failure paths: change the status set in
the max-retries and non-retryable error branches (the block calling
r.updateProgrammed and then setting entry.Status = ResourceStatusDeployed and
entry.GatewayDeploySucceeded = false) to set entry.Status =
ResourceStatusFailed, keep GatewayDeploySucceeded = false, and call
r.Tracker.Set(trackingKey, entry); then update the decision logic in
decideAndProcess to handle ResourceStatusFailed as a terminal/failure state
(instead of treating it like ResourceStatusDeployed) so downstream behavior and
invariants reflect a real failure.
- Around line 246-291: The outer guard "if crGeneration > statusObservedGen"
around the big switch is redundant and adds nesting; remove that outer if and
let the existing inner comparisons and cases (checks of entry.Generation,
ResourceStatusProcessing/Retrying/Deployed branches, and the fallback
entry.Generation < crGeneration path) drive control flow, or alternatively
replace the outer guard with an explicit assertion/early-return that documents
the invariant (e.g., return when crGeneration == statusObservedGen and
Programmed is true) so the rest of the code (calls to r.processDeployment and
r.handleDeploymentSuccess, and use of fields like entry.Generation,
entry.GatewayKey, entry.RetryCount) executes without extra nesting; ensure no
behavior change for the existing branches (keep log messages and return values
intact).

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yaml`:
- Around line 48-52: Replace the inline credential in the LlmProvider auth block
(currently using auth.type, auth.header, auth.value.value) with a secret
reference: create a Kubernetes Secret (e.g., name demo-llm-provider-apim-key in
the same namespace, containing stringData key "api-key" with the token) and
update the auth.value to use valueFrom.secretKeyRef referencing that Secret and
key "api-key" instead of storing "Bearer sk-test-key" inline; ensure the Secret
name and key match the valueFrom.secretKeyRef used by the LlmProvider.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aa4b1c6b-c448-4f07-8548-965383c4afba

📥 Commits

Reviewing files that changed from the base of the PR and between 1da6e05 and a2fab84.

📒 Files selected for processing (98)
  • .github/workflows/operator-integration-test.yml
  • gateway/examples/api-key.yaml
  • gateway/examples/certificate.yaml
  • gateway/examples/managed-secret.yaml
  • gateway/examples/subscription-plan.yaml
  • gateway/examples/subscription.yaml
  • kubernetes/gateway-operator/api/v1alpha1/apikey_types.go
  • kubernetes/gateway-operator/api/v1alpha1/certificate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/common_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovider_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
  • kubernetes/gateway-operator/api/v1alpha1/mcp_types.go
  • kubernetes/gateway-operator/api/v1alpha1/secret_types.go
  • kubernetes/gateway-operator/api/v1alpha1/subscription_types.go
  • kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go
  • kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go
  • kubernetes/gateway-operator/cmd/main.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/gateway-operator/config/rbac/role.yaml
  • kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.md
  • kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/internal/controller/apikey_controller.go
  • kubernetes/gateway-operator/internal/controller/certificate_controller.go
  • kubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.go
  • kubernetes/gateway-operator/internal/controller/generic_reconciler.go
  • kubernetes/gateway-operator/internal/controller/httproute_enqueue_test.go
  • kubernetes/gateway-operator/internal/controller/k8s_gateway_controller.go
  • kubernetes/gateway-operator/internal/controller/llmprovider_controller.go
  • kubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.go
  • kubernetes/gateway-operator/internal/controller/llmproxy_controller.go
  • kubernetes/gateway-operator/internal/controller/managedsecret_controller.go
  • kubernetes/gateway-operator/internal/controller/management_llm_enqueue.go
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.go
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.go
  • kubernetes/gateway-operator/internal/controller/management_resources_helpers.go
  • kubernetes/gateway-operator/internal/controller/management_secretsource_resolve_error.go
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload.go
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload_test.go
  • kubernetes/gateway-operator/internal/controller/mcp_controller.go
  • kubernetes/gateway-operator/internal/controller/payload_metadata_test.go
  • kubernetes/gateway-operator/internal/controller/subscription_controller.go
  • kubernetes/gateway-operator/internal/controller/subscriptionplan_controller.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/paths.go
  • kubernetes/gateway-operator/internal/gatewayclient/resource_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.go
  • kubernetes/gateway-operator/internal/secretsource/resolver.go
  • kubernetes/gateway-operator/internal/secretsource/resolver_test.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/helm/operator-helm-chart/templates/_helpers.tpl
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-02-restapi.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-03-restapi.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f0-apikey-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/llm-mock-openapi-it/Dockerfile
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f0-apikey-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.md
  • kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml
  • kubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml
✅ Files skipped from review due to trivial changes (33)
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/llm-mock-openapi-it/Dockerfile
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f0-apikey-resources.yaml
  • kubernetes/gateway-operator/internal/controller/payload_metadata_test.go
  • kubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml
  • kubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.md
  • gateway/examples/certificate.yaml
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.go
  • gateway/examples/subscription.yaml
  • kubernetes/gateway-operator/internal/controller/k8s_gateway_controller.go
  • kubernetes/gateway-operator/internal/controller/httproute_enqueue_test.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • gateway/examples/api-key.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.go
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload_test.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/api/v1alpha1/mcp_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/secret_types.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/gateway-operator/internal/controller/mcp_controller.go
  • kubernetes/gateway-operator/config/rbac/role.yaml
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload.go
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.md
  • kubernetes/gateway-operator/api/v1alpha1/llmprovider_types.go
  • gateway/examples/subscription-plan.yaml
  • kubernetes/gateway-operator/internal/secretsource/resolver_test.go
  • kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go
🚧 Files skipped from review as they are similar to previous changes (41)
  • kubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.go
  • gateway/examples/managed-secret.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-03-restapi.yaml
  • kubernetes/gateway-operator/api/v1alpha1/certificate_types.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.go
  • kubernetes/gateway-operator/internal/controller/subscriptionplan_controller.go
  • kubernetes/gateway-operator/internal/controller/management_llm_enqueue.go
  • kubernetes/gateway-operator/internal/controller/management_secretsource_resolve_error.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.go
  • kubernetes/gateway-operator/internal/secretsource/resolver.go
  • kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.go
  • kubernetes/gateway-operator/internal/controller/llmproxy_controller.go
  • kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/gateway-operator/internal/controller/management_resources_helpers.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/gateway-operator/internal/controller/apikey_controller.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/gateway-operator/internal/controller/subscription_controller.go
  • kubernetes/gateway-operator/api/v1alpha1/apikey_types.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/gateway-operator/internal/gatewayclient/resource_client.go
  • kubernetes/gateway-operator/internal/controller/certificate_controller.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/gateway-operator/internal/controller/llmprovider_controller.go
  • kubernetes/helm/operator-helm-chart/templates/_helpers.tpl
  • kubernetes/gateway-operator/internal/controller/managedsecret_controller.go

Comment thread kubernetes/gateway-operator/api/v1alpha1/subscription_types.go Outdated
Comment on lines +29 to +83
stringData:
tls.crt: |
-----BEGIN CERTIFICATE-----
MIIECjCCAvKgAwIBAgIUGCH+aywaI2afToRcEzQs8wNN91kwDQYJKoZIhvcNAQEL
BQAwHTEbMBkGA1UEAwwSbW9jay1vcGVuYXBpLWh0dHBzMB4XDTI2MDQzMDA1MDY1
OFoXDTM2MDQyNzA1MDY1OFowHTEbMBkGA1UEAwwSbW9jay1vcGVuYXBpLWh0dHBz
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsCZJgXAJ7npBgXYMZARB
QLhj8fD9TrjRKZ2YhbF2etIO8Q2//wrWauxeZNB2H+2XgVdQf816u/mwBSWlSJCY
r4PIiJN6FvMjUFobtJuglYwiNYSx5JsOjPzAP1ZOjPlQH0UAppAaewBUInupWr03
OS1I1+V05hvtaCBeLLmCWO9nf4UVCDHMbtxgKyobjul3+dPhrhh7swufEncGytuH
y1fCdxBo69Zxv1OqpCmibsTHrOOMugoR5Ka+iSogjKRN26f6XpDcm8lqjp6FNhc9
JEbOqSF91dGe65ARMY3TABYDXY8P2rH/MsaR9utPp0sydALazVLnIWfxSqcdhudY
cQIDAQABo4IBQDCCATwwHQYDVR0OBBYEFEL7Iqyezesz6PVZX8e3FyxmHaYHMB8G
A1UdIwQYMBaAFEL7Iqyezesz6PVZX8e3FyxmHaYHMA8GA1UdEwEB/wQFMAMBAf8w
gegGA1UdEQSB4DCB3YISbW9jay1vcGVuYXBpLWh0dHBzgiJtb2NrLW9wZW5hcGkt
aHR0cHMuYXBpZ2F0ZXdheS1kZW1vgjRtb2NrLW9wZW5hcGktaHR0cHMuYXBpZ2F0
ZXdheS1kZW1vLnN2Yy5jbHVzdGVyLmxvY2Fsgidtb2NrLW9wZW5hcGktaHR0cHMu
YXBpZ2F0ZXdheS1kZW1vLWFwaW2COW1vY2stb3BlbmFwaS1odHRwcy5hcGlnYXRl
d2F5LWRlbW8tYXBpbS5zdmMuY2x1c3Rlci5sb2NhbIIJbG9jYWxob3N0MA0GCSqG
SIb3DQEBCwUAA4IBAQCCIgIt2Wbp2vi5qMNfFy0wP95KqMjF6JtDO/96tl2voQj0
ScQ2UIC5O9Uu0Ado2FT5888IT2XHY7pk9BK1OCuVchQ23Copq1z03FL3I3eSZu0P
PWLlpK4vz52lfLGTvy3XC5kRY3d9y6zgYSxnc9FPTDN14F4jnaqxDNH8TlGGuhPR
nxaTa7vHXZG/DtRSblspUQj351rhkz1HjmUSbjqnQOeMh8FlwIEc9qcrr/IK7xjl
lXpggGSgLLgwoXs4hXFKiROb4pPlrBMEM5sQLPoI00BRUDXHxLdZNFD2qpEpuezG
s6iuc9KpaPvM2jr0QBASZc8urd3asYgi/ckuWjty
-----END CERTIFICATE-----
tls.key: |
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCwJkmBcAnuekGB
dgxkBEFAuGPx8P1OuNEpnZiFsXZ60g7xDb//CtZq7F5k0HYf7ZeBV1B/zXq7+bAF
JaVIkJivg8iIk3oW8yNQWhu0m6CVjCI1hLHkmw6M/MA/Vk6M+VAfRQCmkBp7AFQi
e6lavTc5LUjX5XTmG+1oIF4suYJY72d/hRUIMcxu3GArKhuO6Xf50+GuGHuzC58S
dwbK24fLV8J3EGjr1nG/U6qkKaJuxMes44y6ChHkpr6JKiCMpE3bp/pekNybyWqO
noU2Fz0kRs6pIX3V0Z7rkBExjdMAFgNdjw/asf8yxpH260+nSzJ0AtrNUuchZ/FK
px2G51hxAgMBAAECggEABN72KuRifev3CkGtq4lblZLTvrG7i3T8YbJ4MIA9Br3u
yFcpPz/5OGkzXgXOsmjTRvIXaYVQ1shbh/TnymcnNblG7xHm7yIHkLYBLKAeE2yV
2JWEBs6L2zW1CjtUfjz5rHevwitbdBnI+dAiGX6Ae64HJOM1NZTBnp7Ag+uYUWW3
hREubx77gN0P4k1pxXy8Fbn2A/jk9EoNl1DfHfUVP+Kin1m6ICfVmm1gYP6NglBy
8mF1jR1RE6srqMt2Zve4jx57rdnys/8vSZwRnauETrE/XVYq4swnb5ek6glsYnhj
2W8DI53GvXc12SNGffzczaiAQI84/VtP1Gl+pnbGvQKBgQDy1hCMdOQfGdacEyyJ
JJLbn8KUoyv0pdOjFiN9nGsULpu7q88ifFt4z332JsNEXDx34wcvwNM4g98OEcgX
gBjZ0rsHeoFviR0FTJT56vwx+zkZ7zPq2ff6+nydAVW3QkYvobcUnSg+WNaNeqNO
ByQleMqi0X2t8g8fVEAw9ApWPQKBgQC5sskK6p1LmfLWXu6fiAmdBz82PQyIlF51
oTfkvhvOn8Cm2DOcjPBb43EnmbJvm4avhE2X53gnvpzpXTJ5JjygPJ8AVYKUz3L3
2FvwHrIXCDPfqF8v5OzImXKXlX2nte7b/LOz0JpEDGj2jrLH/UOUYH73vJ9KRCz3
t9ko9/IiRQKBgC4v2uvsG6tyraf32aOoCiqto0PEGUsp8yv0vQFaK9+l0ccLMZV8
k6VRycJ0svUgmfuveaFyinoYUueJKEaX/aIIe7w7Z70AFj34x+kU8azL/JtbLMPB
e3IIDIWMSHJXr6PFnV3VWl8pZH16Uu83Wn+qDtiTAdWA0bGCw8bL0999AoGAClec
vtDmRcmjLrFG1Pe8oVQ//wNTaV/qCt8Zv9GettlK33/3D+cBk/yM1snGmFSvIaNa
tvUbSgthrdWU9A5fHgWCTYB1r1gGbStNo4fyHzpDuVclRgOX9GwnZ63Zu7d0QChI
0eDyVoviZKj56A2A8AiXybBlprs5TeqdW5zDjYkCgYBtRRAWB6Nj1V1w0ok8yrYW
CnJQHFuTsyQXEmbCPA9GWnLOYT8KBBPxDdBek4s1e5HtP9zrj+Z0owtz8p09Byd7
gYtBDBWEU8p20Qrd37sF9JudRl8BytOe+Ht2Fv2gMPJNYLN+rZrqlpqo2aW1NKbX
+0cF+MAY/mrkyzWdRFVy1A==
-----END PRIVATE KEY-----
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid committing private key material in this manifest.

Lines 56-83 embed a private key directly in versioned YAML. Please replace this with an externally provisioned secret flow (or generated-at-deploy instructions) so sensitive key material is not stored in-repo.

🧰 Tools
🪛 Betterleaks (1.1.2)

[high] 56-83: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption.

(private-key)

🤖 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
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`
around lines 29 - 83, The manifest currently embeds sensitive TLS material in
the stringData block (tls.crt and tls.key) — remove the private key content from
the apigateway-restapi-operator-demo Secret and change the chart to reference an
externally provisioned secret or secret controller instead; specifically replace
the in-repo stringData tls.key/tls.crt entries with a reference pattern (e.g.,
use values-driven filePath or annotations for
cert-manager/ExternalSecret/SealedSecret) and update the Secret creation logic
in the Helm template that generates this resource so it reads the certificate
from an external source at deploy time rather than hardcoding tls.key/tls.crt in
this YAML. Ensure the template keys (stringData, tls.crt, tls.key) are removed
and the deployment/values now accept an externalSecretRef or use cert-manager
annotations to provision the cert.

@CrowleyRajapakse CrowleyRajapakse force-pushed the integration-tests-actions branch from a2fab84 to 533032d Compare May 5, 2026 06:39
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

♻️ Duplicate comments (5)
kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml (1)

21-83: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove the inline TLS keypair from the tracked demo manifest.

This file still carries the TLS certificate and private key in source. Please move that material to a pre-created secret or certificate automation flow and keep only the secret reference in the manifest.

🤖 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
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`
around lines 21 - 83, The Secret "mock-openapi-https-tls" embeds a TLS
certificate/private key in stringData (tls.crt/tls.key); remove the inline key
material and instead reference a pre-created TLS Secret. Concretely: delete the
entire stringData block (tls.crt and tls.key) from the Secret manifest (or
remove this Secret file entirely) and ensure any Ingress/Deployment/Service that
expects TLS uses the secret name "mock-openapi-https-tls" which you will
provision out-of-band via your certificate automation or a pre-created Secret;
do not commit any certificate or private key into the repo.
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml (1)

21-83: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove the inline TLS keypair from the tracked demo manifest.

This file still embeds the TLS certificate and private key directly in source. Please switch the demo to reference a pre-created secret or certificate automation flow and keep only the secret name in the repository.

🤖 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
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`
around lines 21 - 83, The manifest currently embeds TLS material (stringData
tls.crt/tls.key) for the Secret named mock-openapi-https-tls in namespace
apigateway-demo-apim; remove the inline certificate/private-key content and
instead keep only the Secret metadata (name/namespace/labels) and a
comment/annotation indicating that the secret must be pre-created or provisioned
by cert-manager/your automation; update any references that expect this Secret
(mock-openapi-https-tls) so the demo uses the externally created secret name
rather than committing keys in the repo.
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml (1)

23-24: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bind the subscription to the reconciled plan id.

subscriptionPlanId is wired to the SubscriptionPlan CR name here. If the controller expects the deployed plan identifier, this subscription will not attach to the reconciled plan. Populate it from SubscriptionPlan.status.id instead of demo-plan-apim.

#!/bin/bash
set -euo pipefail

echo "=== Subscription API types ==="
fd -i 'subscription*types.go' kubernetes/gateway-operator/api --exec sed -n '1,240p' {}

echo
echo "=== subscriptionPlanId usages ==="
rg -n -C3 '\bsubscriptionPlanId\b|\bSubscriptionPlanId\b' kubernetes/gateway-operator

echo
echo "=== SubscriptionPlan status id usages ==="
rg -n -C3 '\bstatus\.id\b|\bStatus\.Id\b|\bStatus\.ID\b' kubernetes/gateway-operator

Expected result: the controller/client flow should show whether subscriptionPlanId is matched against the gateway-issued plan id rather than the plan CR name.

🤖 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
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml`
around lines 23 - 24, The subscription resource currently hardcodes
subscriptionPlanId to "demo-plan-apim"; change it to use the reconciled plan
identifier from the SubscriptionPlan CR status by wiring subscriptionPlanId to
SubscriptionPlan.status.id. Locate the Subscription manifest where
subscriptionPlanId is set (the resource with apiId: hello-normal-api-apim) and
replace the literal with a value obtained from the SubscriptionPlan CR's
status.id (e.g., via your templating/helm lookup or controller injection) so the
subscription binds to the gateway-issued plan id rather than the CR name
(reference symbols: subscriptionPlanId field on the Subscription resource and
SubscriptionPlan.status.id).
kubernetes/gateway-operator/api/v1alpha1/mcp_types.go (1)

41-48: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Tighten MCPUpstream to the backend contract.

MCPUpstream currently allows an empty object and a ref, which means Kubernetes can accept Mcp resources that the downstream MCP API cannot program successfully. Please make url required here and reject/remove ref so the CRD contract matches the reconciler target.

Proposed change
 type MCPUpstream struct {
 	// Url is the direct backend URL.
-	// +optional
-	Url *string `json:"url,omitempty"`
-
-	// Ref is the name of a predefined upstream definition.
-	// +optional
-	Ref *string `json:"ref,omitempty"`
+	// +kubebuilder:validation:Required
+	Url string `json:"url"`

 	// HostRewrite controls how the Host header is handled.
 	// +optional
 	// +kubebuilder:validation:Enum=auto;manual
 	HostRewrite *string `json:"hostRewrite,omitempty"`

Also applies to: 159-161

🤖 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 `@kubernetes/gateway-operator/api/v1alpha1/mcp_types.go` around lines 41 - 48,
MCPUpstream currently allows an empty object and a Ref, so tighten the contract
by making Url required and removing Ref: update the MCPUpstream struct (symbol
MCPUpstream) to remove the Ref field and change Url from *string
`json:"url,omitempty"` to string `json:"url"` (and adjust the comment to reflect
it is required), then update any usages/validation that construct or read
MCPUpstream (including code paths that previously accepted nil Url or used Ref)
to treat Url as required and drop handling for Ref; also update CRD
markers/validation generation where MCPUpstream is used so the generated CRD
enforces the required url field.
kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md (1)

103-114: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make the LLM upstream URL guidance consistent.

This section says the in-cluster flow uses https://mock-openapi-https:9449/..., but the next paragraph says 05b-llm-resources.yaml uses http://mock-openapi:4010/openai/v1. Please keep one authoritative in-cluster URL here and explicitly label the other as compose-only; otherwise the demo instructions remain self-contradictory.

🤖 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 `@kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md` around
lines 103 - 114, The README contains contradictory in-cluster upstream URLs
(mentions https://mock-openapi-https:9449/... in the
05b0-mock-openapi-https.yaml description but then documents
05b-llm-resources.yaml as using http://mock-openapi:4010/openai/v1); pick one
authoritative in-cluster URL (preferably the HTTPS 9449 address used by
05b0-mock-openapi-https.yaml) and update the paragraph that describes
05b-llm-resources.yaml to explicitly state that
http://mock-openapi:4010/openai/v1 is a compose-only URL (or vice versa if you
prefer compose as canonical), and ensure references to LlmProviderTemplate
openai-test, LlmProvider (template: openai-test) and LlmProxy consistently use
the chosen in-cluster URL while labeling the other URL as compose-only.
🧹 Nitpick comments (5)
kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml (1)

67-71: ⚡ Quick win

Rename spec.status to avoid collision with the Kubernetes status convention.

A field named status inside spec is non-idiomatic in Kubernetes API design — status is a reserved term strongly associated with the root-level lifecycle subresource. While the comment documents that it mirrors the upstream SubscriptionCreateRequest.Status enum, users and tooling (e.g., kubectl, validation webhooks, generated clients) may misinterpret this field. Since this is still v1alpha1, renaming before graduation is low-cost; after promotion it becomes a breaking API change.

Consider a more explicit name such as subscriptionState or lifecycleStatus.

♻️ Proposed rename
-              status:
-                description: |-
-                  Status is the lifecycle state for this subscription. Mirrors the
-                  management-API SubscriptionCreateRequest.Status enum.
-                type: string
+              subscriptionState:
+                description: |-
+                  SubscriptionState is the lifecycle state for this subscription. Mirrors the
+                  management-API SubscriptionCreateRequest.Status enum.
+                type: string

The corresponding Go type in subscription_types.go and any management-API mapping in subscription_client.go would need to be updated accordingly.

🤖 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
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml`
around lines 67 - 71, The spec currently exposes a field named status which
collides with Kubernetes' reserved status semantics; rename this field across
the CRD and code (e.g., change spec.status -> spec.subscriptionState or
spec.lifecycleStatus in the CRD schema block and update its description) and
then update the corresponding Go type field in subscription_types.go (rename the
struct field and its JSON/YAML tag) and any mapping or conversion in
subscription_client.go to use the new name; also update any validation/enum
references and documentation strings to reflect the new identifier so all
consumers and the CRD schema stay consistent.
kubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.go (1)

9-58: ⚡ Quick win

LGTM — consider adding empty-parent error cases for LlmProvider and LlmProxy.

The test covers the main happy paths and error cases well. For completeness, it may be worth adding parent: "" error cases for LlmProvider and LlmProxy to confirm the error contract is consistent with the RestApi case already tested.

🤖 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 `@kubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.go`
around lines 9 - 58, Add two test cases to TestBuildAPIKeysPath_AllParents that
assert BuildAPIKeysPath returns an error when parent is empty for the LLM kinds:
one with parentKind set to ApiKeyParentKindLlmProvider and parent:"", key:""
(expect wantErr:true) and another with parentKind set to
ApiKeyParentKindLlmProxy and parent:"", key:"k1" (expect wantErr:true); keep
naming consistent (e.g., "missing parent for llm provider" and "missing parent
for llm proxy") and follow the existing pattern for calling BuildAPIKeysPath,
checking require.Error when c.wantErr is true.
kubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yaml (1)

52-57: ⚡ Quick win

Replace inline auth value with Secret reference.

Line 57 hardcodes credential material in the manifest. Prefer valueFrom so credentials are managed separately from checked-in config.

Suggested change
   upstream:
     url: http://mock-openapi:4010/openai/v1
     auth:
       type: api-key
       header: Authorization
       value:
-        value: Bearer sk-test-key
+        valueFrom:
+          name: <secret-name>
+          key: <secret-key>
🤖 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
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yaml`
around lines 52 - 57, The manifest currently hardcodes credentials under auth ->
value -> value ("Bearer sk-test-key"); replace that inline secret with a Secret
reference by removing the literal and using valueFrom to point to a Kubernetes
Secret (e.g., valueFrom.secretKeyRef with the Secret name and key). Create or
reference a Secret that contains the credential key and update the auth block
(auth.type: api-key, auth.header: Authorization, auth.value ->
valueFrom.secretKeyRef) so the manifest no longer contains the raw credential.
kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go (1)

44-49: ⚡ Quick win

Add fast-fail validation for empty payload fields.

Validate payload.Name and payload.Certificate before marshalling/sending so callers get immediate, deterministic errors.

Suggested change
 import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"strings"
 )
@@
 func UploadCertificate(ctx context.Context, gatewayEndpoint string, payload CertificateUploadPayload, auth AuthHeaderFunc) (*CertificateCreateResponse, error) {
+	if strings.TrimSpace(payload.Name) == "" {
+		return nil, fmt.Errorf("certificate name is required")
+	}
+	if strings.TrimSpace(payload.Certificate) == "" {
+		return nil, fmt.Errorf("certificate content is required")
+	}
 	body, err := json.Marshal(payload)
🤖 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 `@kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go`
around lines 44 - 49, Add fast-fail validation at the start of UploadCertificate
to check CertificateUploadPayload fields: verify payload.Name is non-empty and
payload.Certificate is non-empty and return a clear error (e.g., fmt.Errorf)
before attempting json.Marshal or calling CreateResource; reference the
UploadCertificate function and CertificateUploadPayload, and keep CreateResource
call unchanged so callers receive immediate, deterministic validation errors
instead of marshalling/HTTP errors.
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yaml (1)

47-53: ⚡ Quick win

Use valueFrom instead of inline auth value.

Line 52 hardcodes a credential string in the manifest. Please source it from a Secret to keep demo manifests safer and easier to rotate.

Suggested change
   upstream:
     url: http://mock-openapi:4010/openai/v1
     auth:
       type: api-key
       header: Authorization
       value:
-        value: Bearer sk-test-key
+        valueFrom:
+          name: <secret-name>
+          key: <secret-key>
🤖 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
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yaml`
around lines 47 - 53, Replace the hardcoded credential under the auth block
(fields auth.type, auth.header, auth.value) with a valueFrom reference to a
Kubernetes Secret: remove the inline "value: Bearer sk-test-key" and instead use
valueFrom.secretKeyRef pointing to an existing Secret name and key (e.g.,
secretName: <YOUR_SECRET_NAME>, key: <YOUR_KEY>) so the manifest reads
auth.value.valueFrom.secretKeyRef with the Secret name and key; ensure a
matching Secret containing the "Bearer ..." string is created in the same
namespace beforehand.
🤖 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 @.github/workflows/operator-integration-test.yml:
- Around line 1041-1072: Add the missing certificate manifest by applying
"05e-certificate-resources.yaml" (same DEMO path) in the deploy job, then wait
for its Programmed condition (kubectl wait --for=condition=Programmed
certificate/<name> -n apigateway-demo --timeout=300s) using the actual resource
name from that manifest, and also add the corresponding apply/delete lines in
the cleanup step so the certificate resource is created and removed as part of
the job.

In `@kubernetes/gateway-operator/api/v1alpha1/apikey_types.go`:
- Around line 105-110: The ApiKey type currently makes Spec optional via the
json tag; remove `omitempty` from the Spec field's tag (change
`json:"spec,omitempty"` to `json:"spec"`) and add the kubebuilder validation
marker above the Spec field (e.g., `// +kubebuilder:validation:Required`) so the
generated CRD requires the spec root; after that regenerate CRD artifacts (run
your project’s CRD generation/controller-gen or make target) so the CRD YAMLs
include "spec" in the root required array.

In `@kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go`:
- Line 73: The SubscriptionPlan struct currently tags Spec as
`json:"spec,omitempty"` which can omit the spec entirely; change the struct tag
on the Spec field to `json:"spec"` (remove `omitempty`) so the field is always
serialized; update the tag for the Spec field in the SubscriptionPlan type that
references SubscriptionPlanSpec to `json:"spec"` to follow Kubernetes CRD
conventions.
- Around line 58-59: The CRD enum for ThrottleLimitUnit is using wrong values;
update the kubebuilder validation tag on the ThrottleLimitUnit field in
subscriptionplan_types.go to use the lowercase WSO2 API contract values
"min;hour;day;month;week;year" (i.e., replace
"+kubebuilder:validation:Enum=Day;Hour;Min;Month" with
"+kubebuilder:validation:Enum=min;hour;day;month;week;year") and ensure the
field declaration for ThrottleLimitUnit *string
`json:\"throttleLimitUnit,omitempty\"` remains unchanged.

In
`@kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml`:
- Around line 241-247: The CRD for LlmProvider is missing the root-level
required entry for spec, so an empty resource can be admitted; update the CRD
root schema to include "spec" in the top-level required array (i.e. add "spec"
alongside existing required keys) and then regenerate the CRD artifacts from the
Go LlmProvider type so the YAML reflects the type definitions; ensure the change
is applied to the other CRD file copy as well where the same omission occurs.

In `@kubernetes/gateway-operator/internal/controller/mcp_controller.go`:
- Around line 60-69: The controller only watches apiv1.Mcp and apiv1.APIGateway
so Secret rotations don't trigger reconciles; update
McpReconciler.SetupWithManager to add a field indexer for corev1.Secret
references used in APIGateway.spec.upstream.auth (use
mgr.GetFieldIndexer().IndexField to index APIGateway by the secret
name/namespace/key used by the selector) and add a Watch for corev1.Secret that
uses handler.EnqueueRequestsFromMapFunc with a mapper that lists APIGateway
objects matching the indexed secret and then enqueues their owning/related
apiv1.Mcp (reuse or adapt enqueueAllOfKind logic to map from APIGateway -> Mcp);
keep gatewayWatchPredicate() and existing APIGateway watch but ensure the Secret
watch requeues dependent Mcp on Secret update.

In `@kubernetes/gateway-operator/internal/gatewayclient/resource_client.go`:
- Around line 43-46: The failure returned from http.NewRequestWithContext should
be classified as non-retryable since it's an input/configuration error; locate
the usages around http.NewRequestWithContext in resource_client.go (the block
that creates req with NewRequestWithContext near the top and the similar block
around line 160) and change the return to use &NonRetryableError{Err:
fmt.Errorf("create HTTP request: %w", err)} instead of &RetryableError,
preserving the existing fmt.Errorf message and context so callers receive the
same error text but marked non-retryable.

In
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yaml`:
- Around line 1121-1127: The CRD removed admission-time validation for the
"labels" map (the labels.additionalProperties schema), which lets invalid label
values slip through to reconciliation errors; restore Kubernetes label value
constraints by adding x-kubernetes-validations (or equivalent) on
labels.additionalProperties to enforce maxLength: 63 and the Kubernetes label
value pattern (alphanumeric with allowed -_. and proper start/end rules), and
consider also re-adding key format validation for label keys so
Deployments/Services/ConfigMaps creation fails at admission time instead of
during reconcile; update the schema entry for labels -> additionalProperties
accordingly.

In
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml`:
- Around line 19-20: Replace the personal Docker Hub image reference used by the
mcp-server-backend container (name: mcp-server-backend, image:
rakhitharr/mcp-everything:v3) with the official publisher image by changing the
image field to mcp/everything; if a specific immutable tag is required for
reproducibility, pin to a stable tag from the official mcp/everything repository
instead of using the personal namespace or a mutable tag.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml`:
- Around line 19-20: The container image for the Deployment/Pod named
"mcp-server-backend" uses a personal Docker Hub namespace
("rakhitharr/mcp-everything:v3"); replace that with the official publisher image
"mcp/everything" (preferably pinned to an immutable digest) by updating the
image field for the resource that has name: mcp-server-backend to
"mcp/everything@sha256:<digest>" or at minimum "mcp/everything:<stable-tag>" to
avoid supply-chain and mutability risks.

---

Duplicate comments:
In `@kubernetes/gateway-operator/api/v1alpha1/mcp_types.go`:
- Around line 41-48: MCPUpstream currently allows an empty object and a Ref, so
tighten the contract by making Url required and removing Ref: update the
MCPUpstream struct (symbol MCPUpstream) to remove the Ref field and change Url
from *string `json:"url,omitempty"` to string `json:"url"` (and adjust the
comment to reflect it is required), then update any usages/validation that
construct or read MCPUpstream (including code paths that previously accepted nil
Url or used Ref) to treat Url as required and drop handling for Ref; also update
CRD markers/validation generation where MCPUpstream is used so the generated CRD
enforces the required url field.

In
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`:
- Around line 21-83: The Secret "mock-openapi-https-tls" embeds a TLS
certificate/private key in stringData (tls.crt/tls.key); remove the inline key
material and instead reference a pre-created TLS Secret. Concretely: delete the
entire stringData block (tls.crt and tls.key) from the Secret manifest (or
remove this Secret file entirely) and ensure any Ingress/Deployment/Service that
expects TLS uses the secret name "mock-openapi-https-tls" which you will
provision out-of-band via your certificate automation or a pre-created Secret;
do not commit any certificate or private key into the repo.

In `@kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md`:
- Around line 103-114: The README contains contradictory in-cluster upstream
URLs (mentions https://mock-openapi-https:9449/... in the
05b0-mock-openapi-https.yaml description but then documents
05b-llm-resources.yaml as using http://mock-openapi:4010/openai/v1); pick one
authoritative in-cluster URL (preferably the HTTPS 9449 address used by
05b0-mock-openapi-https.yaml) and update the paragraph that describes
05b-llm-resources.yaml to explicitly state that
http://mock-openapi:4010/openai/v1 is a compose-only URL (or vice versa if you
prefer compose as canonical), and ensure references to LlmProviderTemplate
openai-test, LlmProvider (template: openai-test) and LlmProxy consistently use
the chosen in-cluster URL while labeling the other URL as compose-only.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`:
- Around line 21-83: The manifest currently embeds TLS material (stringData
tls.crt/tls.key) for the Secret named mock-openapi-https-tls in namespace
apigateway-demo-apim; remove the inline certificate/private-key content and
instead keep only the Secret metadata (name/namespace/labels) and a
comment/annotation indicating that the secret must be pre-created or provisioned
by cert-manager/your automation; update any references that expect this Secret
(mock-openapi-https-tls) so the demo uses the externally created secret name
rather than committing keys in the repo.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml`:
- Around line 23-24: The subscription resource currently hardcodes
subscriptionPlanId to "demo-plan-apim"; change it to use the reconciled plan
identifier from the SubscriptionPlan CR status by wiring subscriptionPlanId to
SubscriptionPlan.status.id. Locate the Subscription manifest where
subscriptionPlanId is set (the resource with apiId: hello-normal-api-apim) and
replace the literal with a value obtained from the SubscriptionPlan CR's
status.id (e.g., via your templating/helm lookup or controller injection) so the
subscription binds to the gateway-issued plan id rather than the CR name
(reference symbols: subscriptionPlanId field on the Subscription resource and
SubscriptionPlan.status.id).

---

Nitpick comments:
In `@kubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.go`:
- Around line 9-58: Add two test cases to TestBuildAPIKeysPath_AllParents that
assert BuildAPIKeysPath returns an error when parent is empty for the LLM kinds:
one with parentKind set to ApiKeyParentKindLlmProvider and parent:"", key:""
(expect wantErr:true) and another with parentKind set to
ApiKeyParentKindLlmProxy and parent:"", key:"k1" (expect wantErr:true); keep
naming consistent (e.g., "missing parent for llm provider" and "missing parent
for llm proxy") and follow the existing pattern for calling BuildAPIKeysPath,
checking require.Error when c.wantErr is true.

In `@kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go`:
- Around line 44-49: Add fast-fail validation at the start of UploadCertificate
to check CertificateUploadPayload fields: verify payload.Name is non-empty and
payload.Certificate is non-empty and return a clear error (e.g., fmt.Errorf)
before attempting json.Marshal or calling CreateResource; reference the
UploadCertificate function and CertificateUploadPayload, and keep CreateResource
call unchanged so callers receive immediate, deterministic validation errors
instead of marshalling/HTTP errors.

In
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml`:
- Around line 67-71: The spec currently exposes a field named status which
collides with Kubernetes' reserved status semantics; rename this field across
the CRD and code (e.g., change spec.status -> spec.subscriptionState or
spec.lifecycleStatus in the CRD schema block and update its description) and
then update the corresponding Go type field in subscription_types.go (rename the
struct field and its JSON/YAML tag) and any mapping or conversion in
subscription_client.go to use the new name; also update any validation/enum
references and documentation strings to reflect the new identifier so all
consumers and the CRD schema stay consistent.

In
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yaml`:
- Around line 52-57: The manifest currently hardcodes credentials under auth ->
value -> value ("Bearer sk-test-key"); replace that inline secret with a Secret
reference by removing the literal and using valueFrom to point to a Kubernetes
Secret (e.g., valueFrom.secretKeyRef with the Secret name and key). Create or
reference a Secret that contains the credential key and update the auth block
(auth.type: api-key, auth.header: Authorization, auth.value ->
valueFrom.secretKeyRef) so the manifest no longer contains the raw credential.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yaml`:
- Around line 47-53: Replace the hardcoded credential under the auth block
(fields auth.type, auth.header, auth.value) with a valueFrom reference to a
Kubernetes Secret: remove the inline "value: Bearer sk-test-key" and instead use
valueFrom.secretKeyRef pointing to an existing Secret name and key (e.g.,
secretName: <YOUR_SECRET_NAME>, key: <YOUR_KEY>) so the manifest reads
auth.value.valueFrom.secretKeyRef with the Secret name and key; ensure a
matching Secret containing the "Bearer ..." string is created in the same
namespace beforehand.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4e87a610-a51b-4f4a-96cc-4e0c424d5f23

📥 Commits

Reviewing files that changed from the base of the PR and between 1da6e05 and 533032d.

📒 Files selected for processing (98)
  • .github/workflows/operator-integration-test.yml
  • gateway/examples/api-key.yaml
  • gateway/examples/certificate.yaml
  • gateway/examples/managed-secret.yaml
  • gateway/examples/subscription-plan.yaml
  • gateway/examples/subscription.yaml
  • kubernetes/gateway-operator/api/v1alpha1/apikey_types.go
  • kubernetes/gateway-operator/api/v1alpha1/certificate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/common_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovider_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
  • kubernetes/gateway-operator/api/v1alpha1/mcp_types.go
  • kubernetes/gateway-operator/api/v1alpha1/secret_types.go
  • kubernetes/gateway-operator/api/v1alpha1/subscription_types.go
  • kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go
  • kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go
  • kubernetes/gateway-operator/cmd/main.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/gateway-operator/config/rbac/role.yaml
  • kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.md
  • kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/internal/controller/apikey_controller.go
  • kubernetes/gateway-operator/internal/controller/certificate_controller.go
  • kubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.go
  • kubernetes/gateway-operator/internal/controller/generic_reconciler.go
  • kubernetes/gateway-operator/internal/controller/httproute_enqueue_test.go
  • kubernetes/gateway-operator/internal/controller/k8s_gateway_controller.go
  • kubernetes/gateway-operator/internal/controller/llmprovider_controller.go
  • kubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.go
  • kubernetes/gateway-operator/internal/controller/llmproxy_controller.go
  • kubernetes/gateway-operator/internal/controller/managedsecret_controller.go
  • kubernetes/gateway-operator/internal/controller/management_llm_enqueue.go
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.go
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.go
  • kubernetes/gateway-operator/internal/controller/management_resources_helpers.go
  • kubernetes/gateway-operator/internal/controller/management_secretsource_resolve_error.go
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload.go
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload_test.go
  • kubernetes/gateway-operator/internal/controller/mcp_controller.go
  • kubernetes/gateway-operator/internal/controller/payload_metadata_test.go
  • kubernetes/gateway-operator/internal/controller/subscription_controller.go
  • kubernetes/gateway-operator/internal/controller/subscriptionplan_controller.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/paths.go
  • kubernetes/gateway-operator/internal/gatewayclient/resource_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.go
  • kubernetes/gateway-operator/internal/secretsource/resolver.go
  • kubernetes/gateway-operator/internal/secretsource/resolver_test.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/helm/operator-helm-chart/templates/_helpers.tpl
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-02-restapi.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-03-restapi.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f0-apikey-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/llm-mock-openapi-it/Dockerfile
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f0-apikey-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.md
  • kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml
  • kubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml
✅ Files skipped from review due to trivial changes (38)
  • kubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml
  • kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml
  • gateway/examples/api-key.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • gateway/examples/managed-secret.yaml
  • kubernetes/gateway-operator/internal/controller/k8s_gateway_controller.go
  • kubernetes/gateway-operator/internal/controller/payload_metadata_test.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/llm-mock-openapi-it/Dockerfile
  • kubernetes/gateway-operator/api/v1alpha1/subscription_types.go
  • gateway/examples/subscription.yaml
  • kubernetes/gateway-operator/api/v1alpha1/secret_types.go
  • kubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.md
  • gateway/examples/certificate.yaml
  • kubernetes/gateway-operator/internal/controller/management_secretsource_resolve_error.go
  • kubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload_test.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/gateway-operator/internal/controller/management_resources_helpers.go
  • kubernetes/gateway-operator/internal/controller/management_llm_enqueue.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.go
  • kubernetes/gateway-operator/internal/controller/httproute_enqueue_test.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/gateway-operator/internal/controller/subscription_controller.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.md
  • kubernetes/gateway-operator/internal/secretsource/resolver_test.go
  • kubernetes/gateway-operator/internal/controller/certificate_controller.go
  • kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go
🚧 Files skipped from review as they are similar to previous changes (28)
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.go
  • kubernetes/gateway-operator/cmd/main.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f0-apikey-resources.yaml
  • kubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.go
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.go
  • gateway/examples/subscription-plan.yaml
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-02-restapi.yaml
  • kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/gateway-operator/api/v1alpha1/certificate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovider_types.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_client.go
  • kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/internal/controller/subscriptionplan_controller.go
  • kubernetes/gateway-operator/internal/secretsource/resolver.go
  • kubernetes/gateway-operator/api/v1alpha1/common_types.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go
  • kubernetes/helm/operator-helm-chart/templates/_helpers.tpl
  • kubernetes/gateway-operator/config/rbac/role.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/gateway-operator/internal/controller/apikey_controller.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/gateway-operator/internal/controller/managedsecret_controller.go
  • kubernetes/gateway-operator/internal/controller/llmprovider_controller.go
  • kubernetes/gateway-operator/internal/controller/generic_reconciler.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yaml

Comment on lines +1041 to +1072
- name: Deploy demo management CRs (LLM MCP ApiKey Subscription)
run: |
set -euo pipefail
DEMO="kubernetes/helm/resources/apigateway-restapi-operator-demo"
kubectl apply -f "${DEMO}/00-namespace.yaml"
kubectl apply -f "${DEMO}/03-backend.yaml"
kubectl apply -f "${DEMO}/05a-management-prerequisites.yaml"
kubectl apply -f "${DEMO}/05b0-mock-openapi-https.yaml"
kubectl wait --for=condition=available deployment/hello-backend deployment/mock-openapi deployment/mock-openapi-https -n apigateway-demo --timeout=240s

kubectl apply -f "${DEMO}/05c0-mcp-server-backend.yaml"
kubectl wait --for=condition=available deployment/mcp-server-backend -n apigateway-demo --timeout=240s

sed 's#url: http://mock-openapi:4010#url: http://mock-openapi.apigateway-demo.svc.cluster.local:4010#g' "${DEMO}/05b-llm-resources.yaml" | kubectl apply -f -
sed 's#url: http://mcp-server-backend:3001#url: http://mcp-server-backend.apigateway-demo.svc.cluster.local:3001#g' "${DEMO}/05c-mcp-resources.yaml" | kubectl apply -f -

kubectl apply -f "${DEMO}/04-02-restapi.yaml"
kubectl apply -f "${DEMO}/04-03-restapi.yaml"
kubectl apply -f "${DEMO}/05f-subscription-resources.yaml"
kubectl apply -f "${DEMO}/05f0-apikey-resources.yaml"

kubectl wait --for=condition=Programmed managedsecret/demo-managed-secret -n apigateway-demo --timeout=300s
kubectl wait --for=condition=Programmed llmprovidertemplate/openai-test -n apigateway-demo --timeout=300s
kubectl wait --for=condition=Programmed llmprovider/demo-llm-provider -n apigateway-demo --timeout=300s
kubectl wait --for=condition=Programmed llmproxy/demo-llm-proxy -n apigateway-demo --timeout=300s
kubectl wait --for=condition=Programmed mcp/everything-mcp-v1.0 -n apigateway-demo --timeout=300s
kubectl wait --for=condition=Programmed restapi/hello-sub-api -n apigateway-demo --timeout=300s
kubectl wait --for=condition=Programmed restapi/hello-apikey-api -n apigateway-demo --timeout=300s
kubectl wait --for=condition=Programmed subscriptionplan/demo-plan -n apigateway-demo --timeout=300s
kubectl wait --for=condition=Programmed subscription/demo-subscription -n apigateway-demo --timeout=300s
kubectl wait --for=condition=Programmed apikey/demo-restapi-apikey -n apigateway-demo --timeout=300s

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Include the Certificate demo manifest in this job.

This sequence never applies 05e-certificate-resources.yaml, so the integration test does not exercise one of the CRDs called out in the PR objective. Please add the certificate manifest here, wait for its Programmed condition, and mirror that in the cleanup path so CI covers the full management-resource surface.

🤖 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 @.github/workflows/operator-integration-test.yml around lines 1041 - 1072,
Add the missing certificate manifest by applying
"05e-certificate-resources.yaml" (same DEMO path) in the deploy job, then wait
for its Programmed condition (kubectl wait --for=condition=Programmed
certificate/<name> -n apigateway-demo --timeout=300s) using the actual resource
name from that manifest, and also add the corresponding apply/delete lines in
the cleanup step so the certificate resource is created and removed as part of
the job.

Comment on lines +105 to +110
type ApiKey struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec ApiKeySpec `json:"spec,omitempty"`
Status ResourceStatus `json:"status,omitempty"`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python -m pip -q install pyyaml >/dev/null

python - <<'PY'
import yaml

files = [
    "kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml",
    "kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml",
]

for path in files:
    with open(path, "r", encoding="utf-8") as f:
        doc = yaml.safe_load(f)
    schema = doc["spec"]["versions"][0]["schema"]["openAPIV3Schema"]
    print(f"{path}: root required = {schema.get('required')}")
PY

Repository: wso2/api-platform

Length of output: 277


🏁 Script executed:

cat -n kubernetes/gateway-operator/api/v1alpha1/apikey_types.go | head -115 | tail -20

Repository: wso2/api-platform

Length of output: 811


🏁 Script executed:

grep -n "type.*struct" kubernetes/gateway-operator/api/v1alpha1/apikey_types.go | head -20

Repository: wso2/api-platform

Length of output: 206


🏁 Script executed:

grep -A 8 "^type ApiKeyList struct" kubernetes/gateway-operator/api/v1alpha1/apikey_types.go

Repository: wso2/api-platform

Length of output: 275


🏁 Script executed:

find kubernetes/gateway-operator/api -name "*.go" -type f ! -path "*/test/*" | head -5 | xargs grep -A 3 "type.*struct" | grep -A 3 "Spec.*json:" | head -20

Repository: wso2/api-platform

Length of output: 43


🏁 Script executed:

ls -la kubernetes/gateway-operator/api/v1alpha1/*.go | grep -v test | head -10

Repository: wso2/api-platform

Length of output: 1162


🏁 Script executed:

grep -A 8 "^type ApiGateway struct" kubernetes/gateway-operator/api/v1alpha1/apigateway_types.go

Repository: wso2/api-platform

Length of output: 43


🏁 Script executed:

grep -n "^type.*struct" kubernetes/gateway-operator/api/v1alpha1/apigateway_types.go | head -5

Repository: wso2/api-platform

Length of output: 232


🏁 Script executed:

grep -B 5 "^type.*struct" kubernetes/gateway-operator/api/v1alpha1/apigateway_types.go | grep -A 5 "kubebuilder:object:root=true"

Repository: wso2/api-platform

Length of output: 321


🏁 Script executed:

grep -A 8 "// APIGateway is the Schema for the apigateways API" kubernetes/gateway-operator/api/v1alpha1/apigateway_types.go

Repository: wso2/api-platform

Length of output: 319


🏁 Script executed:

python -m pip -q install pyyaml >/dev/null

python - <<'PY'
import yaml

files = [
    "kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apigateways.yaml",
    "kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yaml",
]

for path in files:
    with open(path, "r", encoding="utf-8") as f:
        doc = yaml.safe_load(f)
    schema = doc["spec"]["versions"][0]["schema"]["openAPIV3Schema"]
    print(f"{path}: root required = {schema.get('required')}")
PY

Repository: wso2/api-platform

Length of output: 285


Require spec on ApiKey resources.

Line 109 uses json:"spec,omitempty", allowing the spec block to be optional in the generated CRD. This permits empty ApiKey objects to be admitted, deferring validation to reconciliation. Remove omitempty and add a validation marker to make spec required.

Suggested source fix
 type ApiKey struct {
 	metav1.TypeMeta   `json:",inline"`
 	metav1.ObjectMeta `json:"metadata,omitempty"`
 
-	Spec   ApiKeySpec     `json:"spec,omitempty"`
+	// +kubebuilder:validation:Required
+	Spec   ApiKeySpec     `json:"spec"`
 	Status ResourceStatus `json:"status,omitempty"`
 }

Regenerate the CRD artifacts after applying the fix to ensure both kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml and kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml include spec in their root required array.

📝 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
type ApiKey struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ApiKeySpec `json:"spec,omitempty"`
Status ResourceStatus `json:"status,omitempty"`
type ApiKey struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// +kubebuilder:validation:Required
Spec ApiKeySpec `json:"spec"`
Status ResourceStatus `json:"status,omitempty"`
}
🤖 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 `@kubernetes/gateway-operator/api/v1alpha1/apikey_types.go` around lines 105 -
110, The ApiKey type currently makes Spec optional via the json tag; remove
`omitempty` from the Spec field's tag (change `json:"spec,omitempty"` to
`json:"spec"`) and add the kubebuilder validation marker above the Spec field
(e.g., `// +kubebuilder:validation:Required`) so the generated CRD requires the
spec root; after that regenerate CRD artifacts (run your project’s CRD
generation/controller-gen or make target) so the CRD YAMLs include "spec" in the
root required array.

Comment on lines +58 to +59
// +kubebuilder:validation:Enum=Day;Hour;Min;Month
ThrottleLimitUnit *string `json:"throttleLimitUnit,omitempty"`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

WSO2 API Platform management API SubscriptionPlan throttleLimitUnit enum values

💡 Result:

The enum values for throttleLimitUnit in the WSO2 API Manager (or API Platform) SubscriptionPlan model, used in the management API, are not explicitly documented in official sources as an enum list. However, from the Admin REST API response examples and policy configuration documentation, the possible values for the time unit (corresponding to throttleLimitUnit) are: min, hour, day, week, month, year.[1][2] These are used in subscription throttling policies, where Unit Time specifies the period (e.g., 1) and timeUnit the enum value (e.g., "min"). For example, a policy might have "timeUnit": "min", "unitTime": 1 for per-minute limits.[1] Default subscription policies like Bronze (1000 requests per minute) imply "min".[3][4] In the API model, related fields like productionTimeUnit default to "1000" (milliseconds), but for subscription plans, it's higher-level units as above.[5] WSO2 API Platform appears to build on API Manager 4.x features, including subscription policies added around 4.5.0.[6]

Citations:


Update enum values to match WSO2 management API contract. The CRD uses capitalized values (Day;Hour;Min;Month), but the WSO2 API Manager expects lowercase values (min, hour, day, month) and also supports week and year. Change the enum to min;hour;day;month;week;year to ensure proper integration with the upstream API.

🤖 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 `@kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go` around
lines 58 - 59, The CRD enum for ThrottleLimitUnit is using wrong values; update
the kubebuilder validation tag on the ThrottleLimitUnit field in
subscriptionplan_types.go to use the lowercase WSO2 API contract values
"min;hour;day;month;week;year" (i.e., replace
"+kubebuilder:validation:Enum=Day;Hour;Min;Month" with
"+kubebuilder:validation:Enum=min;hour;day;month;week;year") and ensure the
field declaration for ThrottleLimitUnit *string
`json:\"throttleLimitUnit,omitempty\"` remains unchanged.

metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec SubscriptionPlanSpec `json:"spec,omitempty"`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

spec field should not be omitempty.

Tagging Spec with json:"spec,omitempty" means the field is omitted from serialized output when it equals the zero value of SubscriptionPlanSpec. For a Kubernetes CRD root object, spec is a structural field that should always be present. Use json:"spec" to keep it consistent with the controller-runtime and upstream Kubernetes conventions.

🔧 Proposed fix
-	Spec   SubscriptionPlanSpec `json:"spec,omitempty"`
+	Spec   SubscriptionPlanSpec `json:"spec"`
📝 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
Spec SubscriptionPlanSpec `json:"spec,omitempty"`
Spec SubscriptionPlanSpec `json:"spec"`
🤖 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 `@kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go` at line
73, The SubscriptionPlan struct currently tags Spec as `json:"spec,omitempty"`
which can omit the spec entirely; change the struct tag on the Spec field to
`json:"spec"` (remove `omitempty`) so the field is always serialized; update the
tag for the Spec field in the SubscriptionPlan type that references
SubscriptionPlanSpec to `json:"spec"` to follow Kubernetes CRD conventions.

Comment on lines +241 to +247
required:
- accessControl
- displayName
- template
- upstream
- version
type: object
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make spec required at the CRD root.

This schema does not mark spec as required, so an otherwise empty LlmProvider object can be admitted even though every field inside spec is mandatory. Add the root requirement here and regenerate the CRD artifacts from the Go type.

Suggested CRD fix
-        type: object
+        required:
+        - spec
+        type: object

Also applies to: 329-329

🤖 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
`@kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml`
around lines 241 - 247, The CRD for LlmProvider is missing the root-level
required entry for spec, so an empty resource can be admitted; update the CRD
root schema to include "spec" in the top-level required array (i.e. add "spec"
alongside existing required keys) and then regenerate the CRD artifacts from the
Go LlmProvider type so the YAML reflects the type definitions; ensure the change
is applied to the other CRD file copy as well where the same omission occurs.

Comment on lines +60 to +69
func (r *McpReconciler) SetupWithManager(mgr ctrl.Manager) error {
opts := controller.Options{MaxConcurrentReconciles: maxConcurrentReconciles(r.Config.Reconciliation.MaxConcurrentReconciles)}
return ctrl.NewControllerManagedBy(mgr).
Named("mcp").
WithOptions(opts).
For(&apiv1.Mcp{}).
Watches(&apiv1.APIGateway{},
handler.EnqueueRequestsFromMapFunc(enqueueAllOfKind(r.Client, &apiv1.McpList{})),
builder.WithPredicates(gatewayWatchPredicate())).
Complete(r)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Secret-backed upstream auth will stay stale after Secret rotation.

Deploy resolves spec.upstream.auth.value from a Secret, but this controller only watches Mcp and APIGateway. If the referenced Secret changes, no reconcile is triggered, so the gateway keeps the old credential until someone edits the CR manually. Please add a Secret watch/index for referenced selectors and requeue dependent Mcp objects on Secret updates.

Also applies to: 100-112

🤖 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 `@kubernetes/gateway-operator/internal/controller/mcp_controller.go` around
lines 60 - 69, The controller only watches apiv1.Mcp and apiv1.APIGateway so
Secret rotations don't trigger reconciles; update McpReconciler.SetupWithManager
to add a field indexer for corev1.Secret references used in
APIGateway.spec.upstream.auth (use mgr.GetFieldIndexer().IndexField to index
APIGateway by the secret name/namespace/key used by the selector) and add a
Watch for corev1.Secret that uses handler.EnqueueRequestsFromMapFunc with a
mapper that lists APIGateway objects matching the indexed secret and then
enqueues their owning/related apiv1.Mcp (reuse or adapt enqueueAllOfKind logic
to map from APIGateway -> Mcp); keep gatewayWatchPredicate() and existing
APIGateway watch but ensure the Secret watch requeues dependent Mcp on Secret
update.

Comment on lines +43 to +46
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return false, &RetryableError{Err: fmt.Errorf("create HTTP request: %w", err)}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Classify request-construction failures as non-retryable.

Line 45 and Line 162 currently classify http.NewRequestWithContext failures as retryable. These are usually input/config issues and should be NonRetryableError to avoid futile retries.

Suggested fix
-    return false, &RetryableError{Err: fmt.Errorf("create HTTP request: %w", err)}
+    return false, &NonRetryableError{Err: fmt.Errorf("create HTTP request: %w", err)}
-    return nil, &RetryableError{Err: fmt.Errorf("create HTTP request: %w", err)}
+    return nil, &NonRetryableError{Err: fmt.Errorf("create HTTP request: %w", err)}

Also applies to: 160-163

🤖 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 `@kubernetes/gateway-operator/internal/gatewayclient/resource_client.go` around
lines 43 - 46, The failure returned from http.NewRequestWithContext should be
classified as non-retryable since it's an input/configuration error; locate the
usages around http.NewRequestWithContext in resource_client.go (the block that
creates req with NewRequestWithContext near the top and the similar block around
line 160) and change the return to use &NonRetryableError{Err:
fmt.Errorf("create HTTP request: %w", err)} instead of &RetryableError,
preserving the existing fmt.Errorf message and context so callers receive the
same error text but marked non-retryable.

Comment on lines +1121 to +1127
labels:
additionalProperties:
type: string
description: |-
Labels are custom labels propagated to all gateway-managed resources:
Deployment metadata, Pod labels, Service metadata, and ConfigMaps.
type: object
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Removing label validation shifts errors from admission time to reconciliation time.

Kubernetes enforces label key and value format rules when labels are applied to child resources (Deployments, Services, ConfigMaps). By removing the x-kubernetes-validations key-format rules and value maxLength/pattern constraints, the CRD now accepts inputs that will fail silently at the operator reconcile level rather than being rejected at resource-creation time. This produces obscure reconciliation errors instead of clear, early user feedback.

Consider restoring at minimum the label value constraint (maxLength: 63 and the alphanumeric pattern) to preserve admission-time validation:

🔧 Suggested restoration of label value constraints
  labels:
    additionalProperties:
-     type: string
+     type: string
+     maxLength: 63
+     pattern: ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$
    description: |-
      Labels are custom labels propagated to all gateway-managed resources:
      Deployment metadata, Pod labels, Service metadata, and ConfigMaps.
    type: object
📝 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
labels:
additionalProperties:
type: string
description: |-
Labels are custom labels propagated to all gateway-managed resources:
Deployment metadata, Pod labels, Service metadata, and ConfigMaps.
type: object
labels:
additionalProperties:
type: string
maxLength: 63
pattern: ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$
description: |-
Labels are custom labels propagated to all gateway-managed resources:
Deployment metadata, Pod labels, Service metadata, and ConfigMaps.
type: object
🤖 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
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yaml`
around lines 1121 - 1127, The CRD removed admission-time validation for the
"labels" map (the labels.additionalProperties schema), which lets invalid label
values slip through to reconciliation errors; restore Kubernetes label value
constraints by adding x-kubernetes-validations (or equivalent) on
labels.additionalProperties to enforce maxLength: 63 and the Kubernetes label
value pattern (alphanumeric with allowed -_. and proper start/end rules), and
consider also re-adding key format validation for label keys so
Deployments/Services/ConfigMaps creation fails at admission time instead of
during reconcile; update the schema entry for labels -> additionalProperties
accordingly.

Comment on lines +19 to +20
- name: mcp-server-backend
image: rakhitharr/mcp-everything:v3
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace personal Docker Hub image with the official mcp/everything image.

Same concern as in apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml: rakhitharr/mcp-everything:v3 is a personal namespace image with a mutable tag. The official equivalent is mcp/everything from Docker's Verified Publisher.

🔧 Proposed fix
-          image: rakhitharr/mcp-everything:v3
+          image: mcp/everything:latest
🤖 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
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml`
around lines 19 - 20, Replace the personal Docker Hub image reference used by
the mcp-server-backend container (name: mcp-server-backend, image:
rakhitharr/mcp-everything:v3) with the official publisher image by changing the
image field to mcp/everything; if a specific immutable tag is required for
reproducibility, pin to a stable tag from the official mcp/everything repository
instead of using the personal namespace or a mutable tag.

Comment on lines +19 to +20
- name: mcp-server-backend
image: rakhitharr/mcp-everything:v3
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace personal Docker Hub image with the official mcp/everything image.

rakhitharr/mcp-everything:v3 is from a personal Docker Hub namespace. The official reference image is mcp/everything, published by the Docker-verified publisher mcp. Personal namespace images carry supply chain risk (account takeover, unreviewed content, no provenance) and the mutable v3 tag can be silently overwritten.

🔧 Proposed fix
-          image: rakhitharr/mcp-everything:v3
+          image: mcp/everything:latest

Or, preferably, pin to a specific digest for reproducibility:

-          image: rakhitharr/mcp-everything:v3
+          image: mcp/everything@sha256:<digest>
🤖 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
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml`
around lines 19 - 20, The container image for the Deployment/Pod named
"mcp-server-backend" uses a personal Docker Hub namespace
("rakhitharr/mcp-everything:v3"); replace that with the official publisher image
"mcp/everything" (preferably pinned to an immutable digest) by updating the
image field for the resource that has name: mcp-server-backend to
"mcp/everything@sha256:<digest>" or at minimum "mcp/everything:<stable-tag>" to
avoid supply-chain and mutability risks.

@CrowleyRajapakse CrowleyRajapakse force-pushed the integration-tests-actions branch from 533032d to 5008ac6 Compare May 5, 2026 07:59
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (4)
kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml (1)

29-83: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid tracking the demo TLS key material in this manifest.

The file still embeds the certificate and private key directly, which makes the demo secret part of repository history. Keep the workload’s secretName reference, but provision mock-openapi-https-tls during setup instead of committing it here.

🤖 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
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`
around lines 29 - 83, This manifest embeds TLS private key and cert under
stringData (tls.crt/tls.key) — remove those fields and stop committing secret
material; instead leave the Secret reference (mock-openapi-https-tls) in the
workload that consumes it and document/implement provisioning of the Kubernetes
Secret named mock-openapi-https-tls during setup (e.g., cluster bootstrap, CI
job, or a sealed-secret/ExternalSecret flow). Update any tests or deployment
scripts that assumed in-repo secret YAML to create the secret at deploy time and
ensure the Secret manifest is not checked into the repository.
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml (1)

29-83: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Provision the TLS Secret outside source control.

This manifest still embeds the demo certificate and private key directly, which keeps key material in repository history. Keep the Deployment’s secretName reference, but create mock-openapi-https-tls at deploy time (or via cert-manager) instead of storing it here.

🤖 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
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`
around lines 29 - 83, The YAML embeds TLS key material for the Secret
(mock-openapi-https-tls) in stringData; remove the tls.crt and tls.key values
from this manifest and instead leave the Secret resource as a reference (or
delete the Secret from repo) so the Deployment/Ingress can continue to use the
secretName "mock-openapi-https-tls"; provision that secret at deploy time (CI/CD
job, helm hook, or cert-manager) and ensure any templates or docs explain
creating the TLS Secret externally rather than committing the private key/cert
to source control.
kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md (1)

103-114: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep the documented LLM upstream URL consistent.

Line 103 says the provider uses https://mock-openapi-https:9449/..., but Line 114 still describes 05b-llm-resources.yaml as using http://mock-openapi:4010/openai/v1. Pick one authoritative in-cluster URL, or explicitly explain why the manifest still uses the HTTP service.

🤖 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 `@kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md` around
lines 103 - 114, The README currently contradicts itself about the in-cluster
mock upstream URL (mentions https://mock-openapi-https:9449 in the 05b0
description but later states http://mock-openapi:4010/openai/v1 for
05b-llm-resources.yaml); update the text so it consistently uses one
authoritative in-cluster URL (preferably https://mock-openapi-https:9449 to
match 05b0-mock-openapi-https.yaml) or explicitly note why
05b-llm-resources.yaml still references http://mock-openapi:4010 (e.g., legacy
compose mapping) and show which LlmProvider/LlmProviderTemplate (openai-test)
and LlmProxy entries must be edited when switching between the two URLs.
.github/workflows/operator-integration-test.yml (1)

1045-1060: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Exercise the Certificate CRD in this job too.

This management-resource sequence still skips 05e-certificate-resources.yaml, so CI does not cover one of the CRDs added in this PR. Please add the apply/wait path here and mirror the delete in cleanup.

Also applies to: 1683-1703

🤖 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 @.github/workflows/operator-integration-test.yml around lines 1045 - 1060,
Add the missing apply/wait for the new Certificate CRD by inserting kubectl
apply -f "${DEMO}/05e-certificate-resources.yaml" into the setup sequence (after
05c/05b steps) and follow it with an appropriate wait for the created
Certificate resources to become ready (e.g., kubectl wait --for=condition=Ready
certificate --all -n apigateway-demo --timeout=240s or equivalent for your CRD).
Also mirror this in the teardown/cleanup section by adding kubectl delete -f
"${DEMO}/05e-certificate-resources.yaml" so the CI both exercises and cleans up
the Certificate resources; make the same additions in the other identical block
referenced around lines 1683-1703.
🧹 Nitpick comments (2)
kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md (1)

1-3: 💤 Low value

Consider reframing as a design document rather than an implementation plan.

The document is labeled "implementation plan" (lines 1, 3), but based on the PR context and code snippets, the implementation is already complete. Consider updating the title and introduction to reflect this as a design document or implementation retrospective to avoid confusing future maintainers.

🤖 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 `@kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md` around
lines 1 - 3, Update the document header and opening text to reflect a design
document or implementation retrospective instead of an "implementation plan":
replace the title line "Feature: WSO2 management resources as operator CRDs
(APIGateway path)" or the parenthetical "implementation plan" wording and change
any first-paragraph sentences that claim this is an implementation plan to state
this is a design document or a retrospective/implementation description; ensure
the intro explains the scope (APIGateway management-API CRDs vs Gateway API) and
the document's purpose (design/retrospective) rather than future-planning.
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yaml (1)

46-52: ⚡ Quick win

Point this provider at the HTTPS mock if 05b0 is part of the demo flow.

The companion demo manifest adds mock-openapi-https:9449, but this provider still calls mock-openapi:4010 directly, so the TLS terminator path is never exercised here. Either switch the URL to the HTTPS service, or make it clear that 05b0-mock-openapi-https.yaml is optional for this flow.

Suggested change
   upstream:
-    url: http://mock-openapi:4010/openai/v1
+    url: https://mock-openapi-https:9449/openai/v1
     auth:
       type: api-key
       header: Authorization
🤖 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
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yaml`
around lines 46 - 52, The upstream block currently points to
http://mock-openapi:4010/openai/v1 which bypasses the TLS terminator; if the
05b0 demo flow is used, change upstream.url to use the HTTPS mock endpoint (e.g.
https://mock-openapi-https:9449/openai/v1) so the TLS path is exercised (keep
upstream.auth the same), or explicitly note that 05b0-mock-openapi-https is
optional; update the upstream.url value accordingly to reference
mock-openapi-https:9449 when exercising TLS.
🤖 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 @.github/workflows/operator-integration-test.yml:
- Around line 1084-1114: The script waits for subscriptionplan.status.id into
PLAN_ID but then hardcodes "demo-plan" in the created Subscription; change the
applied YAML to use the resolved PLAN_ID (replace subscriptionPlanId: demo-plan
with subscriptionPlanId: ${PLAN_ID}) and allow shell variable expansion by
changing the here-doc from a quoted delimiter (<<'EOF') to an unquoted one
(<<EOF) so ${PLAN_ID} is interpolated when kubectl apply -f -.

In `@kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md`:
- Around line 25-36: Update the checklist to reflect implemented work: mark
items completed (change [ ] to [x]) for "Refactor internal/gatewayclient"
(paths.go, resource_client.go), "Extract decision/tracker/retry logic"
(generic_reconciler.go), "Per-kind controllers" (apikey_controller.go and other
*_controller.go files), and any other deliverables already present (e.g., RBAC
markers/CRDs, reconcilers registered in cmd/main.go, types added to scheme);
alternatively, if you want to preserve the checklist as the original plan, add a
clear note above the list stating that the listed items were the initial plan
and indicate which files/PRs implement each item (reference
generic_reconciler.go, apikey_controller.go, paths.go, resource_client.go,
cmd/main.go) so readers know the current implementation status.
- Line 17: Replace the incorrect CRD name "Secret" with the actual CRD name
"ManagedSecret" in the FEATURE_MANAGEMENT_RESOURCES_CRDs.md entries (the list
that currently includes `Secret` and the later occurrence around the resources
list), ensuring both occurrences are updated to `ManagedSecret`; this aligns the
doc with the CRD defined as ManagedSecret (see symbol ManagedSecret /
secret_types.go) and keeps the list consistent with the API types
`LlmProviderTemplate`, `LlmProvider`, `LlmProxy`, `Mcp`, `ManagedSecret`,
`Certificate`, `ApiKey`, `SubscriptionPlan`, `Subscription`.

In `@kubernetes/gateway-operator/internal/controller/managedsecret_controller.go`:
- Around line 99-102: The current code calls secretsource.Resolve and always
converts any error into a gatewayclient.NonRetryableError, which prevents
retries; update the error handling after secretsource.Resolve(ctx, k8sClient,
"spec.value", cr.Spec.Value, cr.Namespace) to use the existing classifier
pattern (e.g., call the project’s error classifier like
gatewayclient.ClassifyError or equivalent) and only wrap the error in
gatewayclient.NonRetryableError when the classifier marks it non-retryable;
otherwise return the raw err (or a retryable error) so reconciliation can be
retried. Ensure you reference the same symbols (secretsource.Resolve,
cr.Spec.Value, cr.Namespace, gatewayclient.NonRetryableError) and adjust imports
if you call the classifier function.

In `@kubernetes/gateway-operator/internal/secretsource/resolver.go`:
- Around line 134-143: The check against secret.StringData in the secret
resolution path is unreachable after an API read and should be removed; in the
resolver function that inspects secret.Data and uses sel.Key/sel.Optional (the
block returning string(v), nil and returning &ErrKeyMissing{Field: fieldPath,
Namespace: ns, Name: sel.Name, Key: sel.Key}), delete the branch that checks
secret.StringData and rely solely on secret.Data for lookups, keeping the
existing optional check and ErrKeyMissing return intact so behavior is unchanged
for missing keys.

---

Duplicate comments:
In @.github/workflows/operator-integration-test.yml:
- Around line 1045-1060: Add the missing apply/wait for the new Certificate CRD
by inserting kubectl apply -f "${DEMO}/05e-certificate-resources.yaml" into the
setup sequence (after 05c/05b steps) and follow it with an appropriate wait for
the created Certificate resources to become ready (e.g., kubectl wait
--for=condition=Ready certificate --all -n apigateway-demo --timeout=240s or
equivalent for your CRD). Also mirror this in the teardown/cleanup section by
adding kubectl delete -f "${DEMO}/05e-certificate-resources.yaml" so the CI both
exercises and cleans up the Certificate resources; make the same additions in
the other identical block referenced around lines 1683-1703.

In
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`:
- Around line 29-83: This manifest embeds TLS private key and cert under
stringData (tls.crt/tls.key) — remove those fields and stop committing secret
material; instead leave the Secret reference (mock-openapi-https-tls) in the
workload that consumes it and document/implement provisioning of the Kubernetes
Secret named mock-openapi-https-tls during setup (e.g., cluster bootstrap, CI
job, or a sealed-secret/ExternalSecret flow). Update any tests or deployment
scripts that assumed in-repo secret YAML to create the secret at deploy time and
ensure the Secret manifest is not checked into the repository.

In `@kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md`:
- Around line 103-114: The README currently contradicts itself about the
in-cluster mock upstream URL (mentions https://mock-openapi-https:9449 in the
05b0 description but later states http://mock-openapi:4010/openai/v1 for
05b-llm-resources.yaml); update the text so it consistently uses one
authoritative in-cluster URL (preferably https://mock-openapi-https:9449 to
match 05b0-mock-openapi-https.yaml) or explicitly note why
05b-llm-resources.yaml still references http://mock-openapi:4010 (e.g., legacy
compose mapping) and show which LlmProvider/LlmProviderTemplate (openai-test)
and LlmProxy entries must be edited when switching between the two URLs.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`:
- Around line 29-83: The YAML embeds TLS key material for the Secret
(mock-openapi-https-tls) in stringData; remove the tls.crt and tls.key values
from this manifest and instead leave the Secret resource as a reference (or
delete the Secret from repo) so the Deployment/Ingress can continue to use the
secretName "mock-openapi-https-tls"; provision that secret at deploy time (CI/CD
job, helm hook, or cert-manager) and ensure any templates or docs explain
creating the TLS Secret externally rather than committing the private key/cert
to source control.

---

Nitpick comments:
In `@kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md`:
- Around line 1-3: Update the document header and opening text to reflect a
design document or implementation retrospective instead of an "implementation
plan": replace the title line "Feature: WSO2 management resources as operator
CRDs (APIGateway path)" or the parenthetical "implementation plan" wording and
change any first-paragraph sentences that claim this is an implementation plan
to state this is a design document or a retrospective/implementation
description; ensure the intro explains the scope (APIGateway management-API CRDs
vs Gateway API) and the document's purpose (design/retrospective) rather than
future-planning.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yaml`:
- Around line 46-52: The upstream block currently points to
http://mock-openapi:4010/openai/v1 which bypasses the TLS terminator; if the
05b0 demo flow is used, change upstream.url to use the HTTPS mock endpoint (e.g.
https://mock-openapi-https:9449/openai/v1) so the TLS path is exercised (keep
upstream.auth the same), or explicitly note that 05b0-mock-openapi-https is
optional; update the upstream.url value accordingly to reference
mock-openapi-https:9449 when exercising TLS.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 51e18f7a-f13f-46b7-862c-fc8c18b785f6

📥 Commits

Reviewing files that changed from the base of the PR and between 533032d and 5008ac6.

📒 Files selected for processing (98)
  • .github/workflows/operator-integration-test.yml
  • gateway/examples/api-key.yaml
  • gateway/examples/certificate.yaml
  • gateway/examples/managed-secret.yaml
  • gateway/examples/subscription-plan.yaml
  • gateway/examples/subscription.yaml
  • kubernetes/gateway-operator/api/v1alpha1/apikey_types.go
  • kubernetes/gateway-operator/api/v1alpha1/certificate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/common_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovider_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
  • kubernetes/gateway-operator/api/v1alpha1/mcp_types.go
  • kubernetes/gateway-operator/api/v1alpha1/secret_types.go
  • kubernetes/gateway-operator/api/v1alpha1/subscription_types.go
  • kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go
  • kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go
  • kubernetes/gateway-operator/cmd/main.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/gateway-operator/config/rbac/role.yaml
  • kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.md
  • kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/internal/controller/apikey_controller.go
  • kubernetes/gateway-operator/internal/controller/certificate_controller.go
  • kubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.go
  • kubernetes/gateway-operator/internal/controller/generic_reconciler.go
  • kubernetes/gateway-operator/internal/controller/httproute_enqueue_test.go
  • kubernetes/gateway-operator/internal/controller/k8s_gateway_controller.go
  • kubernetes/gateway-operator/internal/controller/llmprovider_controller.go
  • kubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.go
  • kubernetes/gateway-operator/internal/controller/llmproxy_controller.go
  • kubernetes/gateway-operator/internal/controller/managedsecret_controller.go
  • kubernetes/gateway-operator/internal/controller/management_llm_enqueue.go
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.go
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.go
  • kubernetes/gateway-operator/internal/controller/management_resources_helpers.go
  • kubernetes/gateway-operator/internal/controller/management_secretsource_resolve_error.go
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload.go
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload_test.go
  • kubernetes/gateway-operator/internal/controller/mcp_controller.go
  • kubernetes/gateway-operator/internal/controller/payload_metadata_test.go
  • kubernetes/gateway-operator/internal/controller/subscription_controller.go
  • kubernetes/gateway-operator/internal/controller/subscriptionplan_controller.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/paths.go
  • kubernetes/gateway-operator/internal/gatewayclient/resource_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.go
  • kubernetes/gateway-operator/internal/secretsource/resolver.go
  • kubernetes/gateway-operator/internal/secretsource/resolver_test.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/helm/operator-helm-chart/templates/_helpers.tpl
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-02-restapi.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-03-restapi.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f0-apikey-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/llm-mock-openapi-it/Dockerfile
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f0-apikey-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.md
  • kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml
  • kubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml
✅ Files skipped from review due to trivial changes (36)
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/llm-mock-openapi-it/Dockerfile
  • kubernetes/gateway-operator/internal/controller/payload_metadata_test.go
  • kubernetes/gateway-operator/internal/controller/k8s_gateway_controller.go
  • gateway/examples/api-key.yaml
  • gateway/examples/subscription.yaml
  • kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml
  • kubernetes/gateway-operator/api/v1alpha1/secret_types.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-03-restapi.yaml
  • kubernetes/gateway-operator/internal/controller/management_secretsource_resolve_error.go
  • kubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.md
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload.go
  • kubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml
  • gateway/examples/managed-secret.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-02-restapi.yaml
  • kubernetes/gateway-operator/internal/controller/httproute_enqueue_test.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/gateway-operator/api/v1alpha1/subscription_types.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.go
  • kubernetes/helm/operator-helm-chart/templates/_helpers.tpl
  • kubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.go
  • kubernetes/gateway-operator/internal/controller/apikey_controller.go
  • kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/gateway-operator/internal/controller/subscriptionplan_controller.go
  • kubernetes/gateway-operator/internal/secretsource/resolver_test.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.md
  • kubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go
🚧 Files skipped from review as they are similar to previous changes (39)
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload_test.go
  • kubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f0-apikey-resources.yaml
  • gateway/examples/subscription-plan.yaml
  • kubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.go
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/paths.go
  • kubernetes/gateway-operator/api/v1alpha1/certificate_types.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_client.go
  • kubernetes/gateway-operator/api/v1alpha1/apikey_types.go
  • kubernetes/gateway-operator/api/v1alpha1/mcp_types.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yaml
  • gateway/examples/certificate.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/gateway-operator/internal/controller/certificate_controller.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/gateway-operator/config/rbac/role.yaml
  • kubernetes/gateway-operator/api/v1alpha1/llmprovider_types.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/gateway-operator/internal/controller/llmprovider_controller.go
  • kubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.go
  • kubernetes/gateway-operator/internal/gatewayclient/resource_client.go
  • kubernetes/gateway-operator/internal/controller/management_resources_helpers.go
  • kubernetes/gateway-operator/internal/controller/management_llm_enqueue.go
  • kubernetes/gateway-operator/internal/controller/mcp_controller.go
  • kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go
  • kubernetes/gateway-operator/internal/controller/generic_reconciler.go

Comment thread .github/workflows/operator-integration-test.yml
Comment on lines +25 to +36
### Checklist

- [ ] Add v1alpha1 Go types for the kinds above, plus `SecretValueSource`; regenerate deepcopy and CRDs.
- [ ] Refactor `internal/gatewayclient` (generic resource client, path constants); bespoke clients for Certificate, Subscription, SubscriptionPlan, ApiKey (nested).
- [ ] Extract decision/tracker/retry logic from `restapi_controller.go` into a reusable `generic_reconciler.go` for new kinds.
- [ ] Per-kind controllers (finalizers, status, gateway selector, `APIGateway` watch).
- [ ] Resolve `SecretValueSource` (`value` vs `valueFrom.secretKeyRef`) against core `Secret` objects.
- [ ] Persist gateway-issued UUIDs in `status` for Subscription, SubscriptionPlan, Certificate.
- [ ] Register reconcilers in `cmd/main.go` and add types to the scheme.
- [ ] RBAC markers, regenerate `role` / Helm `clusterrole`, emit CRDs under Helm `crds/`.
- [ ] Example CR YAML under `gateway/examples/` where missing.
- [ ] Unit tests: payload builders, `SecretValueSource`, ApiKey parent paths, UUID status round-trip.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the checklist to reflect implementation status.

The checklist shows all items as incomplete [ ], but the code snippets and AI summary indicate that generic_reconciler.go, per-kind controllers (apikey_controller.go), gatewayclient components (paths.go, resource_client.go), and other deliverables are already implemented. Either mark completed items as [x] or add a note clarifying this is the original plan and implementation is complete.

🤖 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 `@kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md` around
lines 25 - 36, Update the checklist to reflect implemented work: mark items
completed (change [ ] to [x]) for "Refactor internal/gatewayclient" (paths.go,
resource_client.go), "Extract decision/tracker/retry logic"
(generic_reconciler.go), "Per-kind controllers" (apikey_controller.go and other
*_controller.go files), and any other deliverables already present (e.g., RBAC
markers/CRDs, reconcilers registered in cmd/main.go, types added to scheme);
alternatively, if you want to preserve the checklist as the original plan, add a
clear note above the list stating that the listed items were the initial plan
and indicate which files/PRs implement each item (reference
generic_reconciler.go, apikey_controller.go, paths.go, resource_client.go,
cmd/main.go) so readers know the current implementation status.

Comment on lines +99 to +102
val, err := secretsource.Resolve(ctx, k8sClient, "spec.value", cr.Spec.Value, cr.Namespace)
if err != nil {
return DeployResult{}, &gatewayclient.NonRetryableError{Err: err}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Classify secret resolution errors instead of always marking them non-retryable.

secretsource.Resolve failures are currently all converted to NonRetryableError. This can block reconciliation recovery for temporary failures. Reuse the existing classifier pattern so only permanent resolution failures become non-retryable.

Suggested fix
 	val, err := secretsource.Resolve(ctx, k8sClient, "spec.value", cr.Spec.Value, cr.Namespace)
 	if err != nil {
-		return DeployResult{}, &gatewayclient.NonRetryableError{Err: err}
+		return DeployResult{}, classifySecretSourceResolveError(err)
 	}
🤖 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 `@kubernetes/gateway-operator/internal/controller/managedsecret_controller.go`
around lines 99 - 102, The current code calls secretsource.Resolve and always
converts any error into a gatewayclient.NonRetryableError, which prevents
retries; update the error handling after secretsource.Resolve(ctx, k8sClient,
"spec.value", cr.Spec.Value, cr.Namespace) to use the existing classifier
pattern (e.g., call the project’s error classifier like
gatewayclient.ClassifyError or equivalent) and only wrap the error in
gatewayclient.NonRetryableError when the classifier marks it non-retryable;
otherwise return the raw err (or a retryable error) so reconciliation can be
retried. Ensure you reference the same symbols (secretsource.Resolve,
cr.Spec.Value, cr.Namespace, gatewayclient.NonRetryableError) and adjust imports
if you call the classifier function.

Comment on lines +134 to +143
if v, ok := secret.Data[sel.Key]; ok {
return string(v), nil
}
if v, ok := secret.StringData[sel.Key]; ok {
return v, nil
}
if sel.Optional != nil && *sel.Optional {
return "", nil
}
return "", &ErrKeyMissing{Field: fieldPath, Namespace: ns, Name: sel.Name, Key: sel.Key}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Kubernetes corev1.Secret StringData write-only field API documentation

💡 Result:

In Kubernetes corev1.Secret, stringData is explicitly documented as a write-only input field. 1) What the API documentation says about stringData - Kubernetes’ Secret v1 API reference states that stringData “allows specifying non-binary secret data in string form” and that it “is provided as a write-only input field for convenience.” It further says that “All keys and values are merged into the data field on write, overwriting any existing values.” It also states that “The stringData field is never output when reading from the API.” [1] 2) Relationship to data (base64) and how precedence works - The Secret documentation explains that data is used to store secret data encoded with base64, while stringData lets you provide unencoded strings (it will be encoded for you when the Secret is created/updated). [2][3] - It also explains merging/precedence: if a key appears in both data and stringData, the value from stringData takes precedence. [2][3] 3) Practical implications / gotchas - When retrieving a Secret, you will see encoded values (data), not the plaintext you provided in stringData. [2] - Kubernetes documents a warning that “The stringData field for a Secret does not work well with server-side apply.” [2][3] If what you need is the “write-only” wording for corev1.Secret’s stringData, the canonical source is the Secret v1 API reference page (not a third-party SDK wrapper). [1]

Citations:


🏁 Script executed:

# Verify the file exists and examine the code context around the flagged lines
fd -t f "resolver.go" | grep secretsource | head -5

Repository: wso2/api-platform

Length of output: 123


🏁 Script executed:

# Read lines around 134-143 to verify the code snippet
sed -n '125,145p' kubernetes/gateway-operator/internal/secretsource/resolver.go

Repository: wso2/api-platform

Length of output: 672


Remove secret.StringData check — it's unreachable after an API read.

The Kubernetes API server never returns stringData in Secret objects. According to the official v1 Secret API documentation, stringData is a "write-only input field" that exists only for convenience during creation/updates. When the API server returns a Secret (after k8sClient.Get), all stringData keys and values have been merged into the data field; stringData is always empty. The check at lines 137–139 will never match and should be removed to avoid misleading future maintainers.

Proposed fix
 	if v, ok := secret.Data[sel.Key]; ok {
 		return string(v), nil
 	}
-	if v, ok := secret.StringData[sel.Key]; ok {
-		return v, nil
-	}
 	if sel.Optional != nil && *sel.Optional {
 		return "", nil
 	}
 	return "", &ErrKeyMissing{Field: fieldPath, Namespace: ns, Name: sel.Name, Key: sel.Key}
📝 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
if v, ok := secret.Data[sel.Key]; ok {
return string(v), nil
}
if v, ok := secret.StringData[sel.Key]; ok {
return v, nil
}
if sel.Optional != nil && *sel.Optional {
return "", nil
}
return "", &ErrKeyMissing{Field: fieldPath, Namespace: ns, Name: sel.Name, Key: sel.Key}
if v, ok := secret.Data[sel.Key]; ok {
return string(v), nil
}
if sel.Optional != nil && *sel.Optional {
return "", nil
}
return "", &ErrKeyMissing{Field: fieldPath, Namespace: ns, Name: sel.Name, Key: sel.Key}
🤖 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 `@kubernetes/gateway-operator/internal/secretsource/resolver.go` around lines
134 - 143, The check against secret.StringData in the secret resolution path is
unreachable after an API read and should be removed; in the resolver function
that inspects secret.Data and uses sel.Key/sel.Optional (the block returning
string(v), nil and returning &ErrKeyMissing{Field: fieldPath, Namespace: ns,
Name: sel.Name, Key: sel.Key}), delete the branch that checks secret.StringData
and rely solely on secret.Data for lookups, keeping the existing optional check
and ErrKeyMissing return intact so behavior is unchanged for missing keys.

@CrowleyRajapakse CrowleyRajapakse force-pushed the integration-tests-actions branch from 5008ac6 to 88053fb Compare May 5, 2026 08:55
…y, SubscriptionPlan, Subscription and ManagedSecret CRDs support in Gateway Operator
@CrowleyRajapakse CrowleyRajapakse force-pushed the integration-tests-actions branch from 88053fb to d7bf270 Compare May 5, 2026 11:55
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (11)
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml (2)

18-34: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Container securityContext is missing.

The container spec does not set a securityContext, so it defaults to permissive settings. Even for a demo manifest, set sensible defaults (non-root, no privilege escalation, drop capabilities) so this is not copied as-is into other clusters.

🔒 Proposed fix
         - name: mcp-server-backend
           image: rakhitharr/mcp-everything:v3
           imagePullPolicy: IfNotPresent
+          securityContext:
+            allowPrivilegeEscalation: false
+            runAsNonRoot: true
+            runAsUser: 1000
+            capabilities:
+              drop: ["ALL"]
           ports:
🤖 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
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml`
around lines 18 - 34, The container spec for the container named
"mcp-server-backend" is missing a securityContext; add a securityContext under
the container entry (for the container with name mcp-server-backend) that
enforces non-root execution (e.g., runAsNonRoot: true and a non-root runAsUser),
disables privilege escalation (allowPrivilegeEscalation: false), drops all Linux
capabilities (capabilities.drop: ["ALL"]), and sets a default seccompProfile
(e.g., runtime/default) to provide safe defaults for demos so the pod isn't
deployed with permissive privileges.

19-20: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the official mcp/everything image instead of a personal namespace tag.

rakhitharr/mcp-everything:v3 lives in a personal Docker Hub namespace with a mutable tag. Prefer the official mcp/everything image (ideally pinned to a digest) for provenance and reproducibility.

🔧 Proposed fix
-          image: rakhitharr/mcp-everything:v3
+          image: mcp/everything@sha256:<digest>   # or mcp/everything:<stable-tag>
🤖 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
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml`
around lines 19 - 20, Replace the personal Docker Hub image in the container
spec for the container named "mcp-server-backend": change image:
rakhitharr/mcp-everything:v3 to the official mcp/everything image and preferably
pin it to a specific immutable digest (e.g., mcp/everything@sha256:...) to
ensure provenance and reproducibility; update the image field in the same YAML
block where the container name "mcp-server-backend" is defined.
kubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml (2)

19-20: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace personal Docker Hub image with the official mcp/everything.

Same finding as in the APIM demo manifest: prefer the publisher image, ideally pinned to a digest, instead of rakhitharr/mcp-everything:v3.

🔧 Proposed fix
-          image: rakhitharr/mcp-everything:v3
+          image: mcp/everything@sha256:<digest>   # or mcp/everything:<stable-tag>
🤖 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
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml`
around lines 19 - 20, The manifest uses a personal Docker Hub image
"rakhitharr/mcp-everything:v3" for the container named mcp-server-backend;
replace it with the official publisher image (e.g., "mcp/everything") and
preferably pin to an immutable digest (sha256) instead of a floating tag. Update
the image field for the container with name mcp-server-backend to the official
repository and include the digest (image@sha256:...) to ensure reproducible
builds and to remove the personal image reference.

18-34: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Container securityContext is missing.

Same concern as in apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml. Set non-root, disallow privilege escalation, and drop capabilities so the demo does not propagate permissive defaults.

🔒 Proposed fix
         - name: mcp-server-backend
           image: rakhitharr/mcp-everything:v3
           imagePullPolicy: IfNotPresent
+          securityContext:
+            allowPrivilegeEscalation: false
+            runAsNonRoot: true
+            runAsUser: 1000
+            capabilities:
+              drop: ["ALL"]
           ports:
🤖 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
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml`
around lines 18 - 34, The container spec for the mcp-server-backend container is
missing a securityContext; update the container definition for name:
mcp-server-backend to include a securityContext that enforces non-root and least
privilege (e.g., runAsNonRoot: true and runAsUser: 1000), disables privilege
escalation (allowPrivilegeEscalation: false), and drops capabilities
(capabilities: drop: ["ALL"]); consider also setting readOnlyRootFilesystem:
true and runAsGroup if needed for filesystem access. Ensure these fields are
added under the container block for the mcp-server-backend container so
Kubernetes applies the restrictions at runtime.
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml (1)

24-24: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

subscriptionPlanId should reference the plan's reconciled status.id, not the CR name.

Per the Go type doc (subscription_types.go: "set this to the SubscriptionPlan's .status.id once it has been deployed"), subscriptionPlanId carries the gateway-issued UUID, not the CR metadata.name. As written, demo-plan-apim will not match the deployed plan id. Either remove this field from the demo (and document the two-step apply flow) or wire it via a templating step that injects the plan's status.id after the plan reconciles.

🤖 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
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml`
at line 24, The subscriptionPlanId field in the resource is wrong: it must hold
the SubscriptionPlan's reconciled gateway UUID (.status.id) not the CR
metadata.name (e.g., demo-plan-apim). Fix by either removing subscriptionPlanId
from this demo manifest and documenting the two-step apply (create
SubscriptionPlan, wait for .status.id, then create Subscription), or implement a
templating step that looks up the SubscriptionPlan CR and injects its .status.id
into subscriptionPlanId after the plan reconciles; target symbols:
subscriptionPlanId, SubscriptionPlan, .status.id, metadata.name, and the demo
plan name demo-plan-apim.
kubernetes/gateway-operator/internal/controller/managedsecret_controller.go (1)

99-102: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Classify secretsource.Resolve errors instead of marking them all non-retryable.

Wrapping every error from secretsource.Resolve in NonRetryableError collapses transient failures (e.g., the referenced Secret not yet existing or a transient API-server error) into permanent ones, blocking eventual recovery. Only ErrAmbiguous and ErrNotConfigured are clearly permanent; reference-resolution failures should remain retryable. Consider a small classifier (or reuse an existing one) so only structurally invalid inputs become non-retryable.

Suggested fix
 	val, err := secretsource.Resolve(ctx, k8sClient, "spec.value", cr.Spec.Value, cr.Namespace)
 	if err != nil {
-		return DeployResult{}, &gatewayclient.NonRetryableError{Err: err}
+		return DeployResult{}, classifySecretSourceResolveError(err)
 	}

(Pattern used by sibling controllers handling secretsource.Resolve.)

🤖 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 `@kubernetes/gateway-operator/internal/controller/managedsecret_controller.go`
around lines 99 - 102, The code currently wraps every error returned by
secretsource.Resolve(...) in a gatewayclient.NonRetryableError which makes
transient resolution failures permanent; change the handling in the reconcile
path that calls secretsource.Resolve(ctx, k8sClient, "spec.value",
cr.Spec.Value, cr.Namespace) so you only convert ErrAmbiguous and
ErrNotConfigured into gatewayclient.NonRetryableError and let any other errors
propagate as retryable (i.e., return DeployResult{} with the raw error or a
retryable wrapper). Use the same classifier pattern as sibling controllers:
inspect the error value from secretsource.Resolve, if errors.Is(err,
secretsource.ErrAmbiguous) || errors.Is(err, secretsource.ErrNotConfigured) then
return NonRetryableError{Err: err}, otherwise return the error unchanged (so the
controller can retry).
kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml (1)

29-83: ⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

Avoid committing key material in this manifest.

The TLS Secret embeds a private key inline (lines 55-83). Even for demo purposes, committing key material in version control is discouraged because it pollutes secret-scanning signals and can be inadvertently reused. Prefer generating the cert/key at deploy time (e.g., via cert-manager, an init job, or a helm template hook) and document the generation steps in the README.

🤖 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
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`
around lines 29 - 83, The manifest includes inline private key and cert under
stringData: tls.crt and tls.key which must not be committed; remove the embedded
tls.crt/tls.key values from the Secret and instead reference
externally-generated credentials (e.g., create the Secret from cert-manager, an
initJob, or a Helm pre-install hook) and update templates/README to document the
deploy-time generation steps and how the Secret (named in this manifest) will be
populated; ensure functions/resources that expect this Secret use the same
Secret name and that any Helm charts include conditional logic to skip embedding
keys when using external issuance.
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml (1)

21-83: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the demo TLS key out of the tracked manifest.

This file still embeds the TLS private key in stringData, so rotating or reusing the Secret requires publishing key material in source history. Reference a pre-created Secret instead and keep only the Secret name in the manifest.

🤖 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
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`
around lines 21 - 83, The manifest embeds private key material in the Secret
resource mock-openapi-https-tls (stringData.tls.crt / stringData.tls.key);
remove the TLS data from this checked-in YAML and instead reference a
pre-created Secret by name (mock-openapi-https-tls) from the consuming resources
(Ingress/Deployment) or replace this file with a minimal Secret manifest that
contains only metadata (name/namespace/labels) and a note to create the TLS
Secret out-of-band (e.g., via kubectl create secret tls or a sealed/templated
secret process) so private keys are not stored in source control.
.github/workflows/operator-integration-test.yml (2)

1088-1118: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the resolved plan ID when creating the Subscription.

This block waits for subscriptionplan.status.id, but the quoted heredoc disables interpolation and subscriptionPlanId stays hardcoded to demo-plan. That means the job never exercises the ID-based path it just resolved.

Suggested fix
-          kubectl apply -f - <<'EOF'
+          kubectl apply -f - <<EOF
           apiVersion: gateway.api-platform.wso2.com/v1alpha1
           kind: Subscription
           metadata:
             name: demo-subscription
             namespace: apigateway-demo
             labels:
               gateway.api-platform.wso2.com/restapi-target: wso2-crd
           spec:
             apiId: hello-sub-api
-            subscriptionPlanId: demo-plan
+            subscriptionPlanId: ${PLAN_ID}
             status: ACTIVE
             subscriptionToken:
               valueFrom:
                   name: demo-management-secrets
                   key: subscription-token
           EOF
🤖 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 @.github/workflows/operator-integration-test.yml around lines 1088 - 1118,
The code waits for the resolved PLAN_ID but then uses a single-quoted heredoc so
subscriptionPlanId remains the literal "demo-plan"; change the heredoc to allow
shell interpolation (remove the single quotes from <<'EOF' to use <<EOF) and
substitute the resolved PLAN_ID into the Subscription manifest (set
subscriptionPlanId: ${PLAN_ID}) when creating the demo-subscription so the job
exercises the ID-based path instead of the hardcoded demo-plan.

1041-1077: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add the certificate demo manifest to this job.

This sequence exercises the new management flows except 05e-certificate-resources.yaml, so CI still misses one of the CRDs called out in this PR. Apply it here, wait for its Programmed condition, and mirror that in teardown.

🤖 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 @.github/workflows/operator-integration-test.yml around lines 1041 - 1077,
The job that deploys demo management CRs is missing the certificate manifest;
add an apply for "${DEMO}/05e-certificate-resources.yaml" in the same sequence
where 05b/05c/05f are applied, then wait for its Programmed condition (e.g.,
kubectl wait --for=condition=Programmed <the-certificate-CR-name> -n
apigateway-demo --timeout=500s) and also add the corresponding deletion/cleanup
of that certificate CR in the job's teardown steps so the new certificate CRD is
exercised and removed.
kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md (1)

104-115: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Resolve the remaining HTTP/HTTPS mismatch in the LLM mock section.

Lines 104-105 say the in-cluster provider uses https://mock-openapi-https:9449/..., but Line 115 still says 05b-llm-resources.yaml uses http://mock-openapi:4010/openai/v1. Keep one authoritative in-cluster URL here, or explicitly explain which endpoint is only the backend/parity layer.

🤖 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 `@kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md` around
lines 104 - 115, The README contains an HTTP/HTTPS mismatch between the demo
manifest 05b0-mock-openapi-https.yaml (which exposes the mock as
https://mock-openapi-https:9449/...) and the description for
05b-llm-resources.yaml (which still references
http://mock-openapi:4010/openai/v1); pick one authoritative in-cluster endpoint
and make the wording consistent: either change the 05b-llm-resources.yaml
reference in the README to https://mock-openapi-https:9449/openai/v1 (and
mention the TLS secret / SANs and LlmProviderTemplate openai-test / LlmProvider
template: openai-test and LlmProxy port mapping) or explicitly state that
http://mock-openapi:4010 is only the backend parity address used in compose IT
while https://mock-openapi-https:9449 is the in-cluster endpoint used by the
operator; update all mentions of mock-openapi, mock-openapi-https, ports
4010/9449 and the LlmProvider/LlmProviderTemplate references so a reader sees a
single authoritative URL.
🧹 Nitpick comments (4)
docs/gateway/kubernetes/gateway-operator.md (1)

134-138: ⚡ Quick win

Certificate is listed but never demonstrated in this section.

The intro names Certificate as an operator-supported CRD, and the AI summary references a gateway/examples/certificate.yaml, but this section adds no Certificate YAML, no verification flow, and no validation note for it. Either drop the kind from the list here or add a short example mirroring the others (sourcing the PEM via valueFrom from the prerequisite Secret) for parity.

🤖 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 `@docs/gateway/kubernetes/gateway-operator.md` around lines 134 - 138, The docs
list Certificate among CRDs but show no example; either remove "Certificate"
from the CRD list or add a short Certificate example and notes. If adding,
create a minimal YAML (referencing gateway/examples/certificate.yaml) that
mirrors other examples: include kind: Certificate, metadata, spec with a pem
field populated via valueFrom from a prerequisite Secret (e.g.,
valueFrom.secretKeyRef.name/key), a brief verification/validation step
describing how to confirm the Secret contains the PEM and that the operator will
accept PEM-formatted certs, and a short note about required fields/validation
rules; update the paragraph to reference the example. Ensure the symbols
Certificate, valueFrom, and the example filename
gateway/examples/certificate.yaml are used so reviewers can locate the change.
kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml (1)

67-71: ⚖️ Poor tradeoff

Consider renaming spec.status to avoid clashing with the status subresource.

Having a spec.status string alongside the controller-managed .status subresource is confusing for consumers (kubectl jsonpath, printer columns, etc.) and easy to misuse. If the management-API field is "status", consider exposing it as spec.subscriptionStatus or spec.lifecycleStatus in the CR while still serializing as status on the wire (use a JSON tag mapping). Additionally, since the doc states it mirrors a fixed enum, consider adding an enum constraint here for early validation.

🤖 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
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml`
around lines 67 - 71, Rename the spec field currently defined as "status" to a
non-conflicting name such as spec.subscriptionStatus or spec.lifecycleStatus in
the CRD schema (update the property key under the "spec" schema and any usage in
structs/helpers), keep the original wire name by applying a JSON tag mapping to
"status" (so the API payload still serializes as "status"), and add an "enum"
constraint listing the allowed management-API SubscriptionCreateRequest.Status
values to the schema for validation; update any referenced symbols that
read/write the old spec.status field to the new name (e.g., struct field, JSON
tag, and controller accessors).
kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go (1)

103-108: 💤 Low value

Use http.StatusConflict instead of the magic number 409.

Replace the literal 409 with the named constant from the net/http package for readability and consistency with idiomatic Go HTTP code.

Suggested change
 import (
 	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
+	"net/http"
 	"net/url"
 	"time"
 )
 			var nr *NonRetryableError
-			if errors.As(err, &nr) && nr.StatusCode == 409 {
+			if errors.As(err, &nr) && nr.StatusCode == http.StatusConflict {
 				return DeployResource(ctx, gatewayEndpoint, rp, keyHandle, body, true, PayloadContentTypeJSON, auth)
 			}
🤖 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 `@kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go` around
lines 103 - 108, Replace the magic number 409 with the named constant
http.StatusConflict in the error handling block that checks
NonRetryableError.StatusCode (see NonRetryableError and the conditional branch
that calls DeployResource); ensure the net/http package is imported if not
already so the code uses http.StatusConflict for clarity and idiomatic Go.
kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yaml (1)

10-13: 💤 Low value

Nit: inconsistent indentation under valueFrom.

The keys under valueFrom (lines 12-13) use a 6-space indent under the 4-space parent. Valid YAML, but inconsistent with the rest of the file. Consider aligning to 4 spaces for readability.

Suggested formatting tweak
   certificate:
     valueFrom:
-        name: demo-management-secrets-apim
-        key: cert.pem
+      name: demo-management-secrets-apim
+      key: cert.pem
🤖 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
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yaml`
around lines 10 - 13, The indentation under the certificate -> valueFrom block
is inconsistent (currently 6 spaces); update the YAML so the child keys under
valueFrom (name: demo-management-secrets-apim and key: cert.pem) are indented 4
spaces to match the rest of the file and improve readability, keeping the keys
and values unchanged.
🤖 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 @.github/workflows/operator-integration-test.yml:
- Around line 1745-1777: The teardown is missing deletes for the management API
keys created earlier; add kubectl delete lines (non-blocking with
--ignore-not-found --wait=false and sleep 3) for apikey/openai-test-apikey,
apikey/demo-llm-provider-apikey, apikey/demo-llm-proxy-apikey (and the
duplicates apikey/demo-llmprovider-apikey and apikey/demo-llmproxy-apikey if
both variants are created), and then add matching wait_for_delete calls (e.g.,
wait_for_delete apikey/openai-test-apikey 180s, etc.) so those apikey resources
are explicitly removed before namespace teardown.

In
`@kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.go`:
- Line 35: The code reads nr.Err.Error() without checking nr.Err for nil; update
the handling around the NonRetryableError instance (the variable named nr) so
that if nr.Err == nil you return the original error immediately instead of
dereferencing it, otherwise extract msg via nr.Err.Error(); ensure this
nil-guard is placed before the line that assigns msg := nr.Err.Error() in the
reconcile/error handling path.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yaml`:
- Around line 10-20: The inline cert.pem block contains an invalid X.509 PEM and
will be rejected by the gateway (x509.ParseCertificate) when POST /certificates
is called; fix by replacing the cert.pem value with a valid PEM-encoded X.509
certificate (e.g., a committed self-signed demo cert) or change the manifest to
generate a valid certificate at deploy time (add an init job/Job that runs
openssl to create a key/cert and stores them where the Certificate resource in
05e-certificate-resources.yaml consumes them); ensure the PEM begins with a real
"-----BEGIN CERTIFICATE-----" / base64 ASN.1 body that parses as X.509.

---

Duplicate comments:
In @.github/workflows/operator-integration-test.yml:
- Around line 1088-1118: The code waits for the resolved PLAN_ID but then uses a
single-quoted heredoc so subscriptionPlanId remains the literal "demo-plan";
change the heredoc to allow shell interpolation (remove the single quotes from
<<'EOF' to use <<EOF) and substitute the resolved PLAN_ID into the Subscription
manifest (set subscriptionPlanId: ${PLAN_ID}) when creating the
demo-subscription so the job exercises the ID-based path instead of the
hardcoded demo-plan.
- Around line 1041-1077: The job that deploys demo management CRs is missing the
certificate manifest; add an apply for "${DEMO}/05e-certificate-resources.yaml"
in the same sequence where 05b/05c/05f are applied, then wait for its Programmed
condition (e.g., kubectl wait --for=condition=Programmed
<the-certificate-CR-name> -n apigateway-demo --timeout=500s) and also add the
corresponding deletion/cleanup of that certificate CR in the job's teardown
steps so the new certificate CRD is exercised and removed.

In `@kubernetes/gateway-operator/internal/controller/managedsecret_controller.go`:
- Around line 99-102: The code currently wraps every error returned by
secretsource.Resolve(...) in a gatewayclient.NonRetryableError which makes
transient resolution failures permanent; change the handling in the reconcile
path that calls secretsource.Resolve(ctx, k8sClient, "spec.value",
cr.Spec.Value, cr.Namespace) so you only convert ErrAmbiguous and
ErrNotConfigured into gatewayclient.NonRetryableError and let any other errors
propagate as retryable (i.e., return DeployResult{} with the raw error or a
retryable wrapper). Use the same classifier pattern as sibling controllers:
inspect the error value from secretsource.Resolve, if errors.Is(err,
secretsource.ErrAmbiguous) || errors.Is(err, secretsource.ErrNotConfigured) then
return NonRetryableError{Err: err}, otherwise return the error unchanged (so the
controller can retry).

In
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`:
- Around line 29-83: The manifest includes inline private key and cert under
stringData: tls.crt and tls.key which must not be committed; remove the embedded
tls.crt/tls.key values from the Secret and instead reference
externally-generated credentials (e.g., create the Secret from cert-manager, an
initJob, or a Helm pre-install hook) and update templates/README to document the
deploy-time generation steps and how the Secret (named in this manifest) will be
populated; ensure functions/resources that expect this Secret use the same
Secret name and that any Helm charts include conditional logic to skip embedding
keys when using external issuance.

In
`@kubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml`:
- Around line 19-20: The manifest uses a personal Docker Hub image
"rakhitharr/mcp-everything:v3" for the container named mcp-server-backend;
replace it with the official publisher image (e.g., "mcp/everything") and
preferably pin to an immutable digest (sha256) instead of a floating tag. Update
the image field for the container with name mcp-server-backend to the official
repository and include the digest (image@sha256:...) to ensure reproducible
builds and to remove the personal image reference.
- Around line 18-34: The container spec for the mcp-server-backend container is
missing a securityContext; update the container definition for name:
mcp-server-backend to include a securityContext that enforces non-root and least
privilege (e.g., runAsNonRoot: true and runAsUser: 1000), disables privilege
escalation (allowPrivilegeEscalation: false), and drops capabilities
(capabilities: drop: ["ALL"]); consider also setting readOnlyRootFilesystem:
true and runAsGroup if needed for filesystem access. Ensure these fields are
added under the container block for the mcp-server-backend container so
Kubernetes applies the restrictions at runtime.

In `@kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md`:
- Around line 104-115: The README contains an HTTP/HTTPS mismatch between the
demo manifest 05b0-mock-openapi-https.yaml (which exposes the mock as
https://mock-openapi-https:9449/...) and the description for
05b-llm-resources.yaml (which still references
http://mock-openapi:4010/openai/v1); pick one authoritative in-cluster endpoint
and make the wording consistent: either change the 05b-llm-resources.yaml
reference in the README to https://mock-openapi-https:9449/openai/v1 (and
mention the TLS secret / SANs and LlmProviderTemplate openai-test / LlmProvider
template: openai-test and LlmProxy port mapping) or explicitly state that
http://mock-openapi:4010 is only the backend parity address used in compose IT
while https://mock-openapi-https:9449 is the in-cluster endpoint used by the
operator; update all mentions of mock-openapi, mock-openapi-https, ports
4010/9449 and the LlmProvider/LlmProviderTemplate references so a reader sees a
single authoritative URL.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml`:
- Around line 21-83: The manifest embeds private key material in the Secret
resource mock-openapi-https-tls (stringData.tls.crt / stringData.tls.key);
remove the TLS data from this checked-in YAML and instead reference a
pre-created Secret by name (mock-openapi-https-tls) from the consuming resources
(Ingress/Deployment) or replace this file with a minimal Secret manifest that
contains only metadata (name/namespace/labels) and a note to create the TLS
Secret out-of-band (e.g., via kubectl create secret tls or a sealed/templated
secret process) so private keys are not stored in source control.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml`:
- Around line 18-34: The container spec for the container named
"mcp-server-backend" is missing a securityContext; add a securityContext under
the container entry (for the container with name mcp-server-backend) that
enforces non-root execution (e.g., runAsNonRoot: true and a non-root runAsUser),
disables privilege escalation (allowPrivilegeEscalation: false), drops all Linux
capabilities (capabilities.drop: ["ALL"]), and sets a default seccompProfile
(e.g., runtime/default) to provide safe defaults for demos so the pod isn't
deployed with permissive privileges.
- Around line 19-20: Replace the personal Docker Hub image in the container spec
for the container named "mcp-server-backend": change image:
rakhitharr/mcp-everything:v3 to the official mcp/everything image and preferably
pin it to a specific immutable digest (e.g., mcp/everything@sha256:...) to
ensure provenance and reproducibility; update the image field in the same YAML
block where the container name "mcp-server-backend" is defined.

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml`:
- Line 24: The subscriptionPlanId field in the resource is wrong: it must hold
the SubscriptionPlan's reconciled gateway UUID (.status.id) not the CR
metadata.name (e.g., demo-plan-apim). Fix by either removing subscriptionPlanId
from this demo manifest and documenting the two-step apply (create
SubscriptionPlan, wait for .status.id, then create Subscription), or implement a
templating step that looks up the SubscriptionPlan CR and injects its .status.id
into subscriptionPlanId after the plan reconciles; target symbols:
subscriptionPlanId, SubscriptionPlan, .status.id, metadata.name, and the demo
plan name demo-plan-apim.

---

Nitpick comments:
In `@docs/gateway/kubernetes/gateway-operator.md`:
- Around line 134-138: The docs list Certificate among CRDs but show no example;
either remove "Certificate" from the CRD list or add a short Certificate example
and notes. If adding, create a minimal YAML (referencing
gateway/examples/certificate.yaml) that mirrors other examples: include kind:
Certificate, metadata, spec with a pem field populated via valueFrom from a
prerequisite Secret (e.g., valueFrom.secretKeyRef.name/key), a brief
verification/validation step describing how to confirm the Secret contains the
PEM and that the operator will accept PEM-formatted certs, and a short note
about required fields/validation rules; update the paragraph to reference the
example. Ensure the symbols Certificate, valueFrom, and the example filename
gateway/examples/certificate.yaml are used so reviewers can locate the change.

In `@kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go`:
- Around line 103-108: Replace the magic number 409 with the named constant
http.StatusConflict in the error handling block that checks
NonRetryableError.StatusCode (see NonRetryableError and the conditional branch
that calls DeployResource); ensure the net/http package is imported if not
already so the code uses http.StatusConflict for clarity and idiomatic Go.

In
`@kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml`:
- Around line 67-71: Rename the spec field currently defined as "status" to a
non-conflicting name such as spec.subscriptionStatus or spec.lifecycleStatus in
the CRD schema (update the property key under the "spec" schema and any usage in
structs/helpers), keep the original wire name by applying a JSON tag mapping to
"status" (so the API payload still serializes as "status"), and add an "enum"
constraint listing the allowed management-API SubscriptionCreateRequest.Status
values to the schema for validation; update any referenced symbols that
read/write the old spec.status field to the new name (e.g., struct field, JSON
tag, and controller accessors).

In
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yaml`:
- Around line 10-13: The indentation under the certificate -> valueFrom block is
inconsistent (currently 6 spaces); update the YAML so the child keys under
valueFrom (name: demo-management-secrets-apim and key: cert.pem) are indented 4
spaces to match the rest of the file and improve readability, keeping the keys
and values unchanged.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 97a1cba2-c7f9-4b05-ab4f-0169eaaa387a

📥 Commits

Reviewing files that changed from the base of the PR and between 5008ac6 and d7bf270.

📒 Files selected for processing (101)
  • .github/workflows/operator-integration-test.yml
  • docs/gateway/kubernetes/gateway-operator.md
  • gateway/examples/api-key.yaml
  • gateway/examples/certificate.yaml
  • gateway/examples/managed-secret.yaml
  • gateway/examples/subscription-plan.yaml
  • gateway/examples/subscription.yaml
  • kubernetes/gateway-operator/api/v1alpha1/apikey_types.go
  • kubernetes/gateway-operator/api/v1alpha1/certificate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/common_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovider_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go
  • kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
  • kubernetes/gateway-operator/api/v1alpha1/mcp_types.go
  • kubernetes/gateway-operator/api/v1alpha1/secret_types.go
  • kubernetes/gateway-operator/api/v1alpha1/subscription_types.go
  • kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go
  • kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go
  • kubernetes/gateway-operator/cmd/main.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/gateway-operator/config/rbac/role.yaml
  • kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.md
  • kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/internal/controller/apikey_controller.go
  • kubernetes/gateway-operator/internal/controller/certificate_controller.go
  • kubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.go
  • kubernetes/gateway-operator/internal/controller/generic_reconciler.go
  • kubernetes/gateway-operator/internal/controller/httproute_enqueue_test.go
  • kubernetes/gateway-operator/internal/controller/k8s_gateway_controller.go
  • kubernetes/gateway-operator/internal/controller/llmprovider_controller.go
  • kubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.go
  • kubernetes/gateway-operator/internal/controller/llmproxy_controller.go
  • kubernetes/gateway-operator/internal/controller/managedsecret_controller.go
  • kubernetes/gateway-operator/internal/controller/management_llm_enqueue.go
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.go
  • kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.go
  • kubernetes/gateway-operator/internal/controller/management_resources_helpers.go
  • kubernetes/gateway-operator/internal/controller/management_secretsource_resolve_error.go
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload.go
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload_test.go
  • kubernetes/gateway-operator/internal/controller/mcp_controller.go
  • kubernetes/gateway-operator/internal/controller/payload_metadata_test.go
  • kubernetes/gateway-operator/internal/controller/subscription_controller.go
  • kubernetes/gateway-operator/internal/controller/subscriptionplan_controller.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_client_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/paths.go
  • kubernetes/gateway-operator/internal/gatewayclient/resource_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go
  • kubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.go
  • kubernetes/gateway-operator/internal/secretsource/resolver.go
  • kubernetes/gateway-operator/internal/secretsource/resolver_test.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/helm/operator-helm-chart/templates/_helpers.tpl
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-02-restapi.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-03-restapi.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b-02-llm-resources-apikey.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f0-apikey-resources.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/README.md
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/llm-mock-openapi-it/Dockerfile
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f0-apikey-resources.yaml
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.md
  • kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml
  • kubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml
✅ Files skipped from review due to trivial changes (40)
  • kubernetes/gateway-operator/internal/controller/k8s_gateway_controller.go
  • gateway/examples/subscription-plan.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yaml
  • gateway/examples/certificate.yaml
  • kubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/llm-mock-openapi-it/Dockerfile
  • kubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.go
  • kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go
  • kubernetes/gateway-operator/api/v1alpha1/certificate_types.go
  • kubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yaml
  • kubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.go
  • kubernetes/gateway-operator/internal/controller/httproute_enqueue_test.go
  • kubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.md
  • kubernetes/gateway-operator/api/v1alpha1/secret_types.go
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload_test.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-02-restapi.yaml
  • gateway/examples/subscription.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yaml
  • kubernetes/gateway-operator/internal/controller/payload_metadata_test.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • gateway/examples/managed-secret.yaml
  • gateway/examples/api-key.yaml
  • kubernetes/gateway-operator/internal/controller/management_secretsource_resolve_error.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/04-03-restapi.yaml
  • kubernetes/gateway-operator/api/v1alpha1/apikey_types.go
  • kubernetes/gateway-operator/api/v1alpha1/common_types.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yaml
  • kubernetes/gateway-operator/internal/controller/apikey_controller.go
  • kubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yaml
  • kubernetes/helm/operator-helm-chart/templates/_helpers.tpl
  • kubernetes/gateway-operator/internal/secretsource/resolver_test.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yaml
  • kubernetes/gateway-operator/internal/controller/mcp_controller.go
  • kubernetes/gateway-operator/api/v1alpha1/mcp_types.go
  • kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.md
  • kubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.go
🚧 Files skipped from review as they are similar to previous changes (34)
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yaml
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yaml
  • kubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.go
  • kubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yaml
  • kubernetes/gateway-operator/api/v1alpha1/llmproxy_types.go
  • kubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.go
  • kubernetes/gateway-operator/config/rbac/role.yaml
  • kubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.go
  • kubernetes/gateway-operator/internal/secretsource/resolver.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yaml
  • kubernetes/gateway-operator/internal/controller/management_upstream_auth_payload.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yaml
  • kubernetes/gateway-operator/internal/gatewayclient/resource_client.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/gateway-operator/api/v1alpha1/subscription_types.go
  • kubernetes/gateway-operator/internal/controller/management_llm_enqueue.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yaml
  • kubernetes/gateway-operator/cmd/main.go
  • kubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yaml
  • kubernetes/gateway-operator/internal/controller/certificate_controller.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.go
  • kubernetes/gateway-operator/internal/controller/management_resources_helpers.go
  • kubernetes/gateway-operator/internal/gatewayclient/paths.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yaml
  • kubernetes/gateway-operator/internal/controller/llmprovider_controller.go
  • kubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.go
  • kubernetes/gateway-operator/internal/controller/llmproxy_controller.go
  • kubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.md
  • kubernetes/gateway-operator/internal/controller/subscription_controller.go
  • kubernetes/gateway-operator/api/v1alpha1/llmprovider_types.go
  • kubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yaml
  • kubernetes/gateway-operator/internal/controller/generic_reconciler.go
  • kubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.go

Comment on lines +1745 to +1777
# Non-blocking deletes first.
kubectl delete apikey/demo-restapi-apikey -n apigateway-demo --ignore-not-found --wait=false
sleep 3
kubectl delete subscription/demo-subscription -n apigateway-demo --ignore-not-found --wait=false
sleep 3
kubectl delete subscriptionplan/demo-plan -n apigateway-demo --ignore-not-found --wait=false
sleep 3
kubectl delete restapi/hello-apikey-api -n apigateway-demo --ignore-not-found --wait=false
sleep 3
kubectl delete restapi/hello-sub-api -n apigateway-demo --ignore-not-found --wait=false
sleep 3
kubectl delete mcp/everything-mcp-v1.0 -n apigateway-demo --ignore-not-found --wait=false
sleep 3
kubectl delete llmproxy/demo-llm-proxy -n apigateway-demo --ignore-not-found --wait=false
sleep 3
kubectl delete llmprovider/demo-llm-provider -n apigateway-demo --ignore-not-found --wait=false
sleep 3
kubectl delete llmprovidertemplate/openai-test -n apigateway-demo --ignore-not-found --wait=false
sleep 3
kubectl delete managedsecret/demo-managed-secret -n apigateway-demo --ignore-not-found --wait=false
sleep 3

# Explicit waits with diagnostics.
wait_for_delete apikey/demo-restapi-apikey 180s
wait_for_delete subscription/demo-subscription 240s
wait_for_delete subscriptionplan/demo-plan 240s
wait_for_delete restapi/hello-apikey-api 240s
wait_for_delete restapi/hello-sub-api 240s
wait_for_delete mcp/everything-mcp-v1.0 180s
wait_for_delete llmproxy/demo-llm-proxy 240s
wait_for_delete llmprovider/demo-llm-provider 240s
wait_for_delete llmprovidertemplate/openai-test 180s
wait_for_delete managedsecret/demo-managed-secret 180s
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Teardown skips several management resources created earlier in the job.

The cleanup covers openai-test/demo-llm-provider/demo-llm-proxy, but it does not delete openai-test-apikey, demo-llm-provider-apikey, demo-llm-proxy-apikey, apikey/demo-llmprovider-apikey, or apikey/demo-llmproxy-apikey from Lines 1055-1060. Leaving those to namespace deletion makes finalizer cleanup less deterministic and can hide teardown regressions.

🤖 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 @.github/workflows/operator-integration-test.yml around lines 1745 - 1777,
The teardown is missing deletes for the management API keys created earlier; add
kubectl delete lines (non-blocking with --ignore-not-found --wait=false and
sleep 3) for apikey/openai-test-apikey, apikey/demo-llm-provider-apikey,
apikey/demo-llm-proxy-apikey (and the duplicates apikey/demo-llmprovider-apikey
and apikey/demo-llmproxy-apikey if both variants are created), and then add
matching wait_for_delete calls (e.g., wait_for_delete apikey/openai-test-apikey
180s, etc.) so those apikey resources are explicitly removed before namespace
teardown.

if !errors.As(err, &nr) {
return err
}
msg := nr.Err.Error()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a nil guard before reading nr.Err.Error().

At Line 35, nr.Err is dereferenced unconditionally. If a NonRetryableError instance is created with Err == nil, this will panic in reconcile. Please return the original error when nr.Err is nil before extracting msg.

Proposed fix
 func rewriteLLMDeployDependencyErrors(err error) error {
 	var nr *gatewayclient.NonRetryableError
 	if !errors.As(err, &nr) {
 		return err
 	}
+	if nr.Err == nil {
+		return err
+	}
 	msg := nr.Err.Error()
 	switch nr.StatusCode {
📝 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
msg := nr.Err.Error()
func rewriteLLMDeployDependencyErrors(err error) error {
var nr *gatewayclient.NonRetryableError
if !errors.As(err, &nr) {
return err
}
if nr.Err == nil {
return err
}
msg := nr.Err.Error()
switch nr.StatusCode {
🤖 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
`@kubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.go`
at line 35, The code reads nr.Err.Error() without checking nr.Err for nil;
update the handling around the NonRetryableError instance (the variable named
nr) so that if nr.Err == nil you return the original error immediately instead
of dereferencing it, otherwise extract msg via nr.Err.Error(); ensure this
nil-guard is placed before the line that assigns msg := nr.Err.Error() in the
reconcile/error handling path.

Comment on lines +10 to +20
cert.pem: |
-----BEGIN CERTIFICATE-----
MIIBszCCAVmgAwIBAgIUTk9ULVJFQUwtQ0VSVC1GT1ItREVNTy1PTkxZMAoGCCqGSM49BAMC
MBMxETAPBgNVBAMMCGRlbW8tY2VydDAeFw0yNjAxMDEwMDAwMDBaFw0zNjAxMDEwMDAwMDBa
MBMxETAPBgNVBAMMCGRlbW8tY2VydDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDemoapim
KcG9N4j7r5hP1h3uQhK6B0fVQjvYq1tV8u0sQY4z6Xv5xX8b4n5kL7u2m9vH0r6mQ8t33N5R8
tP9e0qjUzBRMB0GA1UdDgQWBBQdemoapimonlydemoapimonlydemoapimonlydemooajAf
BgNVHSMEGDAWgBQdemoapimonlydemoapimonlydemoapimonlydemooaMA8GA1UdEwEB/wQF
MAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhANOTREALCERTFORDEMOONLYNOTFORPRODUCTION
AiA5NOTREALCERTFORDEMOONLYNOTFORPRODUCTION=
-----END CERTIFICATE-----
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm the embedded PEM does not parse as a real X.509 cert
fd -t f '05a-management-prerequisites\.yaml$'
python - <<'PY'
import base64, re, pathlib, sys
p = list(pathlib.Path('.').rglob('05a-management-prerequisites.yaml'))
for f in p:
    txt = f.read_text()
    m = re.search(r'-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----', txt, re.S)
    if not m:
        continue
    body = re.sub(r'\s+', '', m.group(1))
    try:
        der = base64.b64decode(body, validate=True)
        print(f"{f}: base64 OK, {len(der)} bytes")
    except Exception as e:
        print(f"{f}: base64 decode failed: {e}")
PY
# Also inspect how Certificate.spec.certificate is consumed
rg -nP -C3 'cert\.pem|Certificate\b|x509\.ParseCertificate' --type=go

Repository: wso2/api-platform

Length of output: 50374


🏁 Script executed:

#!/bin/bash
# Parse the decoded PEM as X.509 certificate to determine validity
python3 <<'PYEOF'
import base64
import re
import pathlib
from cryptography import x509
from cryptography.hazmat.backends import default_backend

file_path = pathlib.Path("kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yaml")
if not file_path.exists():
    print(f"File not found: {file_path}")
    exit(1)

txt = file_path.read_text()
m = re.search(r'-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----', txt, re.S)
if not m:
    print("No PEM certificate found")
    exit(1)

body = re.sub(r'\s+', '', m.group(1))
try:
    der = base64.b64decode(body, validate=True)
    print(f"Base64 decode: OK ({len(der)} bytes)")
except Exception as e:
    print(f"Base64 decode FAILED: {e}")
    exit(1)

# Try to parse as X.509
try:
    cert = x509.load_der_x509_certificate(der, default_backend())
    print(f"X.509 parse: OK")
    print(f"  Subject: {cert.subject}")
    print(f"  Issuer: {cert.issuer}")
    print(f"  Serial: {cert.serial_number}")
except Exception as e:
    print(f"X.509 parse FAILED: {e}")
PYEOF

Repository: wso2/api-platform

Length of output: 183


🏁 Script executed:

#!/bin/bash
# Find and inspect how cert.pem is used in 05e
fd -t f '05e-certificate-resources\.yaml$' -x cat -n {}

# Check if there are any Certificate CRDs or validation logic
rg -A5 'cert\.pem|Certificate.*spec' \
  kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/ \
  --type yaml

Repository: wso2/api-platform

Length of output: 1992


🏁 Script executed:

#!/bin/bash
# Search for Certificate controller implementation
rg -l 'type Certificate' --type go | head -5

# Look for validation or parsing of certificates in the codebase
rg 'x509\.ParseCertificate|tls\.X509KeyPair|certificate.*validate|CertificateSpec' \
  --type go -A 3 -B 1 | grep -E '(func|Certificate|validate|Parse)' | head -30

Repository: wso2/api-platform

Length of output: 2586


🏁 Script executed:

#!/bin/bash
# Check Certificate controller implementation
cat -n kubernetes/gateway-operator/internal/controller/certificate_controller.go | head -100

# Also check if it uses the certstore with validation
rg -A 10 'func.*Reconcile' kubernetes/gateway-operator/internal/controller/certificate_controller.go | head -40

Repository: wso2/api-platform

Length of output: 5770


🏁 Script executed:

#!/bin/bash
# Check what happens after the PEM is resolved in Deploy
cat -n kubernetes/gateway-operator/internal/controller/certificate_controller.go | sed -n '96,150p'

# Check the gateway client to see if it validates certificates
rg -A 15 'func.*Post.*Certificate|CertificateUploadRequest' \
  kubernetes/gateway-operator/internal/gatewayclient/certificate_client.go | head -50

Repository: wso2/api-platform

Length of output: 2767


🏁 Script executed:

#!/bin/bash
# Check the gateway's certificate handler to see if it validates
cat -n gateway/gateway-controller/pkg/api/handlers/certificates.go | head -150

# Also check certstore for validation logic
rg -B 5 -A 10 'x509.ParseCertificate' gateway/gateway-controller/pkg/certstore/certstore.go | head -60

Repository: wso2/api-platform

Length of output: 7025


Inline cert.pem is not a valid X.509 certificate and will fail gateway validation.

The PEM body decodes successfully but does not parse as a valid certificate (ASN.1/X.509 structure fails to parse). The Certificate resource in 05e-certificate-resources.yaml uploads this key via the gateway API's POST /certificates endpoint, which validates the certificate format using x509.ParseCertificate(). When integration tests apply this manifest, the gateway will reject the certificate upload with an error. Replace with a real self-signed demo certificate (pinned and committed) or generate it inline via an init job before deployment.

🤖 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
`@kubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yaml`
around lines 10 - 20, The inline cert.pem block contains an invalid X.509 PEM
and will be rejected by the gateway (x509.ParseCertificate) when POST
/certificates is called; fix by replacing the cert.pem value with a valid
PEM-encoded X.509 certificate (e.g., a committed self-signed demo cert) or
change the manifest to generate a valid certificate at deploy time (add an init
job/Job that runs openssl to create a key/cert and stores them where the
Certificate resource in 05e-certificate-resources.yaml consumes them); ensure
the PEM begins with a real "-----BEGIN CERTIFICATE-----" / base64 ASN.1 body
that parses as X.509.

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.

1 participant