Skip to content

Instantly share code, notes, and snippets.

@barakplasma
Created January 5, 2026 14:09
Show Gist options
  • Select an option

  • Save barakplasma/35325abd131b1b23e4dec17374fc5036 to your computer and use it in GitHub Desktop.

Select an option

Save barakplasma/35325abd131b1b23e4dec17374fc5036 to your computer and use it in GitHub Desktop.
Complete Langfuse Organization Admin CronJob Setup - Automatically add jo@example.com as admin to all organizations

Langfuse Organization Admin Cron Job Setup Guide

This guide provides a complete solution for automatically adding jo@example.com as an organization admin to all Langfuse organizations using a Kubernetes CronJob that runs every hour.

📋 Table of Contents

  1. Overview
  2. Prerequisites
  3. Database Schema Discovery
  4. Complete Solution
  5. YAML Configuration
  6. SQL Script
  7. Deployment Instructions
  8. Testing and Verification
  9. Troubleshooting
  10. Customization Options

🎯 Overview

This solution creates a Kubernetes CronJob that:

  • Runs every hour at the top of the hour (0 * * * *)
  • Automatically creates the user jo@example.com if they don't exist
  • Assigns jo@example.com as OWNER of all organizations
  • Is safe to run multiple times (idempotent)
  • Provides comprehensive logging and error handling

🔧 Prerequisites

Required Kubernetes Resources

  • Langfuse deployment with PostgreSQL
  • kubectl access to the cluster
  • Namespace: langfuse-test (or your Langfuse namespace)

Database Access

  • PostgreSQL connection details
  • Appropriate permissions to create users and manage organization memberships

🔍 Database Schema Discovery

Through database exploration, we discovered the following Langfuse schema:

Key Tables

-- Users table
users (id, email, name, created_at, updated_at)

-- Organizations table
organizations (id, name, created_at, updated_at)

-- Organization Memberships table
organization_memberships (id, org_id, user_id, role, created_at, updated_at)

Role Hierarchy

  • OWNER: Highest level (equivalent to admin)
  • Other roles may exist depending on Langfuse configuration

🚀 Complete Solution

Current Status (Before Deployment)

User Email User Name Organization Role
barakplasma@gmail.com Michael Test 1 OWNER
barakplasma@gmail.com Michael Test 2 OWNER

Target Status (After Deployment)

User Email User Name Organization Role
barakplasma@gmail.com Michael Test 1 OWNER
jo@example.com Jo Admin Test 1 OWNER
barakplasma@gmail.com Michael Test 2 OWNER
jo@example.com Jo Admin Test 2 OWNER

📝 YAML Configuration

Option 1: Complete CronJob (Recommended)

Create a file langfuse-org-admin-cronjob.yaml:

# Complete Langfuse Organization Admin CronJob Configuration
# This file adds a CronJob to automatically add jo@example.com as an organization admin to all organizations

