Skip to content

Commit 3cfa3d2

Browse files
committed
align toolchain, better logging, secrets, etc.
Signed-off-by: amanycodes <[email protected]>
1 parent f0ca2ea commit 3cfa3d2

File tree

9 files changed

+203
-32
lines changed

9 files changed

+203
-32
lines changed

.dockerignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
bin/
2+
vendor/
3+
.git/
4+
.gitignore
5+
.idea/
6+
.vscode/
7+
*.iml
8+
*.log
9+
*.swp
10+
*.tmp
11+
*.test
12+
*.out
13+
Dockerfile
14+
deployments/k8s/
15+
tests/

.github/workflows/ci.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
branches: [ main, master ]
6+
pull_request:
7+
branches: [ main, master ]
8+
9+
jobs:
10+
build-test:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: actions/setup-go@v5
15+
with:
16+
go-version: '1.23.x'
17+
- name: Lint
18+
uses: golangci/golangci-lint-action@v6
19+
with:
20+
version: latest
21+
- name: Build
22+
run: go build ./cmd/gateway
23+
- name: Test
24+
run: go test ./... -v
25+
docker-build:
26+
runs-on: ubuntu-latest
27+
needs: build-test
28+
steps:
29+
- uses: actions/checkout@v4
30+
- name: Set up QEMU
31+
uses: docker/setup-qemu-action@v3
32+
- name: Set up Docker Buildx
33+
uses: docker/setup-buildx-action@v3
34+
- name: Build image
35+
uses: docker/build-push-action@v6
36+
with:
37+
context: .
38+
file: deployments/docker/Dockerfile
39+
platforms: linux/amd64
40+
push: false
41+
tags: amanycodes/pine-gate:dev

configs/config.example.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# configs/config.example.yaml
2+
server:
3+
port: 8080
4+
auth:
5+
api_key: "dev-key"
6+
models:
7+
- name: "dummy"
8+
type: "echo"

deployments/docker/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.22 AS build
1+
FROM golang:1.23 AS build
22
WORKDIR /app
33
COPY . .
44
RUN CGO_ENABLED=0 GOOS=linux go build -o /go-ai-gateway ./cmd/gateway

deployments/k8s/deployment.yaml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: pine-gate
5+
spec:
6+
replicas: 1
7+
selector:
8+
matchLabels:
9+
app: pine-gate
10+
template:
11+
metadata:
12+
labels:
13+
app: pine-gate
14+
spec:
15+
containers:
16+
- name: pine-gate
17+
image: amanycodes/pine-gate:dev
18+
ports:
19+
- containerPort: 8080
20+
env:
21+
- name: API_KEY
22+
valueFrom:
23+
secretKeyRef:
24+
name: pine-gate-secret
25+
key: api-key
26+
readinessProbe:
27+
httpGet:
28+
path: /healthz
29+
port: 8080
30+
initialDelaySeconds: 3
31+
periodSeconds: 5
32+
livenessProbe:
33+
httpGet:
34+
path: /healthz
35+
port: 8080
36+
initialDelaySeconds: 8
37+
periodSeconds: 10
38+
resources:
39+
requests:
40+
cpu: "100m"
41+
memory: "128Mi"
42+
limits:
43+
cpu: "500m"
44+
memory: "512Mi"
45+
---
46+
apiVersion: v1
47+
kind: Service
48+
metadata:
49+
name: pine-gate
50+
spec:
51+
selector:
52+
app: pine-gate
53+
ports:
54+
- port: 80
55+
targetPort: 8080
56+
name: http

deployments/k8s/secret.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
apiVersion: v1
2+
kind: Secret
3+
metadata:
4+
name: pine-gate-secret
5+
type: Opaque
6+
stringData:
7+
api-key: dev-key

go.mod

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
module github.com/amanycoes/pine-gate
22

3-
go 1.24.2
3+
go 1.23.0
4+
5+
toolchain go1.24.2
46

