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{