# Add this section to your existing values.yaml or use as a standalone override
extraManifests:
  - |
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: langfuse-sql-scripts
      namespace: langfuse-test  # Replace with your namespace
      labels:
        app: langfuse-sql-runner
        component: sql-scripts
    data:
      # SQL script to add jo@example.com as org admin to all organizations
      add_org_admin.sql: |
        DO $$
        DECLARE
            jo_user_id UUID;
            org_record RECORD;
            processed_count INTEGER := 0;
            error_count INTEGER := 0;
        BEGIN
            RAISE NOTICE '=== Starting Organization Admin Assignment Job ===';
            RAISE NOTICE 'Target: jo@example.com';
            RAISE NOTICE 'Timestamp: %', NOW();

            -- Step 1: Find or create jo@example.com user
            SELECT id INTO jo_user_id FROM "users" WHERE email = 'jo@example.com';

            IF jo_user_id IS NULL THEN
                -- Create the user if they don't exist
                INSERT INTO "users" (id, email, name, created_at, updated_at)
                VALUES (
                    gen_random_uuid(),
                    'jo@example.com',
                    'Jo Admin',
                    NOW(),
                    NOW()
                ) RETURNING id INTO jo_user_id;

                RAISE NOTICE '✓ Created new user jo@example.com with ID: %', jo_user_id;
            ELSE
                RAISE NOTICE '✓ Found existing user jo@example.com with ID: %', jo_user_id;
            END IF;

            -- Step 2: Add jo@example.com as admin to all organizations
            FOR org_record IN
                SELECT id, name FROM "organizations" ORDER BY name
            LOOP
                BEGIN
                    -- Check if user is already a member of this organization
                    IF NOT EXISTS (
                        SELECT 1 FROM "organization_memberships"
                        WHERE "org_id" = org_record.id
                        AND "user_id" = jo_user_id
                    ) THEN
                        -- Add as organization member with admin role
                        INSERT INTO "organization_memberships" (
                            id,
                            "org_id",
                            "user_id",
                            role,
                            created_at,
                            updated_at
                        ) VALUES (
                            gen_random_uuid(),
                            org_record.id,
                            jo_user_id,
                            'OWNER',
                            NOW(),
                            NOW()
                        );

                        RAISE NOTICE '✓ Added jo@example.com as OWNER to organization: % (ID: %)',
                                   org_record.name, org_record.id;
                        processed_count := processed_count + 1;
                    ELSE
                        RAISE NOTICE '! User jo@example.com already member of organization: %', org_record.name;
                    END IF;

                EXCEPTION
                    WHEN unique_violation THEN
                        RAISE NOTICE '! User already has admin role in organization: % (ID: %)',
                                   org_record.name, org_record.id;
                    WHEN OTHERS THEN
                        RAISE WARNING '! Error processing organization % (ID: %): %',
                                     org_record.name, org_record.id, SQLERRM;
                        error_count := error_count + 1;
                END;
            END LOOP;

            -- Step 3: Log completion summary
            RAISE NOTICE '=== Job Summary ===';
            RAISE NOTICE 'Organizations processed: %', processed_count;
            RAISE NOTICE 'Errors encountered: %', error_count;
            RAISE NOTICE 'Completed at: %', NOW();
            RAISE NOTICE '=== End Organization Admin Assignment Job ===';

        END $$;

        -- Return summary statistics
        SELECT
            u.email,
            COUNT(*) as total_org_memberships,
            COUNT(CASE WHEN om.role = 'OWNER' THEN 1 END) as owner_assignments,
            COUNT(CASE WHEN om.role = 'ADMIN' THEN 1 END) as admin_assignments,
            MIN(om.created_at) as first_assignment,
            MAX(om.updated_at) as last_update
        FROM "users" u
        LEFT JOIN "organization_memberships" om ON u.id = om."user_id"
        WHERE u.email = 'jo@example.com'
        GROUP BY u.email;

  - |
    apiVersion: batch/v1
    kind: CronJob
    metadata:
      name: langfuse-org-admin-updater
      namespace: langfuse-test  # Replace with your namespace
      labels:
        app: langfuse-sql-runner
        component: org-admin-updater
      annotations:
        description: "CronJob to add jo@example.com as organization admin to all organizations"
        managed-by: "Helm"
        version: "1.2.0"
    spec:
      # Run every hour at the top of the hour
      schedule: "0 * * * *"
      concurrencyPolicy: Forbid
      successfulJobsHistoryLimit: 3
      failedJobsHistoryLimit: 3
      # Give the job 10 minutes to complete
      startingDeadlineSeconds: 600
      jobTemplate:
        spec:
          # Clean up the job after 24 hours
          ttlSecondsAfterFinished: 86400
          template:
            metadata:
              labels:
                app: langfuse-sql-runner
                component: org-admin-updater
            spec:
              restartPolicy: OnFailure
              containers:
                - name: postgresql-client
                  image: bitnamilegacy/postgresql:17.3.0-debian-12-r1
                  env:
                    # Database connection (uses same config as PostgreSQL subchart)
                    - name: PGHOST
                      value: "langfuse-test-postgresql"
                    - name: PGPORT
                      value: "5432"
                    - name: PGUSER
                      value: "langfuse"
                    - name: PGPASSWORD
                      valueFrom:
                        secretKeyRef:
                          key: "postgres-password"
                          name: "langfuse-test-postgresql"
                    - name: PGDATABASE
                      value: "postgres_langfuse"
                    - name: PGOPTIONS
                      value: "-c client_min_messages=notice"
                    - name: PGAPPNAME
                      value: "langfuse-org-admin-updater"

                  command:
                    - /bin/sh
                    - -c
                    - |
                      echo "═══════════════════════════════════════════════════════════"
                      echo "    Langfuse Organization Admin Assignment Job"
                      echo "═══════════════════════════════════════════════════════════"
                      echo "Started at: $(date '+%Y-%m-%d %H:%M:%S UTC')"
                      echo "Target user: jo@example.com"
                      echo "Database: ${PGDATABASE}@${PGHOST}:${PGPORT}"
                      echo "Namespace: ${POD_NAMESPACE:-default}"
                      echo "Pod: ${HOSTNAME}"
                      echo "═══════════════════════════════════════════════════════════"
                      echo ""

                      # Set error handling (avoiding -o pipefail for compatibility)
                      set -eu

                      # Pre-flight checks
                      echo "🔍 Performing pre-flight checks..."

                      # Test database connection
                      echo "  • Testing database connection..."
                      if psql -c "SELECT 1;" >/dev/null 2>&1; then
                        echo "  ✓ Database connection successful"
                      else
                        echo "  ✗ Database connection failed"
                        exit 1
                      fi

                      # Check if required tables exist
                      echo "  • Checking required tables..."
                      for table in "users" "organizations" "organization_memberships"; do
                        if psql -c "SELECT 1 FROM information_schema.tables WHERE table_name = '$table';" 2>/dev/null | grep -q "1"; then
                          echo "  ✓ Table '$table' exists"
                        else
                          echo "  ✗ Table '$table' not found"
                          exit 1
                        fi
                      done

                      # Check if target user already exists
                      echo "  • Checking for existing user jo@example.com..."
                      existing_user=$(psql -t -c "SELECT COUNT(*) FROM \"users\" WHERE email = 'jo@example.com';" 2>/dev/null | tr -d ' ' || echo "0")
                      if [ "$existing_user" -gt 0 ]; then
                        echo "  ✓ User jo@example.com already exists"
                      else
                        echo "  ℹ User jo@example.com will be created"
                      fi

                      # Count organizations
                      org_count=$(psql -t -c "SELECT COUNT(*) FROM \"organizations\";" 2>/dev/null | tr -d ' ' || echo "0")
                      echo "  ℹ Found $org_count organizations"

                      echo ""
                      echo "🚀 Executing SQL script..."
                      echo "═══════════════════════════════════════════════════════════"

                      # Execute SQL script with detailed output
                      start_time=$(date +%s)

                      if psql -v ON_ERROR_STOP=1 -v VERBOSITY=verbose -f /sql-scripts/add_org_admin.sql; then
                        end_time=$(date +%s)
                        duration=$((end_time - start_time))

                        echo ""
                        echo "═══════════════════════════════════════════════════════════"
                        echo "✅ Job completed successfully!"
                        echo "Duration: ${duration} seconds"
                        echo "Completed at: $(date '+%Y-%m-%d %H:%M:%S UTC')"

                        # Display final summary
                        echo ""
                        echo "📊 Current Organization Memberships for jo@example.com:"
                        if psql -c "
                          SELECT
                            o.name as organization,
                            om.role,
                            om.created_at as added_on
                          FROM organization_memberships om
                          JOIN organizations o ON om.org_id = o.id
                          JOIN users u ON om.user_id = u.id
                          WHERE u.email = 'jo@example.com'
                          ORDER BY o.name;
                        " 2>/dev/null; then
                          echo "Membership details displayed successfully"
                        else
                          echo "Could not retrieve membership details"
                        fi
                      else
                        echo "❌ SQL execution failed!"
                        exit 1
                      fi

                      echo ""
                      echo "═══════════════════════════════════════════════════════════"
                      echo "    End Organization Admin Assignment Job"
                      echo "═══════════════════════════════════════════════════════════"

                  volumeMounts:
                    - name: sql-scripts
                      mountPath: /sql-scripts
                      readOnly: true

                  resources:
                    requests:
                      memory: "256Mi"
                      cpu: "100m"
                    limits:
                      memory: "512Mi"
                      cpu: "500m"

              volumes:
                - name: sql-scripts
                  configMap:
                    name: langfuse-sql-scripts

