Implement LLMProviderTemplate, LLMProvider, LLMProxy, MCPProxy, APIKey, SubscriptionPlan, Subscription and ManagedSecret CRDs support in Gateway Operator#1813
Conversation
📝 WalkthroughImplementation of Management API Resource Support in Gateway OperatorThis 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 AddedNew
All types support controller-managed status tracking with conditions, gateway-issued UUIDs, and update timestamps. Controller FrameworkGeneric reconciliation engine (
Per-resource controllers implemented:
Gateway Client ExtensionsNew management API client primitives:
Configuration & AuthorizationRBAC updates: Extended ClusterRole permissions for:
CRD definitions: Complete OpenAPI v3 schemas with:
Example Manifests & DocumentationComprehensive examples added:
Documentation additions:
Integration TestingExtended operator integration test workflow:
Secret Source HandlingNew
All changes maintain backward compatibility with existing RestApi reconciliation while establishing patterns for future management API resource types. WalkthroughAdds 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
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
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 | 🟠 MajorAdd CRD validation for
spec.statusenum.The field mirrors the management-API
SubscriptionPlanCreateRequest.Statusenum (ACTIVE,INACTIVE), but currently accepts any string. Add the+kubebuilder:validation:Enum=ACTIVE,INACTIVEmarker 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 | 🟠 MajorHarden
upstreamvalidation to reject empty backend config.
upstreamis 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 | 🟠 MajorValidate gateway response contains a non-empty certificate ID.
UploadCertificateshould fail fast ifidis 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 | 🟠 MajorTighten
subscriptionTokenvalidation to require a token source and completevalueFromreferences.
subscriptionTokenis required at the spec level, but the schema allows an empty object{}and an incompletevalueFrom(missing the mandatorynamefield to reference a Secret). Addnameto the required fields invalueFromand useoneOfto 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 | 🟠 MajorAdd
oneOfvalidation to enforce exactly one credential source invalueobject.The schema description requires exactly one of
valueorvalueFrom, 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. UseoneOfto 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 | 🟠 MajorEnforce
expiresAtvsexpiresInmutual 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 | 🟠 MajorEnforce exactly one credential source in
auth.value.The schema text says exactly one of
valueorvalueFrommust 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 | 🟠 MajorMove HTTP method enum validation to array items
methodsis 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 underitemsso 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: arrayAlso 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 | 🟠 MajorFix
methodsenum placement to validate each itemThe enum is attached to the array field rather than array items. Move enum constraints under
itemsto 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 | 🟠 MajorRequire
specat the CR root
specis optional due toomitempty, which can allow admission of incomplete objects. Makespecrequired 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 | 🟠 MajorValidate upstream target selection (
urlxorref)
MCPUpstreamcurrently 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 | 🟠 MajorEnforce 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: 36forspec.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 | 🟠 MajorMake top-level
specrequiredUsing
omitemptyonSpecallows CR creation withoutspec, which bypasses the intended required fields under the spec schema. Makespecmandatory 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 | 🟠 MajorEnforce
SecretValueSourceexclusivity in the API schemaThe type documents “exactly one of
valueorvalueFrom”, 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 | 🟠 MajorPost-create spec changes are silently ignored.
Once
.status.idis set, the reconciler only forwardsspec.status. Changes toapiId,applicationId,subscriptionPlanId, billing IDs, orsubscriptionTokennever 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 | 🟠 MajorSecret-backed keys can get stuck until the
ApiKeychanges.
ResolveOptionalfailures are marked non-retryable, but this controller only watchesApiKeyandAPIGateway. 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 | 🟠 MajorHonor
expiresAtprecedence when both expiry fields are set.The spec says
expiresAtwins, but this code forwards bothExpiresInandExpiresAt. 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 | 🟠 MajorReplace the placeholder certificate with a parseable PEM.
demo-certificate-apimresolvescert.pemverbatim 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 | 🟠 MajorUse the
SecretValueSource.valueFromshape that the controller actually resolves.These blocks nest
secretKeyRefundervalueFrom, but the resolver in this PR consumesvalueFromitself 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-valueApply the same flattening to the other
valueFromblocks 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 | 🟠 MajorHonor
optional: truewhen the Secret object is missing.
SecretKeySelector.Optionalallows 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 | 🟠 MajorPreserve retry classification for delete failures.
DeleteResourcereturns 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 | 🟡 MinorFix type description mismatch in CRD schema.
The
specdescription saysWebhookAPIData, but this CRD isWebSubApi. 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 | 🟡 MinorAlign resource naming with
ManagedSecretthroughout the plan.The plan currently mixes
Secretterminology with the implemented CRD naming. Please standardize toManagedSecretwhere 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 | 🟡 MinorAdd
minLengthfor inline API key value.The description requires at least 36 characters, but this is not validated in the schema. Add
minLength: 36forspec.apiKey.valueto 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 | 🟡 MinorFix the malformed table row.
The formatting around
RestApiis 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 | 🟡 MinorUse the shipped CRD name consistently.
This doc introduces a custom
Secretkind, but the PR scope isManagedSecret. KeepingSecrethere 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 | 🟡 MinorReject empty
parentRef.namevalues at admission.
Requiredonly 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-configmapis commented out but references the ConfigMapcustom-gateway-valuesdefined in01-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
NodePortorClusterIPwith 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 abovestringData.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
stringDatawould 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 incleanupAllGatewayssilently returnsnil.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 sincecleanupAllGatewaysnever returns an error.This best-effort cleanup approach is reasonable to avoid blocking finalizer removal indefinitely, but consider:
- Documenting this behavior explicitly in a comment.
- 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 renamingResourceStatusDeployedusage for failed terminal states.Setting
ResourceStatusDeployedwhen 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
ResourceStatusFailedorResourceStatusTerminalfor 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
📒 Files selected for processing (86)
gateway/examples/api-key.yamlgateway/examples/certificate.yamlgateway/examples/managed-secret.yamlgateway/examples/subscription-plan.yamlgateway/examples/subscription.yamlgateway/examples/websub-api.yamlkubernetes/gateway-operator/api/v1alpha1/apikey_types.gokubernetes/gateway-operator/api/v1alpha1/certificate_types.gokubernetes/gateway-operator/api/v1alpha1/common_types.gokubernetes/gateway-operator/api/v1alpha1/llmprovider_types.gokubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.gokubernetes/gateway-operator/api/v1alpha1/llmproxy_types.gokubernetes/gateway-operator/api/v1alpha1/mcp_types.gokubernetes/gateway-operator/api/v1alpha1/secret_types.gokubernetes/gateway-operator/api/v1alpha1/subscription_types.gokubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.gokubernetes/gateway-operator/api/v1alpha1/websubapi_types.gokubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.gokubernetes/gateway-operator/cmd/main.gokubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_websubapis.yamlkubernetes/gateway-operator/config/rbac/role.yamlkubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.mdkubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.mdkubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.mdkubernetes/gateway-operator/internal/controller/apikey_controller.gokubernetes/gateway-operator/internal/controller/certificate_controller.gokubernetes/gateway-operator/internal/controller/generic_reconciler.gokubernetes/gateway-operator/internal/controller/llmprovider_controller.gokubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.gokubernetes/gateway-operator/internal/controller/llmproxy_controller.gokubernetes/gateway-operator/internal/controller/managedsecret_controller.gokubernetes/gateway-operator/internal/controller/management_resources_helpers.gokubernetes/gateway-operator/internal/controller/mcp_controller.gokubernetes/gateway-operator/internal/controller/subscription_controller.gokubernetes/gateway-operator/internal/controller/subscriptionplan_controller.gokubernetes/gateway-operator/internal/controller/websubapi_controller.gokubernetes/gateway-operator/internal/gatewayclient/apikey_client.gokubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.gokubernetes/gateway-operator/internal/gatewayclient/certificate_client.gokubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.gokubernetes/gateway-operator/internal/gatewayclient/paths.gokubernetes/gateway-operator/internal/gatewayclient/resource_client.gokubernetes/gateway-operator/internal/gatewayclient/subscription_client.gokubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.gokubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.gokubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.gokubernetes/gateway-operator/internal/secretsource/resolver.gokubernetes/gateway-operator/internal/secretsource/resolver_test.gokubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_websubapis.yamlkubernetes/helm/operator-helm-chart/templates/_helpers.tplkubernetes/helm/resources/apigateway-restapi-operator-demo/05-management-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05d-websub-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/README.mdkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05-management-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05d-websub-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.mdkubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yamlkubernetes/helm/resources/gateway-api-operator-demo/02-gateway.yaml
3893e19 to
d5195e9
Compare
There was a problem hiding this comment.
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 | 🟡 MinorUse
ManagedSecretterminology consistently with the implemented CRD.Lines 18 and 58 describe a
SecretCR, but this PR introducesManagedSecretas 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 | 🟡 MinorValidate
keyHandlefor key-specific operations.For
APIKeyExistsandDeleteAPIKey, 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 | 🟡 MinorFix 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 | 🟡 MinorRemove the unsupported
outputSchemafield.
MCPToolcurrently supports onlyname,description,inputSchema, and optionaltitle, sooutputSchemawill 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 | 🟡 MinorAlign
expiresAt/expiresIndescription with validation behaviorLine 102 says
expiresAtwins 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 | 🟡 MinorUpdate 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
📒 Files selected for processing (86)
gateway/examples/api-key.yamlgateway/examples/certificate.yamlgateway/examples/managed-secret.yamlgateway/examples/subscription-plan.yamlgateway/examples/subscription.yamlgateway/examples/websub-api.yamlkubernetes/gateway-operator/api/v1alpha1/apikey_types.gokubernetes/gateway-operator/api/v1alpha1/certificate_types.gokubernetes/gateway-operator/api/v1alpha1/common_types.gokubernetes/gateway-operator/api/v1alpha1/llmprovider_types.gokubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.gokubernetes/gateway-operator/api/v1alpha1/llmproxy_types.gokubernetes/gateway-operator/api/v1alpha1/mcp_types.gokubernetes/gateway-operator/api/v1alpha1/secret_types.gokubernetes/gateway-operator/api/v1alpha1/subscription_types.gokubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.gokubernetes/gateway-operator/api/v1alpha1/websubapi_types.gokubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.gokubernetes/gateway-operator/cmd/main.gokubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_websubapis.yamlkubernetes/gateway-operator/config/rbac/role.yamlkubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.mdkubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.mdkubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.mdkubernetes/gateway-operator/internal/controller/apikey_controller.gokubernetes/gateway-operator/internal/controller/certificate_controller.gokubernetes/gateway-operator/internal/controller/generic_reconciler.gokubernetes/gateway-operator/internal/controller/llmprovider_controller.gokubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.gokubernetes/gateway-operator/internal/controller/llmproxy_controller.gokubernetes/gateway-operator/internal/controller/managedsecret_controller.gokubernetes/gateway-operator/internal/controller/management_resources_helpers.gokubernetes/gateway-operator/internal/controller/mcp_controller.gokubernetes/gateway-operator/internal/controller/subscription_controller.gokubernetes/gateway-operator/internal/controller/subscriptionplan_controller.gokubernetes/gateway-operator/internal/controller/websubapi_controller.gokubernetes/gateway-operator/internal/gatewayclient/apikey_client.gokubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.gokubernetes/gateway-operator/internal/gatewayclient/certificate_client.gokubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.gokubernetes/gateway-operator/internal/gatewayclient/paths.gokubernetes/gateway-operator/internal/gatewayclient/resource_client.gokubernetes/gateway-operator/internal/gatewayclient/subscription_client.gokubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.gokubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.gokubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.gokubernetes/gateway-operator/internal/secretsource/resolver.gokubernetes/gateway-operator/internal/secretsource/resolver_test.gokubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_websubapis.yamlkubernetes/helm/operator-helm-chart/templates/_helpers.tplkubernetes/helm/resources/apigateway-restapi-operator-demo/05-management-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05d-websub-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/README.mdkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05-management-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05d-websub-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.mdkubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yamlkubernetes/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
d5195e9 to
60a12c4
Compare
There was a problem hiding this comment.
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
subscriptionPlanIdshould 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 fromSubscriptionPlan.status.id. Using the CR name may cause subscription creation to fail.Ensure the
subscriptionPlanIdis populated with the value fromSubscriptionPlan.status.idafter 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
LoadBalancerrequires a cloud provider or bare-metal LB controller.In local clusters (kind, minikube without tunneling, etc.) the service will remain in
Pendingstate indefinitely. Since the annotation in02-gateway.yamlis 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 winAll 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 plainerrors.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 valueConsider adding security context for container hardening.
While acceptable for demo purposes, adding
securityContextwithallowPrivilegeEscalation: falseandrunAsNonRoot: truewould 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 valueConsider adding mutual exclusivity validation for
urlandref.Both
UrlandRefare marked optional. If the intent is that exactly one must be provided, consider adding a CEL validation rule similar toSecretValueSourceto 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 valueSilent error handling may mask operational issues.
When
c.Listfails (lines 39-41 and 64-66), returningnilsilently 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 tospec.statuswith the three valid subscription lifecycle states.The
SubscriptionCRD'sspec.statusfield (lines 67-72) lacks an enum constraint, but the management-API defines three valid states forSubscriptionCreateRequest.Status:ACTIVE,INACTIVE, andREVOKED. Adding an enum constraint would catch invalid values at admission time. Note: this differs fromSubscriptionPlan.spec.status, which only allowsACTIVEandINACTIVE(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 winStandardize indentation to 2 spaces.
The
nameandkeyfields undervalueFromuse 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-valueApply 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
📒 Files selected for processing (98)
gateway/examples/api-key.yamlgateway/examples/certificate.yamlgateway/examples/managed-secret.yamlgateway/examples/subscription-plan.yamlgateway/examples/subscription.yamlkubernetes/gateway-operator/api/v1alpha1/apikey_types.gokubernetes/gateway-operator/api/v1alpha1/certificate_types.gokubernetes/gateway-operator/api/v1alpha1/common_types.gokubernetes/gateway-operator/api/v1alpha1/llmprovider_types.gokubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.gokubernetes/gateway-operator/api/v1alpha1/llmproxy_types.gokubernetes/gateway-operator/api/v1alpha1/mcp_types.gokubernetes/gateway-operator/api/v1alpha1/secret_types.gokubernetes/gateway-operator/api/v1alpha1/subscription_types.gokubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.gokubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.gokubernetes/gateway-operator/cmd/main.gokubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yamlkubernetes/gateway-operator/config/rbac/role.yamlkubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.mdkubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.mdkubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.mdkubernetes/gateway-operator/internal/controller/apikey_controller.gokubernetes/gateway-operator/internal/controller/certificate_controller.gokubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.gokubernetes/gateway-operator/internal/controller/generic_reconciler.gokubernetes/gateway-operator/internal/controller/httproute_enqueue_test.gokubernetes/gateway-operator/internal/controller/k8s_gateway_controller.gokubernetes/gateway-operator/internal/controller/llmprovider_controller.gokubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.gokubernetes/gateway-operator/internal/controller/llmproxy_controller.gokubernetes/gateway-operator/internal/controller/managedsecret_controller.gokubernetes/gateway-operator/internal/controller/management_llm_enqueue.gokubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.gokubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.gokubernetes/gateway-operator/internal/controller/management_resources_helpers.gokubernetes/gateway-operator/internal/controller/management_upstream_auth_payload.gokubernetes/gateway-operator/internal/controller/management_upstream_auth_payload_test.gokubernetes/gateway-operator/internal/controller/mcp_controller.gokubernetes/gateway-operator/internal/controller/payload_metadata_test.gokubernetes/gateway-operator/internal/controller/subscription_controller.gokubernetes/gateway-operator/internal/controller/subscriptionplan_controller.gokubernetes/gateway-operator/internal/gatewayclient/apikey_client.gokubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.gokubernetes/gateway-operator/internal/gatewayclient/certificate_client.gokubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.gokubernetes/gateway-operator/internal/gatewayclient/paths.gokubernetes/gateway-operator/internal/gatewayclient/resource_client.gokubernetes/gateway-operator/internal/gatewayclient/subscription_client.gokubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.gokubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.gokubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.gokubernetes/gateway-operator/internal/secretsource/resolver.gokubernetes/gateway-operator/internal/secretsource/resolver_test.gokubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yamlkubernetes/helm/operator-helm-chart/templates/_helpers.tplkubernetes/helm/resources/apigateway-restapi-operator-demo/04-02-restapi.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/04-03-restapi.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05-management-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05f0-apikey-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/README.mdkubernetes/helm/resources/apigateway-restapi-operator-demo/llm-mock-openapi-it/Dockerfilekubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05-management-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f0-apikey-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.mdkubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yamlkubernetes/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
60a12c4 to
1da6e05
Compare
There was a problem hiding this comment.
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 winMove
methods.enumunderitemsin both array schemas.Line 56 and Line 118 attach
enumto the array field instead of array elements. This should be defined underitemsfor 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.yamlAlso 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
subscriptionPlanIdshould reference the reconciled plan'sstatus.id.
subscriptionPlanIdis expected to carry the gateway-issued id fromSubscriptionPlan.status.id, not the CR namedemo-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 winSkip secret resolution when the certificate is already deployed.
Line 98 resolves the certificate source before checking whether
status.idis already set. Since this controller treats certificates as immutable, this can fail reconciles unnecessarily after successful upload. Checkcr.Status.Idfirst 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.upstreamhas no minimum-content validation: bothurlandrefare optional with no XOR constraint.An
Mcpresource withupstream: {}(orupstreamcontaining onlyauth) passes CRD validation but has no backend, which will only surface as a controller reconcile error. A CEL validation rule onupstreamcan enforce that exactly one ofurl/refis present, consistent with thevalue/valueFrompattern 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.gowould 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.conditionsis missingx-kubernetes-list-type: map/x-kubernetes-list-map-keys.Same issue as the
CertificateCRD: 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.conditionsis missingx-kubernetes-list-type: map/x-kubernetes-list-map-keys.Without these annotations, server-side apply treats
status.conditionsas 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'sConditionsfield, not the YAML directly (this file is regenerated by controller-gen).Add the following markers to the
Conditions []metav1.Conditionfield incertificate_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 valueOptionally validate
payload.PlanNamebefore POST.
SubscriptionPlanCreatePayload.PlanNameis a required field on the gateway side, butCreateSubscriptionPlandoes 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 winMarshal/unmarshal failures should be classified as non-retryable.
json.Marshal/json.Unmarshalerrors on Lines 64, 72, 85, and 93 are returned as plainfmt.Errorf. TheGenericReconciler.handleDeploymentErrordefaultbranch wraps unclassified errors asRetryableError, which will keep retrying a deterministic encoding/decoding failure untilMaxRetryAttemptsis reached. Wrapping these ingatewayclient.NonRetryableErrorshort-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 valueHelper for tracker key from object would simplify reconcileDeletion.
Constructing a synthetic
ctrl.Requeston Line 617 just to reusetrackingKeyis awkward. A smallr.trackingKeyForObj(obj client.Object) stringhelper would clarify intent and eliminate the dummyRequest.🤖 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 liftCleanup only targets currently-matching gateways.
cleanupAllGatewaysresolves gateways viaFindMatchingGatewaysat 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 inmatchedand 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
buildAuthFnerrors propagate as unclassified plain errors.
auth.GetAuthSettingsForRegistryGatewayfailures on Line 372 are returned as plainfmt.Errorf. AnyResourceAdapter.Deploycall that surfaces this error eventually reacheshandleDeploymentError, where thedefaultbranch wraps it asRetryableError. Auth/credential resolution errors are typically deterministic (missing secret/key), so this leads to backoff loops untilMaxRetryAttemptsis 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
ResourceStatusDeployedis set on terminal failure paths, which is semantically misleading.In
handleRetryableError(Line 472, after max retries) andhandleNonRetryableError(Line 528), the tracker entry is moved toResourceStatusDeployedeven though the resource was not actually deployed. Subsequent reconciles route through theDeployedbranch indecideAndProcess, which works only because the failedupdateProgrammedwritesObservedGeneration == entry.GenerationandStatus=False.This coupling is fragile. Consider introducing a
ResourceStatusFailed/ResourceStatusTerminalstate to model "no further action needed" without overloadingDeployed, 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 valueRedundant generation check inside
Deployedbranch.The outer block at Line 246 already enforces
crGeneration > statusObservedGen, so the innerif crGeneration > statusObservedGenon Line 268 is always true. Theelsebranch 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 valueRequest-construction error classification is inconsistent across helpers.
ResourceExists(Line 45) andsendBodyForResponse(Line 162) classifyhttp.NewRequestWithContextfailures asRetryableError, whileDeleteResource(Line 124) classifies the same failure asNonRetryableError. 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
authcallback fails on Line 49, Line 128, and Line 170, the raw error is returned to the caller. DownstreamhandleDeploymentErrorthen falls into thedefaultbranch and wraps the error as retryable, which may not be the intended semantics for credential-resolution failures. Consider wrapping these in an explicitNonRetryableError(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 tradeoffReduce duplication in
newObjectListSameType/extractItems.Both helpers must be updated whenever a new CRD kind is added, and
extractItemsrepeats the samefor 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 valueRemove the unused
crSetupOptionsstruct 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-kindSetupWithManagercalls 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 valueLGTM — demo prerequisites are cohesive.
The
Secretkeys (provider-token,subscription-token,managed-secret-value,cert.pem) line up with the consumers in05c/05e/05fmanifests, and theManagedSecretCR correctly references the Secret viavalueFrom. Demo placeholder values are acceptable here.Optional: the indentation of
name/keyundervalueFrom(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
📒 Files selected for processing (98)
gateway/examples/api-key.yamlgateway/examples/certificate.yamlgateway/examples/managed-secret.yamlgateway/examples/subscription-plan.yamlgateway/examples/subscription.yamlkubernetes/gateway-operator/api/v1alpha1/apikey_types.gokubernetes/gateway-operator/api/v1alpha1/certificate_types.gokubernetes/gateway-operator/api/v1alpha1/common_types.gokubernetes/gateway-operator/api/v1alpha1/llmprovider_types.gokubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.gokubernetes/gateway-operator/api/v1alpha1/llmproxy_types.gokubernetes/gateway-operator/api/v1alpha1/mcp_types.gokubernetes/gateway-operator/api/v1alpha1/secret_types.gokubernetes/gateway-operator/api/v1alpha1/subscription_types.gokubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.gokubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.gokubernetes/gateway-operator/cmd/main.gokubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yamlkubernetes/gateway-operator/config/rbac/role.yamlkubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.mdkubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.mdkubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.mdkubernetes/gateway-operator/internal/controller/apikey_controller.gokubernetes/gateway-operator/internal/controller/certificate_controller.gokubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.gokubernetes/gateway-operator/internal/controller/generic_reconciler.gokubernetes/gateway-operator/internal/controller/httproute_enqueue_test.gokubernetes/gateway-operator/internal/controller/k8s_gateway_controller.gokubernetes/gateway-operator/internal/controller/llmprovider_controller.gokubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.gokubernetes/gateway-operator/internal/controller/llmproxy_controller.gokubernetes/gateway-operator/internal/controller/managedsecret_controller.gokubernetes/gateway-operator/internal/controller/management_llm_enqueue.gokubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.gokubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.gokubernetes/gateway-operator/internal/controller/management_resources_helpers.gokubernetes/gateway-operator/internal/controller/management_secretsource_resolve_error.gokubernetes/gateway-operator/internal/controller/management_upstream_auth_payload.gokubernetes/gateway-operator/internal/controller/management_upstream_auth_payload_test.gokubernetes/gateway-operator/internal/controller/mcp_controller.gokubernetes/gateway-operator/internal/controller/payload_metadata_test.gokubernetes/gateway-operator/internal/controller/subscription_controller.gokubernetes/gateway-operator/internal/controller/subscriptionplan_controller.gokubernetes/gateway-operator/internal/gatewayclient/apikey_client.gokubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.gokubernetes/gateway-operator/internal/gatewayclient/certificate_client.gokubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.gokubernetes/gateway-operator/internal/gatewayclient/paths.gokubernetes/gateway-operator/internal/gatewayclient/resource_client.gokubernetes/gateway-operator/internal/gatewayclient/subscription_client.gokubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.gokubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.gokubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.gokubernetes/gateway-operator/internal/secretsource/resolver.gokubernetes/gateway-operator/internal/secretsource/resolver_test.gokubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yamlkubernetes/helm/operator-helm-chart/templates/_helpers.tplkubernetes/helm/operator-helm-chart/values.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/04-02-restapi.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/04-03-restapi.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05f0-apikey-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/README.mdkubernetes/helm/resources/apigateway-restapi-operator-demo/llm-mock-openapi-it/Dockerfilekubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f0-apikey-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.mdkubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yamlkubernetes/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
1da6e05 to
a2fab84
Compare
There was a problem hiding this comment.
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 winAdd a container
securityContextformcp-server-backend.Line 19 defines the container without hardening settings, so it may run with permissive defaults. Please set
runAsNonRootandallowPrivilegeEscalation: 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 winUse 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 winResolve conflicting in-cluster upstream URL guidance in the LLM mock section.
Line 103 says in-cluster
LlmProvidershould usehttps://mock-openapi-https:9449/..., while Line 114 states05b-llm-resources.yamluseshttp://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 winAdd
+listType=mapand+listMapKey=typemarkers toConditions.Without these kubebuilder markers,
controller-genomitsx-kubernetes-list-type: mapfrom the generated CRD, causing Server-Side Apply to treat the conditions array as atomic. Concurrent writers (e.g., multiple controllers orkubectl apply) can inadvertently wipe conditions set by others. This affects every CRD that embedsResourceStatus.♻️ 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 winCleanup ordering does not wait for finalizers between dependent CRs.
kubectl delete ... --ignore-not-foundreturns immediately without waiting for finalizers. IfSubscriptionfinalizer logic depends onSubscriptionPlanstill 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, addkubectl 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
pkillfollowed by a fixed sleep is racy across runners.
pkill ... || trueplussleep 2then 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 probeslocalhost:8080) or usingwaiton 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 winCapture session-id failure with a clearer message.
[ -n "${SESSION_ID}" ]underset -euo pipefailwill exit non-zero if the header is missing, but without any indication of what went wrong. If the MCPinitializeresponse 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 valueSequential
kubectl waitcalls 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(orkubectl 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 tradeoffTerminal failure state is labeled
ResourceStatusDeployed.After max retries are exceeded (line 472) and on non-retryable errors (line 528), the tracker is marked
ResourceStatusDeployedeven though deployment failed. This conflates "successfully deployed" with "gave up", complicates thedecideAndProcessinvariants, and makes log/debug interpretation harder. Consider introducing a distinct terminal status (e.g.,ResourceStatusFailed) and updating the switch indecideAndProcessaccordingly.🤖 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 valueOuter
if crGeneration > statusObservedGenis redundant.Line 238 already returns when
crGeneration == statusObservedGenandProgrammed=True, but execution continues for the remaining==cases (e.g.,Programmed=False, missing condition withstatusObservedGen=0andcrGeneration=0). Reaching line 246 withcrGeneration < statusObservedGenwould also fall through to theno action neededlog 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 valueConstructors run before any
setupcall; an early-construction failure mode should be considered.All nine reconcilers are constructed eagerly when the slice literal is built (line 230–238), then
setupis 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 thesetuppattern.🤖 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 winUse
valueFrominstead of an inline credential value, even for demo manifests.The CRD schema's own documentation warns: "Avoid for production use; prefer
ValueFromso 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 asecrets/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
📒 Files selected for processing (98)
.github/workflows/operator-integration-test.ymlgateway/examples/api-key.yamlgateway/examples/certificate.yamlgateway/examples/managed-secret.yamlgateway/examples/subscription-plan.yamlgateway/examples/subscription.yamlkubernetes/gateway-operator/api/v1alpha1/apikey_types.gokubernetes/gateway-operator/api/v1alpha1/certificate_types.gokubernetes/gateway-operator/api/v1alpha1/common_types.gokubernetes/gateway-operator/api/v1alpha1/llmprovider_types.gokubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.gokubernetes/gateway-operator/api/v1alpha1/llmproxy_types.gokubernetes/gateway-operator/api/v1alpha1/mcp_types.gokubernetes/gateway-operator/api/v1alpha1/secret_types.gokubernetes/gateway-operator/api/v1alpha1/subscription_types.gokubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.gokubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.gokubernetes/gateway-operator/cmd/main.gokubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yamlkubernetes/gateway-operator/config/rbac/role.yamlkubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.mdkubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.mdkubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.mdkubernetes/gateway-operator/internal/controller/apikey_controller.gokubernetes/gateway-operator/internal/controller/certificate_controller.gokubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.gokubernetes/gateway-operator/internal/controller/generic_reconciler.gokubernetes/gateway-operator/internal/controller/httproute_enqueue_test.gokubernetes/gateway-operator/internal/controller/k8s_gateway_controller.gokubernetes/gateway-operator/internal/controller/llmprovider_controller.gokubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.gokubernetes/gateway-operator/internal/controller/llmproxy_controller.gokubernetes/gateway-operator/internal/controller/managedsecret_controller.gokubernetes/gateway-operator/internal/controller/management_llm_enqueue.gokubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.gokubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.gokubernetes/gateway-operator/internal/controller/management_resources_helpers.gokubernetes/gateway-operator/internal/controller/management_secretsource_resolve_error.gokubernetes/gateway-operator/internal/controller/management_upstream_auth_payload.gokubernetes/gateway-operator/internal/controller/management_upstream_auth_payload_test.gokubernetes/gateway-operator/internal/controller/mcp_controller.gokubernetes/gateway-operator/internal/controller/payload_metadata_test.gokubernetes/gateway-operator/internal/controller/subscription_controller.gokubernetes/gateway-operator/internal/controller/subscriptionplan_controller.gokubernetes/gateway-operator/internal/gatewayclient/apikey_client.gokubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.gokubernetes/gateway-operator/internal/gatewayclient/certificate_client.gokubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.gokubernetes/gateway-operator/internal/gatewayclient/paths.gokubernetes/gateway-operator/internal/gatewayclient/resource_client.gokubernetes/gateway-operator/internal/gatewayclient/subscription_client.gokubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.gokubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.gokubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.gokubernetes/gateway-operator/internal/secretsource/resolver.gokubernetes/gateway-operator/internal/secretsource/resolver_test.gokubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yamlkubernetes/helm/operator-helm-chart/templates/_helpers.tplkubernetes/helm/resources/apigateway-restapi-operator-demo/04-02-restapi.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/04-03-restapi.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05f0-apikey-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/README.mdkubernetes/helm/resources/apigateway-restapi-operator-demo/llm-mock-openapi-it/Dockerfilekubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f0-apikey-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.mdkubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yamlkubernetes/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
| 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----- |
There was a problem hiding this comment.
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.
a2fab84 to
533032d
Compare
There was a problem hiding this comment.
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 winRemove 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 winRemove 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 winBind the subscription to the reconciled plan id.
subscriptionPlanIdis wired to theSubscriptionPlanCR name here. If the controller expects the deployed plan identifier, this subscription will not attach to the reconciled plan. Populate it fromSubscriptionPlan.status.idinstead ofdemo-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-operatorExpected result: the controller/client flow should show whether
subscriptionPlanIdis 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 winTighten
MCPUpstreamto the backend contract.
MCPUpstreamcurrently allows an empty object and aref, which means Kubernetes can acceptMcpresources that the downstream MCP API cannot program successfully. Please makeurlrequired here and reject/removerefso 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 winMake the LLM upstream URL guidance consistent.
This section says the in-cluster flow uses
https://mock-openapi-https:9449/..., but the next paragraph says05b-llm-resources.yamluseshttp://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 winRename
spec.statusto avoid collision with the Kubernetes status convention.A field named
statusinsidespecis non-idiomatic in Kubernetes API design —statusis a reserved term strongly associated with the root-level lifecycle subresource. While the comment documents that it mirrors the upstreamSubscriptionCreateRequest.Statusenum, users and tooling (e.g.,kubectl, validation webhooks, generated clients) may misinterpret this field. Since this is stillv1alpha1, renaming before graduation is low-cost; after promotion it becomes a breaking API change.Consider a more explicit name such as
subscriptionStateorlifecycleStatus.♻️ 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: stringThe corresponding Go type in
subscription_types.goand any management-API mapping insubscription_client.gowould 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 winLGTM — consider adding empty-parent error cases for
LlmProviderandLlmProxy.The test covers the main happy paths and error cases well. For completeness, it may be worth adding
parent: ""error cases forLlmProviderandLlmProxyto confirm the error contract is consistent with theRestApicase 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 winReplace inline auth value with Secret reference.
Line 57 hardcodes credential material in the manifest. Prefer
valueFromso 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 winAdd fast-fail validation for empty payload fields.
Validate
payload.Nameandpayload.Certificatebefore 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 winUse
valueFrominstead 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
📒 Files selected for processing (98)
.github/workflows/operator-integration-test.ymlgateway/examples/api-key.yamlgateway/examples/certificate.yamlgateway/examples/managed-secret.yamlgateway/examples/subscription-plan.yamlgateway/examples/subscription.yamlkubernetes/gateway-operator/api/v1alpha1/apikey_types.gokubernetes/gateway-operator/api/v1alpha1/certificate_types.gokubernetes/gateway-operator/api/v1alpha1/common_types.gokubernetes/gateway-operator/api/v1alpha1/llmprovider_types.gokubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.gokubernetes/gateway-operator/api/v1alpha1/llmproxy_types.gokubernetes/gateway-operator/api/v1alpha1/mcp_types.gokubernetes/gateway-operator/api/v1alpha1/secret_types.gokubernetes/gateway-operator/api/v1alpha1/subscription_types.gokubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.gokubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.gokubernetes/gateway-operator/cmd/main.gokubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yamlkubernetes/gateway-operator/config/rbac/role.yamlkubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.mdkubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.mdkubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.mdkubernetes/gateway-operator/internal/controller/apikey_controller.gokubernetes/gateway-operator/internal/controller/certificate_controller.gokubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.gokubernetes/gateway-operator/internal/controller/generic_reconciler.gokubernetes/gateway-operator/internal/controller/httproute_enqueue_test.gokubernetes/gateway-operator/internal/controller/k8s_gateway_controller.gokubernetes/gateway-operator/internal/controller/llmprovider_controller.gokubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.gokubernetes/gateway-operator/internal/controller/llmproxy_controller.gokubernetes/gateway-operator/internal/controller/managedsecret_controller.gokubernetes/gateway-operator/internal/controller/management_llm_enqueue.gokubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.gokubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.gokubernetes/gateway-operator/internal/controller/management_resources_helpers.gokubernetes/gateway-operator/internal/controller/management_secretsource_resolve_error.gokubernetes/gateway-operator/internal/controller/management_upstream_auth_payload.gokubernetes/gateway-operator/internal/controller/management_upstream_auth_payload_test.gokubernetes/gateway-operator/internal/controller/mcp_controller.gokubernetes/gateway-operator/internal/controller/payload_metadata_test.gokubernetes/gateway-operator/internal/controller/subscription_controller.gokubernetes/gateway-operator/internal/controller/subscriptionplan_controller.gokubernetes/gateway-operator/internal/gatewayclient/apikey_client.gokubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.gokubernetes/gateway-operator/internal/gatewayclient/certificate_client.gokubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.gokubernetes/gateway-operator/internal/gatewayclient/paths.gokubernetes/gateway-operator/internal/gatewayclient/resource_client.gokubernetes/gateway-operator/internal/gatewayclient/subscription_client.gokubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.gokubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.gokubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.gokubernetes/gateway-operator/internal/secretsource/resolver.gokubernetes/gateway-operator/internal/secretsource/resolver_test.gokubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yamlkubernetes/helm/operator-helm-chart/templates/_helpers.tplkubernetes/helm/resources/apigateway-restapi-operator-demo/04-02-restapi.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/04-03-restapi.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05f0-apikey-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/README.mdkubernetes/helm/resources/apigateway-restapi-operator-demo/llm-mock-openapi-it/Dockerfilekubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f0-apikey-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.mdkubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yamlkubernetes/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
| - 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 | ||
|
|
There was a problem hiding this comment.
🛠️ 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.
| type ApiKey struct { | ||
| metav1.TypeMeta `json:",inline"` | ||
| metav1.ObjectMeta `json:"metadata,omitempty"` | ||
|
|
||
| Spec ApiKeySpec `json:"spec,omitempty"` | ||
| Status ResourceStatus `json:"status,omitempty"` |
There was a problem hiding this comment.
🧩 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')}")
PYRepository: wso2/api-platform
Length of output: 277
🏁 Script executed:
cat -n kubernetes/gateway-operator/api/v1alpha1/apikey_types.go | head -115 | tail -20Repository: wso2/api-platform
Length of output: 811
🏁 Script executed:
grep -n "type.*struct" kubernetes/gateway-operator/api/v1alpha1/apikey_types.go | head -20Repository: wso2/api-platform
Length of output: 206
🏁 Script executed:
grep -A 8 "^type ApiKeyList struct" kubernetes/gateway-operator/api/v1alpha1/apikey_types.goRepository: 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 -20Repository: wso2/api-platform
Length of output: 43
🏁 Script executed:
ls -la kubernetes/gateway-operator/api/v1alpha1/*.go | grep -v test | head -10Repository: wso2/api-platform
Length of output: 1162
🏁 Script executed:
grep -A 8 "^type ApiGateway struct" kubernetes/gateway-operator/api/v1alpha1/apigateway_types.goRepository: wso2/api-platform
Length of output: 43
🏁 Script executed:
grep -n "^type.*struct" kubernetes/gateway-operator/api/v1alpha1/apigateway_types.go | head -5Repository: 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.goRepository: 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')}")
PYRepository: 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.
| 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.
| // +kubebuilder:validation:Enum=Day;Hour;Min;Month | ||
| ThrottleLimitUnit *string `json:"throttleLimitUnit,omitempty"` |
There was a problem hiding this comment.
🧩 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:
- 1: Adding additional parameters to the Devportal and Publisher REST API responses which return subscription throttling policy-related information. product-apim#9695
- 2: https://apim.docs.wso2.com/en/latest/design/rate-limiting/adding-new-throttling-policies/
- 3: https://apim.docs.wso2.com/en/4.5.0/manage-apis/design/rate-limiting/setting-throttling-limits/
- 4: https://apim.docs.wso2.com/en/3.0.0/learn/rate-limiting/setting-throttling-limits/
- 5: https://github.com/wso2/carbon-apimgt/blob/master/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/model/API.java
- 6: Add subscription policy feature api-manager#3562
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"` |
There was a problem hiding this comment.
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.
| 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.
| required: | ||
| - accessControl | ||
| - displayName | ||
| - template | ||
| - upstream | ||
| - version | ||
| type: object |
There was a problem hiding this comment.
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: objectAlso 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.
| 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) |
There was a problem hiding this comment.
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.
| req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil) | ||
| if err != nil { | ||
| return false, &RetryableError{Err: fmt.Errorf("create HTTP request: %w", err)} | ||
| } |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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.
| 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.
| - name: mcp-server-backend | ||
| image: rakhitharr/mcp-everything:v3 |
There was a problem hiding this comment.
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.
| - name: mcp-server-backend | ||
| image: rakhitharr/mcp-everything:v3 |
There was a problem hiding this comment.
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:latestOr, 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.
533032d to
5008ac6
Compare
There was a problem hiding this comment.
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 winAvoid 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
secretNamereference, but provisionmock-openapi-https-tlsduring 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 winProvision 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
secretNamereference, but createmock-openapi-https-tlsat 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 winKeep the documented LLM upstream URL consistent.
Line 103 says the provider uses
https://mock-openapi-https:9449/..., but Line 114 still describes05b-llm-resources.yamlas usinghttp://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 winExercise the
CertificateCRD 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 valueConsider 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 winPoint this provider at the HTTPS mock if
05b0is part of the demo flow.The companion demo manifest adds
mock-openapi-https:9449, but this provider still callsmock-openapi:4010directly, so the TLS terminator path is never exercised here. Either switch the URL to the HTTPS service, or make it clear that05b0-mock-openapi-https.yamlis 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
📒 Files selected for processing (98)
.github/workflows/operator-integration-test.ymlgateway/examples/api-key.yamlgateway/examples/certificate.yamlgateway/examples/managed-secret.yamlgateway/examples/subscription-plan.yamlgateway/examples/subscription.yamlkubernetes/gateway-operator/api/v1alpha1/apikey_types.gokubernetes/gateway-operator/api/v1alpha1/certificate_types.gokubernetes/gateway-operator/api/v1alpha1/common_types.gokubernetes/gateway-operator/api/v1alpha1/llmprovider_types.gokubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.gokubernetes/gateway-operator/api/v1alpha1/llmproxy_types.gokubernetes/gateway-operator/api/v1alpha1/mcp_types.gokubernetes/gateway-operator/api/v1alpha1/secret_types.gokubernetes/gateway-operator/api/v1alpha1/subscription_types.gokubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.gokubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.gokubernetes/gateway-operator/cmd/main.gokubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yamlkubernetes/gateway-operator/config/rbac/role.yamlkubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.mdkubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.mdkubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.mdkubernetes/gateway-operator/internal/controller/apikey_controller.gokubernetes/gateway-operator/internal/controller/certificate_controller.gokubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.gokubernetes/gateway-operator/internal/controller/generic_reconciler.gokubernetes/gateway-operator/internal/controller/httproute_enqueue_test.gokubernetes/gateway-operator/internal/controller/k8s_gateway_controller.gokubernetes/gateway-operator/internal/controller/llmprovider_controller.gokubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.gokubernetes/gateway-operator/internal/controller/llmproxy_controller.gokubernetes/gateway-operator/internal/controller/managedsecret_controller.gokubernetes/gateway-operator/internal/controller/management_llm_enqueue.gokubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.gokubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.gokubernetes/gateway-operator/internal/controller/management_resources_helpers.gokubernetes/gateway-operator/internal/controller/management_secretsource_resolve_error.gokubernetes/gateway-operator/internal/controller/management_upstream_auth_payload.gokubernetes/gateway-operator/internal/controller/management_upstream_auth_payload_test.gokubernetes/gateway-operator/internal/controller/mcp_controller.gokubernetes/gateway-operator/internal/controller/payload_metadata_test.gokubernetes/gateway-operator/internal/controller/subscription_controller.gokubernetes/gateway-operator/internal/controller/subscriptionplan_controller.gokubernetes/gateway-operator/internal/gatewayclient/apikey_client.gokubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.gokubernetes/gateway-operator/internal/gatewayclient/certificate_client.gokubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.gokubernetes/gateway-operator/internal/gatewayclient/paths.gokubernetes/gateway-operator/internal/gatewayclient/resource_client.gokubernetes/gateway-operator/internal/gatewayclient/subscription_client.gokubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.gokubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.gokubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.gokubernetes/gateway-operator/internal/secretsource/resolver.gokubernetes/gateway-operator/internal/secretsource/resolver_test.gokubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yamlkubernetes/helm/operator-helm-chart/templates/_helpers.tplkubernetes/helm/resources/apigateway-restapi-operator-demo/04-02-restapi.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/04-03-restapi.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05f0-apikey-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/README.mdkubernetes/helm/resources/apigateway-restapi-operator-demo/llm-mock-openapi-it/Dockerfilekubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f0-apikey-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.mdkubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yamlkubernetes/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
| ### 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. |
There was a problem hiding this comment.
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.
| val, err := secretsource.Resolve(ctx, k8sClient, "spec.value", cr.Spec.Value, cr.Namespace) | ||
| if err != nil { | ||
| return DeployResult{}, &gatewayclient.NonRetryableError{Err: err} | ||
| } |
There was a problem hiding this comment.
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.
| 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} |
There was a problem hiding this comment.
🧩 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:
- 1: https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/secret-v1
- 2: https://kubernetes.io/docs/tasks/configmap-secret/managing-secret-using-config-file
- 3: https://kubernetes.io/docs/concepts/configuration/secret/
🏁 Script executed:
# Verify the file exists and examine the code context around the flagged lines
fd -t f "resolver.go" | grep secretsource | head -5Repository: 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.goRepository: 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.
| 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.
5008ac6 to
88053fb
Compare
…y, SubscriptionPlan, Subscription and ManagedSecret CRDs support in Gateway Operator
88053fb to
d7bf270
Compare
There was a problem hiding this comment.
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 winContainer
securityContextis 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 winUse the official
mcp/everythingimage instead of a personal namespace tag.
rakhitharr/mcp-everything:v3lives in a personal Docker Hub namespace with a mutable tag. Prefer the officialmcp/everythingimage (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 winReplace 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 winContainer
securityContextis 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
subscriptionPlanIdshould reference the plan's reconciledstatus.id, not the CR name.Per the Go type doc (
subscription_types.go: "set this to the SubscriptionPlan's.status.idonce it has been deployed"),subscriptionPlanIdcarries the gateway-issued UUID, not the CRmetadata.name. As written,demo-plan-apimwill 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'sstatus.idafter 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 winClassify
secretsource.Resolveerrors instead of marking them all non-retryable.Wrapping every error from
secretsource.ResolveinNonRetryableErrorcollapses transient failures (e.g., the referencedSecretnot yet existing or a transient API-server error) into permanent ones, blocking eventual recovery. OnlyErrAmbiguousandErrNotConfiguredare 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 tradeoffAvoid 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
helmtemplate 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 winKeep 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 winUse the resolved plan ID when creating the
Subscription.This block waits for
subscriptionplan.status.id, but the quoted heredoc disables interpolation andsubscriptionPlanIdstays hardcoded todemo-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 winAdd 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 itsProgrammedcondition, 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 winResolve 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 says05b-llm-resources.yamluseshttp://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
Certificateis listed but never demonstrated in this section.The intro names
Certificateas an operator-supported CRD, and the AI summary references agateway/examples/certificate.yaml, but this section adds noCertificateYAML, 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 viavalueFromfrom 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 tradeoffConsider renaming
spec.statusto avoid clashing with thestatussubresource.Having a
spec.statusstring alongside the controller-managed.statussubresource is confusing for consumers (kubectl jsonpath, printer columns, etc.) and easy to misuse. If the management-API field is "status", consider exposing it asspec.subscriptionStatusorspec.lifecycleStatusin the CR while still serializing asstatuson the wire (use a JSON tag mapping). Additionally, since the doc states it mirrors a fixed enum, consider adding anenumconstraint 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 valueUse
http.StatusConflictinstead of the magic number409.Replace the literal
409with the named constant from thenet/httppackage 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 valueNit: 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
📒 Files selected for processing (101)
.github/workflows/operator-integration-test.ymldocs/gateway/kubernetes/gateway-operator.mdgateway/examples/api-key.yamlgateway/examples/certificate.yamlgateway/examples/managed-secret.yamlgateway/examples/subscription-plan.yamlgateway/examples/subscription.yamlkubernetes/gateway-operator/api/v1alpha1/apikey_types.gokubernetes/gateway-operator/api/v1alpha1/certificate_types.gokubernetes/gateway-operator/api/v1alpha1/common_types.gokubernetes/gateway-operator/api/v1alpha1/llmprovider_types.gokubernetes/gateway-operator/api/v1alpha1/llmprovidertemplate_types.gokubernetes/gateway-operator/api/v1alpha1/llmproxy_types.gokubernetes/gateway-operator/api/v1alpha1/mcp_types.gokubernetes/gateway-operator/api/v1alpha1/secret_types.gokubernetes/gateway-operator/api/v1alpha1/subscription_types.gokubernetes/gateway-operator/api/v1alpha1/subscriptionplan_types.gokubernetes/gateway-operator/api/v1alpha1/zz_generated.deepcopy.gokubernetes/gateway-operator/cmd/main.gokubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_apikeys.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_certificates.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproviders.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmprovidertemplates.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_llmproxies.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_managedsecrets.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_mcps.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptionplans.yamlkubernetes/gateway-operator/config/crd/bases/gateway.api-platform.wso2.com_subscriptions.yamlkubernetes/gateway-operator/config/rbac/role.yamlkubernetes/gateway-operator/docs/FEATURE_MANAGEMENT_RESOURCES_CRDs.mdkubernetes/gateway-operator/docs/GATEWAY_API_IMPLEMENTATION_NOTES.mdkubernetes/gateway-operator/docs/GITHUB_DISCUSSION_MANAGEMENT_RESOURCES_CRDs.mdkubernetes/gateway-operator/internal/controller/apikey_controller.gokubernetes/gateway-operator/internal/controller/certificate_controller.gokubernetes/gateway-operator/internal/controller/gateway_infrastructure_overlay_test.gokubernetes/gateway-operator/internal/controller/generic_reconciler.gokubernetes/gateway-operator/internal/controller/httproute_enqueue_test.gokubernetes/gateway-operator/internal/controller/k8s_gateway_controller.gokubernetes/gateway-operator/internal/controller/llmprovider_controller.gokubernetes/gateway-operator/internal/controller/llmprovidertemplate_controller.gokubernetes/gateway-operator/internal/controller/llmproxy_controller.gokubernetes/gateway-operator/internal/controller/managedsecret_controller.gokubernetes/gateway-operator/internal/controller/management_llm_enqueue.gokubernetes/gateway-operator/internal/controller/management_llm_gateway_retry.gokubernetes/gateway-operator/internal/controller/management_llm_gateway_retry_test.gokubernetes/gateway-operator/internal/controller/management_resources_helpers.gokubernetes/gateway-operator/internal/controller/management_secretsource_resolve_error.gokubernetes/gateway-operator/internal/controller/management_upstream_auth_payload.gokubernetes/gateway-operator/internal/controller/management_upstream_auth_payload_test.gokubernetes/gateway-operator/internal/controller/mcp_controller.gokubernetes/gateway-operator/internal/controller/payload_metadata_test.gokubernetes/gateway-operator/internal/controller/subscription_controller.gokubernetes/gateway-operator/internal/controller/subscriptionplan_controller.gokubernetes/gateway-operator/internal/gatewayclient/apikey_client.gokubernetes/gateway-operator/internal/gatewayclient/apikey_client_test.gokubernetes/gateway-operator/internal/gatewayclient/apikey_path_test.gokubernetes/gateway-operator/internal/gatewayclient/certificate_client.gokubernetes/gateway-operator/internal/gatewayclient/envelope_payload_test.gokubernetes/gateway-operator/internal/gatewayclient/paths.gokubernetes/gateway-operator/internal/gatewayclient/resource_client.gokubernetes/gateway-operator/internal/gatewayclient/subscription_client.gokubernetes/gateway-operator/internal/gatewayclient/subscription_plan_client.gokubernetes/gateway-operator/internal/gatewayclient/uuid_roundtrip_test.gokubernetes/gateway-operator/internal/gatewayclient/yaml_payload_envelope.gokubernetes/gateway-operator/internal/secretsource/resolver.gokubernetes/gateway-operator/internal/secretsource/resolver_test.gokubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apigateways.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_apikeys.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_certificates.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproviders.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmprovidertemplates.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_llmproxies.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_managedsecrets.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_mcps.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptionplans.yamlkubernetes/helm/operator-helm-chart/crds/gateway.api-platform.wso2.com_subscriptions.yamlkubernetes/helm/operator-helm-chart/templates/_helpers.tplkubernetes/helm/resources/apigateway-restapi-operator-demo/04-02-restapi.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/04-03-restapi.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05a-management-prerequisites.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05b-02-llm-resources-apikey.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05b-llm-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05b0-mock-openapi-https.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05c-mcp-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05c0-mcp-server-backend.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05e-certificate-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05f-subscription-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/05f0-apikey-resources.yamlkubernetes/helm/resources/apigateway-restapi-operator-demo/README.mdkubernetes/helm/resources/apigateway-restapi-operator-demo/llm-mock-openapi-it/Dockerfilekubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05a-management-prerequisites.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b-llm-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05b0-mock-openapi-https.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c-mcp-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05c0-mcp-server-backend.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05e-certificate-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f-subscription-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/05f0-apikey-resources.yamlkubernetes/helm/resources/apim-apigateway-restapi-operator-demo/README.mdkubernetes/helm/resources/gateway-api-operator-demo/01-gateway-values-configmap.yamlkubernetes/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
| # 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 |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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.
| 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.
| cert.pem: | | ||
| -----BEGIN CERTIFICATE----- | ||
| MIIBszCCAVmgAwIBAgIUTk9ULVJFQUwtQ0VSVC1GT1ItREVNTy1PTkxZMAoGCCqGSM49BAMC | ||
| MBMxETAPBgNVBAMMCGRlbW8tY2VydDAeFw0yNjAxMDEwMDAwMDBaFw0zNjAxMDEwMDAwMDBa | ||
| MBMxETAPBgNVBAMMCGRlbW8tY2VydDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDemoapim | ||
| KcG9N4j7r5hP1h3uQhK6B0fVQjvYq1tV8u0sQY4z6Xv5xX8b4n5kL7u2m9vH0r6mQ8t33N5R8 | ||
| tP9e0qjUzBRMB0GA1UdDgQWBBQdemoapimonlydemoapimonlydemoapimonlydemooajAf | ||
| BgNVHSMEGDAWgBQdemoapimonlydemoapimonlydemoapimonlydemooaMA8GA1UdEwEB/wQF | ||
| MAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhANOTREALCERTFORDEMOONLYNOTFORPRODUCTION | ||
| AiA5NOTREALCERTFORDEMOONLYNOTFORPRODUCTION= | ||
| -----END CERTIFICATE----- |
There was a problem hiding this comment.
🧩 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=goRepository: 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}")
PYEOFRepository: 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 yamlRepository: 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 -30Repository: 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 -40Repository: 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 -50Repository: 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 -60Repository: 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.
Purpose
Implement LLMProviderTemplate, LLMProvider, LLMProxy, MCPProxy, APIKey, SubscriptionPlan, Subscription and ManagedSecret CRDs support in Gateway Operator