Compare commits
3 Commits
bee4bb3c1f
...
7139a3ca74
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7139a3ca74 | ||
|
|
c509ae2a5f | ||
|
|
228ee57d4c |
@@ -11,6 +11,7 @@ jobs:
|
|||||||
name: Build and deploy
|
name: Build and deploy
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' }}
|
if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' }}
|
||||||
|
environment: staging
|
||||||
env:
|
env:
|
||||||
SERVICE: supervisor
|
SERVICE: supervisor
|
||||||
IMAGE: gitea.d-ma.be/mathias/supervisor
|
IMAGE: gitea.d-ma.be/mathias/supervisor
|
||||||
@@ -119,3 +120,74 @@ jobs:
|
|||||||
git push
|
git push
|
||||||
|
|
||||||
echo "Infra repo updated: ${SERVICE}+ingestion → ${IMAGE_TAG}"
|
echo "Infra repo updated: ${SERVICE}+ingestion → ${IMAGE_TAG}"
|
||||||
|
|
||||||
|
- name: Trigger Flux reconcile (immediate)
|
||||||
|
run: |
|
||||||
|
kubectl -n flux-system annotate gitrepository flux-system \
|
||||||
|
reconcile.fluxcd.io/requestedAt="$(date +%s)" --overwrite
|
||||||
|
kubectl -n flux-system annotate kustomization apps \
|
||||||
|
reconcile.fluxcd.io/requestedAt="$(date +%s)" --overwrite
|
||||||
|
|
||||||
|
- name: Wait for Flux to apply new supervisor image
|
||||||
|
run: |
|
||||||
|
EXPECTED="gitea.d-ma.be/mathias/supervisor:${{ github.sha }}"
|
||||||
|
for i in $(seq 1 60); do
|
||||||
|
CURRENT=$(kubectl get deploy supervisor -n supervisor \
|
||||||
|
-o jsonpath='{.spec.template.spec.containers[0].image}' 2>/dev/null || echo "")
|
||||||
|
if [ "$CURRENT" = "$EXPECTED" ]; then
|
||||||
|
echo "✓ Flux applied supervisor image after ${i}s"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
kubectl get deploy supervisor -n supervisor \
|
||||||
|
-o jsonpath='{.spec.template.spec.containers[0].image}' \
|
||||||
|
| grep -qx "$EXPECTED" \
|
||||||
|
|| { echo "✗ Flux did not apply supervisor image within 60s"; exit 1; }
|
||||||
|
|
||||||
|
- name: Wait for Flux to apply new ingestion image
|
||||||
|
run: |
|
||||||
|
EXPECTED="gitea.d-ma.be/mathias/ingestion:${{ github.sha }}"
|
||||||
|
for i in $(seq 1 60); do
|
||||||
|
CURRENT=$(kubectl get deploy ingestion -n supervisor \
|
||||||
|
-o jsonpath='{.spec.template.spec.containers[0].image}' 2>/dev/null || echo "")
|
||||||
|
if [ "$CURRENT" = "$EXPECTED" ]; then
|
||||||
|
echo "✓ Flux applied ingestion image after ${i}s"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
kubectl get deploy ingestion -n supervisor \
|
||||||
|
-o jsonpath='{.spec.template.spec.containers[0].image}' \
|
||||||
|
| grep -qx "$EXPECTED" \
|
||||||
|
|| { echo "✗ Flux did not apply ingestion image within 60s"; exit 1; }
|
||||||
|
|
||||||
|
- name: Verify supervisor rollout
|
||||||
|
run: |
|
||||||
|
kubectl rollout status deployment/supervisor \
|
||||||
|
--namespace supervisor \
|
||||||
|
--timeout=120s \
|
||||||
|
|| {
|
||||||
|
echo "── pod status ──"
|
||||||
|
kubectl get pods -n supervisor -o wide
|
||||||
|
echo "── events ──"
|
||||||
|
kubectl get events -n supervisor --sort-by='.lastTimestamp' | tail -20
|
||||||
|
echo "── describe ──"
|
||||||
|
kubectl describe pods -n supervisor -l app=supervisor | tail -40
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Verify ingestion rollout
|
||||||
|
run: |
|
||||||
|
kubectl rollout status deployment/ingestion \
|
||||||
|
--namespace supervisor \
|
||||||
|
--timeout=120s \
|
||||||
|
|| {
|
||||||
|
echo "── pod status ──"
|
||||||
|
kubectl get pods -n supervisor -o wide
|
||||||
|
echo "── events ──"
|
||||||
|
kubectl get events -n supervisor --sort-by='.lastTimestamp' | tail -20
|
||||||
|
echo "── describe ──"
|
||||||
|
kubectl describe pods -n supervisor -l app=ingestion | tail -40
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ func main() {
|
|||||||
|
|
||||||
mcpSrv := mcp.NewServer(brainDir, &pipelineCfg, llmClient.Complete)
|
mcpSrv := mcp.NewServer(brainDir, &pipelineCfg, llmClient.Complete)
|
||||||
|
|
||||||
|
mcpToken := os.Getenv("BRAIN_MCP_TOKEN")
|
||||||
|
if mcpToken == "" {
|
||||||
|
logger.Error("BRAIN_MCP_TOKEN not set")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if watchInterval > 0 {
|
if watchInterval > 0 {
|
||||||
watcher.Start(ctx, watcher.Config{
|
watcher.Start(ctx, watcher.Config{
|
||||||
@@ -74,7 +80,7 @@ func main() {
|
|||||||
mux.HandleFunc("POST /ingest-raw", h.IngestRaw)
|
mux.HandleFunc("POST /ingest-raw", h.IngestRaw)
|
||||||
mux.HandleFunc("POST /backfill-refs", h.BackfillRefs)
|
mux.HandleFunc("POST /backfill-refs", h.BackfillRefs)
|
||||||
mux.HandleFunc("GET /pass-rate", h.PassRate)
|
mux.HandleFunc("GET /pass-rate", h.PassRate)
|
||||||
mux.Handle("POST /mcp", mcpSrv)
|
mux.Handle("POST /mcp", mcp.BearerAuth(mcpToken, mcpSrv))
|
||||||
|
|
||||||
addr := ":" + port
|
addr := ":" + port
|
||||||
watchIntervalLog := "disabled"
|
watchIntervalLog := "disabled"
|
||||||
|
|||||||
23
ingestion/internal/mcp/auth.go
Normal file
23
ingestion/internal/mcp/auth.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package mcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BearerAuth returns a middleware that enforces a static bearer token on every
|
||||||
|
// request. token must be non-empty; if it is empty, every request is rejected.
|
||||||
|
func BearerAuth(token string, next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if token == "" {
|
||||||
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
got, ok := strings.CutPrefix(r.Header.Get("Authorization"), "Bearer ")
|
||||||
|
if !ok || got != token {
|
||||||
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
56
ingestion/internal/mcp/auth_test.go
Normal file
56
ingestion/internal/mcp/auth_test.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package mcp_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mathiasbq/hyperguild/ingestion/internal/mcp"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBearerAuth_MissingHeader(t *testing.T) {
|
||||||
|
handler := mcp.BearerAuth("secret", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/mcp", nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBearerAuth_WrongToken(t *testing.T) {
|
||||||
|
handler := mcp.BearerAuth("secret", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/mcp", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer wrong")
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBearerAuth_CorrectToken(t *testing.T) {
|
||||||
|
called := false
|
||||||
|
handler := mcp.BearerAuth("secret", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
called = true
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/mcp", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer secret")
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
assert.Equal(t, http.StatusOK, rr.Code)
|
||||||
|
assert.True(t, called)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBearerAuth_EmptyConfiguredToken(t *testing.T) {
|
||||||
|
// Server started without a token configured — every request must fail.
|
||||||
|
handler := mcp.BearerAuth("", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/mcp", nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user