Option 2: Separate Resources

ConfigMap (configmap.yaml)

apiVersion: v1
kind: ConfigMap
metadata:
  name: langfuse-sql-scripts
  namespace: langfuse-test
  labels:
    app: langfuse-sql-runner
    component: sql-scripts
data:
  add_org_admin.sql: |
    DO $$
    DECLARE
        jo_user_id UUID;
        org_record RECORD;
        processed_count INTEGER := 0;
        error_count INTEGER := 0;
    BEGIN
        -- Find or create jo@example.com user
        SELECT id INTO jo_user_id FROM "users" WHERE email = 'jo@example.com';

        IF jo_user_id IS NULL THEN
            INSERT INTO "users" (id, email, name, created_at, updated_at)
            VALUES (
                gen_random_uuid(),
                'jo@example.com',
                'Jo Admin',
                NOW(),
                NOW()
            ) RETURNING id INTO jo_user_id;

            RAISE NOTICE '✓ Created new user jo@example.com';
        ELSE
            RAISE NOTICE '✓ Found existing user jo@example.com';
        END IF;

        -- Add as admin to all organizations
        FOR org_record IN
            SELECT id, name FROM "organizations" ORDER BY name
        LOOP
            IF NOT EXISTS (
                SELECT 1 FROM "organization_memberships"
                WHERE "org_id" = org_record.id
                AND "user_id" = jo_user_id
            ) THEN
                INSERT INTO "organization_memberships" (
                    id, "org_id", "user_id", role, created_at, updated_at
                ) VALUES (
                    gen_random_uuid(),
                    org_record.id,
                    jo_user_id,
                    'OWNER',
                    NOW(),
                    NOW()
                );
                processed_count := processed_count + 1;
            END IF;
        END LOOP;

        RAISE NOTICE 'Processed % organizations', processed_count;
    END $$;

