Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
FROM golang:1.26-alpine AS builder

RUN apk add --no-cache git

WORKDIR /src

COPY go.mod go.sum ./
RUN go mod download

COPY . .

ARG VERSION=latest
ARG COMMIT=unknown
ARG LICENCE_PUBLIC_KEY_B64=""

WORKDIR /src/cmd

RUN set -e; \
LDFLAGS="-X github.com/formancehq/operator/v3/cmd.Version=${VERSION} \
-X github.com/formancehq/operator/v3/cmd.BuildDate=$(date +%s) \
-X github.com/formancehq/operator/v3/cmd.Commit=${COMMIT}"; \
if [ -n "$LICENCE_PUBLIC_KEY_B64" ]; then \
LICENCE_PUBLIC_KEY="$(printf '%s' "$LICENCE_PUBLIC_KEY_B64" | base64 -d)"; \
LDFLAGS="${LDFLAGS} -X 'github.com/formancehq/go-libs/v5/pkg/authn/licence.formancePublicKey=${LICENCE_PUBLIC_KEY}'"; \
fi; \
CGO_ENABLED=0 go build -buildvcs=false -o /usr/bin/operator -ldflags="${LDFLAGS}" .

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
Comment on lines +28 to +34
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.

10 changes: 7 additions & 3 deletions Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,13 @@ compile:
SAVE ARTIFACT main

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
Comment on lines 47 to 57
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 ^^

Expand Down
3 changes: 3 additions & 0 deletions api/formance.com/v1beta1/gateway_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ type GatewayStatus struct {
// Detected http apis. See [GatewayHTTPAPI](#gatewayhttpapi)
//+optional
SyncHTTPAPIs []string `json:"syncHTTPAPIs"`
// Detected grpc apis. See [GatewayGRPCAPI](#gatewaygrpcapi)
//+optional
SyncGRPCAPIs []string `json:"syncGRPCAPIs,omitempty"`
}

//+kubebuilder:object:root=true
Expand Down
88 changes: 88 additions & 0 deletions api/formance.com/v1beta1/gatewaygrpcapi_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
Copyright 2022.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1beta1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type GatewayGRPCAPISpec struct {
StackDependency `json:",inline"`
// Name indicates the module name (e.g. "ledger")
Name string `json:"name"`
// GRPCServices is the list of fully-qualified gRPC service names
// exposed by this module (e.g. "formance.ledger.v1.LedgerService")
GRPCServices []string `json:"grpcServices"`
// Port is the gRPC port on the backend service
//+optional
//+kubebuilder:default:=8081
Port int32 `json:"port,omitempty"`
Comment on lines +30 to +33
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.

}

type GatewayGRPCAPIStatus struct {
Status `json:",inline"`
//+optional
Ready bool `json:"ready,omitempty"`
}
Comment on lines +36 to +40
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.


//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:resource:scope=Cluster
//+kubebuilder:printcolumn:name="Stack",type=string,JSONPath=".spec.stack",description="Stack"
//+kubebuilder:printcolumn:name="Ready",type=string,JSONPath=".status.ready",description="Ready"

// GatewayGRPCAPI is the Schema for the GRPCAPIs API
type GatewayGRPCAPI struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec GatewayGRPCAPISpec `json:"spec,omitempty"`
Status GatewayGRPCAPIStatus `json:"status,omitempty"`
}

func (in *GatewayGRPCAPI) SetReady(b bool) {
in.Status.Ready = b
}

func (in *GatewayGRPCAPI) IsReady() bool {
return in.Status.Ready
}

func (in *GatewayGRPCAPI) SetError(s string) {
in.Status.Info = s
}

func (a GatewayGRPCAPI) GetStack() string {
return a.Spec.Stack
}

func (in *GatewayGRPCAPI) GetConditions() *Conditions {
return &in.Status.Conditions
}

//+kubebuilder:object:root=true

// GatewayGRPCAPIList contains a list of GatewayGRPCAPI
type GatewayGRPCAPIList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []GatewayGRPCAPI `json:"items"`
}

func init() {
SchemeBuilder.Register(&GatewayGRPCAPI{}, &GatewayGRPCAPIList{})
}
101 changes: 101 additions & 0 deletions api/formance.com/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading