Skip to content

feat(gateway): add GatewayGRPCAPI CRD for gRPC service exposure#469

Open
gfyrag wants to merge 5 commits into
mainfrom
feat/gateway-grpc-api
Open

feat(gateway): add GatewayGRPCAPI CRD for gRPC service exposure#469
gfyrag wants to merge 5 commits into
mainfrom
feat/gateway-grpc-api

Conversation

@gfyrag
Copy link
Copy Markdown
Contributor

@gfyrag gfyrag commented May 25, 2026

Summary

  • Introduce a new GatewayGRPCAPI CRD that allows modules to expose gRPC services through the gateway
  • gRPC routing uses fully-qualified protobuf service names (e.g. formance.ledger.v1.LedgerService) instead of HTTP path prefixes (/api/<module>)
  • Traffic is served on the same port (8080) with h2c protocol enabled conditionally when gRPC APIs exist
  • Each GatewayGRPCAPI creates a dedicated <name>-grpc Kubernetes Service to avoid conflicts with HTTP services

Usage

gatewaygrpcapis.Create(ctx, myModule,
    gatewaygrpcapis.WithGRPCServices("formance.mymodule.v1.MyService"),
)
apiVersion: formance.com/v1beta1
kind: GatewayGRPCAPI
metadata:
  name: my-grpc-api
spec:
  stack: my-stack
  name: mymodule
  grpcServices:
    - formance.mymodule.v1.MyService
  port: 8081

Test plan

  • Unit tests: 107/107 pass (3 new GatewayGRPCAPI tests)
  • Golden file test for Caddyfile with gRPC routes
  • Chainsaw e2e test (26-gatewaygrpcapi-sync) covering create/mutate/delete lifecycle
  • CI pipeline

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 25, 2026

Review Change Stack

Warning

Review limit reached

@gfyrag, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 44 minutes and 13 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 705c8cf4-0426-4217-8119-000f6e03cf53

📥 Commits

Reviewing files that changed from the base of the PR and between 2ac109d and 6aa2687.

⛔ Files ignored due to path filters (18)
  • config/crd/bases/formance.com_gatewaygrpcapis.yaml is excluded by !**/*.yaml
  • config/crd/bases/formance.com_gateways.yaml is excluded by !**/*.yaml
  • config/crd/kustomization.yaml is excluded by !**/*.yaml
  • config/rbac/role.yaml is excluded by !**/*.yaml
  • deployment/operator/Pulumi.yaml is excluded by !**/*.yaml
  • deployment/operator/go.mod is excluded by !**/*.mod
  • deployment/operator/go.sum is excluded by !**/*.sum, !**/*.sum
  • docs/09-Configuration reference/settings.catalog.json is excluded by !**/*.json
  • helm/crds/templates/crds/apiextensions.k8s.io_v1_customresourcedefinition_gatewaygrpcapis.formance.com.yaml is excluded by !**/*.yaml
  • helm/crds/templates/crds/apiextensions.k8s.io_v1_customresourcedefinition_gateways.formance.com.yaml is excluded by !**/*.yaml
  • helm/operator/templates/gen/rbac.authorization.k8s.io_v1_clusterrole_formance-manager-role.yaml is excluded by !**/gen/**, !**/*.yaml, !**/gen/**
  • internal/tests/testdata/resources/gateway-controller/configmap-with-ledger-and-grpc.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/chainsaw-test.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/resources/gateway.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/resources/grpcapi-updated.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/resources/grpcapi.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/resources/httpapi-ledger.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/resources/stack.yaml is excluded by !**/*.yaml
📒 Files selected for processing (18)
  • Dockerfile
  • Earthfile
  • api/formance.com/v1beta1/gateway_types.go
  • api/formance.com/v1beta1/gatewaygrpcapi_types.go
  • api/formance.com/v1beta1/zz_generated.deepcopy.go
  • deployment/operator/.gitignore
  • deployment/operator/helpers.go
  • deployment/operator/main.go
  • docs/09-Configuration reference/02-Custom Resource Definitions.md
  • internal/resources/all.go
  • internal/resources/gatewaygrpcapis/create.go
  • internal/resources/gatewaygrpcapis/init.go
  • internal/resources/gateways/Caddyfile.gotpl
  • internal/resources/gateways/caddyfile.go
  • internal/resources/gateways/configuration.go
  • internal/resources/gateways/init.go
  • internal/tests/gateway_controller_test.go
  • internal/tests/gatewaygrpcapi_controller_test.go

Walkthrough

Adds a new GatewayGRPCAPI CRD and controller to expose gRPC services through the gateway, integrates gRPC wiring into Caddyfile generation and gateway status, adds tests and docs, and updates build/deploy tooling (Dockerfile, Earthfile, Pulumi) for building and deploying the operator image.

Changes

gRPC API Gateway Support