CronJob (cronjob.yaml)

apiVersion: batch/v1
kind: CronJob
metadata:
  name: langfuse-org-admin-updater
  namespace: langfuse-test
spec:
  schedule: "0 * * * *"  # Every hour
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: postgresql-client
            image: bitnamilegacy/postgresql:17.3.0-debian-12-r1
            env:
            - name: PGHOST
              value: "langfuse-test-postgresql"
            - name: PGPORT
              value: "5432"
            - name: PGUSER
              value: "langfuse"
            - name: PGPASSWORD
              valueFrom:
                secretKeyRef:
                  key: "postgres-password"
                  name: "langfuse-test-postgresql"
            - name: PGDATABASE
              value: "postgres_langfuse"
            command:
            - /bin/sh
            - -c
            - |
              set -eu
              psql -v ON_ERROR_STOP=1 -f /sql-scripts/add_org_admin.sql
            volumeMounts:
            - name: sql-scripts
              mountPath: /sql-scripts
          volumes:
          - name: sql-scripts
            configMap:
              name: langfuse-sql-scripts

📄 SQL Script Details

Core Logic

-- Complete SQL script for adding jo@example.com as organization admin
DO $$
DECLARE
    jo_user_id UUID;
    org_record RECORD;
    processed_count INTEGER := 0;
    error_count INTEGER := 0;
BEGIN
    -- Step 1: Find or create jo@example.com user
    SELECT id INTO jo_user_id FROM "users" WHERE email = 'jo@example.com';

    IF jo_user_id IS NULL THEN
        INSERT INTO "users" (id, email, name, created_at, updated_at)
        VALUES (
            gen_random_uuid(),
            'jo@example.com',
            'Jo Admin',
            NOW(),
            NOW()
        ) RETURNING id INTO jo_user_id;
    END IF;

    -- Step 2: Add to all organizations
    FOR org_record IN
        SELECT id, name FROM "organizations" ORDER BY name
    LOOP
        IF NOT EXISTS (
            SELECT 1 FROM "organization_memberships"
            WHERE "org_id" = org_record.id
            AND "user_id" = jo_user_id
        ) THEN
            INSERT INTO "organization_memberships" (
                id, "org_id", "user_id", role, created_at, updated_at
            ) VALUES (
                gen_random_uuid(),
                org_record.id,
                jo_user_id,
                'OWNER',
                NOW(),
                NOW()
            );
            processed_count := processed_count + 1;
        END IF;
    END LOOP;