57
require (
68
github.com/go-chi/chi/v5 v5.2.3
9+
github.com/google/uuid v1.6.0
710
github.com/prometheus/client_golang v1.23.2
811
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
912
go.opentelemetry.io/otel v1.38.0
@@ -18,7 +21,6 @@ require (
1821
github.com/felixge/httpsnoop v1.0.4 // indirect
1922
github.com/go-logr/logr v1.4.3 // indirect
2023
github.com/go-logr/stdr v1.2.2 // indirect
21-
github.com/google/uuid v1.6.0 // indirect
2224
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
2325
github.com/prometheus/client_model v0.6.2 // indirect
2426
github.com/prometheus/common v0.66.1 // indirect

pkg/api/handlers.go

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,25 @@ type completionResp struct {
2020
}
2121

2222
func HandleCompletions(m *telemetry.Metrics) http.HandlerFunc {
23-
return func(w http.ResponseWriter, r *http.Request) {
24-
timer := prometheusObserve(m, "/v1/completions", r.Method)
25-
defer timer()
23+
return func(w http.ResponseWriter, r *http.Request) {
24+
timer := prometheusObserve(m, "/v1/completions", r.Method)
25+
defer timer()
2626

27-
var req completionReq
28-
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
29-
recordErr(m, "/v1/completions", r.Method, http.StatusBadRequest)
30-
http.Error(w, "bad request", http.StatusBadRequest)
31-
return
32-
}
33-
resp := completionResp{
34-
Model: ifEmpty(req.Model, "dummy"),
35-
Output: fmt.Sprintf("echo: %s", req.Prompt),
36-
}
37-
_ = json.NewEncoder(w).Encode(resp)
38-
}
27+
w.Header().Set("Content-Type", "application/json")
28+
29+
var req completionReq
30+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
31+
recordErr(m, "/v1/completions", r.Method, http.StatusBadRequest)
32+
http.Error(w, "bad request", http.StatusBadRequest)
33+
return
34+
}
35+
resp := completionResp{
36+
Model: ifEmpty(req.Model, "dummy"),
37+
Output: fmt.Sprintf("echo: %s", req.Prompt),
38+
}
39+
w.WriteHeader(http.StatusOK)
40+
_ = json.NewEncoder(w).Encode(resp)
41+
}
3942
}
4043

4144
func HandleSSEStream(m *telemetry.Metrics) http.HandlerFunc {

pkg/middlewares/logging.go

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,61 @@
11
package middlewares
22

33
import (
4-
"net/http"
5-
"time"
4+
"net/http"
5+
"time"
66

7-
"go.uber.org/zap"
7+
"github.com/google/uuid"
8+
"go.uber.org/zap"
89
)
910

11+
type loggingResponseWriter struct {
12+
http.ResponseWriter
13+
status int
14+
size int
15+
}
16+
17+
func (lrw *loggingResponseWriter) WriteHeader(code int) {
18+
lrw.status = code
19+
lrw.ResponseWriter.WriteHeader(code)
20+
}
21+
22+
func (lrw *loggingResponseWriter) Write(b []byte) (int, error) {
23+
if lrw.status == 0 {
24+
lrw.status = http.StatusOK
25+
}
26+
n, err := lrw.ResponseWriter.Write(b)
27+
lrw.size += n
28+
return n, err
29+
}
30+
1031
func RequestLogger(logger *zap.Logger) func(http.Handler) http.Handler {
11-
return func(next http.Handler) http.Handler {
12-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
13-
start := time.Now()
14-
next.ServeHTTP(w, r)
15-
logger.Info("request",
16-
zap.String("method", r.Method),
17-
zap.String("path", r.URL.Path),
18-
zap.Duration("dur", time.Since(start)),
19-
zap.String("ua", r.UserAgent()))
20-
})
21-
}
32+
return func(next http.Handler) http.Handler {
33+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
34+
start := time.Now()
35+
36+
// Ensure we have a request ID for correlation
37+
reqID := r.Header.Get("X-Request-ID")
38+
if reqID == "" {
39+
reqID = uuid.New().String()
40+
}
41+
42+
w.Header().Set("X-Request-ID", reqID)
43+
44+
lrw := &loggingResponseWriter{ResponseWriter: w}
45+
next.ServeHTTP(lrw, r)
46+
47+
if lrw.status == 0 {
48+
lrw.status = http.StatusOK
49+
}
50+
51+
logger.Info("request",
52+
zap.String("id", reqID),
53+
zap.String("method", r.Method),
54+
zap.String("path", r.URL.Path),
55+
zap.Int("status", lrw.status),
56+
zap.Int("bytes", lrw.size),
57+
zap.Duration("dur", time.Since(start)),
58+
zap.String("ua", r.UserAgent()))
59+
})
60+
}
2261
}

0 commit comments

Comments
 (0)