Layer / File(s) Summary
GatewayGRPCAPI CRD type definitions
api/formance.com/v1beta1/gatewaygrpcapi_types.go
GatewayGRPCAPISpec holds stack dependency, module name, gRPC service names, and optional port (default 8081). Status tracks readiness and conditions; helper accessors and scheme registration included.
Status field and deep-copy support
api/formance.com/v1beta1/gateway_types.go, api/formance.com/v1beta1/zz_generated.deepcopy.go
GatewayStatus gains SyncGRPCAPIs to list discovered gRPC APIs. Autogenerated deepcopy methods added/updated for the new types and slice.
GatewayGRPCAPI creation and controller reconciliation
internal/resources/all.go, internal/resources/gatewaygrpcapis/create.go, internal/resources/gatewaygrpcapis/init.go
Package blank-import registers the resource; Create builds/applies GatewayGRPCAPI CRs with options. Reconciler creates <name>-grpc Service resources with configured port and owner references.
Gateway configuration with gRPC routing
internal/resources/gateways/caddyfile.go, internal/resources/gateways/Caddyfile.gotpl, internal/resources/gateways/configuration.go, internal/resources/gateways/init.go
Gateway reconciler discovers GatewayGRPCAPIs, syncs names to status, and passes specs into Caddyfile creation. Template enables protocols h1 h2c when GRPC services exist and emits per-service reverse-proxy handlers to <name>-grpc:<port> via h2c.
Tests and documentation
internal/tests/gateway_controller_test.go, internal/tests/gatewaygrpcapi_controller_test.go, docs/09-Configuration reference/02-Custom Resource Definitions.md
Integration tests verify Gateway status sync and Caddyfile with gRPC entries; controller tests validate Service creation, ownership, port, and annotation propagation. Docs add GatewayGRPCAPI schema and GatewayStatus.syncGRPCAPIs.

Build & Deploy Tooling

Layer / File(s) Summary
Dockerfile and Earthfile
Dockerfile, Earthfile, deployment/operator/.gitignore
Replaces prior build wiring with a multi-stage Dockerfile embedding version/build/commit metadata and optional license key; Earthfile build-image now uses the local Dockerfile and forwards build args; .gitignore updated.
Pulumi build helpers and multi-arch image
deployment/operator/helpers.go
Adds Pulumi helpers for computing build version, registry configuration, per-platform Docker builds with cache, multi-arch index creation, and config parsing helpers.
Pulumi operator deployment program
deployment/operator/main.go
New Pulumi main() builds the operator image, applies CRDs, deploys the operator Helm release with configured values, and exports namespace/image/release outputs.

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels: build-images

"I nibble lines of code and hum,
Caddy sings and services drum,
CRDs sprout fields in tidy rows,
Builders bake images, watch them go,
A rabbit hops—deployments done!" 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately summarizes the main change: adding a new GatewayGRPCAPI CRD to enable gRPC service exposure through the gateway.
Description check ✅ Passed The PR description clearly relates to the changeset, providing comprehensive details about the GatewayGRPCAPI CRD, usage examples, and test coverage for the implemented feature.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/gateway-grpc-api

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

❤️ Share

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

@gfyrag gfyrag marked this pull request as ready for review May 25, 2026 14:41
@gfyrag gfyrag requested a review from a team as a code owner May 25, 2026 14:41
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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 `@api/formance.com/v1beta1/gatewaygrpcapi_types.go`:
- Around line 36-40: GatewayGRPCAPIStatus embeds Status which already contains
the ready field, so the duplicate Ready bool in GatewayGRPCAPIStatus causes
ambiguous JSON serialization; remove the Ready bool declaration from the
GatewayGRPCAPIStatus struct and rely on the embedded Status.ready instead, then
search for and update any callers that referenced GatewayGRPCAPIStatus.Ready to
use the embedded Status's readiness accessor or field (i.e., Status.ready or its
getter) to avoid breaking references.
- Around line 30-33: Add CRD numeric validation annotations to the Port field so
Kubernetes rejects out-of-range values; on the Port int32 field in the
GatewayGRPCAPI type (Port) add kubebuilder validation tags to enforce Minimum=1
and Maximum=65535 (e.g., +kubebuilder:validation:Minimum=1 and
+kubebuilder:validation:Maximum=65535) so spec.port maps to a valid Service port
range.

In `@docs/09-Configuration` reference/02-Custom Resource Definitions.md:
- Around line 2177-2180: The GatewayGRPCAPIStatus table contains a duplicated
`ready` row; remove the second redundant row (the third line in the shown diff)
so the schema only documents `ready` once, and ensure the remaining rows
correctly list `ready` _boolean_, `info` _string_, and any other unique status
fields under the GatewayGRPCAPIStatus section.

In `@internal/tests/gateway_controller_test.go`:
- Around line 290-292: The test is calling g.Expect(LoadResource("",
gateway.Name, gateway)) inside Eventually without an assertion, so LoadResource
failures can be ignored; update each call site (e.g., the Eventually closure
that references LoadResource, Gomega, gateway and gateway.Status.SyncGRPCAPIs)
to assert success by appending a matcher like .To(Succeed()) to the Expect call
(replace bare g.Expect(LoadResource(...)) with
g.Expect(LoadResource(...)).To(Succeed()) in the Eventually blocks and the other
mentioned lines).
🪄 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: dbcfc20f-5754-4add-9bd5-60e758335d75

📥 Commits

Reviewing files that changed from the base of the PR and between f379389 and 743a350.

⛔ Files ignored due to path filters (15)
  • config/crd/bases/formance.com_gatewaygrpcapis.yaml is excluded by !**/*.yaml
  • config/crd/bases/formance.com_gateways.yaml is excluded by !**/*.yaml
  • config/crd/kustomization.yaml is excluded by !**/*.yaml
  • config/rbac/role.yaml is excluded by !**/*.yaml
  • docs/09-Configuration reference/settings.catalog.json is excluded by !**/*.json
  • helm/crds/templates/crds/apiextensions.k8s.io_v1_customresourcedefinition_gatewaygrpcapis.formance.com.yaml is excluded by !**/*.yaml
  • helm/crds/templates/crds/apiextensions.k8s.io_v1_customresourcedefinition_gateways.formance.com.yaml is excluded by !**/*.yaml
  • helm/operator/templates/gen/rbac.authorization.k8s.io_v1_clusterrole_formance-manager-role.yaml is excluded by !**/gen/**, !**/*.yaml, !**/gen/**
  • internal/tests/testdata/resources/gateway-controller/configmap-with-ledger-and-grpc.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/chainsaw-test.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/resources/gateway.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/resources/grpcapi-updated.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/resources/grpcapi.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/resources/httpapi-ledger.yaml is excluded by !**/*.yaml
  • tests/e2e/chainsaw/26-gatewaygrpcapi-sync/resources/stack.yaml is excluded by !**/*.yaml
📒 Files selected for processing (13)
  • api/formance.com/v1beta1/gateway_types.go
  • api/formance.com/v1beta1/gatewaygrpcapi_types.go
  • api/formance.com/v1beta1/zz_generated.deepcopy.go
  • docs/09-Configuration reference/02-Custom Resource Definitions.md
  • internal/resources/all.go
  • internal/resources/gatewaygrpcapis/create.go
  • internal/resources/gatewaygrpcapis/init.go
  • internal/resources/gateways/Caddyfile.gotpl
  • internal/resources/gateways/caddyfile.go
  • internal/resources/gateways/configuration.go
  • internal/resources/gateways/init.go
  • internal/tests/gateway_controller_test.go
  • internal/tests/gatewaygrpcapi_controller_test.go

Comment on lines +30 to +33
// Port is the gRPC port on the backend service
//+optional
//+kubebuilder:default:=8081
Port int32 `json:"port,omitempty"`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

What is the valid range for Kubernetes Service.spec.ports[].port?

💡 Result:

Kubernetes Service.spec.ports[].port is the TCP/UDP/SCTP service port exposed by the Service. Its valid range is 1–65535 (inclusive). [1] Kubernetes does not allow 0 for spec.ports[*].port; the API field is an integer port number exposed by the Service. [1]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the file and show the relevant section
ls -la api/formance.com/v1beta1/gatewaygrpcapi_types.go >/dev/null 2>&1 || true
sed -n '1,120p' api/formance.com/v1beta1/gatewaygrpcapi_types.go | nl -ba

# Find all uses of the Port field (likely .Port or spec.port) across the repo
rg -n --hidden --glob '!*vendor/*' "Gateway.*grpc|gatewaygrpc|gatewaygrpcapi|spec\.port|\.Port\b|json:\"port\"|Port int32" .

# Look for existing kubebuilder numeric bound markers to match repo style
rg -n --hidden --glob '!*vendor/*' "kubebuilder:validation:(Minimum|Maximum):=" .

Repository: formancehq/operator

Length of output: 106


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="api/formance.com/v1beta1/gatewaygrpcapi_types.go"
if [ -f "$FILE" ]; then
  sed -n '1,120p' "$FILE" | sed -n '1,120p'
else
  echo "Missing expected file: $FILE"
  echo "Listing matching gatewaygrpcapi_types.go files:"
  find . -name 'gatewaygrpcapi_types.go' -print
fi

# Show the exact Port field vicinity (more focused)
if [ -f "$FILE" ]; then
  rg -n "Port int32" "$FILE" -n
  sed -n '20,60p' "$FILE"
fi

# Find usages of this Port field / json:"port" tags / gateway grpc port conversion
rg -n --hidden --glob '!*vendor/*' --glob '!*dist/*' \
  'json:"port"|spec\.port|\.Port\b|gatewaygrpcapi|Gatewaygrpc|grpc port|Service.*port|spec\.ports' .

# Find existing kubebuilder numeric bound markers in the repo
rg -n --hidden --glob '!*vendor/*' 'kubebuilder:validation:(Minimum|Maximum):=' .

Repository: formancehq/operator

Length of output: 10858


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== CRD base schema (formance.com_gatewaygrpcapis.yaml) ==="
CRD="config/crd/bases/formance.com_gatewaygrpcapis.yaml"
if [ -f "$CRD" ]; then
  # show only port schema area
  rg -n "port" "$CRD" | head -n 80
  # show likely properties section around "port"
  sed -n '1,120p' "$CRD"
  # try to locate port property block by searching for "port:" and show nearby lines
  LINE=$(rg -n "port" "$CRD" | head -n 1 | cut -d: -f1 || true)
  if [ -n "${LINE:-}" ]; then
    START=$((LINE-20)); END=$((LINE+60))
    sed -n "${START},${END}p" "$CRD"
  fi
else
  echo "Missing $CRD"
fi

echo "=== Helm CRD template ==="
HELM_CRD="helm/crds/templates/crds/apiextensions.k8s.io_v1_customresourcedefinition_gatewaygrpcapis.formance.com.yaml"
if [ -f "$HELM_CRD" ]; then
  rg -n "port" "$HELM_CRD" | head -n 80
  LINE=$(rg -n "port" "$HELM_CRD" | head -n 1 | cut -d: -f1 || true)
  if [ -n "${LINE:-}" ]; then
    START=$((LINE-20)); END=$((LINE+60))
    sed -n "${START},${END}p" "$HELM_CRD"
  fi
else
  echo "Missing $HELM_CRD"
fi

echo "=== Code usage ==="
echo "--- internal/resources/gatewaygrpcapis/create.go (port setting) ---"
sed -n '1,120p' internal/resources/gatewaygrpcapis/create.go

echo "--- internal/resources/gatewaygrpcapis/init.go (service creation input) ---"
sed -n '1,120p' internal/resources/gatewaygrpcapis/init.go

echo "--- internal/resources/services/services.go (service spec ports) ---"
sed -n '1,120p' internal/resources/services/services.go

echo "=== Any explicit handling of port==0 ==="
rg -n --hidden --glob '!*vendor/*' --glob '!*dist/*' '== *0|<= *0|minimum|maximum|Port[^A-Za-z_]|cfg\.Port|Spec\.Port' internal | head -n 200

Repository: formancehq/operator

Length of output: 26154


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --hidden --glob '!*vendor/*' 'kubebuilder:validation:' .
rg -n --hidden --glob '!*vendor/*' 'kubebuilder:validation:(Minimum|Maximum):=' .
rg -n --hidden --glob '!*vendor/*' 'kubebuilder:validation:(ExclusiveMinimum|ExclusiveMaximum):=' .

Repository: formancehq/operator

Length of output: 1465


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== shared.go kubebuilder validation section (around Minimum=0 etc.) ==="
sed -n '40,120p' api/formance.com/v1beta1/shared.go | cat

echo "=== Any Maximum= / Minimum= markers in repo ==="
rg -n --hidden --glob '!*vendor/*' 'kubebuilder:validation:(Minimum|Maximum)=' api/formance.com/v1beta1

Repository: formancehq/operator

Length of output: 3943


Add CRD validation bounds for spec.port

GatewayGRPCAPI.spec.port is fed directly into Service.spec.ports[].port, but the generated CRD schema currently imposes no numeric bounds—so values like 0 can be admitted even though Kubernetes Service ports must be in the range 1–65535.

Suggested fix
 	// Port is the gRPC port on the backend service
 	//+optional
 	//+kubebuilder:default:=8081
+	//+kubebuilder:validation:Minimum=1
+	//+kubebuilder:validation:Maximum=65535
 	Port int32 `json:"port,omitempty"`
📝 Committable suggestion

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

Suggested change
// Port is the gRPC port on the backend service
//+optional
//+kubebuilder:default:=8081
Port int32 `json:"port,omitempty"`
// Port is the gRPC port on the backend service
//+optional
//+kubebuilder:default:=8081
//+kubebuilder:validation:Minimum=1
//+kubebuilder:validation:Maximum=65535
Port int32 `json:"port,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 `@api/formance.com/v1beta1/gatewaygrpcapi_types.go` around lines 30 - 33, Add
CRD numeric validation annotations to the Port field so Kubernetes rejects
out-of-range values; on the Port int32 field in the GatewayGRPCAPI type (Port)
add kubebuilder validation tags to enforce Minimum=1 and Maximum=65535 (e.g.,
+kubebuilder:validation:Minimum=1 and +kubebuilder:validation:Maximum=65535) so
spec.port maps to a valid Service port range.

Comment on lines +36 to +40
type GatewayGRPCAPIStatus struct {
Status `json:",inline"`
//+optional
Ready bool `json:"ready,omitempty"`
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove duplicate ready status field to avoid ambiguous serialization.

GatewayGRPCAPIStatus already embeds Status (which contains ready). Re-declaring Ready here creates two fields for the same JSON key and can desync readiness reads/writes.

Suggested fix
 type GatewayGRPCAPIStatus struct {
 	Status `json:",inline"`
-	//+optional
-	Ready bool `json:"ready,omitempty"`
 }
📝 Committable suggestion

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

Suggested change
type GatewayGRPCAPIStatus struct {
Status `json:",inline"`
//+optional
Ready bool `json:"ready,omitempty"`
}
type GatewayGRPCAPIStatus struct {
Status `json:",inline"`
}
🤖 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 `@api/formance.com/v1beta1/gatewaygrpcapi_types.go` around lines 36 - 40,
GatewayGRPCAPIStatus embeds Status which already contains the ready field, so
the duplicate Ready bool in GatewayGRPCAPIStatus causes ambiguous JSON
serialization; remove the Ready bool declaration from the GatewayGRPCAPIStatus
struct and rely on the embedded Status.ready instead, then search for and update
any callers that referenced GatewayGRPCAPIStatus.Ready to use the embedded
Status's readiness accessor or field (i.e., Status.ready or its getter) to avoid
breaking references.

Comment on lines +2177 to +2180
| `ready` _boolean_ | Ready indicates if the resource is seen as completely reconciled | | |
| `info` _string_ | Info can contain any additional like reconciliation errors | | |
| `ready` _boolean_ | | | |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove duplicated ready field row in GatewayGRPCAPIStatus table.

Line 2179 repeats the ready field already documented on Line 2177, which makes the schema table inconsistent.

📝 Proposed fix
 | `ready` _boolean_ | Ready indicates if the resource is seen as completely reconciled |  |  |
 | `info` _string_ | Info can contain any additional like reconciliation errors |  |  |
-| `ready` _boolean_ |  |  |  |
📝 Committable suggestion

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

Suggested change
| `ready` _boolean_ | Ready indicates if the resource is seen as completely reconciled | | |
| `info` _string_ | Info can contain any additional like reconciliation errors | | |
| `ready` _boolean_ | | | |
| `ready` _boolean_ | Ready indicates if the resource is seen as completely reconciled | | |
| `info` _string_ | Info can contain any additional like reconciliation errors | | |
🤖 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/09-Configuration` reference/02-Custom Resource Definitions.md around
lines 2177 - 2180, The GatewayGRPCAPIStatus table contains a duplicated `ready`
row; remove the second redundant row (the third line in the shown diff) so the
schema only documents `ready` once, and ensure the remaining rows correctly list
`ready` _boolean_, `info` _string_, and any other unique status fields under the
GatewayGRPCAPIStatus section.

Comment on lines +290 to +292
Eventually(func(g Gomega) []string {
g.Expect(LoadResource("", gateway.Name, gateway))
return gateway.Status.SyncGRPCAPIs
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP 'g\.Expect\(LoadResource\([^)]*\)\)\s*$' --type go

Repository: formancehq/operator

Length of output: 365


🏁 Script executed:

#!/bin/bash
rg -nP 'g\.Expect\(LoadResource\([^)]*\)\)\s*$' --type go

Repository: formancehq/operator

Length of output: 365


🏁 Script executed:

#!/bin/bash
rg -nP 'g\.Expect\(LoadResource\([^)]*\)\)\s*$' --type go

Repository: formancehq/operator

Length of output: 365


🏁 Script executed:

#!/bin/bash
rg -nP 'g\.Expect\(LoadResource\([^)]*\)\)\s*$' --type go

Repository: formancehq/operator

Length of output: 365


🏁 Script executed:

#!/bin/bash
rg -nP 'g\.Expect\(LoadResource\([^)]*\)\)\s*$' --type go

Repository: formancehq/operator

Length of output: 365


🏁 Script executed:

#!/bin/bash
rg -nP 'g\.Expect\(LoadResource\([^)]*\)\)\s*$' --type go

Repository: formancehq/operator

Length of output: 365


🏁 Script executed:

#!/bin/bash
rg -nP 'g\.Expect\(LoadResource\([^)]*\)\)\s*$' --type go

Repository: formancehq/operator

Length of output: 365


🏁 Script executed:

#!/bin/bash
rg -nP 'g\.Expect\(LoadResource\([^)]*\)\)\s*$' --type go

Repository: formancehq/operator

Length of output: 365


🏁 Script executed:

#!/bin/bash
rg -nP 'g\.Expect\(LoadResource\([^)]*\)\)\s*$' --type go

Repository: formancehq/operator

Length of output: 365


🏁 Script executed:

#!/bin/bash
rg -nP 'g\.Expect\(LoadResource\([^)]*\)\)\s*$' --type go

Repository: formancehq/operator

Length of output: 365


🏁 Script executed:

#!/bin/bash
rg -nP 'g\.Expect\(LoadResource\([^)]*\)\)\s*$' --type go

Repository: formancehq/operator

Length of output: 365


🏁 Script executed:

#!/bin/bash
rg -nP 'g\.Expect\(LoadResource\([^)]*\)\)\s*$' --type go

Repository: formancehq/operator

Length of output: 365


🏁 Script executed:

#!/bin/bash
rg -nP 'g\.Expect\(LoadResource\([^)]*\)\)\s*$' --type go

Repository: formancehq/operator

Length of output: 365


🏁 Script executed:

#!/bin/bash
rg -nP 'g\.Expect\(LoadResource\([^)]*\)\)\s*$' --type go

Repository: formancehq/operator

Length of output: 365


Assert LoadResource failures inside Eventually (and other call sites).

internal/tests/gateway_controller_test.go has matcher-less g.Expect(LoadResource("", gateway.Name, gateway)) at lines 91, 258, and 291, so LoadResource errors aren’t asserted during polling and can be masked. Change each to assert success (e.g., .To(Succeed())).

✅ Proposed fix (gRPC `Eventually` case around lines 290-292)
 Eventually(func(g Gomega) []string {
-	g.Expect(LoadResource("", gateway.Name, gateway))
+	g.Expect(LoadResource("", gateway.Name, gateway)).To(Succeed())
 	return gateway.Status.SyncGRPCAPIs
 }).Should(ContainElements(grpcAPI.Spec.Name))
📝 Committable suggestion

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

Suggested change
Eventually(func(g Gomega) []string {
g.Expect(LoadResource("", gateway.Name, gateway))
return gateway.Status.SyncGRPCAPIs
Eventually(func(g Gomega) []string {
g.Expect(LoadResource("", gateway.Name, gateway)).To(Succeed())
return gateway.Status.SyncGRPCAPIs
🤖 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 `@internal/tests/gateway_controller_test.go` around lines 290 - 292, The test
is calling g.Expect(LoadResource("", gateway.Name, gateway)) inside Eventually
without an assertion, so LoadResource failures can be ignored; update each call
site (e.g., the Eventually closure that references LoadResource, Gomega, gateway
and gateway.Status.SyncGRPCAPIs) to assert success by appending a matcher like
.To(Succeed()) to the Expect call (replace bare g.Expect(LoadResource(...)) with
g.Expect(LoadResource(...)).To(Succeed()) in the Eventually blocks and the other
mentioned lines).

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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 `@deployment/operator/main.go`:
- Around line 34-49: After calling filepath.Glob to populate crdFiles, add an
explicit empty-check and return an error if no files were found: check
len(crdFiles) == 0 and return a descriptive fmt.Errorf (e.g., "no CRD files
found in config/crd/bases") before proceeding to the loop that creates resources
(the block using variables crdFiles, name, and k8syaml.NewConfigFile); this
ensures the function fails fast when no CRDs are discovered.
- Around line 63-73: The code reads the secret `licence-token` using cfg.Get and
wraps it with pulumi.String, which bypasses Pulumi secret propagation; change
the retrieval to use cfg.GetSecret or cfg.RequireSecret to get a pulumi.Secret
Output and pass that Output directly into the Helm values map (the
`licenceValues` / `"token"` entry) instead of converting to a plain string so
the secret remains encrypted/redacted in state and outputs.

In `@Dockerfile`:
- Around line 28-34: The runtime stage currently runs as root; create a
dedicated non-root user/group (e.g., operator:operator) in the final stage,
chown the installed binary (/usr/bin/operator) to that user, and add a USER
operator line before ENTRYPOINT so the container drops root privileges;
reference the final-stage Dockerfile lines that copy the binary (COPY
--from=builder /usr/bin/operator /usr/bin/operator) and the ENTRYPOINT
["/usr/bin/operator"] when making the changes.
🪄 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: f5e43e92-db75-4fdf-b8db-6c4fa0e40f5e

📥 Commits

Reviewing files that changed from the base of the PR and between 743a350 and 2ac109d.

⛔ Files ignored due to path filters (3)
  • deployment/operator/Pulumi.yaml is excluded by !**/*.yaml
  • deployment/operator/go.mod is excluded by !**/*.mod
  • deployment/operator/go.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (5)
  • Dockerfile
  • Earthfile
  • deployment/operator/.gitignore
  • deployment/operator/helpers.go
  • deployment/operator/main.go
✅ Files skipped from review due to trivial changes (1)
  • deployment/operator/.gitignore

Comment on lines +34 to +49
crdFiles, err := filepath.Glob(filepath.Join("..", "..", "config", "crd", "bases", "*.yaml"))
if err != nil {
return fmt.Errorf("failed to glob CRD files: %w", err)
}

var crds []pulumi.Resource
for _, crdFile := range crdFiles {
name := strings.TrimSuffix(filepath.Base(crdFile), filepath.Ext(crdFile))
crd, crdErr := k8syaml.NewConfigFile(ctx, name+"-crd", &k8syaml.ConfigFileArgs{
File: crdFile,
}, pulumi.Provider(k8sProvider))
if crdErr != nil {
return fmt.Errorf("failed to apply CRD %s: %w", name, crdErr)
}
crds = append(crds, crd)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast when no CRD files are discovered.

If the CRD glob resolves to zero files, deployment continues, but Helm CRD creation is disabled later. Add an explicit empty-check and return an error.

Suggested fix
 		crdFiles, err := filepath.Glob(filepath.Join("..", "..", "config", "crd", "bases", "*.yaml"))
 		if err != nil {
 			return fmt.Errorf("failed to glob CRD files: %w", err)
 		}
+		if len(crdFiles) == 0 {
+			return fmt.Errorf("no CRD files found in config/crd/bases; refusing to deploy with operator-crds.create=false")
+		}
 
 		var crds []pulumi.Resource
📝 Committable suggestion

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

Suggested change
crdFiles, err := filepath.Glob(filepath.Join("..", "..", "config", "crd", "bases", "*.yaml"))
if err != nil {
return fmt.Errorf("failed to glob CRD files: %w", err)
}
var crds []pulumi.Resource
for _, crdFile := range crdFiles {
name := strings.TrimSuffix(filepath.Base(crdFile), filepath.Ext(crdFile))
crd, crdErr := k8syaml.NewConfigFile(ctx, name+"-crd", &k8syaml.ConfigFileArgs{
File: crdFile,
}, pulumi.Provider(k8sProvider))
if crdErr != nil {
return fmt.Errorf("failed to apply CRD %s: %w", name, crdErr)
}
crds = append(crds, crd)
}
crdFiles, err := filepath.Glob(filepath.Join("..", "..", "config", "crd", "bases", "*.yaml"))
if err != nil {
return fmt.Errorf("failed to glob CRD files: %w", err)
}
if len(crdFiles) == 0 {
return fmt.Errorf("no CRD files found in config/crd/bases; refusing to deploy with operator-crds.create=false")
}
var crds []pulumi.Resource
for _, crdFile := range crdFiles {
name := strings.TrimSuffix(filepath.Base(crdFile), filepath.Ext(crdFile))
crd, crdErr := k8syaml.NewConfigFile(ctx, name+"-crd", &k8syaml.ConfigFileArgs{
File: crdFile,
}, pulumi.Provider(k8sProvider))
if crdErr != nil {
return fmt.Errorf("failed to apply CRD %s: %w", name, crdErr)
}
crds = append(crds, crd)
}
🤖 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 `@deployment/operator/main.go` around lines 34 - 49, After calling
filepath.Glob to populate crdFiles, add an explicit empty-check and return an
error if no files were found: check len(crdFiles) == 0 and return a descriptive
fmt.Errorf (e.g., "no CRD files found in config/crd/bases") before proceeding to
the loop that creates resources (the block using variables crdFiles, name, and
k8syaml.NewConfigFile); this ensures the function fails fast when no CRDs are
discovered.

Comment on lines +63 to +73
licenceToken := cfg.Get("licence-token")
if licenceToken != "" {
licenceIssuer := cfg.Get("licence-issuer")
if licenceIssuer == "" {
licenceIssuer = "https://license.formance.cloud/keys"
}
licenceValues = pulumi.Map{
"createSecret": pulumi.Bool(true),
"token": pulumi.String(licenceToken),
"issuer": pulumi.String(licenceIssuer),
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Pulumi Go SDK: what is the difference between config.Config.Get and config.Config.GetSecret for secret config values, and how does each affect state redaction?

💡 Result:

In the Pulumi Go SDK, the primary difference between config.Config.Get and config.Config.GetSecret lies in how the retrieved value is handled by the Pulumi engine regarding state encryption and redaction [1][2]. config.Config.Get This method retrieves a configuration value as a plain string (or other standard type) [3][2]. If the underlying value in your configuration is encrypted (a secret), using Get will decrypt it for use in your program [1]. However, because it returns a plain value rather than an Output, Pulumi cannot track the "secret-ness" of that data through your program [1][2]. Consequently, any resource inputs or outputs derived from this value may be stored in plaintext in your state file and could appear in plain text in terminal output or logs [1][4]. config.Config.GetSecret This method retrieves a configuration value and returns it wrapped in a Pulumi Output (e.g., pulumi.StringOutput) [5][3]. This Output object carries metadata indicating that the underlying value is sensitive [2][4]. Effect on State Redaction The critical advantage of GetSecret is that it enables Pulumi's automated secrets propagation [4]: 1. Encryption in State: Because the value is wrapped in a secret Output, the Pulumi engine knows to treat it as sensitive [2]. If this value is passed to a resource property, Pulumi will encrypt it before storing it in the stack's state file [4][6]. 2. Propagation: Any values derived from this Output (such as strings concatenated with the secret) are also automatically marked as secrets, ensuring the sensitive nature of the data is maintained throughout your infrastructure graph [4]. 3. Redaction in CLI/Logs: Pulumi will automatically redact the value in CLI output, logs, and other diagnostic information, replacing it with placeholders like [secret] [4][7]. Best Practice Even if a configuration value is stored as a secret, you should always access it using GetSecret or RequireSecret [1]. Using standard getters like Get is discouraged because it breaks the tracking of sensitive data, leading to potential accidental exposure in your state files and logs [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="deployment/operator/main.go"
echo "== File =="
ls -la "$FILE"

echo "== Lines 1-140 =="
nl -ba "$FILE" | sed -n '1,140p'

echo "== Search for licence-token and CRD glob =="
rg -n --context=2 "licence-token|licenceValues|crd|glob|operator-crds\.create|Create.*CRD" "$FILE" || true

echo "== Lines 40-120 =="
nl -ba "$FILE" | sed -n '40,120p'

echo "== Lines 120-220 =="
nl -ba "$FILE" | sed -n '120,220p'

Repository: formancehq/operator

Length of output: 230


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="deployment/operator/main.go"
echo "== File =="
ls -la "$FILE"

echo "== Lines 1-220 =="
awk '{printf "%5d\t%s\n", NR, $0}' "$FILE" | sed -n '1,220p'

echo "== Search for relevant tokens =="
rg -n --context=2 "licence-token|licenceValues|licenceIssuer|operator-crds\.create|crd|glob|Create.*CRD" "$FILE" || true

Repository: formancehq/operator

Length of output: 6509


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check CRD glob directory contents =="
ls -la config/crd/bases 2>/dev/null || true

echo "== Count CRD base YAMLs (for glob '..' '..' config/crd/bases/*.yaml relative to deployment/operator/) =="
# run from deployment/operator so relative paths match the code
cd deployment/operator
echo "CWD: $(pwd)"
echo "Glob: ../../config/crd/bases/*.yaml"
shopt -s nullglob
files=(../../config/crd/bases/*.yaml)
echo "Matches: ${`#files`[@]}"
printf '%s\n' "${files[@]}" | head -n 50

echo "== Search for licence-token secret-aware access =="
cd ..
rg -n --hidden --no-ignore-vcs "licence-token|GetSecret\\(\"licence-token\"\\)|RequireSecret\\(\"licence-token\"\\)" -S . || true

Repository: formancehq/operator

Length of output: 3886


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show Pulumi.yaml around licence-token config
FILE="deployment/operator/Pulumi.yaml"
if [ -f "$FILE" ]; then
  echo "== Contents: $FILE (lines 1-120) =="
  awk 'NR>=1 && NR<=140 {printf "%5d\t%s\n", NR, $0}' "$FILE"
fi

echo "== Search for other secret-aware config accessors in repo =="
rg -n --hidden --no-ignore-vcs "GetSecret\\(|RequireSecret\\(|secret\\s*:\\s*true" . || true

echo "== Show remaining helper usage in main.go (tail) =="
awk 'NR>=120 && NR<=220 {printf "%5d\t%s\n", NR, $0}' deployment/operator/main.go

Repository: formancehq/operator

Length of output: 3108


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --hidden --no-ignore-vcs "licence-token" . || true
rg -n --hidden --no-ignore-vcs "licenceValues|licenceIssuer|createSecret" deployment/operator -S || true

# show the exact helpers for getImagePullSecrets / config helpers
sed -n '1,220p' deployment/operator/helpers.go

Repository: formancehq/operator

Length of output: 6655


Use secret-aware config access for licence-token.

deployment/operator/Pulumi.yaml marks licence-token as secret: true, but deployment/operator/main.go reads it with cfg.Get("licence-token") and passes it into Helm values as pulumi.String(licenceToken). This bypasses Pulumi secret propagation/redaction and can leak the token into stack state/outputs. Use cfg.GetSecret/cfg.RequireSecret and pass the secret Output directly to the Helm global.licence.token value.

🤖 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 `@deployment/operator/main.go` around lines 63 - 73, The code reads the secret
`licence-token` using cfg.Get and wraps it with pulumi.String, which bypasses
Pulumi secret propagation; change the retrieval to use cfg.GetSecret or
cfg.RequireSecret to get a pulumi.Secret Output and pass that Output directly
into the Helm values map (the `licenceValues` / `"token"` entry) instead of
converting to a plain string so the secret remains encrypted/redacted in state
and outputs.

Comment thread Dockerfile
Comment on lines +28 to +34
FROM alpine:3.20

RUN apk update && apk add --no-cache ca-certificates curl

ENTRYPOINT ["/usr/bin/operator"]

COPY --from=builder /usr/bin/operator /usr/bin/operator
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Run the runtime container as a non-root user.

The final image runs as root by default. Please create a dedicated user/group and switch to it in the runtime stage.

Suggested fix
 FROM alpine:3.20
 
-RUN apk update && apk add --no-cache ca-certificates curl
+RUN apk update && apk add --no-cache ca-certificates curl \
+    && addgroup -S operator \
+    && adduser -S -G operator operator
 
 ENTRYPOINT ["/usr/bin/operator"]
 
 COPY --from=builder /usr/bin/operator /usr/bin/operator
+USER operator
📝 Committable suggestion

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

Suggested change
FROM alpine:3.20
RUN apk update && apk add --no-cache ca-certificates curl
ENTRYPOINT ["/usr/bin/operator"]
COPY --from=builder /usr/bin/operator /usr/bin/operator
FROM alpine:3.20
RUN apk update && apk add --no-cache ca-certificates curl \
&& addgroup -S operator \
&& adduser -S -G operator operator
ENTRYPOINT ["/usr/bin/operator"]
COPY --from=builder /usr/bin/operator /usr/bin/operator
USER operator
🤖 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 `@Dockerfile` around lines 28 - 34, The runtime stage currently runs as root;
create a dedicated non-root user/group (e.g., operator:operator) in the final
stage, chown the installed binary (/usr/bin/operator) to that user, and add a
USER operator line before ENTRYPOINT so the container drops root privileges;
reference the final-stage Dockerfile lines that copy the binary (COPY
--from=builder /usr/bin/operator /usr/bin/operator) and the ENTRYPOINT
["/usr/bin/operator"] when making the changes.

gfyrag and others added 4 commits May 28, 2026 17:33
Introduce a new GatewayGRPCAPI custom resource that allows modules to
expose gRPC services through the gateway, following the same pattern as
GatewayHTTPAPI.

gRPC routing uses fully-qualified protobuf service names
(e.g. formance.ledger.v1.LedgerService) instead of HTTP path prefixes.
Traffic is served on the same port (8080) with h2c protocol enabled
conditionally when gRPC APIs are registered.

Each GatewayGRPCAPI creates a dedicated <name>-grpc Kubernetes Service
pointing to the module's gRPC port, avoiding conflicts with existing
HTTP services.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The deletion step was timing out on K8s 1.34 because the script
timeout (2m) was being consumed by kubectl wait --for=delete. Increase
the script timeout to 5m and the wait to 3m for more headroom.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract the operator image build from Earthly into a standard
Dockerfile so it can be used by both Earthly (via FROM DOCKERFILE)
and the new Pulumi deployment app.

The Pulumi app (deployment/operator/) builds the operator image,
applies CRDs, and deploys the operator via its Helm chart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Helm release creates the namespace itself via createNamespace.
Defaults to formance-system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gfyrag gfyrag force-pushed the feat/gateway-grpc-api branch from 4557660 to c0fdbff Compare May 28, 2026 15:35
Comment thread Earthfile
Comment on lines 47 to 57
build-image:
FROM core+final-image
ENTRYPOINT ["/usr/bin/operator"]
COPY --pass-args (+compile/main) /usr/bin/operator
ARG LICENCE_PUBLIC_KEY_B64=""
ARG EARTHLY_BUILD_SHA
FROM DOCKERFILE \
--build-arg LICENCE_PUBLIC_KEY_B64=$LICENCE_PUBLIC_KEY_B64 \
--build-arg VERSION=$tag \
--build-arg COMMIT=$EARTHLY_BUILD_SHA \
-f Dockerfile .
ARG REPOSITORY=ghcr.io
ARG tag=latest
DO --pass-args core+SAVE_IMAGE --COMPONENT=operator --TAG=$tag
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

OH we still have a earthfile here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately yes ^^

The Docker build context may not include .git, causing go build to
fail with "error obtaining VCS status". Disable VCS stamping since
version info is already passed via ldflags.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants