diff --git a/.github/workflows/k8s-deploy.yml b/.github/workflows/k8s-deploy.yml index 5390516..a698aea 100644 --- a/.github/workflows/k8s-deploy.yml +++ b/.github/workflows/k8s-deploy.yml @@ -50,19 +50,6 @@ jobs: ) | tee -a /tmp/kube.log fi - - name: 'Apply manifests: namespaced resources' - run: | - ( - find . \ - -maxdepth 1 \ - -type d \ - -not -name '_' \ - -not -name '.*' \ - -print0 \ - | sort -z \ - | xargs -r0 -n 1 kubectl apply -Rf - ) | tee -a /tmp/kube.log - - name: 'Apply manifests: generated regcred secrets' run: | @@ -81,6 +68,19 @@ jobs: EOF done <<< "$(find . -maxdepth 1 -type d -not -name '_' -not -name '.*')" + - name: 'Apply manifests: namespaced resources' + run: | + ( + find . \ + -maxdepth 1 \ + -type d \ + -not -name '_' \ + -not -name '.*' \ + -print0 \ + | sort -z \ + | xargs -r0 -n 1 kubectl apply -Rf + ) | tee -a /tmp/kube.log + - name: 'Apply manifests: deleted resources' run: | for manifest_path in $(git diff-tree --name-only --diff-filter=D -r HEAD^ HEAD); do diff --git a/_/ClusterRole/squadquest-supabase-reader.yaml b/_/ClusterRole/squadquest-supabase-reader.yaml new file mode 100644 index 0000000..707970f --- /dev/null +++ b/_/ClusterRole/squadquest-supabase-reader.yaml @@ -0,0 +1,22 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: squadquest-supabase-reader +rules: + - apiGroups: + - '' + resources: + - nodes + - namespaces + - pods + verbs: + - list + - watch + - apiGroups: + - '' + resourceNames: + - squadquest-supabase-* + resources: + - pods/log + verbs: + - get diff --git a/_/ClusterRoleBinding/squadquest-supabase-view.yaml b/_/ClusterRoleBinding/squadquest-supabase-view.yaml new file mode 100644 index 0000000..131a388 --- /dev/null +++ b/_/ClusterRoleBinding/squadquest-supabase-view.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: squadquest-supabase-view +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: squadquest-supabase-reader +subjects: + - kind: ServiceAccount + name: squadquest-supabase-supabase-vector + namespace: squadquest-supabase diff --git a/_/Namespace/squadquest-supabase.yaml b/_/Namespace/squadquest-supabase.yaml new file mode 100644 index 0000000..1684a4d --- /dev/null +++ b/_/Namespace/squadquest-supabase.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: squadquest-supabase diff --git a/choose-native-plants/Deployment/choose-native-plants.yaml b/choose-native-plants/Deployment/choose-native-plants.yaml index c2ea2e9..31e7e47 100644 --- a/choose-native-plants/Deployment/choose-native-plants.yaml +++ b/choose-native-plants/Deployment/choose-native-plants.yaml @@ -37,6 +37,9 @@ spec: key: password name: mongo optional: false + envFrom: + - secretRef: + name: pac-api image: 'mongo:5.0.6' imagePullPolicy: IfNotPresent name: choose-native-plants-db diff --git a/squadquest-supabase/ConfigMap/squadquest-supabase-supabase-db-initdb.yaml b/squadquest-supabase/ConfigMap/squadquest-supabase-supabase-db-initdb.yaml new file mode 100644 index 0000000..597ffb7 --- /dev/null +++ b/squadquest-supabase/ConfigMap/squadquest-supabase-supabase-db-initdb.yaml @@ -0,0 +1,245 @@ +apiVersion: v1 +data: + 98-webhooks.sql: | + BEGIN; + -- Create pg_net extension + CREATE EXTENSION IF NOT EXISTS pg_net SCHEMA extensions; + -- Create supabase_functions schema + CREATE SCHEMA supabase_functions AUTHORIZATION supabase_admin; + GRANT USAGE ON SCHEMA supabase_functions TO postgres, anon, authenticated, service_role; + ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON TABLES TO postgres, anon, authenticated, service_role; + ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON FUNCTIONS TO postgres, anon, authenticated, service_role; + ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON SEQUENCES TO postgres, anon, authenticated, service_role; + -- supabase_functions.migrations definition + CREATE TABLE supabase_functions.migrations ( + version text PRIMARY KEY, + inserted_at timestamptz NOT NULL DEFAULT NOW() + ); + -- Initial supabase_functions migration + INSERT INTO supabase_functions.migrations (version) VALUES ('initial'); + -- supabase_functions.hooks definition + CREATE TABLE supabase_functions.hooks ( + id bigserial PRIMARY KEY, + hook_table_id integer NOT NULL, + hook_name text NOT NULL, + created_at timestamptz NOT NULL DEFAULT NOW(), + request_id bigint + ); + CREATE INDEX supabase_functions_hooks_request_id_idx ON supabase_functions.hooks USING btree (request_id); + CREATE INDEX supabase_functions_hooks_h_table_id_h_name_idx ON supabase_functions.hooks USING btree (hook_table_id, hook_name); + COMMENT ON TABLE supabase_functions.hooks IS 'Supabase Functions Hooks: Audit trail for triggered hooks.'; + CREATE FUNCTION supabase_functions.http_request() + RETURNS trigger + LANGUAGE plpgsql + AS $function$ + DECLARE + request_id bigint; + payload jsonb; + url text := TG_ARGV[0]::text; + method text := TG_ARGV[1]::text; + headers jsonb DEFAULT '{}'::jsonb; + params jsonb DEFAULT '{}'::jsonb; + timeout_ms integer DEFAULT 1000; + BEGIN + IF url IS NULL OR url = 'null' THEN + RAISE EXCEPTION 'url argument is missing'; + END IF; + + IF method IS NULL OR method = 'null' THEN + RAISE EXCEPTION 'method argument is missing'; + END IF; + + IF TG_ARGV[2] IS NULL OR TG_ARGV[2] = 'null' THEN + headers = '{"Content-Type": "application/json"}'::jsonb; + ELSE + headers = TG_ARGV[2]::jsonb; + END IF; + + IF TG_ARGV[3] IS NULL OR TG_ARGV[3] = 'null' THEN + params = '{}'::jsonb; + ELSE + params = TG_ARGV[3]::jsonb; + END IF; + + IF TG_ARGV[4] IS NULL OR TG_ARGV[4] = 'null' THEN + timeout_ms = 1000; + ELSE + timeout_ms = TG_ARGV[4]::integer; + END IF; + + CASE + WHEN method = 'GET' THEN + SELECT http_get INTO request_id FROM net.http_get( + url, + params, + headers, + timeout_ms + ); + WHEN method = 'POST' THEN + payload = jsonb_build_object( + 'old_record', OLD, + 'record', NEW, + 'type', TG_OP, + 'table', TG_TABLE_NAME, + 'schema', TG_TABLE_SCHEMA + ); + + SELECT http_post INTO request_id FROM net.http_post( + url, + payload, + params, + headers, + timeout_ms + ); + ELSE + RAISE EXCEPTION 'method argument % is invalid', method; + END CASE; + + INSERT INTO supabase_functions.hooks + (hook_table_id, hook_name, request_id) + VALUES + (TG_RELID, TG_NAME, request_id); + + RETURN NEW; + END + $function$; + -- Supabase super admin + DO + $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_roles + WHERE rolname = 'supabase_functions_admin' + ) + THEN + CREATE USER supabase_functions_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION; + END IF; + END + $$; + GRANT ALL PRIVILEGES ON SCHEMA supabase_functions TO supabase_functions_admin; + GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA supabase_functions TO supabase_functions_admin; + GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA supabase_functions TO supabase_functions_admin; + ALTER USER supabase_functions_admin SET search_path = "supabase_functions"; + ALTER table "supabase_functions".migrations OWNER TO supabase_functions_admin; + ALTER table "supabase_functions".hooks OWNER TO supabase_functions_admin; + ALTER function "supabase_functions".http_request() OWNER TO supabase_functions_admin; + GRANT supabase_functions_admin TO postgres; + -- Remove unused supabase_pg_net_admin role + DO + $$ + BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_roles + WHERE rolname = 'supabase_pg_net_admin' + ) + THEN + REASSIGN OWNED BY supabase_pg_net_admin TO supabase_admin; + DROP OWNED BY supabase_pg_net_admin; + DROP ROLE supabase_pg_net_admin; + END IF; + END + $$; + -- pg_net grants when extension is already enabled + DO + $$ + BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_extension + WHERE extname = 'pg_net' + ) + THEN + GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role; + ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER; + ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER; + ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net; + ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net; + REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC; + REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC; + GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role; + GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role; + END IF; + END + $$; + -- Event trigger for pg_net + CREATE OR REPLACE FUNCTION extensions.grant_pg_net_access() + RETURNS event_trigger + LANGUAGE plpgsql + AS $$ + BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_event_trigger_ddl_commands() AS ev + JOIN pg_extension AS ext + ON ev.objid = ext.oid + WHERE ext.extname = 'pg_net' + ) + THEN + GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role; + ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER; + ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER; + ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net; + ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net; + REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC; + REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC; + GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role; + GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role; + END IF; + END; + $$; + COMMENT ON FUNCTION extensions.grant_pg_net_access IS 'Grants access to pg_net'; + DO + $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_event_trigger + WHERE evtname = 'issue_pg_net_access' + ) THEN + CREATE EVENT TRIGGER issue_pg_net_access ON ddl_command_end WHEN TAG IN ('CREATE EXTENSION') + EXECUTE PROCEDURE extensions.grant_pg_net_access(); + END IF; + END + $$; + INSERT INTO supabase_functions.migrations (version) VALUES ('20210809183423_update_grants'); + ALTER function supabase_functions.http_request() SECURITY DEFINER; + ALTER function supabase_functions.http_request() SET search_path = supabase_functions; + REVOKE ALL ON FUNCTION supabase_functions.http_request() FROM PUBLIC; + GRANT EXECUTE ON FUNCTION supabase_functions.http_request() TO postgres, anon, authenticated, service_role; + COMMIT; + 99-jwt.sql: | + \set jwt_secret `echo "$JWT_SECRET"` + \set jwt_exp `echo "$JWT_EXP"` + + ALTER DATABASE postgres SET "app.settings.jwt_secret" TO :jwt_secret; + ALTER DATABASE postgres SET "app.settings.jwt_exp" TO :jwt_exp; + 99-logs.sql: | + \set pguser `echo "$POSTGRES_USER"` + + create schema if not exists _analytics; + alter schema _analytics owner to :pguser; + 99-realtime.sql: | + \set pguser `echo "$POSTGRES_USER"` + + create schema if not exists _realtime; + alter schema _realtime owner to :pguser; + 99-roles.sql: | + -- NOTE: change to your own passwords for production environments + \set pgpass `echo "$POSTGRES_PASSWORD"` + + ALTER USER authenticator WITH PASSWORD :'pgpass'; + ALTER USER pgbouncer WITH PASSWORD :'pgpass'; + ALTER USER supabase_auth_admin WITH PASSWORD :'pgpass'; + ALTER USER supabase_functions_admin WITH PASSWORD :'pgpass'; + ALTER USER supabase_storage_admin WITH PASSWORD :'pgpass'; +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/instance: squadquest-supabase + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: supabase + helm.sh/chart: supabase-0.1.3 + name: squadquest-supabase-supabase-db-initdb + namespace: squadquest-supabase diff --git a/squadquest-supabase/ConfigMap/squadquest-supabase-supabase-db-migrations.yaml b/squadquest-supabase/ConfigMap/squadquest-supabase-supabase-db-migrations.yaml new file mode 100644 index 0000000..ce1f862 --- /dev/null +++ b/squadquest-supabase/ConfigMap/squadquest-supabase-supabase-db-migrations.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +data: {} +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/instance: squadquest-supabase + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: supabase + helm.sh/chart: supabase-0.1.3 + name: squadquest-supabase-supabase-db-migrations + namespace: squadquest-supabase diff --git a/squadquest-supabase/ConfigMap/squadquest-supabase-supabase-functions-main.yaml b/squadquest-supabase/ConfigMap/squadquest-supabase-supabase-functions-main.yaml new file mode 100644 index 0000000..8b6b3d5 --- /dev/null +++ b/squadquest-supabase/ConfigMap/squadquest-supabase-supabase-functions-main.yaml @@ -0,0 +1,106 @@ +apiVersion: v1 +data: + index.ts: | + import { serve } from 'https://deno.land/std@0.131.0/http/server.ts' + import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts' + + console.log('main function started') + + const JWT_SECRET = Deno.env.get('JWT_SECRET') + const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true' + + function getAuthToken(req: Request) { + const authHeader = req.headers.get('authorization') + if (!authHeader) { + throw new Error('Missing authorization header') + } + const [bearer, token] = authHeader.split(' ') + if (bearer !== 'Bearer') { + throw new Error(`Auth header is not 'Bearer {token}'`) + } + return token + } + + async function verifyJWT(jwt: string): Promise { + const encoder = new TextEncoder() + const secretKey = encoder.encode(JWT_SECRET) + try { + await jose.jwtVerify(jwt, secretKey) + } catch (err) { + console.error(err) + return false + } + return true + } + + serve(async (req: Request) => { + if (req.method !== 'OPTIONS' && VERIFY_JWT) { + try { + const token = getAuthToken(req) + const isValidJWT = await verifyJWT(token) + + if (!isValidJWT) { + return new Response(JSON.stringify({ msg: 'Invalid JWT' }), { + status: 401, + headers: { 'Content-Type': 'application/json' }, + }) + } + } catch (e) { + console.error(e) + return new Response(JSON.stringify({ msg: e.toString() }), { + status: 401, + headers: { 'Content-Type': 'application/json' }, + }) + } + } + + const url = new URL(req.url) + const { pathname } = url + const path_parts = pathname.split('/') + const service_name = path_parts[1] + + if (!service_name || service_name === '') { + const error = { msg: 'missing function name in request' } + return new Response(JSON.stringify(error), { + status: 400, + headers: { 'Content-Type': 'application/json' }, + }) + } + + const servicePath = `/home/deno/functions/${service_name}` + console.error(`serving the request with ${servicePath}`) + + const memoryLimitMb = 150 + const workerTimeoutMs = 1 * 60 * 1000 + const noModuleCache = false + const importMapPath = null + const envVarsObj = Deno.env.toObject() + const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]]) + + try { + const worker = await EdgeRuntime.userWorkers.create({ + servicePath, + memoryLimitMb, + workerTimeoutMs, + noModuleCache, + importMapPath, + envVars, + }) + return await worker.fetch(req) + } catch (e) { + const error = { msg: e.toString() } + return new Response(JSON.stringify(error), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }) + } + }) +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/instance: squadquest-supabase + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: supabase + helm.sh/chart: supabase-0.1.3 + name: squadquest-supabase-supabase-functions-main + namespace: squadquest-supabase diff --git a/squadquest-supabase/ConfigMap/squadquest-supabase-supabase-kong.yaml b/squadquest-supabase/ConfigMap/squadquest-supabase-supabase-kong.yaml new file mode 100644 index 0000000..7d29ca0 --- /dev/null +++ b/squadquest-supabase/ConfigMap/squadquest-supabase-supabase-kong.yaml @@ -0,0 +1,215 @@ +apiVersion: v1 +data: + template.yml: | + _format_version: '2.1' + _transform: true + + consumers: + - username: DASHBOARD + - username: anon + keyauth_credentials: + - key: ${SUPABASE_ANON_KEY} + - username: service_role + keyauth_credentials: + - key: ${SUPABASE_SERVICE_KEY} + acls: + - consumer: anon + group: anon + - consumer: service_role + group: admin + basicauth_credentials: + - consumer: DASHBOARD + username: ${DASHBOARD_USERNAME} + password: ${DASHBOARD_PASSWORD} + services: + - name: auth-v1-open + url: http://squadquest-supabase-supabase-auth:9999/verify + routes: + - name: auth-v1-open + strip_path: true + paths: + - /auth/v1/verify + plugins: + - name: cors + - name: auth-v1-open-callback + url: http://squadquest-supabase-supabase-auth:9999/callback + routes: + - name: auth-v1-open-callback + strip_path: true + paths: + - /auth/v1/callback + plugins: + - name: cors + - name: auth-v1-open-authorize + url: http://squadquest-supabase-supabase-auth:9999/authorize + routes: + - name: auth-v1-open-authorize + strip_path: true + paths: + - /auth/v1/authorize + plugins: + - name: cors + - name: auth-v1 + _comment: "GoTrue: /auth/v1/* -> http://squadquest-supabase-supabase-auth:9999/*" + url: http://squadquest-supabase-supabase-auth:9999 + routes: + - name: auth-v1-all + strip_path: true + paths: + - /auth/v1/ + plugins: + - name: cors + - name: key-auth + config: + hide_credentials: false + - name: acl + config: + hide_groups_header: true + allow: + - admin + - anon + - name: rest-v1 + _comment: "PostgREST: /rest/v1/* -> http://squadquest-supabase-supabase-rest:3000/*" + url: http://squadquest-supabase-supabase-rest:3000/ + routes: + - name: rest-v1-all + strip_path: true + paths: + - /rest/v1/ + plugins: + - name: cors + - name: key-auth + config: + hide_credentials: true + - name: acl + config: + hide_groups_header: true + allow: + - admin + - anon + - name: graphql-v1 + _comment: 'PostgREST: /graphql/v1/* -> http://squadquest-supabase-supabase-rest:3000/rpc/graphql' + url: http://squadquest-supabase-supabase-rest:3000/rpc/graphql + routes: + - name: graphql-v1-all + strip_path: true + paths: + - /graphql/v1 + plugins: + - name: cors + - name: key-auth + config: + hide_credentials: true + - name: request-transformer + config: + add: + headers: + - Content-Profile:graphql_public + - name: acl + config: + hide_groups_header: true + allow: + - admin + - anon + - name: realtime-v1 + _comment: "Realtime: /realtime/v1/* -> ws://squadquest-supabase-supabase-realtime:4000/socket/*" + url: http://squadquest-supabase-supabase-realtime:4000/socket + routes: + - name: realtime-v1-all + strip_path: true + paths: + - /realtime/v1/ + plugins: + - name: cors + - name: key-auth + config: + hide_credentials: false + - name: acl + config: + hide_groups_header: true + allow: + - admin + - anon + - name: storage-v1 + _comment: "Storage: /storage/v1/* -> http://squadquest-supabase-supabase-storage:5000/*" + url: http://squadquest-supabase-supabase-storage:5000/ + routes: + - name: storage-v1-all + strip_path: true + paths: + - /storage/v1/ + plugins: + - name: cors + - name: functions-v1 + _comment: 'Edge Functions: /functions/v1/* -> http://squadquest-supabase-supabase-functions:9000/*' + url: http://functions:9000/ + routes: + - name: functions-v1-all + strip_path: true + paths: + - /functions/v1/ + plugins: + - name: cors + - name: analytics-v1 + _comment: 'Analytics: /analytics/v1/* -> http://squadquest-supabase-supabase-analytics:4000/*' + url: http://squadquest-supabase-supabase-analytics:4000/ + routes: + - name: analytics-v1-all + strip_path: true + paths: + - /analytics/v1/ + - name: meta + _comment: "pg-meta: /pg/* -> http://squadquest-supabase-supabase-meta:8080/*" + url: http://squadquest-supabase-supabase-meta:8080/ + routes: + - name: meta-all + strip_path: true + paths: + - /pg/ + plugins: + - name: key-auth + config: + hide_credentials: false + - name: acl + config: + hide_groups_header: true + allow: + - admin + - name: dashboard + _comment: 'Studio: /* -> http://squadquest-supabase-supabase-studio:3000/*' + url: http://squadquest-supabase-supabase-studio:3000/ + routes: + - name: dashboard-all + strip_path: true + paths: + - / + plugins: + - name: cors + - name: basic-auth + config: + hide_credentials: true + wrapper.sh: | + #!/bin/bash + + set -euo pipefail + + echo "Replacing env placeholders of /usr/local/kong/kong.yml" + + sed \ + -e "s/\${SUPABASE_ANON_KEY}/${SUPABASE_ANON_KEY}/" \ + -e "s/\${SUPABASE_SERVICE_KEY}/${SUPABASE_SERVICE_KEY}/" \ + -e "s/\${DASHBOARD_USERNAME}/${DASHBOARD_USERNAME}/" \ + -e "s/\${DASHBOARD_PASSWORD}/${DASHBOARD_PASSWORD}/" \ + /usr/local/kong/template.yml \ + > /usr/local/kong/kong.yml + + exec /docker-entrypoint.sh kong docker-start +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/instance: squadquest-supabase + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: supabase + helm.sh/chart: supabase-0.1.3 + name: squadquest-supabase-supabase-kong + namespace: squadquest-supabase diff --git a/squadquest-supabase/ConfigMap/squadquest-supabase-supabase-vector-config.yaml b/squadquest-supabase/ConfigMap/squadquest-supabase-supabase-vector-config.yaml new file mode 100644 index 0000000..8fbbb88 --- /dev/null +++ b/squadquest-supabase/ConfigMap/squadquest-supabase-supabase-vector-config.yaml @@ -0,0 +1,254 @@ +apiVersion: v1 +data: + secret.sh: | + #!/bin/sh + cat << EOF + { + "logflare_api_key": { + "value": "$LOGFLARE_API_KEY", + "error": null + } + } + EOF + vector.yml: | + secret: + credentials: + type: exec + command: + - /etc/vector/secret.sh + + api: + enabled: true + address: 0.0.0.0:9001 + + sources: + kubernetes_host: + type: kubernetes_logs + extra_label_selector: app.kubernetes.io/instance=squadquest-supabase,app.kubernetes.io/name!=supabase-vector + + transforms: + project_logs: + type: remap + inputs: + - kubernetes_host + source: |- + .project = "default" + .event_message = del(.message) + .appname = del(.kubernetes.container_name) + del(.file) + del(.kubernetes) + del(.source_type) + del(.stream) + router: + type: route + inputs: + - project_logs + route: + kong: '.appname == "supabase-kong"' + auth: '.appname == "supabase-auth"' + rest: '.appname == "supabase-rest"' + realtime: '.appname == "supabase-realtime"' + storage: '.appname == "supabase-storage"' + functions: '.appname == "supabase-functions"' + db: '.appname == "supabase-db"' + # Ignores non nginx errors since they are related with kong booting up + kong_logs: + type: remap + inputs: + - router.kong + source: |- + req, err = parse_nginx_log(.event_message, "combined") + if err == null { + .timestamp = req.timestamp + .metadata.request.headers.referer = req.referer + .metadata.request.headers.user_agent = req.agent + .metadata.request.headers.cf_connecting_ip = req.client + .metadata.request.method = req.method + .metadata.request.path = req.path + .metadata.request.protocol = req.protocol + .metadata.response.status_code = req.status + } + if err != null { + abort + } + # Ignores non nginx errors since they are related with kong booting up + kong_err: + type: remap + inputs: + - router.kong + source: |- + .metadata.request.method = "GET" + .metadata.response.status_code = 200 + parsed, err = parse_nginx_log(.event_message, "error") + if err == null { + .timestamp = parsed.timestamp + .severity = parsed.severity + .metadata.request.host = parsed.host + .metadata.request.headers.cf_connecting_ip = parsed.client + url, err = split(parsed.request, " ") + if err == null { + .metadata.request.method = url[0] + .metadata.request.path = url[1] + .metadata.request.protocol = url[2] + } + } + if err != null { + abort + } + # Gotrue logs are structured json strings which frontend parses directly. But we keep metadata for consistency. + auth_logs: + type: remap + inputs: + - router.auth + source: |- + parsed, err = parse_json(.event_message) + if err == null { + .metadata.timestamp = parsed.time + .metadata = merge!(.metadata, parsed) + } + # PostgREST logs are structured so we separate timestamp from message using regex + rest_logs: + type: remap + inputs: + - router.rest + source: |- + parsed, err = parse_regex(.event_message, r'^(?P