"""Seed the admin-editable basic-role list for Azure. Revision ID: 060 Revises: 059 Create Date: 2026-04-23 Mirrors migration 054 (which seeded AWS IAM + GCP). Key: * ``cloud.azure.default_roles`` — JSON list of ``{role_key, display_name, description?}``. ``role_key`` is the full ARM role-definition path (``/providers/Microsoft.Authorization/ roleDefinitions/``); the driver uses this directly in ``PUT /roleAssignments`` so Azure accepts it across any subscription the SP targets. Built-in role GUIDs are stable across every Azure tenant (see ``Azure built-in roles`` docs); customer-defined custom roles continue to be listed live via ``list_scope_roles`` and don't need to be maintained here. """ from __future__ import annotations import json import sqlalchemy as sa from alembic import op revision = "060" down_revision = "059" branch_labels = None depends_on = None _AZURE_DEFAULTS = [ { "role_key": ( "/providers/Microsoft.Authorization/roleDefinitions/" "8e3af657-a8ff-443c-a75c-2fe8c4bcb635" ), "display_name": "Owner", "description": "Full access to all resources + IAM delegation. " "Hand out sparingly.", }, { "role_key": ( "/providers/Microsoft.Authorization/roleDefinitions/" "b24988ac-6180-42a0-ab88-20f7382dd24c" ), "display_name": "Contributor", "description": "Full access except granting IAM roles.", }, { "role_key": ( "/providers/Microsoft.Authorization/roleDefinitions/" "acdd72a7-3385-48ef-bd42-f606fba81ae7" ), "display_name": "Reader", "description": "Read-only on all resources.", }, { "role_key": ( "/providers/Microsoft.Authorization/roleDefinitions/" "fa23ad8b-c56e-40d8-ac0c-ce449e1d2c64" ), "display_name": "Billing Reader", "description": "View billing, invoices, budgets.", }, { "role_key": ( "/providers/Microsoft.Authorization/roleDefinitions/" "36243c78-bf99-498c-9df9-86d9f8d28608" ), "display_name": "User Access Administrator", "description": "Manage user access to Azure resources.", }, { "role_key": ( "/providers/Microsoft.Authorization/roleDefinitions/" "ba92f5b4-2d11-453d-a403-e96b0029c9fe" ), "display_name": "Storage Blob Data Contributor", "description": "Read/write/delete on blob containers + data.", }, { "role_key": ( "/providers/Microsoft.Authorization/roleDefinitions/" "2a2b9908-6ea1-4ae2-8e65-a410df84e7d1" ), "display_name": "Storage Blob Data Reader", "description": "Read blob containers + data.", }, { "role_key": ( "/providers/Microsoft.Authorization/roleDefinitions/" "9980e02c-c2be-4d73-94e8-173b1dc7cf3c" ), "display_name": "Virtual Machine Contributor", "description": "Manage VMs, but not the networks or storage they " "depend on.", }, { "role_key": ( "/providers/Microsoft.Authorization/roleDefinitions/" "fb879df8-f326-4884-b1cf-06f3ad86be52" ), "display_name": "Virtual Machine User Login", "description": "Log in to a VM as a regular user.", }, { "role_key": ( "/providers/Microsoft.Authorization/roleDefinitions/" "1c0163c0-47e6-4577-8991-ea5c82e286e4" ), "display_name": "Virtual Machine Administrator Login", "description": "Log in to a VM with administrator rights.", }, ] def _table_exists(bind, name: str) -> bool: return bind.execute( sa.text( "SELECT 1 FROM information_schema.TABLES " "WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :n", ), {"n": name}, ).scalar() is not None def _seed_setting(bind, key: str, value_obj, description: str) -> None: bind.execute( sa.text( "INSERT INTO platform_settings (`key`, value, description, " "created_at, updated_at) " "SELECT :k, CAST(:v AS JSON), :d, NOW(), NOW() " "FROM DUAL WHERE NOT EXISTS " "(SELECT 1 FROM platform_settings WHERE `key` = :k)" ), { "k": key, "v": json.dumps(value_obj, ensure_ascii=False), "d": description, }, ) def upgrade() -> None: bind = op.get_bind() if not _table_exists(bind, "platform_settings"): return _seed_setting( bind, "cloud.azure.default_roles", _AZURE_DEFAULTS, "Azure 申请流的默认 built-in role 下拉。role_key = 完整 ARM " "role-definition 路径(不带 subscription 前缀);驱动直接拿去 " "PUT roleAssignments。Custom role 仍由驱动实时拉,不在这维护。", ) def downgrade() -> None: bind = op.get_bind() if not _table_exists(bind, "platform_settings"): return bind.execute( sa.text( "DELETE FROM platform_settings WHERE `key` = " "'cloud.azure.default_roles'", ) )