END $$;

Manual Execution Commands

# Create user manually
INSERT INTO users (id, email, name, created_at, updated_at)
VALUES (gen_random_uuid(), 'jo@example.com', 'Jo Admin', NOW(), NOW())
ON CONFLICT (email) DO NOTHING;

# Add to all organizations
INSERT INTO organization_memberships (id, org_id, user_id, role, created_at, updated_at)
SELECT gen_random_uuid(), o.id, u.id, 'OWNER', NOW(), NOW()
FROM organizations o
CROSS JOIN users u
WHERE u.email = 'jo@example.com'
AND NOT EXISTS (
    SELECT 1 FROM organization_memberships om
    WHERE om.org_id = o.id
    AND om.user_id = u.id
);

🚀 Deployment Instructions

Step 1: Apply Configuration

# Option 1: Using the complete YAML file
kubectl apply -f langfuse-org-admin-cronjob.yaml

# Option 2: Using separate files
kubectl apply -f configmap.yaml
kubectl apply -f cronjob.yaml

Step 2: Verify Deployment

# Check ConfigMap
kubectl get configmap langfuse-sql-scripts -n langfuse-test

# Check CronJob
kubectl get cronjob langfuse-org-admin-updater -n langfuse-test

# View CronJob details
kubectl describe cronjob langfuse-org-admin-updater -n langfuse-test

Step 3: Monitor Schedule

# View upcoming executions
kubectl get events -n langfuse-test --field-selector involvedObject.name=langfuse-org-admin-updater

# Check job history
kubectl get jobs -n langfuse-test -l component=org-admin-updater

🧪 Testing and Verification

Manual Test Execution

# Create a manual job to test the cron job
kubectl create job --from=cronjob/langfuse-org-admin-updater manual-test -n langfuse-test

# Monitor the test job
kubectl get jobs -n langfuse-test manual-test
kubectl get pods -n langfuse-test -l job-name=manual-test

# View logs
kubectl logs -l job-name=manual-test -n langfuse-test

Database Verification

-- Check if jo@example.com exists
SELECT id, email, name, created_at FROM users WHERE email = 'jo@example.com';

-- Check organization memberships
SELECT
    u.email,
    u.name,
    o.name as org_name,
    om.role,
    om.created_at as member_since
FROM organization_memberships om
JOIN users u ON om.user_id = u.id
JOIN organizations o ON om.org_id = o.id
WHERE u.email = 'jo@example.com'
ORDER BY o.name;

Quick Verification Query

# Quick count of jo@example.com's memberships
kubectl exec -it deployment/langfuse-postgresql -n langfuse-test -- \
  psql -U langfuse -d postgres_langfuse -c "
    SELECT COUNT(*) as total_org_memberships,
           COUNT(CASE WHEN role = 'OWNER' THEN 1 END) as owner_count
    FROM organization_memberships om
    JOIN users u ON om.user_id = u.id
    WHERE u.email = 'jo@example.com';
  "

🔧 Troubleshooting

Common Issues

1. Database Connection Failed

Problem: ✗ Database connection failed

Solution: Check PostgreSQL credentials and service names:

# Check PostgreSQL service
kubectl get svc -n langfuse-test | grep postgres

# Check secret
kubectl get secret langfuse-test-postgresql -n langfuse-test -o yaml

# Verify credentials
kubectl get secret langfuse-test-postgresql -n langfuse-test -o jsonpath='{.data.postgres-password}' | base64 -d

2. Table Not Found

Problem: ✗ Table 'organization_memberships' not found

Solution: Verify table names in your database:

SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name;

3. Permission Denied

Problem: User lacks permissions to modify tables

Solution: Ensure the database user has appropriate permissions:

-- Grant necessary permissions
GRANT INSERT, UPDATE, SELECT ON users, organizations, organization_memberships TO langfuse;
GRANT USAGE ON SCHEMA public TO langfuse;

4. Job Not Running

Problem: CronJob created but no jobs are executed

Solution: Check CronJob status and schedule:

kubectl get cronjob langfuse-org-admin-updater -n langfuse-test -o yaml

5. Pod CrashLoopBackOff

Problem: Pods are crashing repeatedly

Solution: Check pod logs and events:

kubectl describe pod -n langfuse-test -l component=org-admin-updater
kubectl logs -n langfuse-test -l component=org-admin-updater

Debug Commands

# Check pod logs in real-time
kubectl logs -f -n langfuse-test -l component=org-admin-updater

# Delete failed jobs and pods
kubectl delete jobs -n langfuse-test -l component=org-admin-updater

# Clean up and redeploy
kubectl delete configmap langfuse-sql-scripts -n langfuse-test
kubectl delete cronjob langfuse-org-admin-updater -n langfuse-test

⚙️ Customization Options

Change Target User

Update the SQL script to use a different email:

-- Change this line in the SQL script:
SELECT id INTO jo_user_id FROM "users" WHERE email = 'your-email@domain.com';

Change Role

Use a different role instead of 'OWNER':

-- Change 'OWNER' to your preferred role:
'ADMIN', 'MANAGER', 'MEMBER', etc.

Change Schedule

Modify the cron schedule in the CronJob spec:

spec:
  schedule: "30 */6 * * *"  # Every 6 hours at minute 30
  # "0 2 * * *"           # Daily at 2 AM
  # "0 0 * * 0"           # Weekly on Sunday at midnight
  # "0 0 1 * *"           # Monthly on the 1st at midnight

Change Namespace

Update all namespace references:

# Change from:
namespace: langfuse-test
# To:
namespace: your-namespace

Add Additional Features

Email Notifications

env:
- name: SMTP_HOST
  value: "smtp.gmail.com"
- name: SMTP_PORT
  value: "587"
- name: SMTP_USER
  value: "your-email@gmail.com"
- name: SMTP_PASSWORD
  valueFrom:
    secretKeyRef:
      key: smtp-password
      name: email-secrets

Slack Notifications

env:
- name: SLACK_WEBHOOK
  valueFrom:
    secretKeyRef:
      key: webhook-url
      name: slack-secrets

Resource Limits

Adjust resources based on your database size:

resources:
  requests:
    memory: "256Mi"
    cpu: "100m"
  limits:
    memory: "1Gi"
    cpu: "1000m"

📊 Monitoring and Maintenance

Monitoring Commands

# Check current jobs
kubectl get jobs -n langfuse-test -l component=org-admin-updater

# Check recent logs
kubectl logs -n langfuse-test -l component=org-admin-updater --tail=50

# Monitor resource usage
kubectl top pods -n langfuse-test -l component=org-admin-updater

# Check CronJob status
kubectl get cronjob langfuse-org-admin-updater -n langfuse-test -o wide

Maintenance Tasks

Cleanup Old Jobs

# Delete jobs older than 7 days
kubectl delete jobs -n langfuse-test -l component=org-admin-updater --field-selector=status.successful=1

Update SQL Script

# Update the ConfigMap
kubectl edit configmap langfuse-sql-scripts -n langfuse-test

# Force recreation of jobs
kubectl delete jobs -n langfuse-test -l component=org-admin-updater

🎯 Success Criteria

Expected Results

After successful deployment and execution:

  1. CronJob Created: langfuse-org-admin-updater scheduled to run hourly
  2. User Exists: jo@example.com is created in the users table
  3. Admin Access: jo@example.com has OWNER role in ALL organizations
  4. Idempotent: Job can be run multiple times without issues
  5. Logging: Comprehensive logs show processing details

Verification Checklist

  • CronJob schedule is active (kubectl get cronjob)
  • ConfigMap contains correct SQL script
  • jo@example.com exists in users table
  • jo@example.com has OWNER role in organizations table
  • Job logs show successful execution
  • No errors in recent job runs

📚 Additional Resources

🔗 Related Guides


This setup guide provides a complete, production-ready solution for automatically managing organization admin access in Langfuse through a Kubernetes CronJob.

# Langfuse Organization Admin CronJob Configuration
# This file adds a CronJob to automatically add jo@example.com as an organization admin to all organizations
# Add this section to your existing values.yaml or use as a standalone override
extraManifests:
- |
apiVersion: v1
kind: ConfigMap
metadata:
name: langfuse-sql-scripts
namespace: langfuse-test # Replace with your namespace
labels:
app: langfuse-sql-runner
component: sql-scripts
data:
# SQL script to add jo@example.com as org admin to all organizations
add_org_admin.sql: |
DO $$
DECLARE
jo_user_id UUID;
org_record RECORD;
processed_count INTEGER := 0;
error_count INTEGER := 0;
BEGIN
RAISE NOTICE '=== Starting Organization Admin Assignment Job ===';
RAISE NOTICE 'Target: jo@example.com';
RAISE NOTICE 'Timestamp: %', NOW();
-- Step 1: Find or create jo@example.com user
SELECT id INTO jo_user_id FROM "users" WHERE email = 'jo@example.com';
IF jo_user_id IS NULL THEN
-- Create the user if they don't exist
INSERT INTO "users" (id, email, name, created_at, updated_at)
VALUES (
gen_random_uuid(),
'jo@example.com',
'Jo Admin',
NOW(),
NOW()
) RETURNING id INTO jo_user_id;
RAISE NOTICE '✓ Created new user jo@example.com with ID: %', jo_user_id;
ELSE
RAISE NOTICE '✓ Found existing user jo@example.com with ID: %', jo_user_id;
END IF;
-- Step 2: Add jo@example.com as admin to all organizations
FOR org_record IN
SELECT id, name FROM "organizations" ORDER BY name
LOOP
BEGIN
-- Check if user is already a member of this organization
IF NOT EXISTS (
SELECT 1 FROM "organization_members"
WHERE "organizationId" = org_record.id
AND "userId" = jo_user_id
) THEN
-- Add as organization member with admin role
INSERT INTO "organization_members" (
id,
"organizationId",
"userId",
role,
created_at,
updated_at
) VALUES (
gen_random_uuid(),
org_record.id,
jo_user_id,
'ADMIN',
NOW(),
NOW()
);
RAISE NOTICE '✓ Added jo@example.com as ADMIN to organization: % (ID: %)',
org_record.name, org_record.id;
processed_count := processed_count + 1;
ELSE
-- Update existing membership to admin role if not already admin
UPDATE "organization_members"
SET role = 'ADMIN', updated_at = NOW()
WHERE "organizationId" = org_record.id
AND "userId" = jo_user_id
AND role != 'ADMIN';
RAISE NOTICE '✓ Updated jo@example.com role to ADMIN for organization: % (ID: %)',
org_record.name, org_record.id;
processed_count := processed_count + 1;
END IF;
EXCEPTION
WHEN unique_violation THEN
RAISE NOTICE '! User already has admin role in organization: % (ID: %)',
org_record.name, org_record.id;
WHEN OTHERS THEN
RAISE WARNING '! Error processing organization % (ID: %): %',
org_record.name, org_record.id, SQLERRM;
error_count := error_count + 1;
END;
END LOOP;
-- Step 3: Log completion summary
RAISE NOTICE '=== Job Summary ===';
RAISE NOTICE 'Organizations processed: %', processed_count;
RAISE NOTICE 'Errors encountered: %', error_count;
RAISE NOTICE 'Completed at: %', NOW();
RAISE NOTICE '=== End Organization Admin Assignment Job ===';
END $$;
-- Return summary statistics
SELECT
u.email,
COUNT(*) as total_org_memberships,
COUNT(CASE WHEN om.role = 'ADMIN' THEN 1 END) as admin_assignments,
MIN(om.created_at) as first_assignment,
MAX(om.updated_at) as last_update
FROM "users" u
LEFT JOIN "organization_members" om ON u.id = om."userId"
WHERE u.email = 'jo@example.com'
GROUP BY u.email;
- |
apiVersion: batch/v1
kind: CronJob
metadata:
name: langfuse-org-admin-updater
namespace: langfuse-test # Replace with your namespace
labels:
app: langfuse-sql-runner
component: org-admin-updater
annotations:
description: "CronJob to add jo@example.com as organization admin to all organizations"
managed-by: "Helm"
version: "1.0.0"
spec:
# Run every hour at the top of the hour (adjust schedule as needed)
schedule: "0 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
# Give the job 10 minutes to complete
startingDeadlineSeconds: 600
jobTemplate:
spec:
# Clean up the job after 24 hours
ttlSecondsAfterFinished: 86400
template:
metadata:
labels:
app: langfuse-sql-runner
component: org-admin-updater
spec:
restartPolicy: OnFailure
containers:
- name: postgresql-client
image: bitnamilegacy/postgresql:17.3.0-debian-12-r1
env:
# Database connection (uses same config as PostgreSQL subchart)
- name: PGHOST
value: "{{ .Release.Name }}-postgresql"
- name: PGPORT
value: "5432"
- name: PGUSER
value: "{{ .Values.postgresql.auth.username }}"
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-postgresql"
key: postgres-password
- name: PGDATABASE
value: "{{ .Values.postgresql.auth.database }}"
- name: PGOPTIONS
value: "-c client_min_messages=notice"
- name: PGAPPNAME
value: "langfuse-org-admin-updater"
command:
- /bin/sh
- -c
- |
echo "═══════════════════════════════════════════════════════════"
echo " Langfuse Organization Admin Assignment Job"
echo "═══════════════════════════════════════════════════════════"
echo "Started at: $(date '+%Y-%m-%d %H:%M:%S UTC')"
echo "Target user: jo@example.com"
echo "Database: ${PGDATABASE}@${PGHOST}:${PGPORT}"
echo "Namespace: ${POD_NAMESPACE:-default}"
echo "Pod: ${HOSTNAME}"
echo "═══════════════════════════════════════════════════════════"
echo ""
# Set error handling
set -euo pipefail
# Pre-flight checks
echo "🔍 Performing pre-flight checks..."
# Test database connection
echo " • Testing database connection..."
if psql -c "SELECT 1;" >/dev/null 2>&1; then
echo " ✓ Database connection successful"
else
echo " ✗ Database connection failed"
exit 1
fi
# Check if required tables exist
echo " • Checking required tables..."
for table in "users" "organizations" "organization_members"; do
if psql -c "SELECT 1 FROM information_schema.tables WHERE table_name = '$table';" | grep -q "1"; then
echo " ✓ Table '$table' exists"
else
echo " ✗ Table '$table' not found"
exit 1
fi
done
# Check if target user already exists
echo " • Checking for existing user jo@example.com..."
existing_user=$(psql -t -c "SELECT COUNT(*) FROM \"users\" WHERE email = 'jo@example.com';" | tr -d ' ')
if [ "$existing_user" -gt 0 ]; then
echo " ✓ User jo@example.com already exists"
else
echo " ℹ User jo@example.com will be created"
fi
# Count organizations
org_count=$(psql -t -c "SELECT COUNT(*) FROM \"organizations\";" | tr -d ' ')
echo " ℹ Found $org_count organizations"
echo ""
echo "🚀 Executing SQL script..."
echo "═══════════════════════════════════════════════════════════"
# Execute SQL script with detailed output
start_time=$(date +%s)
if psql \
-v ON_ERROR_STOP=1 \
-v VERBOSITY=verbose \
-f /sql-scripts/add_org_admin.sql; then
end_time=$(date +%s)
duration=$((end_time - start_time))
echo ""
echo "═══════════════════════════════════════════════════════════"
echo "✅ Job completed successfully!"
echo "Duration: ${duration} seconds"
echo "Completed at: $(date '+%Y-%m-%d %H:%M:%S UTC')"
# Display final summary
echo ""
echo "📊 Current Organization Memberships for jo@example.com:"
psql -c "
SELECT
o.name as organization,
om.role,
om.created_at as added_on
FROM organization_members om
JOIN organizations o ON om.\"organizationId\" = o.id
JOIN users u ON om.\"userId\" = u.id
WHERE u.email = 'jo@example.com'
ORDER BY o.name;
" || echo "Could not retrieve membership details"
else
echo "❌ SQL execution failed!"
exit 1
fi
echo ""
echo "═══════════════════════════════════════════════════════════"
echo " End Organization Admin Assignment Job"
echo "═══════════════════════════════════════════════════════════"
volumeMounts:
- name: sql-scripts
mountPath: /sql-scripts
readOnly: true
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: sql-scripts
configMap:
name: langfuse-sql-scripts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment