From fa66a691209b6f156173a02abc2a3f2746cdf53f Mon Sep 17 00:00:00 2001 From: jordan Date: Sat, 24 Jan 2026 23:28:54 -0700 Subject: [PATCH] fix: Defer health endpoints to Run() for proper middleware ordering Chi requires middleware to be defined before routes. Moved setupHealthEndpoints() from New() to Run() to allow callers to add middleware before routes are registered. Also: - Updated rdev-api.yaml with DB env vars, RBAC, ServiceAccount - Added Dockerfile.api.simple for pre-built binary deployment Co-Authored-By: Claude Opus 4.5 --- Dockerfile.api.simple | 26 ++++++++++++ deployments/k8s/base/rdev-api.yaml | 64 ++++++++++++++++++++++++++++-- pkg/api/app.go | 6 ++- 3 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 Dockerfile.api.simple diff --git a/Dockerfile.api.simple b/Dockerfile.api.simple new file mode 100644 index 0000000..ebb4b25 --- /dev/null +++ b/Dockerfile.api.simple @@ -0,0 +1,26 @@ +# rdev-api - Pre-built binary runtime +FROM alpine:3.19 + +# Install runtime dependencies +RUN apk add --no-cache ca-certificates curl \ + && curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \ + && chmod +x kubectl \ + && mv kubectl /usr/local/bin/ + +# Create non-root user +RUN adduser -D -g '' appuser + +WORKDIR /app + +# Copy pre-built binary +COPY rdev-api . + +# Use non-root user +USER appuser + +EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1 + +ENTRYPOINT ["./rdev-api"] diff --git a/deployments/k8s/base/rdev-api.yaml b/deployments/k8s/base/rdev-api.yaml index be8f462..d24d211 100644 --- a/deployments/k8s/base/rdev-api.yaml +++ b/deployments/k8s/base/rdev-api.yaml @@ -1,5 +1,5 @@ # rdev-api - Go REST API for controlling claudebox pods -# v0.4 - API Server +# v0.5 - API Server with Authentication apiVersion: apps/v1 kind: Deployment @@ -24,7 +24,7 @@ spec: serviceAccountName: rdev-api containers: - name: rdev-api - image: ghcr.io/orchard9/rdev-api:v0.4.0 + image: ghcr.io/orchard9/rdev-api:v0.5.0 imagePullPolicy: Always ports: @@ -43,7 +43,7 @@ spec: httpGet: path: /health port: http - initialDelaySeconds: 5 + initialDelaySeconds: 10 periodSeconds: 30 readinessProbe: @@ -58,6 +58,28 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + - name: PORT + value: "8080" + - name: DB_HOST + value: "postgres.databases.svc" + - name: DB_PORT + value: "5432" + - name: DB_USER + value: "appuser" + - name: DB_NAME + value: "rdev" + - name: DB_SSL_MODE + value: "disable" + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: rdev-credentials + key: DB_PASSWORD + - name: RDEV_ADMIN_KEY + valueFrom: + secretKeyRef: + name: rdev-credentials + key: RDEV_ADMIN_KEY imagePullSecrets: - name: ghcr-secret @@ -79,3 +101,39 @@ spec: - port: 8080 targetPort: http name: http +--- +# ServiceAccount for rdev-api +apiVersion: v1 +kind: ServiceAccount +metadata: + name: rdev-api + namespace: rdev +--- +# Role for rdev-api to exec into claudebox pods +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: rdev-api + namespace: rdev +rules: +- apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] +- apiGroups: [""] + resources: ["pods/exec"] + verbs: ["create"] +--- +# RoleBinding for rdev-api +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: rdev-api + namespace: rdev +subjects: +- kind: ServiceAccount + name: rdev-api + namespace: rdev +roleRef: + kind: Role + name: rdev-api + apiGroup: rbac.authorization.k8s.io diff --git a/pkg/api/app.go b/pkg/api/app.go index cec6b9f..a0a632b 100644 --- a/pkg/api/app.go +++ b/pkg/api/app.go @@ -48,6 +48,7 @@ func WithLogger(logger *slog.Logger) Option { } // New creates a new App instance. +// Note: Call Use() to add middleware before mounting routes. func New(name string, opts ...Option) *App { app := &App{ name: name, @@ -67,7 +68,7 @@ func New(name string, opts ...Option) *App { app.router = chi.NewRouter() app.setupMiddleware() - app.setupHealthEndpoints() + // Health endpoints are set up in Run() to allow middleware to be added first return app } @@ -155,6 +156,9 @@ func (a *App) OnShutdown(fn func(context.Context) error) { // Run starts the HTTP server and blocks until shutdown. func (a *App) Run() { + // Set up health endpoints after all middleware has been added + a.setupHealthEndpoints() + addr := fmt.Sprintf(":%d", a.port) a.server = &http.Server{