Add --setup-only and --base-uri flags to seed-keycloak script

--setup-only creates just the realm, service client, OIDC client, and
sponsor attribute — no demo users, groups, or memberships. Ideal for
production Keycloak setup.

--base-uri sets the OIDC redirect URIs and web origins to the given
app URL instead of localhost. Also updates URIs on existing clients.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-01 14:46:21 -07:00
parent bce35c6035
commit 220d0587d5

View File

@@ -10,7 +10,15 @@ Usage:
# Against local Keycloak (defaults)
python scripts/seed-keycloak.py
# Against a remote instance
# Production setup — realm + clients only, no demo data
python scripts/seed-keycloak.py --setup-only \
--url https://keycloak.example.com \
--admin-user admin \
--admin-password changeme \
--realm osa \
--base-uri https://myapp.example.com
# Against a remote instance with demo data
python scripts/seed-keycloak.py \
--url https://keycloak.example.com \
--admin-user admin \
@@ -635,8 +643,21 @@ class KeycloakSeeder:
# ── OIDC client (for browser login) ─────────────────────────
def ensure_oidc_client(self):
def ensure_oidc_client(self, base_uri=None):
"""Create the osa-web OIDC client for browser-based login. Returns client secret."""
if base_uri:
base = base_uri.rstrip("/")
redirect_uris = [f"{base}/api/auth/oidc/callback", f"{base}/*"]
web_origins = [base]
logout_uris = f"{base}/*"
else:
redirect_uris = [
"http://localhost:5001/api/auth/oidc/callback",
"http://localhost:5001/*",
]
web_origins = ["http://localhost:5001", "http://localhost:5173"]
logout_uris = "http://localhost:5173/*"
resp = requests.get(
self._admin_url(f"/clients?clientId={OIDC_CLIENT_ID}"),
headers=self._headers(),
@@ -646,6 +667,26 @@ class KeycloakSeeder:
if clients:
client_uuid = clients[0]["id"]
print(f" OIDC client '{OIDC_CLIENT_ID}' already exists (id={client_uuid[:8]}...)")
if base_uri:
# Update redirect URIs and web origins on existing client
resp = requests.put(
self._admin_url(f"/clients/{client_uuid}"),
headers=self._headers(),
json={
**clients[0],
"redirectUris": redirect_uris,
"webOrigins": web_origins,
"attributes": {
**clients[0].get("attributes", {}),
"post.logout.redirect.uris": logout_uris,
},
},
timeout=10,
)
if resp.status_code == 204:
print(f" OIDC client redirect URIs updated for {base}")
else:
print(f" WARN: Failed to update OIDC client URIs: {resp.status_code}")
else:
resp = requests.post(
self._admin_url("/clients"),
@@ -660,13 +701,10 @@ class KeycloakSeeder:
"standardFlowEnabled": True,
"directAccessGrantsEnabled": False,
"serviceAccountsEnabled": False,
"redirectUris": [
"http://localhost:5001/api/auth/oidc/callback",
"http://localhost:5001/*",
],
"webOrigins": ["http://localhost:5001", "http://localhost:5173"],
"redirectUris": redirect_uris,
"webOrigins": web_origins,
"attributes": {
"post.logout.redirect.uris": "http://localhost:5173/*",
"post.logout.redirect.uris": logout_uris,
},
},
timeout=10,
@@ -867,40 +905,57 @@ def main():
default=os.environ.get("KEYCLOAK_REALM", "osa"),
help="Realm name to create/seed (default: $KEYCLOAK_REALM or osa)",
)
parser.add_argument(
"--setup-only",
action="store_true",
help="Only create realm, clients, and sponsor attribute — no demo users/groups/data",
)
parser.add_argument(
"--base-uri",
default=None,
help="App base URI for OIDC redirect URIs (e.g. https://myapp.example.com). "
"Defaults to localhost for dev.",
)
args = parser.parse_args()
print(f"Seeding Keycloak at {args.url}, realm '{args.realm}'")
mode = "setup-only" if args.setup_only else "full seed"
print(f"Seeding Keycloak at {args.url}, realm '{args.realm}' ({mode})")
if args.base_uri:
print(f" OIDC redirect base: {args.base_uri}")
print()
seeder = KeycloakSeeder(args.url, args.admin_user, args.admin_password, args.realm)
print("[1/9] Realm")
total = 4 if args.setup_only else 9
print(f"[1/{total}] Realm")
seeder.ensure_realm()
print("[2/9] Service client")
print(f"[2/{total}] Service client")
secret = seeder.ensure_service_client()
print("[3/9] OIDC client")
oidc_secret = seeder.ensure_oidc_client()
print(f"[3/{total}] OIDC client")
oidc_secret = seeder.ensure_oidc_client(base_uri=args.base_uri)
print("[4/9] User profile (sponsor attribute)")
print(f"[4/{total}] User profile (sponsor attribute)")
seeder.ensure_sponsor_attribute()
print("[5/9] Users")
user_map = seeder.create_users()
if not args.setup_only:
print(f"[5/{total}] Users")
user_map = seeder.create_users()
print("[6/9] User passwords")
seeder.set_user_passwords(user_map)
print(f"[6/{total}] User passwords")
seeder.set_user_passwords(user_map)
print("[7/9] Groups & child groups")
group_map = seeder.create_groups()
child_map = seeder.create_child_groups(group_map)
print(f"[7/{total}] Groups & child groups")
group_map = seeder.create_groups()
child_map = seeder.create_child_groups(group_map)
print("[8/9] Memberships")
seeder.assign_memberships(user_map, group_map, child_map)
print(f"[8/{total}] Memberships")
seeder.assign_memberships(user_map, group_map, child_map)
print("[9/9] Sponsor attributes")
seeder.assign_sponsors(user_map)
print(f"[9/{total}] Sponsor attributes")
seeder.assign_sponsors(user_map)
print()
print("Done! To connect OSA Suite to this Keycloak instance:")