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 <noreply@anthropic.com>
This commit is contained in:
jordan 2026-01-24 23:28:54 -07:00
parent d2de49a591
commit fa66a69120
3 changed files with 92 additions and 4 deletions

26
Dockerfile.api.simple Normal file
View File

@ -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"]

View File

@ -1,5 +1,5 @@
# rdev-api - Go REST API for controlling claudebox pods # rdev-api - Go REST API for controlling claudebox pods
# v0.4 - API Server # v0.5 - API Server with Authentication
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
@ -24,7 +24,7 @@ spec:
serviceAccountName: rdev-api serviceAccountName: rdev-api
containers: containers:
- name: rdev-api - name: rdev-api
image: ghcr.io/orchard9/rdev-api:v0.4.0 image: ghcr.io/orchard9/rdev-api:v0.5.0
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
@ -43,7 +43,7 @@ spec:
httpGet: httpGet:
path: /health path: /health
port: http port: http
initialDelaySeconds: 5 initialDelaySeconds: 10
periodSeconds: 30 periodSeconds: 30
readinessProbe: readinessProbe:
@ -58,6 +58,28 @@ spec:
valueFrom: valueFrom:
fieldRef: fieldRef:
fieldPath: metadata.namespace 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: imagePullSecrets:
- name: ghcr-secret - name: ghcr-secret
@ -79,3 +101,39 @@ spec:
- port: 8080 - port: 8080
targetPort: http targetPort: http
name: 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

View File

@ -48,6 +48,7 @@ func WithLogger(logger *slog.Logger) Option {
} }
// New creates a new App instance. // New creates a new App instance.
// Note: Call Use() to add middleware before mounting routes.
func New(name string, opts ...Option) *App { func New(name string, opts ...Option) *App {
app := &App{ app := &App{
name: name, name: name,
@ -67,7 +68,7 @@ func New(name string, opts ...Option) *App {
app.router = chi.NewRouter() app.router = chi.NewRouter()
app.setupMiddleware() app.setupMiddleware()
app.setupHealthEndpoints() // Health endpoints are set up in Run() to allow middleware to be added first
return app return app
} }
@ -155,6 +156,9 @@ func (a *App) OnShutdown(fn func(context.Context) error) {
// Run starts the HTTP server and blocks until shutdown. // Run starts the HTTP server and blocks until shutdown.
func (a *App) Run() { func (a *App) Run() {
// Set up health endpoints after all middleware has been added
a.setupHealthEndpoints()
addr := fmt.Sprintf(":%d", a.port) addr := fmt.Sprintf(":%d", a.port)
a.server = &http.Server{ a.server = &http.Server{