wip: add skeleton for cloud shim mt-frontend

This commit is contained in:
konsalex
2025-12-10 16:03:21 +01:00
parent 8272edda96
commit 5b6d2e642d
6 changed files with 442 additions and 0 deletions
@@ -0,0 +1,39 @@
# Local Cloud Shim
This is a local development shim, that forwards MT-Front-end requests to MT-tilt back-end setup.
This is still very much work in progress, so things will change a lot.
```
┌──────────────────┐
│ │
┌─────────▶ MT Front-end │
│ │ │
┌──────────────────┐ ┌──────────────────┐ │ └──────────────────┘
│ │ │ │ │
│ User request │────────▶ Nginx ├─────┼─┐ ┌──────────────────────┐
│ │ │ │ │ │ │ │
└──────────────────┘ └──────────────────┘ │ └───────▶ Downstream MT-tilt │
│ │ │
│ └──────────────────────┘
│
│ ┌──────────────────────┐
│ │ │
└─────────▶ Mocked responses │
│ │
└──────────────────────┘
```
## Proxying
Currently, the proxying is done by an Nginx handling the requests in the following way:
1. Most requests are forwarded to the MT-Front-end
2. All the requests that match this pattern `apis|logout|swagger|openapi/v3|login` are forwarded to the MT-tilt back-end
3. Some requests that are breaking the front-end are mocked. For now `/bootdata` and `/api/user/orgs` are mocked. The mock data are stored under the `./mock-data` folder.
## Dev setup
The Tiltfile and the docker compose file, are simplified to avoid spinning up all services, as most are not needed for the shim setup to work.
The rest of the downstream services are handled by `mt-tilt` setup in Enterprise repo.
@@ -0,0 +1,49 @@
# --- Frontend Build (One-time)
local_resource(
'frontend-assets',
cmd='yarn install && yarn dev',
dir='../../..',
deps=[
'../../../public/app',
'../../../packages',
'../../../scripts/webpack',
'../../../tsconfig.json',
'../../../package.json',
'../../../yarn.lock',
],
labels=["local"]
)
# --- Backend Build
local_resource(
'backend-build',
cmd='mkdir -p build && bash ./build-grafana.sh',
dir='..',
deps=[
'../../../pkg',
'../../../apps',
'../../../kinds',
'../../../kindsv2',
'../../../local',
'../../../scripts',
'../../../conf',
'../../../go.sum',
'../../../go.mod',
],
allow_parallel=True,
labels=["local"]
)
# --- Docker Compose
docker_compose('./docker-compose.yaml')
dc_resource("proxy",
resource_deps=["frontend-service"],
labels=["services"]
)
dc_resource("frontend-service",
resource_deps=["frontend-assets", "backend-build"],
labels=["services"],
)
@@ -0,0 +1,47 @@
name: grafana-fs-dev-shim
services:
proxy:
image: grafana-proxy
build:
context: ../
dockerfile: proxy.dockerfile
volumes:
- ../../../public/build:/cdn/public/build
- ../../../public/app/plugins:/cdn/public/app/plugins
- ../../../public/fonts:/cdn/public/fonts
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./mock-data:/etc/nginx/mock-data:ro
ports:
- "12345:80" # Gateway
- "3010:81" # CDN
depends_on:
- frontend-service
labels:
- "alloy.logs=true"
frontend-service:
image: grafana-fs-dev
build:
context: ../../..
dockerfile: devenv/frontend-service/grafana-fs-dev.dockerfile
entrypoint: ["bin/grafana", "server", "target"]
volumes:
- ../configs/frontend-service.local.ini:/grafana/conf/custom.ini
# Port 3000 is only accessible via the proxy, not directly exposed
labels:
- "alloy.logs=true"
environment:
OTEL_BSP_SCHEDULE_DELAY: 500
GF_DATABASE_SKIP_MIGRATIONS: true
GF_DATABASE_ENSURE_DEFAULT_ORG_AND_USER: false
GF_DEFAULT_APP_MODE: development
GF_DEFAULT_TARGET: frontend-server
GF_SECURITY_CONTENT_SECURITY_POLICY: false
GF_FEATURE_TOGGLES_ENABLE: enableNativeHTTPHistogram
GF_SERVER_CDN_URL: http://localhost:3010
GF_SERVER_ROUTER_LOGGING: true
GF_LOG_LEVEL: info
OTEL_SERVICE_NAME: frontend-service
GF_TRACING_OPENTELEMETRY_OTLP_ADDRESS: "alloy:4317"
GF_TRACING_OPENTELEMETRY_OTLP_PROPAGATION: jaeger,w3c
@@ -0,0 +1,138 @@
{
"user": {
"isSignedIn": true,
"id": 0,
"uid": "",
"login": "",
"email": "",
"name": "",
"theme": "dark",
"lightTheme": false,
"orgCount": 1,
"orgId": 1,
"orgName": "Main Org.",
"orgRole": "Viewer",
"isGrafanaAdmin": false,
"gravatarUrl": "",
"timezone": "browser",
"weekStart": "browser",
"locale": "en-US",
"regionalFormat": "",
"language": "en-US",
"helpFlags1": 0,
"hasEditPermissionInFolders": false,
"authenticatedBy": "",
"permissions": {},
"analytics": {
"identifier": ""
}
},
"settings": {
"defaultDatasource": "-- Grafana --",
"datasources": {},
"minRefreshInterval": "5s",
"panels": {},
"apps": {},
"appUrl": "http://localhost:12345/",
"appSubUrl": "",
"allowOrgCreate": false,
"authProxyEnabled": false,
"ldapEnabled": false,
"jwtHeaderName": "",
"jwtUrlLogin": false,
"liveEnabled": true,
"autoAssignOrg": true,
"verifyEmailEnabled": false,
"sigV4AuthEnabled": false,
"azureAuthEnabled": false,
"rbacEnabled": true,
"exploreEnabled": true,
"helpEnabled": true,
"profileEnabled": true,
"newsFeedEnabled": true,
"queryHistoryEnabled": true,
"googleAnalyticsId": "",
"googleAnalytics4Id": "",
"rudderstackWriteKey": "",
"rudderstackDataPlaneUrl": "",
"feedbackLinksEnabled": true,
"disableLoginForm": false,
"disableUserSignUp": true,
"loginHint": "",
"passwordHint": "",
"viewersCanEdit": false,
"disableSanitizeHtml": false,
"auth": {
"disableLogin": false
},
"buildInfo": {
"hideVersion": false,
"version": "12.4.0-dev",
"commit": "dev",
"commitShort": "dev",
"buildstamp": 0,
"edition": "Open Source",
"latestVersion": "",
"hasUpdate": false,
"env": "development"
},
"licenseInfo": {
"expiry": 0,
"stateInfo": "",
"licenseUrl": "",
"edition": "Open Source"
},
"featureToggles": {},
"anonymousEnabled": false,
"rendererAvailable": false,
"http2Enabled": false,
"pluginCatalogURL": "https://grafana.com/grafana/plugins/",
"pluginAdminEnabled": true,
"pluginAdminExternalManageEnabled": false,
"pluginCatalogHiddenPlugins": [],
"expressionsEnabled": true,
"awsAllowedAuthProviders": ["keys"],
"awsAssumeRoleEnabled": false,
"azure": {
"cloud": "AzureCloud"
},
"caching": {
"enabled": false
},
"unifiedAlertingEnabled": true,
"unifiedAlerting": {
"minInterval": "10s"
},
"oauth": {},
"samlEnabled": false,
"samlName": "",
"dateFormats": {
"fullDate": "YYYY-MM-DD HH:mm:ss",
"useBrowserLocale": false,
"interval": {
"second": "HH:mm:ss",
"minute": "HH:mm",
"hour": "MM/DD HH:mm",
"day": "MM/DD",
"month": "YYYY-MM",
"year": "YYYY"
},
"defaultTimezone": "browser",
"defaultWeekStart": "browser"
},
"licensing": {
"usageBilling": {
"enabled": false
}
}
},
"navTree": [
{
"id": "home",
"text": "Home",
"icon": "home-alt",
"url": "/",
"sortWeight": -4000
}
]
}
@@ -0,0 +1,7 @@
[
{
"orgId": 1,
"name": "Main Org.",
"role": "Admin"
}
]
@@ -0,0 +1,162 @@
otel_exporter {
endpoint alloy:4317;
}
otel_service_name "proxy";
###
# Instance
###
upstream backend {
server host.docker.internal:3001;
}
upstream frontend {
server frontend-service:3000;
}
map "$request_method:$cookie_fs_unavailable" $reject_login {
default 0;
"POST:1" 1;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name _;
otel_trace on;
otel_trace_context propagate;
otel_span_name "$request_method Request";
location ~ ^/-/down/?$ {
add_header Set-Cookie "fs_unavailable=true; Max-Age=60; Path=/; HttpOnly" always;
return 302 $scheme://$http_host/;
}
location ~ ^/-/down/(?<age>\d+)/?$ {
add_header Set-Cookie "fs_unavailable=true; Max-Age=$age; Path=/; HttpOnly" always;
return 302 $scheme://$http_host/;
}
location ~ ^/-/up/?$ {
return 302 $scheme://$http_host/-/down/0;
}
# Mock bootdata endpoints (both /bootdata and /api/frontend/settings)
location = /bootdata {
default_type application/json;
add_header Access-Control-Allow-Origin "*" always;
alias /etc/nginx/mock-data/bootdata.json;
}
# Mock user organizations endpoint
location = /api/user/orgs {
default_type application/json;
add_header Access-Control-Allow-Origin "*" always;
alias /etc/nginx/mock-data/user-orgs.json;
}
# Special‐case POST /login to backend, GET to frontend
location = /login {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
if ($reject_login) {
add_header Content-Type application/json always;
return 503 '{"code":"Loading", "message": "Soon!"}';
}
if ($request_method = POST) {
proxy_pass http://backend;
break;
}
if ($request_method = GET) {
proxy_pass http://frontend;
break;
}
return 405;
}
# API calls go to the backend
# location ~ ^/(api|apis|avatar|bootdata|render|logout|public/plugins|public/openapi3|public/api-merged|goto|swagger|openapi/v3) {
# For test only allow logout, apis, and login
location ~ ^/(apis|logout|swagger|openapi/v3|login) {
# Add debug headers to the response
add_header Nginx-Trace-Id $otel_trace_id always;
add_header Nginx-Route "backend" always;
if ($cookie_fs_unavailable) {
add_header Content-Type application/json always;
return 503 '{"code":"Loading", "message": "Soon!"}';
}
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Everything else to the frontend
location / {
# Add debug headers to the response
add_header Nginx-Trace-Id $otel_trace_id always;
add_header Nginx-Route "frontend" always;
proxy_pass http://frontend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
###
# CDN
###
map $request $loggable {
default 1;
"~^\x16\x03" 0;
}
server {
listen 81;
server_name localhost;
root /cdn;
# Enable directory listing
autoindex on;
autoindex_exact_size off;
autoindex_format html;
autoindex_localtime on;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Headers "X-Grafana-Device-Id" always;
# Suppress access log for malformed HTTPS requests (those starting with a TLS handshake)
access_log /var/log/nginx/access.log main if=$loggable;
# This serves paths like /grafana/12.1.0-88106/public/build/foo
location ~ ^/grafana[^/]*/[^/]+/(.*)$ {
alias /cdn/$1;
}
}