Compare commits
1 Commits
main
...
backup/mac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0025043999 |
@@ -12,6 +12,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends curl unzip ca-c
|
|||||||
COPY scripts/download_bicep_ls.sh /scripts/
|
COPY scripts/download_bicep_ls.sh /scripts/
|
||||||
RUN chmod +x /scripts/download_bicep_ls.sh && BICEP_VERSION=${BICEP_VERSION} /scripts/download_bicep_ls.sh
|
RUN chmod +x /scripts/download_bicep_ls.sh && BICEP_VERSION=${BICEP_VERSION} /scripts/download_bicep_ls.sh
|
||||||
|
|
||||||
|
# Download Azure DevOps pipeline schema for YAML task completions
|
||||||
|
RUN curl -f -o /azdo-pipeline-schema.json \
|
||||||
|
"https://raw.githubusercontent.com/microsoft/azure-pipelines-vscode/main/service-schema.json"
|
||||||
|
|
||||||
|
|
||||||
# ── Stage 2: Python wheel build ───────────────────────────────────────────────
|
# ── Stage 2: Python wheel build ───────────────────────────────────────────────
|
||||||
FROM python:3.12-slim AS builder
|
FROM python:3.12-slim AS builder
|
||||||
@@ -41,6 +45,7 @@ RUN apt-get update \
|
|||||||
|
|
||||||
# Copy Bicep Language Server (baked in at build time — no volume needed)
|
# Copy Bicep Language Server (baked in at build time — no volume needed)
|
||||||
COPY --from=bicep-downloader /opt/bicep-langserver /opt/bicep-langserver
|
COPY --from=bicep-downloader /opt/bicep-langserver /opt/bicep-langserver
|
||||||
|
COPY --from=bicep-downloader /azdo-pipeline-schema.json /azdo-pipeline-schema.json
|
||||||
|
|
||||||
# Install Python package and dependencies
|
# Install Python package and dependencies
|
||||||
COPY --from=builder /dist/*.whl /tmp/
|
COPY --from=builder /dist/*.whl /tmp/
|
||||||
|
|||||||
@@ -14,49 +14,17 @@ supports LSP WebSocket transport.
|
|||||||
|
|
||||||
## IntelliJ IDEA setup (LSP4IJ)
|
## IntelliJ IDEA setup (LSP4IJ)
|
||||||
|
|
||||||
Install the [LSP4IJ](https://plugins.jetbrains.com/plugin/23257-lsp4ij) plugin, then add language servers:
|
Install the [LSP4IJ](https://plugins.jetbrains.com/plugin/23257-lsp4ij) plugin, then:
|
||||||
|
|
||||||
### Bicep
|
|
||||||
|
|
||||||
1. **Settings → Languages & Frameworks → LSP → Language Servers → +**
|
1. **Settings → Languages & Frameworks → LSP → Language Servers → +**
|
||||||
- Name: `iLSP Bicep`
|
- Name: `iLSP Bicep`
|
||||||
- Server type: `Command` (not WebSocket — see note below)
|
- Server type: `WebSocket`
|
||||||
- Command: `/Users/lrihni/Projects/iLSP/scripts/lsp_bridge_debug.sh wss://ilsp.i80.dk/bicep`
|
- URL: `wss://ilsp.i80.dk/bicep`
|
||||||
- Arguments: (leave blank)
|
|
||||||
- File pattern: `*.bicep;*.bicepparam`
|
|
||||||
|
|
||||||
### YAML (Azure DevOps + GitHub Actions)
|
2. **Add a file-type mapping** under the new server entry:
|
||||||
|
- File type: `Bicep` (or pattern `*.bicep`)
|
||||||
|
|
||||||
2. **Settings → Languages & Frameworks → LSP → Language Servers → +**
|
3. **Disable Azure Toolkit Bicep completions** — this is critical if you have the
|
||||||
- Name: `iLSP YAML`
|
|
||||||
- Server type: `Command`
|
|
||||||
- Command: `/Users/lrihni/Projects/iLSP/scripts/lsp_bridge_debug.sh wss://ilsp.i80.dk/yaml`
|
|
||||||
- Arguments: (leave blank)
|
|
||||||
- File pattern: `*.yaml;*.yml;azure-pipelines.yml`
|
|
||||||
|
|
||||||
### Python (optional)
|
|
||||||
|
|
||||||
3. **Settings → Languages & Frameworks → LSP → Language Servers → +**
|
|
||||||
- Name: `iLSP Python`
|
|
||||||
- Server type: `Command`
|
|
||||||
- Command: `/Users/lrihni/Projects/iLSP/scripts/lsp_bridge_debug.sh wss://ilsp.i80.dk/python`
|
|
||||||
- Arguments: (leave blank)
|
|
||||||
- File pattern: `*.py`
|
|
||||||
|
|
||||||
> **Note**: LSP4IJ's WebSocket mode doesn't handle LSP Content-Length framing correctly,
|
|
||||||
> causing "starting..." to hang or "Stream closed" errors. Use `lsp_bridge_debug.sh` wrapper
|
|
||||||
> which calls `lsp_bridge.py` and logs errors to `/tmp/lsp_bridge_debug.log` for debugging.
|
|
||||||
|
|
||||||
### Troubleshooting
|
|
||||||
|
|
||||||
If the language server doesn't start, check the debug log:
|
|
||||||
```bash
|
|
||||||
tail -f /tmp/lsp_bridge_debug.log
|
|
||||||
```
|
|
||||||
|
|
||||||
### Disable conflicting plugins
|
|
||||||
|
|
||||||
**Disable Azure Toolkit Bicep completions** — this is critical if you have the
|
|
||||||
Azure Toolkit plugin installed. Without this step, IDEA merges completions from
|
Azure Toolkit plugin installed. Without this step, IDEA merges completions from
|
||||||
both LSP4IJ and Azure Toolkit, resulting in noisy suggestions (`resource`,
|
both LSP4IJ and Azure Toolkit, resulting in noisy suggestions (`resource`,
|
||||||
`projectName`, Bicep schema types, etc.) appearing alongside iLSP results.
|
`projectName`, Bicep schema types, etc.) appearing alongside iLSP results.
|
||||||
|
|||||||
@@ -1,684 +0,0 @@
|
|||||||
[
|
|
||||||
"ACCESS_REVIEW_OPERATOR_SERVICE_ROLE",
|
|
||||||
"ACRDELETE",
|
|
||||||
"ACRIMAGESIGNER",
|
|
||||||
"ACRPULL",
|
|
||||||
"ACRPUSH",
|
|
||||||
"ACRQUARANTINEREADER",
|
|
||||||
"ACRQUARANTINEWRITER",
|
|
||||||
"ADVISOR_RECOMMENDATIONS_CONTRIBUTOR_ASSESSMENTS_AND_REVIEWS",
|
|
||||||
"ADVISOR_REVIEWS_CONTRIBUTOR",
|
|
||||||
"ADVISOR_REVIEWS_READER",
|
|
||||||
"AGENTLESS_SCANNING_FOR_SERVERLESS_SCANNER_SERVICE_ROLE",
|
|
||||||
"AGFOOD_PLATFORM_DATASET_ADMIN",
|
|
||||||
"AGFOOD_PLATFORM_SENSOR_PARTNER_CONTRIBUTOR",
|
|
||||||
"AGFOOD_PLATFORM_SERVICE_ADMIN",
|
|
||||||
"AGFOOD_PLATFORM_SERVICE_CONTRIBUTOR",
|
|
||||||
"AGFOOD_PLATFORM_SERVICE_READER",
|
|
||||||
"ANYBUILD_BUILDER",
|
|
||||||
"API_MANAGEMENT_DEVELOPER_PORTAL_CONTENT_EDITOR",
|
|
||||||
"API_MANAGEMENT_SERVICE_CONTRIBUTOR",
|
|
||||||
"API_MANAGEMENT_SERVICE_OPERATOR_ROLE",
|
|
||||||
"API_MANAGEMENT_SERVICE_READER_ROLE",
|
|
||||||
"API_MANAGEMENT_SERVICE_WORKSPACE_API_DEVELOPER",
|
|
||||||
"API_MANAGEMENT_SERVICE_WORKSPACE_API_PRODUCT_MANAGER",
|
|
||||||
"API_MANAGEMENT_WORKSPACE_API_DEVELOPER",
|
|
||||||
"API_MANAGEMENT_WORKSPACE_API_PRODUCT_MANAGER",
|
|
||||||
"API_MANAGEMENT_WORKSPACE_CONTRIBUTOR",
|
|
||||||
"API_MANAGEMENT_WORKSPACE_READER",
|
|
||||||
"APPGW_FOR_CONTAINERS_CONFIGURATION_MANAGER",
|
|
||||||
"APPLICATION_GROUP_CONTRIBUTOR",
|
|
||||||
"APPLICATION_INSIGHTS_COMPONENT_CONTRIBUTOR",
|
|
||||||
"APPLICATION_INSIGHTS_SNAPSHOT_DEBUGGER",
|
|
||||||
"APP_COMPLIANCE_AUTOMATION_ADMINISTRATOR",
|
|
||||||
"APP_COMPLIANCE_AUTOMATION_READER",
|
|
||||||
"APP_CONFIGURATION_CONTRIBUTOR",
|
|
||||||
"APP_CONFIGURATION_DATA_OWNER",
|
|
||||||
"APP_CONFIGURATION_DATA_READER",
|
|
||||||
"APP_CONFIGURATION_DATA_SAS_USER",
|
|
||||||
"APP_CONFIGURATION_READER",
|
|
||||||
"APP_SERVICE_ENVIRONMENT_CONTRIBUTOR",
|
|
||||||
"ARC_GATEWAY_MANAGER",
|
|
||||||
"ATTESTATION_CONTRIBUTOR",
|
|
||||||
"ATTESTATION_READER",
|
|
||||||
"AUTOMATION_CONTRIBUTOR",
|
|
||||||
"AUTOMATION_JOB_OPERATOR",
|
|
||||||
"AUTOMATION_OPERATOR",
|
|
||||||
"AUTOMATION_RUNBOOK_OPERATOR",
|
|
||||||
"AUTONOMOUS_DEVELOPMENT_PLATFORM_DATA_CONTRIBUTOR_PREVIEW",
|
|
||||||
"AUTONOMOUS_DEVELOPMENT_PLATFORM_DATA_OWNER_PREVIEW",
|
|
||||||
"AUTONOMOUS_DEVELOPMENT_PLATFORM_DATA_READER_PREVIEW",
|
|
||||||
"AUTO_ACTIONS_CONTRIBUTOR",
|
|
||||||
"AVERE_CONTRIBUTOR",
|
|
||||||
"AVERE_OPERATOR",
|
|
||||||
"AVS_ON_FLEET_VIS_ROLE",
|
|
||||||
"AVS_ORCHESTRATOR_ROLE",
|
|
||||||
"AZUREML_COMPUTE_OPERATOR",
|
|
||||||
"AZUREML_DATA_SCIENTIST",
|
|
||||||
"AZUREML_METRICS_WRITER_PREVIEW",
|
|
||||||
"AZUREML_REGISTRY_USER",
|
|
||||||
"AZURE_AI_ACCOUNT_OWNER",
|
|
||||||
"AZURE_AI_ADMINISTRATOR",
|
|
||||||
"AZURE_AI_DEVELOPER",
|
|
||||||
"AZURE_AI_ENTERPRISE_NETWORK_CONNECTION_APPROVER",
|
|
||||||
"AZURE_AI_INFERENCE_DEPLOYMENT_OPERATOR",
|
|
||||||
"AZURE_AI_PROJECT_MANAGER",
|
|
||||||
"AZURE_AI_SAFETY_EVALUATOR",
|
|
||||||
"AZURE_AI_USER",
|
|
||||||
"AZURE_API_CENTER_COMPLIANCE_MANAGER",
|
|
||||||
"AZURE_API_CENTER_CREDENTIAL_ACCESS_READER",
|
|
||||||
"AZURE_API_CENTER_DATA_READER",
|
|
||||||
"AZURE_API_CENTER_SERVICE_CONTRIBUTOR",
|
|
||||||
"AZURE_API_CENTER_SERVICE_READER",
|
|
||||||
"AZURE_ARC_ENABLED_KUBERNETES_CLUSTER_USER_ROLE",
|
|
||||||
"AZURE_ARC_KUBERNETES_ADMIN",
|
|
||||||
"AZURE_ARC_KUBERNETES_CLUSTER_ADMIN",
|
|
||||||
"AZURE_ARC_KUBERNETES_VIEWER",
|
|
||||||
"AZURE_ARC_KUBERNETES_WRITER",
|
|
||||||
"AZURE_ARC_SCVMM_ADMINISTRATOR_ROLE",
|
|
||||||
"AZURE_ARC_SCVMM_PRIVATE_CLOUDS_ONBOARDING",
|
|
||||||
"AZURE_ARC_SCVMM_PRIVATE_CLOUD_USER",
|
|
||||||
"AZURE_ARC_SCVMM_VM_CONTRIBUTOR",
|
|
||||||
"AZURE_ARC_VMWARE_ADMINISTRATOR_ROLE",
|
|
||||||
"AZURE_ARC_VMWARE_PRIVATE_CLOUDS_ONBOARDING",
|
|
||||||
"AZURE_ARC_VMWARE_PRIVATE_CLOUD_USER",
|
|
||||||
"AZURE_ARC_VMWARE_VM_CONTRIBUTOR",
|
|
||||||
"AZURE_AUTOMANAGE_CONTRIBUTOR",
|
|
||||||
"AZURE_BACKUP_SNAPSHOT_CONTRIBUTOR",
|
|
||||||
"AZURE_BATCH_ACCOUNT_CONTRIBUTOR",
|
|
||||||
"AZURE_BATCH_ACCOUNT_READER",
|
|
||||||
"AZURE_BATCH_DATA_CONTRIBUTOR",
|
|
||||||
"AZURE_BATCH_JOB_SUBMITTER",
|
|
||||||
"AZURE_BATCH_SERVICE_ORCHESTRATION_ROLE",
|
|
||||||
"AZURE_BOT_SERVICE_CONTRIBUTOR_ROLE",
|
|
||||||
"AZURE_CENTER_FOR_SAP_SOLUTIONS_ADMINISTRATOR",
|
|
||||||
"AZURE_CENTER_FOR_SAP_SOLUTIONS_MANAGEMENT_ROLE",
|
|
||||||
"AZURE_CENTER_FOR_SAP_SOLUTIONS_READER",
|
|
||||||
"AZURE_CENTER_FOR_SAP_SOLUTIONS_SERVICE_ROLE",
|
|
||||||
"AZURE_CENTER_FOR_SAP_SOLUTIONS_SERVICE_ROLE_FOR_MANAGEMENT",
|
|
||||||
"AZURE_CONNECTED_MACHINE_ONBOARDING",
|
|
||||||
"AZURE_CONNECTED_MACHINE_RESOURCE_ADMINISTRATOR",
|
|
||||||
"AZURE_CONNECTED_MACHINE_RESOURCE_MANAGER",
|
|
||||||
"AZURE_CONNECTED_SQL_SERVER_ONBOARDING",
|
|
||||||
"AZURE_CONTAINERAPPS_SESSION_EXECUTOR",
|
|
||||||
"AZURE_CONTAINER_INSTANCES_CONTRIBUTOR_ROLE",
|
|
||||||
"AZURE_CONTAINER_REGISTRY_SECURE_SUPPLY_CHAIN_OPERATOR_SERVICE_ROLE",
|
|
||||||
"AZURE_CONTAINER_STORAGE_CONTRIBUTOR",
|
|
||||||
"AZURE_CONTAINER_STORAGE_OPERATOR",
|
|
||||||
"AZURE_CONTAINER_STORAGE_OWNER",
|
|
||||||
"AZURE_CUSTOMER_LOCKBOX_APPROVER_FOR_SUBSCRIPTION",
|
|
||||||
"AZURE_DEPLOYMENT_STACK_CONTRIBUTOR",
|
|
||||||
"AZURE_DEPLOYMENT_STACK_OWNER",
|
|
||||||
"AZURE_DEVICE_ONBOARDING_DISCOVERY_CONTRIBUTOR",
|
|
||||||
"AZURE_DEVICE_UPDATE_AGENT",
|
|
||||||
"AZURE_DIGITAL_TWINS_DATA_OWNER",
|
|
||||||
"AZURE_DIGITAL_TWINS_DATA_READER",
|
|
||||||
"AZURE_EDGE_HARDWARE_CENTER_ADMINISTRATOR",
|
|
||||||
"AZURE_EDGE_ON_SITE_DEPLOYMENT_ENGINEER",
|
|
||||||
"AZURE_EVENT_HUBS_DATA_OWNER",
|
|
||||||
"AZURE_EVENT_HUBS_DATA_RECEIVER",
|
|
||||||
"AZURE_EVENT_HUBS_DATA_SENDER",
|
|
||||||
"AZURE_EXTENSION_FOR_SQL_SERVER_DEPLOYMENT",
|
|
||||||
"AZURE_FILE_SYNC_ADMINISTRATOR",
|
|
||||||
"AZURE_FILE_SYNC_READER",
|
|
||||||
"AZURE_FRONT_DOOR_DOMAIN_CONTRIBUTOR",
|
|
||||||
"AZURE_FRONT_DOOR_DOMAIN_READER",
|
|
||||||
"AZURE_FRONT_DOOR_PROFILE_READER",
|
|
||||||
"AZURE_FRONT_DOOR_SECRET_CONTRIBUTOR",
|
|
||||||
"AZURE_FRONT_DOOR_SECRET_READER",
|
|
||||||
"AZURE_HYBRID_DATABASE_ADMINISTRATOR_READ_ONLY_SERVICE_ROLE",
|
|
||||||
"AZURE_IMPACT_INSIGHT_READER",
|
|
||||||
"AZURE_KUBERNETES_FLEET_MANAGER_CONTRIBUTOR_ROLE",
|
|
||||||
"AZURE_KUBERNETES_FLEET_MANAGER_HUB_AGENT_ROLE",
|
|
||||||
"AZURE_KUBERNETES_FLEET_MANAGER_RBAC_ADMIN",
|
|
||||||
"AZURE_KUBERNETES_FLEET_MANAGER_RBAC_CLUSTER_ADMIN",
|
|
||||||
"AZURE_KUBERNETES_FLEET_MANAGER_RBAC_CLUSTER_READER",
|
|
||||||
"AZURE_KUBERNETES_FLEET_MANAGER_RBAC_CLUSTER_WRITER",
|
|
||||||
"AZURE_KUBERNETES_FLEET_MANAGER_RBAC_READER",
|
|
||||||
"AZURE_KUBERNETES_FLEET_MANAGER_RBAC_WRITER",
|
|
||||||
"AZURE_KUBERNETES_SERVICE_AGENT_POOL_MANAGER_ROLE",
|
|
||||||
"AZURE_KUBERNETES_SERVICE_ARC_CLUSTER_ADMIN_ROLE",
|
|
||||||
"AZURE_KUBERNETES_SERVICE_ARC_CLUSTER_USER_ROLE",
|
|
||||||
"AZURE_KUBERNETES_SERVICE_ARC_CONTRIBUTOR_ROLE",
|
|
||||||
"AZURE_KUBERNETES_SERVICE_CLUSTER_ADMIN_ROLE",
|
|
||||||
"AZURE_KUBERNETES_SERVICE_CLUSTER_MONITORING_USER",
|
|
||||||
"AZURE_KUBERNETES_SERVICE_CLUSTER_USER_ROLE",
|
|
||||||
"AZURE_KUBERNETES_SERVICE_CONTRIBUTOR_ROLE",
|
|
||||||
"AZURE_KUBERNETES_SERVICE_HYBRID_CLUSTER_ADMIN_ROLE",
|
|
||||||
"AZURE_KUBERNETES_SERVICE_HYBRID_CLUSTER_USER_ROLE",
|
|
||||||
"AZURE_KUBERNETES_SERVICE_HYBRID_CONTRIBUTOR_ROLE",
|
|
||||||
"AZURE_KUBERNETES_SERVICE_POLICY_ADD_ON_DEPLOYMENT",
|
|
||||||
"AZURE_KUBERNETES_SERVICE_RBAC_ADMIN",
|
|
||||||
"AZURE_KUBERNETES_SERVICE_RBAC_CLUSTER_ADMIN",
|
|
||||||
"AZURE_KUBERNETES_SERVICE_RBAC_READER",
|
|
||||||
"AZURE_KUBERNETES_SERVICE_RBAC_WRITER",
|
|
||||||
"AZURE_MACHINE_LEARNING_WORKSPACE_CONNECTION_SECRETS_READER",
|
|
||||||
"AZURE_MANAGED_GRAFANA_WORKSPACE_CONTRIBUTOR",
|
|
||||||
"AZURE_MAPS_CONTRIBUTOR",
|
|
||||||
"AZURE_MAPS_DATA_CONTRIBUTOR",
|
|
||||||
"AZURE_MAPS_DATA_READER",
|
|
||||||
"AZURE_MAPS_DATA_READ_AND_BATCH_ROLE",
|
|
||||||
"AZURE_MAPS_SEARCH_AND_RENDER_DATA_READER",
|
|
||||||
"AZURE_MESSAGING_CATALOG_DATA_OWNER",
|
|
||||||
"AZURE_MESSAGING_CONNECTORS_OWNER",
|
|
||||||
"AZURE_MONITOR_DASHBOARDS_WITH_GRAFANA_CONTRIBUTOR",
|
|
||||||
"AZURE_PROGRAMMABLE_CONNECTIVITY_GATEWAY_DATAPLANE_USER",
|
|
||||||
"AZURE_PROGRAMMABLE_CONNECTIVITY_GATEWAY_USER",
|
|
||||||
"AZURE_RED_HAT_OPENSHIFT_CLOUD_CONTROLLER_MANAGER",
|
|
||||||
"AZURE_RED_HAT_OPENSHIFT_CLUSTER_INGRESS_OPERATOR",
|
|
||||||
"AZURE_RED_HAT_OPENSHIFT_DISK_STORAGE_OPERATOR",
|
|
||||||
"AZURE_RED_HAT_OPENSHIFT_FEDERATED_CREDENTIAL",
|
|
||||||
"AZURE_RED_HAT_OPENSHIFT_FILE_STORAGE_OPERATOR",
|
|
||||||
"AZURE_RED_HAT_OPENSHIFT_HOSTED_CONTROL_PLANES_CLUSTER_API_PROVIDER",
|
|
||||||
"AZURE_RED_HAT_OPENSHIFT_HOSTED_CONTROL_PLANES_CONTROL_PLANE_OPERATOR",
|
|
||||||
"AZURE_RED_HAT_OPENSHIFT_HOSTED_CONTROL_PLANES_SERVICE_MANAGED_IDENTITY",
|
|
||||||
"AZURE_RED_HAT_OPENSHIFT_IMAGE_REGISTRY_OPERATOR",
|
|
||||||
"AZURE_RED_HAT_OPENSHIFT_MACHINE_API_OPERATOR",
|
|
||||||
"AZURE_RED_HAT_OPENSHIFT_NETWORK_OPERATOR",
|
|
||||||
"AZURE_RED_HAT_OPENSHIFT_SERVICE_OPERATOR",
|
|
||||||
"AZURE_RELAY_LISTENER",
|
|
||||||
"AZURE_RELAY_OWNER",
|
|
||||||
"AZURE_RELAY_SENDER",
|
|
||||||
"AZURE_RESOURCE_BRIDGE_DEPLOYMENT_ROLE",
|
|
||||||
"AZURE_RESOURCE_NOTIFICATIONS_SYSTEM_TOPICS_SUBSCRIBER",
|
|
||||||
"AZURE_SERVICE_BUS_DATA_OWNER",
|
|
||||||
"AZURE_SERVICE_BUS_DATA_RECEIVER",
|
|
||||||
"AZURE_SERVICE_BUS_DATA_SENDER",
|
|
||||||
"AZURE_SPHERE_CONTRIBUTOR",
|
|
||||||
"AZURE_SPHERE_OWNER",
|
|
||||||
"AZURE_SPHERE_PUBLISHER",
|
|
||||||
"AZURE_SPHERE_READER",
|
|
||||||
"AZURE_SPRING_APPS_APPLICATION_CONFIGURATION_SERVICE_CONFIG_FILE_PATTERN_READER_ROLE",
|
|
||||||
"AZURE_SPRING_APPS_APPLICATION_CONFIGURATION_SERVICE_LOG_READER_ROLE",
|
|
||||||
"AZURE_SPRING_APPS_CONNECT_ROLE",
|
|
||||||
"AZURE_SPRING_APPS_JOB_EXECUTION_INSTANCE_LIST_ROLE",
|
|
||||||
"AZURE_SPRING_APPS_JOB_LOG_READER_ROLE",
|
|
||||||
"AZURE_SPRING_APPS_MANAGED_COMPONENTS_LOG_READER_ROLE",
|
|
||||||
"AZURE_SPRING_APPS_REMOTE_DEBUGGING_ROLE",
|
|
||||||
"AZURE_SPRING_APPS_SPRING_CLOUD_CONFIG_SERVER_LOG_READER_ROLE",
|
|
||||||
"AZURE_SPRING_APPS_SPRING_CLOUD_GATEWAY_LOG_READER_ROLE",
|
|
||||||
"AZURE_SPRING_CLOUD_CONFIG_SERVER_CONTRIBUTOR",
|
|
||||||
"AZURE_SPRING_CLOUD_CONFIG_SERVER_READER",
|
|
||||||
"AZURE_SPRING_CLOUD_DATA_READER",
|
|
||||||
"AZURE_SPRING_CLOUD_SERVICE_REGISTRY_CONTRIBUTOR",
|
|
||||||
"AZURE_SPRING_CLOUD_SERVICE_REGISTRY_READER",
|
|
||||||
"AZURE_STACK_HCI_ADMINISTRATOR",
|
|
||||||
"AZURE_STACK_HCI_CONNECTED_INFRAVMS",
|
|
||||||
"AZURE_STACK_HCI_DEVICE_MANAGEMENT_ROLE",
|
|
||||||
"AZURE_STACK_HCI_EDGE_MACHINE_CONTRIBUTOR_ROLE",
|
|
||||||
"AZURE_STACK_HCI_VM_CONTRIBUTOR",
|
|
||||||
"AZURE_STACK_HCI_VM_READER",
|
|
||||||
"AZURE_STACK_REGISTRATION_OWNER",
|
|
||||||
"AZURE_USAGE_BILLING_DATA_SENDER",
|
|
||||||
"AZURE_VM_MANAGED_IDENTITIES_RESTORE_CONTRIBUTOR",
|
|
||||||
"BACKUP_CONTRIBUTOR",
|
|
||||||
"BACKUP_MUA_ADMIN",
|
|
||||||
"BACKUP_MUA_OPERATOR",
|
|
||||||
"BACKUP_OPERATOR",
|
|
||||||
"BACKUP_READER",
|
|
||||||
"BAYER_AG_POWERED_SERVICES_CROP_ID_SOLUTION_USER_ROLE",
|
|
||||||
"BAYER_AG_POWERED_SERVICES_CWUM_SOLUTION",
|
|
||||||
"BAYER_AG_POWERED_SERVICES_FIELD_IMAGERY_SOLUTION_SERVICE_ROLE",
|
|
||||||
"BAYER_AG_POWERED_SERVICES_GDU_SOLUTION",
|
|
||||||
"BAYER_AG_POWERED_SERVICES_HISTORICAL_WEATHER_DATA_SOLUTION_USER_ROLE",
|
|
||||||
"BAYER_AG_POWERED_SERVICES_IMAGERY_SOLUTION",
|
|
||||||
"BAYER_AG_POWERED_SERVICES_SMART_BOUNDARY_SOLUTION_USER_ROLE",
|
|
||||||
"BILLING_READER",
|
|
||||||
"BIZTALK_CONTRIBUTOR",
|
|
||||||
"BLOCKCHAIN_MEMBER_NODE_ACCESS_PREVIEW",
|
|
||||||
"BLUEPRINT_CONTRIBUTOR",
|
|
||||||
"BLUEPRINT_OPERATOR",
|
|
||||||
"CARBON_OPTIMIZATION_READER",
|
|
||||||
"CDN_ENDPOINT_CONTRIBUTOR",
|
|
||||||
"CDN_ENDPOINT_READER",
|
|
||||||
"CDN_PROFILE_CONTRIBUTOR",
|
|
||||||
"CDN_PROFILE_READER",
|
|
||||||
"CHAOS_STUDIO_EXPERIMENT_CONTRIBUTOR",
|
|
||||||
"CHAOS_STUDIO_OPERATOR",
|
|
||||||
"CHAOS_STUDIO_READER",
|
|
||||||
"CHAOS_STUDIO_TARGET_CONTRIBUTOR",
|
|
||||||
"CLASSIC_NETWORK_CONTRIBUTOR",
|
|
||||||
"CLASSIC_STORAGE_ACCOUNT_CONTRIBUTOR",
|
|
||||||
"CLASSIC_STORAGE_ACCOUNT_KEY_OPERATOR_SERVICE_ROLE",
|
|
||||||
"CLASSIC_VIRTUAL_MACHINE_CONTRIBUTOR",
|
|
||||||
"CLEARDB_MYSQL_DB_CONTRIBUTOR",
|
|
||||||
"CLOUDTEST_CONTRIBUTOR_ROLE",
|
|
||||||
"COGNITIVE_SEARCH_SERVERLESS_DATA_CONTRIBUTOR_DEPRECATED",
|
|
||||||
"COGNITIVE_SEARCH_SERVERLESS_DATA_READER_DEPRECATED",
|
|
||||||
"COGNITIVE_SERVICES_CONTRIBUTOR",
|
|
||||||
"COGNITIVE_SERVICES_CUSTOM_VISION_CONTRIBUTOR",
|
|
||||||
"COGNITIVE_SERVICES_CUSTOM_VISION_DEPLOYMENT",
|
|
||||||
"COGNITIVE_SERVICES_CUSTOM_VISION_LABELER",
|
|
||||||
"COGNITIVE_SERVICES_CUSTOM_VISION_READER",
|
|
||||||
"COGNITIVE_SERVICES_CUSTOM_VISION_TRAINER",
|
|
||||||
"COGNITIVE_SERVICES_DATA_CONTRIBUTOR_PREVIEW",
|
|
||||||
"COGNITIVE_SERVICES_DATA_READER",
|
|
||||||
"COGNITIVE_SERVICES_FACE_CONTRIBUTOR",
|
|
||||||
"COGNITIVE_SERVICES_FACE_RECOGNIZER",
|
|
||||||
"COGNITIVE_SERVICES_IMMERSIVE_READER_USER",
|
|
||||||
"COGNITIVE_SERVICES_LANGUAGE_OWNER",
|
|
||||||
"COGNITIVE_SERVICES_LANGUAGE_READER",
|
|
||||||
"COGNITIVE_SERVICES_LANGUAGE_WRITER",
|
|
||||||
"COGNITIVE_SERVICES_LUIS_OWNER",
|
|
||||||
"COGNITIVE_SERVICES_LUIS_READER",
|
|
||||||
"COGNITIVE_SERVICES_LUIS_WRITER",
|
|
||||||
"COGNITIVE_SERVICES_METRICS_ADVISOR_ADMINISTRATOR",
|
|
||||||
"COGNITIVE_SERVICES_METRICS_ADVISOR_USER",
|
|
||||||
"COGNITIVE_SERVICES_OPENAI_CONTRIBUTOR",
|
|
||||||
"COGNITIVE_SERVICES_OPENAI_USER",
|
|
||||||
"COGNITIVE_SERVICES_QNA_MAKER_EDITOR",
|
|
||||||
"COGNITIVE_SERVICES_QNA_MAKER_READER",
|
|
||||||
"COGNITIVE_SERVICES_SPEECH_CONTRIBUTOR",
|
|
||||||
"COGNITIVE_SERVICES_SPEECH_USER",
|
|
||||||
"COGNITIVE_SERVICES_USAGES_READER",
|
|
||||||
"COGNITIVE_SERVICES_USER",
|
|
||||||
"COLLABORATIVE_DATA_CONTRIBUTOR",
|
|
||||||
"COLLABORATIVE_RUNTIME_OPERATOR",
|
|
||||||
"COMMUNICATION_AND_EMAIL_SERVICE_OWNER",
|
|
||||||
"COMMUNITY_CONTRIBUTOR_ROLE",
|
|
||||||
"COMMUNITY_OWNER_ROLE",
|
|
||||||
"COMMUNITY_READER_ROLE",
|
|
||||||
"COMPUTE_DIAGNOSTICS_ROLE",
|
|
||||||
"COMPUTE_FLEET_CONTRIBUTOR",
|
|
||||||
"COMPUTE_GALLERY_ARTIFACTS_PUBLISHER",
|
|
||||||
"COMPUTE_GALLERY_IMAGE_READER",
|
|
||||||
"COMPUTE_GALLERY_SHARING_ADMIN",
|
|
||||||
"COMPUTE_RECOMMENDATIONS_ROLE",
|
|
||||||
"CONNECTED_CLUSTER_MANAGED_IDENTITY_CHECKACCESS_READER",
|
|
||||||
"CONNECTOR_READER",
|
|
||||||
"CONTAINERAPP_READER",
|
|
||||||
"CONTAINER_APPS_CONNECTEDENVIRONMENTS_CONTRIBUTOR",
|
|
||||||
"CONTAINER_APPS_CONNECTEDENVIRONMENTS_READER",
|
|
||||||
"CONTAINER_APPS_CONTRIBUTOR",
|
|
||||||
"CONTAINER_APPS_JOBS_CONTRIBUTOR",
|
|
||||||
"CONTAINER_APPS_JOBS_OPERATOR",
|
|
||||||
"CONTAINER_APPS_JOBS_READER",
|
|
||||||
"CONTAINER_APPS_MANAGEDENVIRONMENTS_CONTRIBUTOR",
|
|
||||||
"CONTAINER_APPS_MANAGEDENVIRONMENTS_READER",
|
|
||||||
"CONTAINER_APPS_OPERATOR",
|
|
||||||
"CONTAINER_APPS_SESSIONPOOLS_CONTRIBUTOR",
|
|
||||||
"CONTAINER_APPS_SESSIONPOOLS_READER",
|
|
||||||
"CONTAINER_INSTANCES_CONTRIBUTOR",
|
|
||||||
"CONTAINER_REGISTRY_CACHE_RULE_ADMINISTRATOR",
|
|
||||||
"CONTAINER_REGISTRY_CACHE_RULE_READER",
|
|
||||||
"CONTAINER_REGISTRY_CONFIGURATION_READER_AND_DATA_ACCESS_CONFIGURATION_READER",
|
|
||||||
"CONTAINER_REGISTRY_CONTRIBUTOR_AND_DATA_ACCESS_CONFIGURATION_ADMINISTRATOR",
|
|
||||||
"CONTAINER_REGISTRY_CREDENTIAL_SET_ADMINISTRATOR",
|
|
||||||
"CONTAINER_REGISTRY_CREDENTIAL_SET_READER",
|
|
||||||
"CONTAINER_REGISTRY_DATA_IMPORTER_AND_DATA_READER",
|
|
||||||
"CONTAINER_REGISTRY_REPOSITORY_CATALOG_LISTER",
|
|
||||||
"CONTAINER_REGISTRY_REPOSITORY_CONTRIBUTOR",
|
|
||||||
"CONTAINER_REGISTRY_REPOSITORY_READER",
|
|
||||||
"CONTAINER_REGISTRY_REPOSITORY_WRITER",
|
|
||||||
"CONTAINER_REGISTRY_TASKS_CONTRIBUTOR",
|
|
||||||
"CONTAINER_REGISTRY_TRANSFER_PIPELINE_CONTRIBUTOR",
|
|
||||||
"CONTRIBUTOR",
|
|
||||||
"COSMOSBACKUPOPERATOR",
|
|
||||||
"COSMOSRESTOREOPERATOR",
|
|
||||||
"COSMOS_DB_ACCOUNT_READER_ROLE",
|
|
||||||
"COSMOS_DB_OPERATOR",
|
|
||||||
"COST_MANAGEMENT_CONTRIBUTOR",
|
|
||||||
"COST_MANAGEMENT_READER",
|
|
||||||
"CROSSCONNECTIONMANAGER",
|
|
||||||
"CROSSCONNECTIONREADER",
|
|
||||||
"CUSTOM_AUTOMATION_ROLE_ROOT",
|
|
||||||
"DATA_BOUNDARY_TENANT_ADMINISTRATOR",
|
|
||||||
"DATA_BOX_CONTRIBUTOR",
|
|
||||||
"DATA_BOX_READER",
|
|
||||||
"DATA_FACTORY_CONTRIBUTOR",
|
|
||||||
"DATA_LABELING_LABELER",
|
|
||||||
"DATA_LAKE_ANALYTICS_DEVELOPER",
|
|
||||||
"DATA_OPERATOR_FOR_MANAGED_DISKS",
|
|
||||||
"DATA_PURGER",
|
|
||||||
"DEDICATED_HOST_CONTRIBUTOR_ROLE",
|
|
||||||
"DEFENDER_CSPM_STORAGE_DATA_SCANNER",
|
|
||||||
"DEFENDER_CSPM_STORAGE_SCANNER_OPERATOR",
|
|
||||||
"DEFENDER_FOR_STORAGE_DATA_SCANNER",
|
|
||||||
"DEFENDER_FOR_STORAGE_SCANNER_OPERATOR",
|
|
||||||
"DEFENDER_KUBERNETES_AGENT_OPERATOR",
|
|
||||||
"DEID_BATCH_DATA_OWNER",
|
|
||||||
"DEID_BATCH_DATA_READER",
|
|
||||||
"DEID_DATA_OWNER",
|
|
||||||
"DEID_REALTIME_DATA_USER",
|
|
||||||
"DEPLOYMENT_ENVIRONMENTS_READER",
|
|
||||||
"DEPLOYMENT_ENVIRONMENTS_USER",
|
|
||||||
"DESKTOP_VIRTUALIZATION_APPLICATION_GROUP_CONTRIBUTOR",
|
|
||||||
"DESKTOP_VIRTUALIZATION_APPLICATION_GROUP_READER",
|
|
||||||
"DESKTOP_VIRTUALIZATION_APP_ATTACH_CONTRIBUTOR",
|
|
||||||
"DESKTOP_VIRTUALIZATION_CONTRIBUTOR",
|
|
||||||
"DESKTOP_VIRTUALIZATION_HOST_POOL_CONTRIBUTOR",
|
|
||||||
"DESKTOP_VIRTUALIZATION_HOST_POOL_READER",
|
|
||||||
"DESKTOP_VIRTUALIZATION_POWER_ON_CONTRIBUTOR",
|
|
||||||
"DESKTOP_VIRTUALIZATION_POWER_ON_OFF_CONTRIBUTOR",
|
|
||||||
"DESKTOP_VIRTUALIZATION_READER",
|
|
||||||
"DESKTOP_VIRTUALIZATION_SESSION_HOST_OPERATOR",
|
|
||||||
"DESKTOP_VIRTUALIZATION_USER",
|
|
||||||
"DESKTOP_VIRTUALIZATION_USER_SESSION_OPERATOR",
|
|
||||||
"DESKTOP_VIRTUALIZATION_VIRTUAL_MACHINE_CONTRIBUTOR",
|
|
||||||
"DESKTOP_VIRTUALIZATION_WORKSPACE_CONTRIBUTOR",
|
|
||||||
"DESKTOP_VIRTUALIZATION_WORKSPACE_READER",
|
|
||||||
"DEVCENTER_DEV_BOX_USER",
|
|
||||||
"DEVCENTER_PROJECT_ADMIN",
|
|
||||||
"DEVICE_PROVISIONING_SERVICE_DATA_CONTRIBUTOR",
|
|
||||||
"DEVICE_PROVISIONING_SERVICE_DATA_READER",
|
|
||||||
"DEVICE_UPDATE_ADMINISTRATOR",
|
|
||||||
"DEVICE_UPDATE_CONTENT_ADMINISTRATOR",
|
|
||||||
"DEVICE_UPDATE_CONTENT_READER",
|
|
||||||
"DEVICE_UPDATE_DEPLOYMENTS_ADMINISTRATOR",
|
|
||||||
"DEVICE_UPDATE_DEPLOYMENTS_READER",
|
|
||||||
"DEVICE_UPDATE_READER",
|
|
||||||
"DEVTEST_LABS_USER",
|
|
||||||
"DICOM_DATA_OWNER",
|
|
||||||
"DICOM_DATA_READER",
|
|
||||||
"DISK_BACKUP_READER",
|
|
||||||
"DISK_ENCRYPTION_SET_OPERATOR_FOR_MANAGED_DISKS",
|
|
||||||
"DISK_POOL_OPERATOR",
|
|
||||||
"DISK_RESTORE_OPERATOR",
|
|
||||||
"DISK_SNAPSHOT_CONTRIBUTOR",
|
|
||||||
"DNS_RESOLVER_CONTRIBUTOR",
|
|
||||||
"DNS_ZONE_CONTRIBUTOR",
|
|
||||||
"DOCUMENTDB_ACCOUNT_CONTRIBUTOR",
|
|
||||||
"DOMAIN_SERVICES_CONTRIBUTOR",
|
|
||||||
"DOMAIN_SERVICES_READER",
|
|
||||||
"DURABLE_TASK_DATA_CONTRIBUTOR",
|
|
||||||
"DURABLE_TASK_DATA_READER",
|
|
||||||
"DURABLE_TASK_WORKER",
|
|
||||||
"EDGE_MANAGEMENT_COPILOT_USER",
|
|
||||||
"ELASTIC_SAN_NETWORK_ADMIN",
|
|
||||||
"ELASTIC_SAN_OWNER",
|
|
||||||
"ELASTIC_SAN_READER",
|
|
||||||
"ELASTIC_SAN_SNAPSHOT_EXPORTER",
|
|
||||||
"ELASTIC_SAN_VOLUME_GROUP_OWNER",
|
|
||||||
"ELASTIC_SAN_VOLUME_IMPORTER",
|
|
||||||
"ENCLAVE_APPROVER_ROLE",
|
|
||||||
"ENCLAVE_CONTRIBUTOR_ROLE",
|
|
||||||
"ENCLAVE_OWNER_ROLE",
|
|
||||||
"ENCLAVE_READER_ROLE",
|
|
||||||
"EVENTGRID_CONTRIBUTOR",
|
|
||||||
"EVENTGRID_DATA_CONTRIBUTOR",
|
|
||||||
"EVENTGRID_DATA_RECEIVER",
|
|
||||||
"EVENTGRID_DATA_SENDER",
|
|
||||||
"EVENTGRID_EVENTSUBSCRIPTION_CONTRIBUTOR",
|
|
||||||
"EVENTGRID_EVENTSUBSCRIPTION_READER",
|
|
||||||
"EVENTGRID_TOPICSPACES_PUBLISHER",
|
|
||||||
"EVENTGRID_TOPICSPACES_SUBSCRIBER",
|
|
||||||
"EXPERIMENTATION_ADMINISTRATOR",
|
|
||||||
"EXPERIMENTATION_CONTRIBUTOR",
|
|
||||||
"EXPERIMENTATION_METRIC_CONTRIBUTOR",
|
|
||||||
"EXPERIMENTATION_READER",
|
|
||||||
"FHIR_DATA_CONTRIBUTOR",
|
|
||||||
"FHIR_DATA_CONVERTER",
|
|
||||||
"FHIR_DATA_EXPORTER",
|
|
||||||
"FHIR_DATA_IMPORTER",
|
|
||||||
"FHIR_DATA_READER",
|
|
||||||
"FHIR_DATA_WRITER",
|
|
||||||
"FHIR_SMART_USER",
|
|
||||||
"FIRMWARE_ANALYSIS_ADMIN",
|
|
||||||
"FLUX_CONFIGURATIONS_CONTRIBUTOR",
|
|
||||||
"GEOCATALOG_ADMINISTRATOR",
|
|
||||||
"GEOCATALOG_READER",
|
|
||||||
"GITOPS_LZ_LIST_ACTIONS",
|
|
||||||
"GRAFANA_ADMIN",
|
|
||||||
"GRAFANA_EDITOR",
|
|
||||||
"GRAFANA_LIMITED_VIEWER",
|
|
||||||
"GRAFANA_VIEWER",
|
|
||||||
"GRAPH_OWNER",
|
|
||||||
"GROUPQUOTA_READER",
|
|
||||||
"GROUPQUOTA_REQUEST_OPERATOR",
|
|
||||||
"GUEST_CONFIGURATION_RESOURCE_CONTRIBUTOR",
|
|
||||||
"HDINSIGHT_CLUSTER_ADMIN",
|
|
||||||
"HDINSIGHT_CLUSTER_OPERATOR",
|
|
||||||
"HDINSIGHT_DOMAIN_SERVICES_CONTRIBUTOR",
|
|
||||||
"HDINSIGHT_ON_AKS_CLUSTER_ADMIN",
|
|
||||||
"HDINSIGHT_ON_AKS_CLUSTER_OPERATOR",
|
|
||||||
"HDINSIGHT_ON_AKS_CLUSTER_POOL_ADMIN",
|
|
||||||
"HEALTHCARE_AGENT_ADMIN",
|
|
||||||
"HEALTHCARE_AGENT_EDITOR",
|
|
||||||
"HEALTHCARE_AGENT_READER",
|
|
||||||
"HEALTH_SAFEGUARDS_DATA_USER",
|
|
||||||
"HIERARCHY_SETTINGS_ADMINISTRATOR",
|
|
||||||
"HYBRIDCOMPUTE_MACHINE_LISTACCESSDETAILS_ACTION_IN_BUILT_ROLE",
|
|
||||||
"HYBRID_SERVER_ONBOARDING",
|
|
||||||
"HYBRID_SERVER_RESOURCE_ADMINISTRATOR",
|
|
||||||
"IMPACT_READER",
|
|
||||||
"IMPACT_REPORTER",
|
|
||||||
"INTEGRATION_SERVICE_ENVIRONMENT_CONTRIBUTOR",
|
|
||||||
"INTEGRATION_SERVICE_ENVIRONMENT_DEVELOPER",
|
|
||||||
"INTELLIGENT_SYSTEMS_ACCOUNT_CONTRIBUTOR",
|
|
||||||
"IOT_HUB_DATA_CONTRIBUTOR",
|
|
||||||
"IOT_HUB_DATA_READER",
|
|
||||||
"IOT_HUB_REGISTRY_CONTRIBUTOR",
|
|
||||||
"IOT_HUB_TWIN_CONTRIBUTOR",
|
|
||||||
"IPAM_POOL_USER",
|
|
||||||
"ISSUE_CONTRIBUTOR",
|
|
||||||
"KEY_VAULT_ADMINISTRATOR",
|
|
||||||
"KEY_VAULT_CERTIFICATES_OFFICER",
|
|
||||||
"KEY_VAULT_CERTIFICATE_USER",
|
|
||||||
"KEY_VAULT_CONTRIBUTOR",
|
|
||||||
"KEY_VAULT_CRYPTO_OFFICER",
|
|
||||||
"KEY_VAULT_CRYPTO_SERVICE_ENCRYPTION_USER",
|
|
||||||
"KEY_VAULT_CRYPTO_SERVICE_RELEASE_USER",
|
|
||||||
"KEY_VAULT_CRYPTO_USER",
|
|
||||||
"KEY_VAULT_DATA_ACCESS_ADMINISTRATOR",
|
|
||||||
"KEY_VAULT_PURGE_OPERATOR",
|
|
||||||
"KEY_VAULT_READER",
|
|
||||||
"KEY_VAULT_SECRETS_OFFICER",
|
|
||||||
"KEY_VAULT_SECRETS_USER",
|
|
||||||
"KNOWLEDGE_CONSUMER",
|
|
||||||
"KOSTNER_DFS_BASE_ROLE",
|
|
||||||
"KUBERNETESRUNTIME_LOAD_BALANCER_CONTRIBUTOR_ROLE",
|
|
||||||
"KUBERNETES_AGENTLESS_OPERATOR",
|
|
||||||
"KUBERNETES_AGENT_OPERATOR",
|
|
||||||
"KUBERNETES_AGENT_SUBSCRIPTION_LEVEL_OPERATOR",
|
|
||||||
"KUBERNETES_CLUSTER_AZURE_ARC_ONBOARDING",
|
|
||||||
"KUBERNETES_EXTENSION_CONTRIBUTOR",
|
|
||||||
"KUBERNETES_NAMESPACE_USER",
|
|
||||||
"KUBERNETES_RUNTIME_STORAGE_CLASS_CONTRIBUTOR_ROLE",
|
|
||||||
"LAB_ASSISTANT",
|
|
||||||
"LAB_CONTRIBUTOR",
|
|
||||||
"LAB_CREATOR",
|
|
||||||
"LAB_OPERATOR",
|
|
||||||
"LAB_SERVICES_CONTRIBUTOR",
|
|
||||||
"LAB_SERVICES_READER",
|
|
||||||
"LANDING_ZONE_ACCOUNT_OWNER",
|
|
||||||
"LANDING_ZONE_ACCOUNT_READER",
|
|
||||||
"LANDING_ZONE_MANAGEMENT_OWNER",
|
|
||||||
"LANDING_ZONE_MANAGEMENT_READER",
|
|
||||||
"LOAD_TEST_CONTRIBUTOR",
|
|
||||||
"LOAD_TEST_OWNER",
|
|
||||||
"LOAD_TEST_READER",
|
|
||||||
"LOCALNGFIREWALLADMINISTRATOR_ROLE",
|
|
||||||
"LOCALRULESTACKSADMINISTRATOR_ROLE",
|
|
||||||
"LOCKS_CONTRIBUTOR",
|
|
||||||
"LOGIC_APPS_STANDARD_CONTRIBUTOR_PREVIEW",
|
|
||||||
"LOGIC_APPS_STANDARD_DEVELOPER_PREVIEW",
|
|
||||||
"LOGIC_APPS_STANDARD_OPERATOR_PREVIEW",
|
|
||||||
"LOGIC_APPS_STANDARD_READER_PREVIEW",
|
|
||||||
"LOGIC_APP_CONTRIBUTOR",
|
|
||||||
"LOGIC_APP_OPERATOR",
|
|
||||||
"LOG_ANALYTICS_CONTRIBUTOR",
|
|
||||||
"LOG_ANALYTICS_READER",
|
|
||||||
"MANAGED_APPLICATION_CONTRIBUTOR_ROLE",
|
|
||||||
"MANAGED_APPLICATION_OPERATOR_ROLE",
|
|
||||||
"MANAGED_APPLICATION_PUBLISHER_OPERATOR",
|
|
||||||
"MANAGED_HSM_CONTRIBUTOR",
|
|
||||||
"MANAGED_IDENTITY_CONTRIBUTOR",
|
|
||||||
"MANAGED_IDENTITY_FEDERATED_IDENTITY_CREDENTIAL_CONTRIBUTOR",
|
|
||||||
"MANAGED_IDENTITY_OPERATOR",
|
|
||||||
"MANAGED_SERVICES_REGISTRATION_ASSIGNMENT_DELETE_ROLE",
|
|
||||||
"MANAGEMENT_GROUP_CONTRIBUTOR",
|
|
||||||
"MANAGEMENT_GROUP_READER",
|
|
||||||
"MEDIA_SERVICES_ACCOUNT_ADMINISTRATOR",
|
|
||||||
"MEDIA_SERVICES_LIVE_EVENTS_ADMINISTRATOR",
|
|
||||||
"MEDIA_SERVICES_MEDIA_OPERATOR",
|
|
||||||
"MEDIA_SERVICES_POLICY_ADMINISTRATOR",
|
|
||||||
"MEDIA_SERVICES_STREAMING_ENDPOINTS_ADMINISTRATOR",
|
|
||||||
"MICROSOFT_EDGE_WINFIELDS_FEDERATED_SUBSCRIPTION_READ_ACCESS_ROLE",
|
|
||||||
"MICROSOFT_KUBERNETES_CONNECTED_CLUSTER_ROLE",
|
|
||||||
"MICROSOFT_POWERBI_TENANT_OPERATIONS_ROLE",
|
|
||||||
"MICROSOFT_SENTINEL_AUTOMATION_CONTRIBUTOR",
|
|
||||||
"MICROSOFT_SENTINEL_BUSINESS_APPLICATIONS_AGENT_OPERATOR",
|
|
||||||
"MICROSOFT_SENTINEL_CONTRIBUTOR",
|
|
||||||
"MICROSOFT_SENTINEL_PLAYBOOK_OPERATOR",
|
|
||||||
"MICROSOFT_SENTINEL_READER",
|
|
||||||
"MICROSOFT_SENTINEL_RESPONDER",
|
|
||||||
"MONITORING_CONTRIBUTOR",
|
|
||||||
"MONITORING_DATA_READER",
|
|
||||||
"MONITORING_METRICS_PUBLISHER",
|
|
||||||
"MONITORING_READER",
|
|
||||||
"MYSQL_BACKUP_AND_EXPORT_OPERATOR",
|
|
||||||
"NETWORK_CONTRIBUTOR",
|
|
||||||
"NEW_RELIC_APM_ACCOUNT_CONTRIBUTOR",
|
|
||||||
"NEXUS_NETWORK_FABRIC_SERVICE_READER",
|
|
||||||
"NEXUS_NETWORK_FABRIC_SERVICE_WRITER",
|
|
||||||
"OBJECT_ANCHORS_ACCOUNT_OWNER",
|
|
||||||
"OBJECT_ANCHORS_ACCOUNT_READER",
|
|
||||||
"OBJECT_UNDERSTANDING_ACCOUNT_OWNER",
|
|
||||||
"OBJECT_UNDERSTANDING_ACCOUNT_READER",
|
|
||||||
"ONLINE_EXPERIMENTATION_CONTRIBUTOR",
|
|
||||||
"ONLINE_EXPERIMENTATION_DATA_OWNER",
|
|
||||||
"ONLINE_EXPERIMENTATION_DATA_READER",
|
|
||||||
"ONLINE_EXPERIMENTATION_READER",
|
|
||||||
"OPENCOST_RATE_CARD_READER",
|
|
||||||
"OPERATOR_NEXUS_COMPUTE_CONTRIBUTOR_ROLE_PREVIEW",
|
|
||||||
"OPERATOR_NEXUS_KEY_VAULT_WRITER_SERVICE_ROLE_PREVIEW",
|
|
||||||
"OPERATOR_NEXUS_OWNER_PREVIEW",
|
|
||||||
"ORACLE_DATABASE_AUTONOMOUS_DATABASE_ADMINISTRATOR",
|
|
||||||
"ORACLE_DATABASE_EXADATA_INFRASTRUCTURE_ADMINISTRATOR_BUILT_IN_ROLE",
|
|
||||||
"ORACLE_DATABASE_EXASCALE_STORAGE_VAULT_ADMINISTRATOR",
|
|
||||||
"ORACLE_DATABASE_EXASCALE_VMCLUSTER_ADMINISTRATOR",
|
|
||||||
"ORACLE_DATABASE_OWNER_BUILT_IN_ROLE",
|
|
||||||
"ORACLE_DATABASE_READER_BUILT_IN_ROLE",
|
|
||||||
"ORACLE_DATABASE_VMCLUSTER_ADMINISTRATOR_BUILT_IN_ROLE",
|
|
||||||
"ORACLE_SUBSCRIPTIONS_MANAGER_BUILT_IN_ROLE",
|
|
||||||
"OWNER",
|
|
||||||
"PLAYFAB_CONTRIBUTOR",
|
|
||||||
"PLAYFAB_READER",
|
|
||||||
"POLICY_INSIGHTS_DATA_WRITER_PREVIEW",
|
|
||||||
"PORTAL_DASHBOARD_WRITER_SERVICE_ROLE",
|
|
||||||
"POSTGRESQL_FLEXIBLE_SERVER_LONG_TERM_RETENTION_BACKUP_ROLE",
|
|
||||||
"POWER_PLATFORM_ACCOUNT_CONTRIBUTOR",
|
|
||||||
"POWER_PLATFORM_ENTERPRISE_POLICY_CONTRIBUTOR",
|
|
||||||
"PRIVATE_DNS_ZONE_CONTRIBUTOR",
|
|
||||||
"PROCUREMENT_CONTRIBUTOR",
|
|
||||||
"PROJECT_BABYLON_DATA_CURATOR",
|
|
||||||
"PROJECT_BABYLON_DATA_READER",
|
|
||||||
"PROJECT_BABYLON_DATA_SOURCE_ADMINISTRATOR",
|
|
||||||
"PROVIDERHUB_CONTRIBUTOR",
|
|
||||||
"PROVIDERHUB_READER",
|
|
||||||
"QUANTUM_WORKSPACE_DATA_CONTRIBUTOR",
|
|
||||||
"QUOTA_REQUEST_OPERATOR",
|
|
||||||
"READER",
|
|
||||||
"READER_AND_DATA_ACCESS",
|
|
||||||
"REDIS_CACHE_CONTRIBUTOR",
|
|
||||||
"REMOTE_RENDERING_ADMINISTRATOR",
|
|
||||||
"REMOTE_RENDERING_CLIENT",
|
|
||||||
"RESERVATION_PURCHASER",
|
|
||||||
"RESOURCE_POLICY_CONTRIBUTOR",
|
|
||||||
"ROLE_BASED_ACCESS_CONTROL_ADMINISTRATOR",
|
|
||||||
"SAAS_HUB_CONTRIBUTOR",
|
|
||||||
"SAVINGS_PLAN_PURCHASER",
|
|
||||||
"SCHEDULED_EVENTS_CONTRIBUTOR",
|
|
||||||
"SCHEDULED_PATCHING_CONTRIBUTOR",
|
|
||||||
"SCHEDULER_JOB_COLLECTIONS_CONTRIBUTOR",
|
|
||||||
"SCHEMA_REGISTRY_CONTRIBUTOR",
|
|
||||||
"SCHEMA_REGISTRY_READER",
|
|
||||||
"SEARCH_INDEX_DATA_CONTRIBUTOR",
|
|
||||||
"SEARCH_INDEX_DATA_READER",
|
|
||||||
"SEARCH_PARAMETER_MANAGER",
|
|
||||||
"SEARCH_SERVICE_CONTRIBUTOR",
|
|
||||||
"SECRETS_STORE_EXTENSION_OWNER",
|
|
||||||
"SECURE_SCORE_READER",
|
|
||||||
"SECURITY_ADMIN",
|
|
||||||
"SECURITY_ASSESSMENT_CONTRIBUTOR",
|
|
||||||
"SECURITY_DETONATION_CHAMBER_PUBLISHER",
|
|
||||||
"SECURITY_DETONATION_CHAMBER_READER",
|
|
||||||
"SECURITY_DETONATION_CHAMBER_SUBMISSION_MANAGER",
|
|
||||||
"SECURITY_DETONATION_CHAMBER_SUBMITTER",
|
|
||||||
"SECURITY_MANAGER_LEGACY",
|
|
||||||
"SECURITY_READER",
|
|
||||||
"SERVICES_HUB_OPERATOR",
|
|
||||||
"SERVICE_CONNECTOR_CONTRIBUTOR",
|
|
||||||
"SERVICE_FABRIC_CLUSTER_CONTRIBUTOR",
|
|
||||||
"SERVICE_FABRIC_MANAGED_CLUSTER_CONTRIBUTOR",
|
|
||||||
"SERVICE_GROUP_READER",
|
|
||||||
"SIGNALR_ACCESSKEY_READER",
|
|
||||||
"SIGNALR_APP_SERVER",
|
|
||||||
"SIGNALR_REST_API_OWNER",
|
|
||||||
"SIGNALR_REST_API_READER",
|
|
||||||
"SIGNALR_SERVICE_OWNER",
|
|
||||||
"SIGNALR_WEB_PUBSUB_CONTRIBUTOR",
|
|
||||||
"SITE_RECOVERY_CONTRIBUTOR",
|
|
||||||
"SITE_RECOVERY_OPERATOR",
|
|
||||||
"SITE_RECOVERY_READER",
|
|
||||||
"SPATIALMAPSACCOUNTS_ACCOUNT_OWNER",
|
|
||||||
"SPATIAL_ANCHORS_ACCOUNT_CONTRIBUTOR",
|
|
||||||
"SPATIAL_ANCHORS_ACCOUNT_OWNER",
|
|
||||||
"SPATIAL_ANCHORS_ACCOUNT_READER",
|
|
||||||
"SQLDB_MIGRATION_ROLE",
|
|
||||||
"SQLMI_MIGRATION_ROLE",
|
|
||||||
"SQLVM_MIGRATION_ROLE",
|
|
||||||
"SQL_DB_CONTRIBUTOR",
|
|
||||||
"SQL_MANAGED_INSTANCE_CONTRIBUTOR",
|
|
||||||
"SQL_SECURITY_MANAGER",
|
|
||||||
"SQL_SERVER_CONTRIBUTOR",
|
|
||||||
"SSH_PUBLICKEYS_CONTRIBUTOR_ROLE",
|
|
||||||
"SSH_PUBLICKEYS_READER_ROLE",
|
|
||||||
"STANDBY_CONTAINER_GROUP_POOL_CONTRIBUTOR",
|
|
||||||
"STORAGE_ACCOUNT_BACKUP_CONTRIBUTOR",
|
|
||||||
"STORAGE_ACCOUNT_CONTRIBUTOR",
|
|
||||||
"STORAGE_ACCOUNT_ENCRYPTION_SCOPE_CONTRIBUTOR_ROLE",
|
|
||||||
"STORAGE_ACCOUNT_KEY_OPERATOR_SERVICE_ROLE",
|
|
||||||
"STORAGE_BLOB_DATA_CONTRIBUTOR",
|
|
||||||
"STORAGE_BLOB_DATA_OWNER",
|
|
||||||
"STORAGE_BLOB_DATA_READER",
|
|
||||||
"STORAGE_BLOB_DELEGATOR",
|
|
||||||
"STORAGE_FILE_DATA_PRIVILEGED_CONTRIBUTOR",
|
|
||||||
"STORAGE_FILE_DATA_PRIVILEGED_READER",
|
|
||||||
"STORAGE_FILE_DATA_SMB_SHARE_CONTRIBUTOR",
|
|
||||||
"STORAGE_FILE_DATA_SMB_SHARE_ELEVATED_CONTRIBUTOR",
|
|
||||||
"STORAGE_FILE_DATA_SMB_SHARE_READER",
|
|
||||||
"STORAGE_QUEUE_DATA_CONTRIBUTOR",
|
|
||||||
"STORAGE_QUEUE_DATA_MESSAGE_PROCESSOR",
|
|
||||||
"STORAGE_QUEUE_DATA_MESSAGE_SENDER",
|
|
||||||
"STORAGE_QUEUE_DATA_READER",
|
|
||||||
"STORAGE_TABLE_DATA_CONTRIBUTOR",
|
|
||||||
"STORAGE_TABLE_DATA_READER",
|
|
||||||
"STREAM_ANALYTICS_CONTRIBUTOR",
|
|
||||||
"STREAM_ANALYTICS_QUERY_TESTER",
|
|
||||||
"STREAM_ANALYTICS_READER",
|
|
||||||
"SUBSCRIPTION_CREATOR",
|
|
||||||
"SUBSCRIPTION_CREATOR_OLD",
|
|
||||||
"SUPPORT_REQUEST_CONTRIBUTOR",
|
|
||||||
"TAG_CONTRIBUTOR",
|
|
||||||
"TAG_READER",
|
|
||||||
"TEMPLATE_SPEC_CONTRIBUTOR",
|
|
||||||
"TEMPLATE_SPEC_READER",
|
|
||||||
"TEST_BASE_READER",
|
|
||||||
"TOOLCHAINORCHESTRATOR_ADMIN_ROLE",
|
|
||||||
"TOOLCHAINORCHESTRATOR_VIEWER_ROLE",
|
|
||||||
"TRAFFIC_MANAGER_CONTRIBUTOR",
|
|
||||||
"TRANSPARENCY_LOGS_OWNER",
|
|
||||||
"TRUSTED_SIGNING_CERTIFICATE_PROFILE_SIGNER",
|
|
||||||
"TRUSTED_SIGNING_IDENTITY_VERIFIER",
|
|
||||||
"USAGE_BILLING_CONTRIBUTOR",
|
|
||||||
"USER_ACCESS_ADMINISTRATOR",
|
|
||||||
"VIDEO_INDEXER_RESTRICTED_VIEWER",
|
|
||||||
"VIRTUAL_MACHINE_ADMINISTRATOR_LOGIN",
|
|
||||||
"VIRTUAL_MACHINE_CONTRIBUTOR",
|
|
||||||
"VIRTUAL_MACHINE_DATA_ACCESS_ADMINISTRATOR_PREVIEW",
|
|
||||||
"VIRTUAL_MACHINE_LOCAL_USER_LOGIN",
|
|
||||||
"VIRTUAL_MACHINE_USER_LOGIN",
|
|
||||||
"VM_RESTORE_OPERATOR",
|
|
||||||
"VM_SCANNER_OPERATOR",
|
|
||||||
"WEBSITE_CONTRIBUTOR",
|
|
||||||
"WEB_PLAN_CONTRIBUTOR",
|
|
||||||
"WEB_PUBSUB_SERVICE_OWNER",
|
|
||||||
"WEB_PUBSUB_SERVICE_READER",
|
|
||||||
"WINDOWS_ADMIN_CENTER_ADMINISTRATOR_LOGIN",
|
|
||||||
"WORKBOOK_CONTRIBUTOR",
|
|
||||||
"WORKBOOK_READER",
|
|
||||||
"WORKLOADBUILDER_MIGRATION_AGENT_ROLE",
|
|
||||||
"WORKLOAD_ORCHESTRATION_IT_ADMIN",
|
|
||||||
"WORKLOAD_ORCHESTRATION_SOLUTION_EXTERNAL_VALIDATOR"
|
|
||||||
]
|
|
||||||
@@ -18,20 +18,8 @@ from typing import Any
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Known Azure enum values not always captured in the catalog schema
|
# Known Azure enum values not always captured in the catalog schema
|
||||||
def _load_azure_roles() -> list[str]:
|
|
||||||
"""Load Azure roles from azure_roles.json (generated from bicep/lookup/rbaclookup)."""
|
|
||||||
roles_file = pathlib.Path(__file__).parent / "azure_roles.json"
|
|
||||||
if roles_file.exists():
|
|
||||||
try:
|
|
||||||
return json.loads(roles_file.read_text())
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning("Failed to load azure_roles.json: %s", e)
|
|
||||||
return []
|
|
||||||
|
|
||||||
_KNOWN_ENUMS: dict[str, list[str]] = {
|
_KNOWN_ENUMS: dict[str, list[str]] = {
|
||||||
"principalType": ["User", "Group", "ServicePrincipal", "Device", "ForeignGroup"],
|
"principalType": ["User", "Group", "ServicePrincipal", "Device", "ForeignGroup"],
|
||||||
"roles": _load_azure_roles(),
|
|
||||||
"roleDefinitionIds": _load_azure_roles(), # alias for roles
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Catalog is baked into the image root at /bicep_modules_catalog.json
|
# Catalog is baked into the image root at /bicep_modules_catalog.json
|
||||||
@@ -48,6 +36,34 @@ _IAC_SOURCE_PATHS = [
|
|||||||
pathlib.Path(__file__).parent.parent.parent / "iac_source_catalog.json", # dev
|
pathlib.Path(__file__).parent.parent.parent / "iac_source_catalog.json", # dev
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Principals catalog — known object IDs for array params (e.g. additionalAccess)
|
||||||
|
# Format: {"params": {"additionalAccess": [{"id": "<guid>", "label": "...", "description": "..."}]}}
|
||||||
|
_PRINCIPALS_CATALOG_PATHS = [
|
||||||
|
pathlib.Path("/data/principals_catalog.json"), # volume-mount (freshest)
|
||||||
|
pathlib.Path("/principals_catalog.json"), # baked into image (fallback)
|
||||||
|
pathlib.Path(__file__).parent.parent.parent / "principals_catalog.json", # dev
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _load_principals_catalog() -> dict[str, list[dict[str, Any]]]:
|
||||||
|
"""Load principals catalog for array param completions.
|
||||||
|
|
||||||
|
Returns dict keyed by param name (e.g. 'additionalAccess') → list of
|
||||||
|
{id, label, description} entries.
|
||||||
|
"""
|
||||||
|
for path in _PRINCIPALS_CATALOG_PATHS:
|
||||||
|
if path.exists():
|
||||||
|
try:
|
||||||
|
data = json.loads(path.read_text())
|
||||||
|
params = data.get("params", {})
|
||||||
|
count = sum(len(v) for v in params.values())
|
||||||
|
logger.info("Principals catalog loaded from %s: %d entries", path, count)
|
||||||
|
return params
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Failed to parse principals catalog at %s", path)
|
||||||
|
logger.debug("No principals_catalog.json found — array param completions unavailable")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def _load_iac_source_catalog() -> dict[str, dict[str, Any]]:
|
def _load_iac_source_catalog() -> dict[str, dict[str, Any]]:
|
||||||
"""Load IAC source catalog for enriched param descriptions.
|
"""Load IAC source catalog for enriched param descriptions.
|
||||||
@@ -106,12 +122,14 @@ class BicepModuleCatalog:
|
|||||||
|
|
||||||
_modules: list[dict[str, Any]] = []
|
_modules: list[dict[str, Any]] = []
|
||||||
_iac: dict[str, dict[str, Any]] = {} # module name → IAC source info
|
_iac: dict[str, dict[str, Any]] = {} # module name → IAC source info
|
||||||
|
_principals: dict[str, list[dict[str, Any]]] = {} # param name → [{id, label, description}]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls) -> None:
|
def load(cls) -> None:
|
||||||
"""Load both catalogs from disk. Call once at startup."""
|
"""Load all catalogs from disk. Call once at startup."""
|
||||||
cls._modules = _load_catalog()
|
cls._modules = _load_catalog()
|
||||||
cls._iac = _load_iac_source_catalog()
|
cls._iac = _load_iac_source_catalog()
|
||||||
|
cls._principals = _load_principals_catalog()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _iac_param_map(cls, module_name: str) -> dict[str, dict[str, Any]]:
|
def _iac_param_map(cls, module_name: str) -> dict[str, dict[str, Any]]:
|
||||||
@@ -258,6 +276,44 @@ class BicepModuleCatalog:
|
|||||||
})
|
})
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def param_array_item_completion_items(
|
||||||
|
cls,
|
||||||
|
module_name: str,
|
||||||
|
version: str,
|
||||||
|
param_name: str,
|
||||||
|
has_open_quote: bool = False,
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
"""Completions for items inside an array param (e.g. additionalAccess objectIds).
|
||||||
|
|
||||||
|
Looks up known entries from the principals catalog keyed by param name.
|
||||||
|
"""
|
||||||
|
entries = cls._principals.get(param_name, [])
|
||||||
|
if not entries:
|
||||||
|
return []
|
||||||
|
|
||||||
|
items = []
|
||||||
|
for i, entry in enumerate(entries):
|
||||||
|
val = entry["id"]
|
||||||
|
label = entry.get("label", val)
|
||||||
|
description = entry.get("description", "")
|
||||||
|
insert = f"{val}'" if has_open_quote else f"'{val}'"
|
||||||
|
doc = f"**{label}**\n\n`{val}`"
|
||||||
|
if description:
|
||||||
|
doc += f"\n\n{description}"
|
||||||
|
items.append({
|
||||||
|
"label": label,
|
||||||
|
"kind": 12, # Value
|
||||||
|
"detail": val, # GUID shown as detail
|
||||||
|
"insertText": insert,
|
||||||
|
"sortText": f"0_lru_arr_{i:03d}_{label}",
|
||||||
|
"documentation": {
|
||||||
|
"kind": "markdown",
|
||||||
|
"value": doc,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return items
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def param_completion_items(cls, module_name: str, version: str) -> list[dict[str, Any]]:
|
def param_completion_items(cls, module_name: str, version: str) -> list[dict[str, Any]]:
|
||||||
"""Param completions for a specific module+version combination."""
|
"""Param completions for a specific module+version combination."""
|
||||||
@@ -280,56 +336,6 @@ class BicepModuleCatalog:
|
|||||||
iac_params = cls._iac_param_map(module_name)
|
iac_params = cls._iac_param_map(module_name)
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
|
|
||||||
# Build snippet for full params block (shown first)
|
|
||||||
snippet_params = []
|
|
||||||
tabstop = 1
|
|
||||||
for param_name, param_info in ver_params.items():
|
|
||||||
iac = iac_params.get(param_name, {})
|
|
||||||
required = iac.get("required", False)
|
|
||||||
ptype = param_info.get("type", "any")
|
|
||||||
allowed = param_info.get("allowed", [])
|
|
||||||
|
|
||||||
# Include required params + first few optional params in snippet
|
|
||||||
if required or len(snippet_params) < 5:
|
|
||||||
if allowed:
|
|
||||||
# Enum: use placeholder with first allowed value
|
|
||||||
placeholder = f"'{allowed[0]}'"
|
|
||||||
elif ptype == "bool":
|
|
||||||
placeholder = "true"
|
|
||||||
elif ptype == "int":
|
|
||||||
placeholder = "0"
|
|
||||||
elif ptype == "array":
|
|
||||||
placeholder = "[]"
|
|
||||||
elif ptype == "object":
|
|
||||||
placeholder = "{{}}"
|
|
||||||
else:
|
|
||||||
placeholder = "''"
|
|
||||||
snippet_params.append(f" {param_name}: ${{{tabstop}:{placeholder}}}")
|
|
||||||
tabstop += 1
|
|
||||||
|
|
||||||
if snippet_params:
|
|
||||||
snippet_text = "\n" + "\n".join(snippet_params) + "\n"
|
|
||||||
required_count = sum(1 for p, i in ver_params.items()
|
|
||||||
if iac_params.get(p, {}).get("required", False))
|
|
||||||
items.append({
|
|
||||||
"label": "⚡ Fill params block",
|
|
||||||
"kind": 15, # Snippet
|
|
||||||
"detail": f"{len(snippet_params)} params ({required_count} required)",
|
|
||||||
"insertText": snippet_text,
|
|
||||||
"insertTextFormat": 2, # Snippet
|
|
||||||
"sortText": "0_lru_snippet_000",
|
|
||||||
"documentation": {
|
|
||||||
"kind": "markdown",
|
|
||||||
"value": (
|
|
||||||
f"**Fill params block**\n\n"
|
|
||||||
f"Inserts {len(snippet_params)} params for `{module_name}`.\n"
|
|
||||||
f"Use Tab to navigate between fields."
|
|
||||||
),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
# Individual param completions
|
|
||||||
for param_name, param_info in ver_params.items():
|
for param_name, param_info in ver_params.items():
|
||||||
ptype = param_info.get("type", "any")
|
ptype = param_info.get("type", "any")
|
||||||
allowed = param_info.get("allowed", [])
|
allowed = param_info.get("allowed", [])
|
||||||
|
|||||||
@@ -130,6 +130,19 @@ class _ProxySession:
|
|||||||
mod_name = last_mod.group(1)
|
mod_name = last_mod.group(1)
|
||||||
mod_ver = last_mod.group(2)
|
mod_ver = last_mod.group(2)
|
||||||
|
|
||||||
|
# Array item context: cursor inside [...] for an array param.
|
||||||
|
# Must be checked BEFORE value_m, since array lines also match
|
||||||
|
# the value pattern (e.g. `additionalAccess: ['`).
|
||||||
|
array_m = re.search(r"^\s*(\w+):\s*\[", current)
|
||||||
|
if array_m and current.count("[") > current.count("]"):
|
||||||
|
return {
|
||||||
|
"type": "param_array_item",
|
||||||
|
"module": mod_name,
|
||||||
|
"version": mod_ver,
|
||||||
|
"param": array_m.group(1),
|
||||||
|
"has_open_quote": bool(re.search(r"'[^']*$", current)),
|
||||||
|
}
|
||||||
|
|
||||||
# Check if cursor is after 'paramname: ' on the current line
|
# Check if cursor is after 'paramname: ' on the current line
|
||||||
# (value context — inject enum/allowed values)
|
# (value context — inject enum/allowed values)
|
||||||
value_m = re.search(r"^\s*(\w+):\s*('?)([^'{}]*)$", current)
|
value_m = re.search(r"^\s*(\w+):\s*('?)([^'{}]*)$", current)
|
||||||
@@ -142,43 +155,6 @@ class _ProxySession:
|
|||||||
"has_open_quote": bool(value_m.group(2)),
|
"has_open_quote": bool(value_m.group(2)),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if cursor is inside an array value for a param
|
|
||||||
# e.g. "roles: ['KEY_VAULT_" or "roles: [ '"
|
|
||||||
array_m = re.search(r"^\s*(\w+):\s*\[[^\]]*?('?)([^',\]]*)$", current)
|
|
||||||
if array_m and array_m.group(1) not in {"params", "name", "module", "resource"}:
|
|
||||||
return {
|
|
||||||
"type": "param_value",
|
|
||||||
"module": mod_name,
|
|
||||||
"version": mod_ver,
|
|
||||||
"param": array_m.group(1),
|
|
||||||
"has_open_quote": bool(array_m.group(2)),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if cursor is inside a multi-line array element
|
|
||||||
# e.g. " 'APP_CONFIG" on line after "roles: ["
|
|
||||||
# Walk backwards to find the array opening
|
|
||||||
for lookback_idx in range(line_idx - 1, max(0, line_idx - 10), -1):
|
|
||||||
prev_line = lines[lookback_idx]
|
|
||||||
# Found array opening like "roles: [" or " roles: ["
|
|
||||||
array_open_m = re.match(r"^\s*(\w+):\s*\[$", prev_line.rstrip())
|
|
||||||
if array_open_m:
|
|
||||||
param_name = array_open_m.group(1)
|
|
||||||
if param_name not in {"params", "name", "module", "resource"}:
|
|
||||||
# Check if current line is inside the array (has quote or is indented)
|
|
||||||
if re.match(r"^\s+('?)([^',\]]*)\s*$", current):
|
|
||||||
has_quote = bool(re.match(r"^\s+'", current))
|
|
||||||
return {
|
|
||||||
"type": "param_value",
|
|
||||||
"module": mod_name,
|
|
||||||
"version": mod_ver,
|
|
||||||
"param": param_name,
|
|
||||||
"has_open_quote": has_quote,
|
|
||||||
}
|
|
||||||
break
|
|
||||||
# Stop if we hit a closing bracket (we're outside the array)
|
|
||||||
if "]" in prev_line:
|
|
||||||
break
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"type": "param",
|
"type": "param",
|
||||||
"module": mod_name,
|
"module": mod_name,
|
||||||
@@ -234,11 +210,18 @@ def _inject_completions(msg: dict[str, Any], context: dict | None = None) -> byt
|
|||||||
context["param"],
|
context["param"],
|
||||||
context.get("has_open_quote", False),
|
context.get("has_open_quote", False),
|
||||||
)
|
)
|
||||||
|
elif ctx_type == "param_array_item":
|
||||||
|
lru_items = BicepModuleCatalog.param_array_item_completion_items(
|
||||||
|
context["module"],
|
||||||
|
context["version"],
|
||||||
|
context["param"],
|
||||||
|
context.get("has_open_quote", False),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Default: module name completions
|
# Default: module name completions
|
||||||
lru_items = BicepModuleCatalog.as_completion_items()
|
lru_items = BicepModuleCatalog.as_completion_items()
|
||||||
|
|
||||||
if ctx_type in ("version", "param", "param_value"):
|
if ctx_type in ("version", "param", "param_value", "param_array_item"):
|
||||||
# Always replace LS completions for private-registry contexts — the
|
# Always replace LS completions for private-registry contexts — the
|
||||||
# Bicep LS doesn't know about our ACR, so anything it returns is noise.
|
# Bicep LS doesn't know about our ACR, so anything it returns is noise.
|
||||||
# Even if lru_items is empty (no enum values for a param), suppress LS.
|
# Even if lru_items is empty (no enum values for a param), suppress LS.
|
||||||
|
|||||||
11
principals_catalog.json
Normal file
11
principals_catalog.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"params": {
|
||||||
|
"additionalAccess": [
|
||||||
|
{
|
||||||
|
"id": "c88bf29d-b13a-4153-9738-8995085a451e",
|
||||||
|
"label": "LRIADMPRO-IaC-Bicep",
|
||||||
|
"description": "IaC Bicep pipeline service principal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Extract all Azure role names from rbacLookup.bicep and generate Python code
|
|
||||||
for _KNOWN_ENUMS in modules.py.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python3 scripts/extract_roles_from_rbaclookup.py /path/to/rbacLookup.bicep
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
def extract_role_names(bicep_file: Path) -> list[str]:
|
|
||||||
"""Extract all role names from the rbacLookup.bicep var roles = {...} block."""
|
|
||||||
content = bicep_file.read_text()
|
|
||||||
|
|
||||||
# Find the "var roles = {" block
|
|
||||||
roles_match = re.search(r'@export\(\)\s*var\s+roles\s*=\s*\{(.+?)\n\}', content, re.DOTALL)
|
|
||||||
if not roles_match:
|
|
||||||
raise ValueError("Could not find 'var roles = {' block in Bicep file")
|
|
||||||
|
|
||||||
roles_block = roles_match.group(1)
|
|
||||||
|
|
||||||
# Extract all role names (keys before the colon)
|
|
||||||
# Pattern: " ROLE_NAME: 'guid'"
|
|
||||||
role_names = re.findall(r'^\s+([A-Z_]+):', roles_block, re.MULTILINE)
|
|
||||||
|
|
||||||
return sorted(role_names)
|
|
||||||
|
|
||||||
|
|
||||||
def generate_python_code(role_names: list[str]) -> str:
|
|
||||||
"""Generate Python code for _KNOWN_ENUMS["roles"]."""
|
|
||||||
lines = ['_KNOWN_ENUMS = {']
|
|
||||||
lines.append(' "roles": [')
|
|
||||||
|
|
||||||
for role in role_names:
|
|
||||||
lines.append(f' "{role}",')
|
|
||||||
|
|
||||||
lines.append(' ],')
|
|
||||||
lines.append(' "roleDefinitionIds": [ # alias for roles')
|
|
||||||
|
|
||||||
for role in role_names:
|
|
||||||
lines.append(f' "{role}",')
|
|
||||||
|
|
||||||
lines.append(' ],')
|
|
||||||
lines.append('}')
|
|
||||||
|
|
||||||
return '\n'.join(lines)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print("Usage: extract_roles_from_rbaclookup.py /path/to/rbacLookup.bicep", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
bicep_path = Path(sys.argv[1])
|
|
||||||
if not bicep_path.exists():
|
|
||||||
print(f"Error: File not found: {bicep_path}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
roles = extract_role_names(bicep_path)
|
|
||||||
print(f"# Extracted {len(roles)} roles from {bicep_path.name}")
|
|
||||||
print(f"# Generated: {Path(__file__).name}\n")
|
|
||||||
print(generate_python_code(roles))
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error: {e}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
@@ -17,7 +17,6 @@ frames straight to stdout.
|
|||||||
import asyncio
|
import asyncio
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
|
||||||
|
|
||||||
import websockets
|
import websockets
|
||||||
|
|
||||||
@@ -72,9 +71,8 @@ async def main(uri: str) -> None:
|
|||||||
if msg is None:
|
if msg is None:
|
||||||
break
|
break
|
||||||
await ws.send(msg)
|
await ws.send(msg)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
print(f"stdin_to_ws error: {e}", file=sys.stderr)
|
pass
|
||||||
traceback.print_exc(file=sys.stderr)
|
|
||||||
|
|
||||||
async def ws_to_stdout() -> None:
|
async def ws_to_stdout() -> None:
|
||||||
try:
|
try:
|
||||||
@@ -82,9 +80,8 @@ async def main(uri: str) -> None:
|
|||||||
data = frame if isinstance(frame, bytes) else frame.encode()
|
data = frame if isinstance(frame, bytes) else frame.encode()
|
||||||
sys.stdout.buffer.write(data)
|
sys.stdout.buffer.write(data)
|
||||||
sys.stdout.buffer.flush()
|
sys.stdout.buffer.flush()
|
||||||
except Exception as e:
|
except Exception:
|
||||||
print(f"ws_to_stdout error: {e}", file=sys.stderr)
|
pass
|
||||||
traceback.print_exc(file=sys.stderr)
|
|
||||||
|
|
||||||
await asyncio.gather(stdin_to_ws(), ws_to_stdout())
|
await asyncio.gather(stdin_to_ws(), ws_to_stdout())
|
||||||
|
|
||||||
@@ -93,9 +90,4 @@ if __name__ == "__main__":
|
|||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
print("Usage: lsp_bridge.py <wss://...>", file=sys.stderr)
|
print("Usage: lsp_bridge.py <wss://...>", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
try:
|
|
||||||
asyncio.run(main(sys.argv[1]))
|
asyncio.run(main(sys.argv[1]))
|
||||||
except Exception as e:
|
|
||||||
print(f"Fatal error: {e}", file=sys.stderr)
|
|
||||||
traceback.print_exc(file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Wrapper for lsp_bridge.py that logs to a file for debugging IntelliJ issues
|
|
||||||
|
|
||||||
LOG_FILE="/tmp/lsp_bridge_debug.log"
|
|
||||||
|
|
||||||
echo "=== LSP Bridge started at $(date) ===" >> "$LOG_FILE"
|
|
||||||
echo "Args: $@" >> "$LOG_FILE"
|
|
||||||
echo "PWD: $(pwd)" >> "$LOG_FILE"
|
|
||||||
echo "Python: $(which python3)" >> "$LOG_FILE"
|
|
||||||
echo "" >> "$LOG_FILE"
|
|
||||||
|
|
||||||
exec /opt/homebrew/bin/python3 /Users/lrihni/Projects/iLSP/scripts/lsp_bridge.py "$@" 2>> "$LOG_FILE"
|
|
||||||
@@ -31,6 +31,7 @@ BICEP_CATALOG="$DEVOPS_MCP_REPO/bicep_modules_catalog.json"
|
|||||||
IAC_CATALOG="$DEVOPS_MCP_REPO/iac_source_catalog.json"
|
IAC_CATALOG="$DEVOPS_MCP_REPO/iac_source_catalog.json"
|
||||||
ILSP_REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
ILSP_REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
TMPL_CATALOG="$ILSP_REPO/pipeline_templates_catalog.json"
|
TMPL_CATALOG="$ILSP_REPO/pipeline_templates_catalog.json"
|
||||||
|
PRINCIPALS_CATALOG="$ILSP_REPO/principals_catalog.json"
|
||||||
|
|
||||||
echo "── iLSP catalog push ──────────────────────────────"
|
echo "── iLSP catalog push ──────────────────────────────"
|
||||||
|
|
||||||
@@ -50,6 +51,13 @@ if [[ ! -f "$TMPL_CATALOG" ]]; then
|
|||||||
fi
|
fi
|
||||||
echo " ✓ $(basename "$TMPL_CATALOG") ($(du -sh "$TMPL_CATALOG" | cut -f1))"
|
echo " ✓ $(basename "$TMPL_CATALOG") ($(du -sh "$TMPL_CATALOG" | cut -f1))"
|
||||||
|
|
||||||
|
if [[ ! -f "$PRINCIPALS_CATALOG" ]]; then
|
||||||
|
echo " ✗ Not found: $PRINCIPALS_CATALOG"
|
||||||
|
echo " Run: python3 $ILSP_REPO/scripts/sync_principals_catalog.py"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " ✓ $(basename "$PRINCIPALS_CATALOG") ($(du -sh "$PRINCIPALS_CATALOG" | cut -f1))"
|
||||||
|
|
||||||
# Copy iac_source_catalog.json to iLSP repo root so it gets baked into the Docker image
|
# Copy iac_source_catalog.json to iLSP repo root so it gets baked into the Docker image
|
||||||
echo ""
|
echo ""
|
||||||
echo " → Copying iac_source_catalog.json to iLSP repo root (for Docker bake) …"
|
echo " → Copying iac_source_catalog.json to iLSP repo root (for Docker bake) …"
|
||||||
@@ -59,7 +67,7 @@ echo " ✓ Copied to $ILSP_REPO/iac_source_catalog.json"
|
|||||||
echo ""
|
echo ""
|
||||||
echo " → Copying to $AUTOBOX:$REMOTE_DIR/ …"
|
echo " → Copying to $AUTOBOX:$REMOTE_DIR/ …"
|
||||||
ssh "$AUTOBOX" "mkdir -p $REMOTE_DIR"
|
ssh "$AUTOBOX" "mkdir -p $REMOTE_DIR"
|
||||||
scp "$BICEP_CATALOG" "$IAC_CATALOG" "$TMPL_CATALOG" "$AUTOBOX:$REMOTE_DIR/"
|
scp "$BICEP_CATALOG" "$IAC_CATALOG" "$TMPL_CATALOG" "$PRINCIPALS_CATALOG" "$AUTOBOX:$REMOTE_DIR/"
|
||||||
echo " ✓ Upload done"
|
echo " ✓ Upload done"
|
||||||
|
|
||||||
if [[ "$NO_RELOAD" == "true" ]]; then
|
if [[ "$NO_RELOAD" == "true" ]]; then
|
||||||
@@ -78,5 +86,5 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo " Done. Bicep + YAML pipeline template completions updated."
|
echo " Done. Bicep + YAML pipeline template + principals completions updated."
|
||||||
echo " Note: iac_source_catalog.json was copied to repo root — commit + push to bake into next Docker image."
|
echo " Note: iac_source_catalog.json was copied to repo root — commit + push to bake into next Docker image."
|
||||||
|
|||||||
214
scripts/sync_principals_catalog.py
Normal file
214
scripts/sync_principals_catalog.py
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
sync_principals_catalog.py — Build principals_catalog.json by scanning .bicep files
|
||||||
|
for array param values (GUIDs) and their inline comments.
|
||||||
|
|
||||||
|
Scans configured IaC repo directories for patterns like:
|
||||||
|
|
||||||
|
additionalAccess: ['c88bf29d-...'] // LRIADMPRO-IaC-Bicep
|
||||||
|
additionalAccess: [
|
||||||
|
'c88bf29d-...' // LRIADMPRO-IaC-Bicep
|
||||||
|
'another-guid' // Another-SP
|
||||||
|
]
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 scripts/sync_principals_catalog.py
|
||||||
|
python3 scripts/sync_principals_catalog.py --paths ~/IdeaProjects/Bitbucket/IaC
|
||||||
|
python3 scripts/sync_principals_catalog.py --dry-run
|
||||||
|
python3 scripts/sync_principals_catalog.py --output /path/to/principals_catalog.json
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_REPO_ROOT = pathlib.Path(__file__).parent.parent
|
||||||
|
_DEFAULT_OUTPUT = _REPO_ROOT / "principals_catalog.json"
|
||||||
|
|
||||||
|
# Default paths to scan — adjust to match your IaC repo locations
|
||||||
|
_DEFAULT_SCAN_PATHS = [
|
||||||
|
"~/IdeaProjects/Bitbucket/IaC",
|
||||||
|
"~/IdeaProjects/Bitbucket/LRU",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Matches a UUID/GUID
|
||||||
|
_GUID_RE = re.compile(
|
||||||
|
r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Matches a single quoted GUID optionally followed by an inline comment:
|
||||||
|
# 'c88bf29d-...' // Some label text
|
||||||
|
# 'c88bf29d-...' // or with hash comments
|
||||||
|
_ITEM_RE = re.compile(
|
||||||
|
r"'(" + _GUID_RE.pattern + r")'\s*(?://+\s*(.+?)\s*)?$",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Matches the opening of an array param assignment:
|
||||||
|
# additionalAccess: [ or additionalAccess: ['guid'
|
||||||
|
_ARRAY_OPEN_RE = re.compile(r"^\s*(\w+)\s*:\s*\[")
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_label(comment: str | None) -> str | None:
|
||||||
|
"""Clean up an inline comment to use as a display label."""
|
||||||
|
if not comment:
|
||||||
|
return None
|
||||||
|
# Strip trailing punctuation and whitespace
|
||||||
|
return comment.strip().rstrip(".,;")
|
||||||
|
|
||||||
|
|
||||||
|
def scan_file(path: pathlib.Path) -> dict[str, list[dict[str, Any]]]:
|
||||||
|
"""Scan a single .bicep file and return {param_name: [{id, label, source}]}."""
|
||||||
|
try:
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
except Exception as exc:
|
||||||
|
log.debug("Cannot read %s: %s", path, exc)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
lines = text.splitlines()
|
||||||
|
results: dict[str, list[dict[str, Any]]] = {}
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
while i < len(lines):
|
||||||
|
line = lines[i]
|
||||||
|
array_m = _ARRAY_OPEN_RE.match(line)
|
||||||
|
if not array_m:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
param_name = array_m.group(1)
|
||||||
|
# Collect all characters on this and subsequent lines until array closes
|
||||||
|
collected = line[array_m.end() - 1:] # from '[' onwards
|
||||||
|
j = i + 1
|
||||||
|
|
||||||
|
# If the array doesn't close on the same line, keep accumulating
|
||||||
|
while collected.count("[") > collected.count("]") and j < len(lines):
|
||||||
|
collected += "\n" + lines[j]
|
||||||
|
j += 1
|
||||||
|
|
||||||
|
# Extract all GUID items from the collected block
|
||||||
|
for item_line in collected.splitlines():
|
||||||
|
m = _ITEM_RE.search(item_line)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
guid = m.group(1).lower()
|
||||||
|
label = _extract_label(m.group(2))
|
||||||
|
entry: dict[str, Any] = {
|
||||||
|
"id": guid,
|
||||||
|
"label": label or guid,
|
||||||
|
"source": str(path),
|
||||||
|
}
|
||||||
|
if label:
|
||||||
|
entry["description"] = f"From {path.name}"
|
||||||
|
results.setdefault(param_name, [])
|
||||||
|
results[param_name].append(entry)
|
||||||
|
|
||||||
|
i = j
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def scan_paths(paths: list[pathlib.Path]) -> dict[str, list[dict[str, Any]]]:
|
||||||
|
"""Scan all .bicep files under the given paths, deduplicating GUIDs per param."""
|
||||||
|
# param_name → {guid → entry} (dict for dedup)
|
||||||
|
merged: dict[str, dict[str, dict[str, Any]]] = {}
|
||||||
|
files_scanned = 0
|
||||||
|
|
||||||
|
for base in paths:
|
||||||
|
if not base.exists():
|
||||||
|
log.warning("Path not found, skipping: %s", base)
|
||||||
|
continue
|
||||||
|
for bicep_file in sorted(base.rglob("*.bicep")):
|
||||||
|
file_results = scan_file(bicep_file)
|
||||||
|
files_scanned += 1
|
||||||
|
for param, entries in file_results.items():
|
||||||
|
bucket = merged.setdefault(param, {})
|
||||||
|
for entry in entries:
|
||||||
|
guid = entry["id"]
|
||||||
|
if guid not in bucket:
|
||||||
|
bucket[guid] = entry
|
||||||
|
else:
|
||||||
|
# Keep the entry with the most informative label
|
||||||
|
existing = bucket[guid]
|
||||||
|
if entry.get("label") and entry["label"] != guid:
|
||||||
|
if existing.get("label") == guid or not existing.get("label"):
|
||||||
|
bucket[guid] = entry
|
||||||
|
|
||||||
|
log.info("Scanned %d .bicep files across %d path(s)", files_scanned, len(paths))
|
||||||
|
|
||||||
|
# Flatten back to lists, sorted by label
|
||||||
|
return {
|
||||||
|
param: sorted(entries.values(), key=lambda e: e.get("label", e["id"]).lower())
|
||||||
|
for param, entries in sorted(merged.items())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_catalog(scan_paths_list: list[pathlib.Path]) -> dict[str, Any]:
|
||||||
|
params = scan_paths(scan_paths_list)
|
||||||
|
total = sum(len(v) for v in params.values())
|
||||||
|
log.info("Found %d unique entries across %d param(s)", total, len(params))
|
||||||
|
return {
|
||||||
|
"synced_at": datetime.now(timezone.utc).isoformat(),
|
||||||
|
"entry_count": total,
|
||||||
|
"params": params,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description=__doc__,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--paths",
|
||||||
|
nargs="+",
|
||||||
|
default=_DEFAULT_SCAN_PATHS,
|
||||||
|
metavar="PATH",
|
||||||
|
help="Directories to scan for .bicep files (default: %(default)s)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dry-run",
|
||||||
|
action="store_true",
|
||||||
|
help="Print findings without writing the catalog",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--output",
|
||||||
|
default=str(_DEFAULT_OUTPUT),
|
||||||
|
help="Output JSON file (default: %(default)s)",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
resolved = [pathlib.Path(p).expanduser().resolve() for p in args.paths]
|
||||||
|
catalog = build_catalog(resolved)
|
||||||
|
|
||||||
|
if args.dry_run:
|
||||||
|
print(f"\n── Principals catalog (dry-run) ──────────────────────")
|
||||||
|
if not catalog["params"]:
|
||||||
|
print(" No GUID values found in .bicep files.")
|
||||||
|
for param, entries in catalog["params"].items():
|
||||||
|
print(f"\n param: {param} ({len(entries)} entries)")
|
||||||
|
for e in entries:
|
||||||
|
print(f" {e['label']:<40} {e['id']}")
|
||||||
|
print(f"\n Total: {catalog['entry_count']} entries")
|
||||||
|
return
|
||||||
|
|
||||||
|
out = pathlib.Path(args.output)
|
||||||
|
# Strip internal 'source' field — not needed at runtime
|
||||||
|
for entries in catalog["params"].values():
|
||||||
|
for e in entries:
|
||||||
|
e.pop("source", None)
|
||||||
|
|
||||||
|
out.write_text(json.dumps(catalog, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||||
|
log.info("Written: %s (%d entries)", out, catalog["entry_count"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -18,6 +18,7 @@ def test_frame_produces_correct_header():
|
|||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def reset_modules():
|
def reset_modules():
|
||||||
BicepModuleCatalog._modules = []
|
BicepModuleCatalog._modules = []
|
||||||
|
BicepModuleCatalog._principals = {}
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
@@ -173,12 +174,8 @@ def test_param_completions_injected_on_param_context():
|
|||||||
assert "roleDefinitionIds" in labels
|
assert "roleDefinitionIds" in labels
|
||||||
assert "principalId" in labels
|
assert "principalId" in labels
|
||||||
assert "principalType" in labels
|
assert "principalType" in labels
|
||||||
# First item is now the snippet completion
|
assert items[0]["sortText"].startswith("0_lru_param_")
|
||||||
assert items[0]["label"] == "⚡ Fill params block"
|
assert items[0]["kind"] == 5 # Field
|
||||||
assert items[0]["kind"] == 15 # Snippet
|
|
||||||
# Second item should be a regular param
|
|
||||||
assert items[1]["sortText"].startswith("0_lru_param_")
|
|
||||||
assert items[1]["kind"] == 5 # Field
|
|
||||||
|
|
||||||
|
|
||||||
def test_param_completion_items_have_insert_text():
|
def test_param_completion_items_have_insert_text():
|
||||||
@@ -186,12 +183,7 @@ def test_param_completion_items_have_insert_text():
|
|||||||
"roleassignments", versions=["1.1.x"], schema=_ROLEASSIGNMENTS_SCHEMA
|
"roleassignments", versions=["1.1.x"], schema=_ROLEASSIGNMENTS_SCHEMA
|
||||||
)]
|
)]
|
||||||
items = BicepModuleCatalog.param_completion_items("roleassignments", "1.1.x")
|
items = BicepModuleCatalog.param_completion_items("roleassignments", "1.1.x")
|
||||||
# First item should be the snippet completion
|
for item in items:
|
||||||
assert items[0]["label"] == "⚡ Fill params block"
|
|
||||||
assert items[0]["insertTextFormat"] == 2
|
|
||||||
assert "principalId" in items[0]["insertText"]
|
|
||||||
# Individual param items should have ": " suffix
|
|
||||||
for item in items[1:]:
|
|
||||||
assert item["insertText"].endswith(": ")
|
assert item["insertText"].endswith(": ")
|
||||||
|
|
||||||
|
|
||||||
@@ -311,48 +303,6 @@ def test_detect_param_value_context_open_quote():
|
|||||||
assert ctx["has_open_quote"] is True
|
assert ctx["has_open_quote"] is True
|
||||||
|
|
||||||
|
|
||||||
def test_detect_param_value_context_in_array():
|
|
||||||
"""Cursor inside array value → param_value context."""
|
|
||||||
lines = [
|
|
||||||
"module myMod 'br/modules:roleassignments:1.1.x' = {",
|
|
||||||
" params: {",
|
|
||||||
" roles: ['KEY_VAULT_", # ← cursor inside array element
|
|
||||||
" }",
|
|
||||||
"}",
|
|
||||||
]
|
|
||||||
session = _make_session_with_doc(URI, lines)
|
|
||||||
# character = len(" roles: ['KEY_VAULT_") = 23
|
|
||||||
ctx = session._detect_context(URI, {"line": 2, "character": 23})
|
|
||||||
assert ctx["type"] == "param_value"
|
|
||||||
assert ctx["module"] == "roleassignments"
|
|
||||||
assert ctx["param"] == "roles"
|
|
||||||
assert ctx["has_open_quote"] is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_detect_param_value_context_in_multiline_array():
|
|
||||||
"""Cursor in multi-line array element → param_value context."""
|
|
||||||
lines = [
|
|
||||||
"module myMod 'br/modules:roleassignments:2.0.x' = {",
|
|
||||||
" params: {",
|
|
||||||
" assignments: [",
|
|
||||||
" {",
|
|
||||||
" roles: [",
|
|
||||||
" 'APP_CONFIGURATION_", # ← cursor on separate line
|
|
||||||
" ]",
|
|
||||||
" }",
|
|
||||||
" ]",
|
|
||||||
" }",
|
|
||||||
"}",
|
|
||||||
]
|
|
||||||
session = _make_session_with_doc(URI, lines)
|
|
||||||
# character = len(" 'APP_CONFIGURATION_") = 27
|
|
||||||
ctx = session._detect_context(URI, {"line": 5, "character": 27})
|
|
||||||
assert ctx["type"] == "param_value"
|
|
||||||
assert ctx["module"] == "roleassignments"
|
|
||||||
assert ctx["param"] == "roles"
|
|
||||||
assert ctx["has_open_quote"] is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_param_value_items_from_catalog_allowed():
|
def test_param_value_items_from_catalog_allowed():
|
||||||
"""environmentType completions come from catalog 'allowed' field."""
|
"""environmentType completions come from catalog 'allowed' field."""
|
||||||
BicepModuleCatalog._modules = [_make_module(
|
BicepModuleCatalog._modules = [_make_module(
|
||||||
@@ -404,24 +354,6 @@ def test_param_value_items_known_enum_fallback():
|
|||||||
assert "User" in labels
|
assert "User" in labels
|
||||||
|
|
||||||
|
|
||||||
def test_param_value_items_roles_enum():
|
|
||||||
"""roles parameter uses _KNOWN_ENUMS for Azure role completions."""
|
|
||||||
BicepModuleCatalog._modules = [_make_module(
|
|
||||||
"roleassignments",
|
|
||||||
versions=["1.1.x"],
|
|
||||||
schema={"1.1.x": {"parameters": {
|
|
||||||
"roles": {"type": "array"}, # no 'allowed' in catalog
|
|
||||||
}}},
|
|
||||||
)]
|
|
||||||
items = BicepModuleCatalog.param_value_completion_items(
|
|
||||||
"roleassignments", "1.1.x", "roles"
|
|
||||||
)
|
|
||||||
labels = [i["label"] for i in items]
|
|
||||||
assert "KEY_VAULT_SECRETS_USER" in labels
|
|
||||||
assert "STORAGE_BLOB_DATA_READER" in labels
|
|
||||||
assert "CONTRIBUTOR" in labels
|
|
||||||
|
|
||||||
|
|
||||||
def test_param_value_items_empty_for_free_string():
|
def test_param_value_items_empty_for_free_string():
|
||||||
"""A plain string param with no allowed values returns no completions."""
|
"""A plain string param with no allowed values returns no completions."""
|
||||||
BicepModuleCatalog._modules = [_make_module(
|
BicepModuleCatalog._modules = [_make_module(
|
||||||
@@ -455,6 +387,111 @@ def test_param_value_injected_in_completion_response():
|
|||||||
assert labels == ["DEV", "TEST", "PROD"]
|
assert labels == ["DEV", "TEST", "PROD"]
|
||||||
|
|
||||||
|
|
||||||
|
# ── Array item completion tests ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def test_detect_param_array_item_context():
|
||||||
|
"""Cursor inside [...] for an array param → param_array_item context."""
|
||||||
|
lines = [
|
||||||
|
"module keyVault 'br/modules:modules/keyvault:2.1.x' = {",
|
||||||
|
" params: {",
|
||||||
|
" additionalAccess: ['", # ← cursor after opening quote inside array
|
||||||
|
" }",
|
||||||
|
"}",
|
||||||
|
]
|
||||||
|
session = _make_session_with_doc(URI, lines)
|
||||||
|
# character 24 = after the `'` (4 spaces + 16 "additionalAccess" + 2 ": " + 1 "[" + 1 "'" = 24)
|
||||||
|
ctx = session._detect_context(URI, {"line": 2, "character": 24})
|
||||||
|
assert ctx["type"] == "param_array_item"
|
||||||
|
assert ctx["module"] == "modules/keyvault"
|
||||||
|
assert ctx["param"] == "additionalAccess"
|
||||||
|
assert ctx["has_open_quote"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_detect_param_array_item_context_no_quote():
|
||||||
|
"""Cursor at start of empty array → param_array_item, no open quote."""
|
||||||
|
lines = [
|
||||||
|
"module keyVault 'br/modules:modules/keyvault:2.1.x' = {",
|
||||||
|
" params: {",
|
||||||
|
" additionalAccess: [", # ← cursor after [
|
||||||
|
" }",
|
||||||
|
"}",
|
||||||
|
]
|
||||||
|
session = _make_session_with_doc(URI, lines)
|
||||||
|
ctx = session._detect_context(URI, {"line": 2, "character": 23})
|
||||||
|
assert ctx["type"] == "param_array_item"
|
||||||
|
assert ctx["has_open_quote"] is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_detect_param_array_item_context_after_existing_item():
|
||||||
|
"""Cursor after an existing item inside array → still param_array_item."""
|
||||||
|
lines = [
|
||||||
|
"module keyVault 'br/modules:modules/keyvault:2.1.x' = {",
|
||||||
|
" params: {",
|
||||||
|
" additionalAccess: ['c88bf29d-b13a-4153-9738-8995085a451e', '",
|
||||||
|
" }",
|
||||||
|
"}",
|
||||||
|
]
|
||||||
|
session = _make_session_with_doc(URI, lines)
|
||||||
|
char = len(" additionalAccess: ['c88bf29d-b13a-4153-9738-8995085a451e', '")
|
||||||
|
ctx = session._detect_context(URI, {"line": 2, "character": char})
|
||||||
|
assert ctx["type"] == "param_array_item"
|
||||||
|
assert ctx["has_open_quote"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_param_array_item_completion_items():
|
||||||
|
"""param_array_item_completion_items returns entries from principals catalog."""
|
||||||
|
BicepModuleCatalog._principals = {
|
||||||
|
"additionalAccess": [
|
||||||
|
{"id": "aaaa-bbbb", "label": "My-SP", "description": "Test SP"},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
items = BicepModuleCatalog.param_array_item_completion_items(
|
||||||
|
"modules/keyvault", "2.1.x", "additionalAccess"
|
||||||
|
)
|
||||||
|
assert len(items) == 1
|
||||||
|
assert items[0]["label"] == "My-SP"
|
||||||
|
assert items[0]["detail"] == "aaaa-bbbb"
|
||||||
|
assert items[0]["insertText"] == "'aaaa-bbbb'"
|
||||||
|
|
||||||
|
|
||||||
|
def test_param_array_item_completion_items_open_quote():
|
||||||
|
BicepModuleCatalog._principals = {
|
||||||
|
"additionalAccess": [{"id": "aaaa-bbbb", "label": "My-SP"}]
|
||||||
|
}
|
||||||
|
items = BicepModuleCatalog.param_array_item_completion_items(
|
||||||
|
"modules/keyvault", "2.1.x", "additionalAccess", has_open_quote=True
|
||||||
|
)
|
||||||
|
assert items[0]["insertText"] == "aaaa-bbbb'"
|
||||||
|
|
||||||
|
|
||||||
|
def test_param_array_item_empty_when_no_catalog_entry():
|
||||||
|
BicepModuleCatalog._principals = {}
|
||||||
|
items = BicepModuleCatalog.param_array_item_completion_items(
|
||||||
|
"modules/keyvault", "2.1.x", "additionalAccess"
|
||||||
|
)
|
||||||
|
assert items == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_param_array_item_injected_in_completion_response():
|
||||||
|
"""Full pipeline: param_array_item context injects principal completions."""
|
||||||
|
BicepModuleCatalog._principals = {
|
||||||
|
"additionalAccess": [{"id": "aaaa-1111", "label": "IaC-SP", "description": "Pipeline SP"}]
|
||||||
|
}
|
||||||
|
msg = _completion_response([{"label": "noise", "sortText": "z"}])
|
||||||
|
ctx = {
|
||||||
|
"type": "param_array_item",
|
||||||
|
"module": "modules/keyvault",
|
||||||
|
"version": "2.1.x",
|
||||||
|
"param": "additionalAccess",
|
||||||
|
"has_open_quote": False,
|
||||||
|
}
|
||||||
|
out = json.loads(_inject_completions(msg, ctx))
|
||||||
|
items = out["result"]["items"]
|
||||||
|
assert len(items) == 1
|
||||||
|
assert items[0]["label"] == "IaC-SP"
|
||||||
|
assert items[0]["detail"] == "aaaa-1111"
|
||||||
|
|
||||||
|
|
||||||
def test_detect_unknown_context_outside_module():
|
def test_detect_unknown_context_outside_module():
|
||||||
lines = ["var x = 'hello'"]
|
lines = ["var x = 'hello'"]
|
||||||
session = _make_session_with_doc(URI, lines)
|
session = _make_session_with_doc(URI, lines)
|
||||||
|
|||||||
Reference in New Issue
Block a user