From f223508d0de7923d7845cc3c3da8226eee41bb0f Mon Sep 17 00:00:00 2001 From: VishalSh-Microsoft Date: Tue, 18 Aug 2026 12:09:49 +0530 Subject: [PATCH 01/13] fix: Pin GitHub Actions to commit SHAs --- .github/workflows/azure-dev.yml | 18 +++++++++--------- .github/workflows/template-validation.yml | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/azure-dev.yml b/.github/workflows/azure-dev.yml index c98e9013..f8ee6419 100644 --- a/.github/workflows/azure-dev.yml +++ b/.github/workflows/azure-dev.yml @@ -109,10 +109,10 @@ jobs: resource_group_name: ${{ steps.check_create_rg.outputs.RESOURCE_GROUP_NAME }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Setup Azure CLI - uses: azure/login@v3 + uses: azure/login@f5d393ae46f8fde4be8b75f32e3fc50e654ad0ca # v3 with: client-id: ${{ env.AZURE_CLIENT_ID }} tenant-id: ${{ env.AZURE_TENANT_ID }} @@ -135,7 +135,7 @@ jobs: echo "✅ Bicep linting completed" - name: Install azd - uses: Azure/setup-azd@v2 + uses: Azure/setup-azd@0b7e3a35ab00f2eee7080c845eb39c3f0ebfa553 # v2 - name: Azure Developer CLI login shell: bash @@ -222,17 +222,17 @@ jobs: solution_suffix: ${{ steps.get_output_linux.outputs.solution_suffix }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Azure CLI login - uses: azure/login@v3 + uses: azure/login@f5d393ae46f8fde4be8b75f32e3fc50e654ad0ca # v3 with: client-id: ${{ env.AZURE_CLIENT_ID }} tenant-id: ${{ env.AZURE_TENANT_ID }} subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} - name: Install azd - uses: Azure/setup-azd@v2 + uses: Azure/setup-azd@0b7e3a35ab00f2eee7080c845eb39c3f0ebfa553 # v2 - name: Azure Developer CLI login shell: bash @@ -390,17 +390,17 @@ jobs: SOLUTION_SUFFIX: ${{ needs.deploy.outputs.solution_suffix }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Azure CLI login - uses: azure/login@v3 + uses: azure/login@f5d393ae46f8fde4be8b75f32e3fc50e654ad0ca # v3 with: client-id: ${{ env.AZURE_CLIENT_ID }} tenant-id: ${{ env.AZURE_TENANT_ID }} subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} - name: Install azd - uses: Azure/setup-azd@v2 + uses: Azure/setup-azd@0b7e3a35ab00f2eee7080c845eb39c3f0ebfa553 # v2 - name: Azure Developer CLI login shell: bash diff --git a/.github/workflows/template-validation.yml b/.github/workflows/template-validation.yml index 2740cc19..9de65720 100644 --- a/.github/workflows/template-validation.yml +++ b/.github/workflows/template-validation.yml @@ -25,10 +25,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - name: Validate Azure Template - uses: microsoft/template-validation-action@v0.4.3 + uses: microsoft/template-validation-action@9f56864b1ddbfb56dd27f3cbc71a00750cdb9a18 # v0.4.3 id: validation env: AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} From dbe90d3e6cf4432cee72aac91440b9ec9625ff48 Mon Sep 17 00:00:00 2001 From: VishalSh-Microsoft Date: Wed, 19 Aug 2026 13:12:34 +0530 Subject: [PATCH 02/13] fix: Update Azure CLI and azd action versions in workflow --- .github/workflows/azure-dev.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/azure-dev.yml b/.github/workflows/azure-dev.yml index f8ee6419..ae28503e 100644 --- a/.github/workflows/azure-dev.yml +++ b/.github/workflows/azure-dev.yml @@ -112,7 +112,7 @@ jobs: uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Setup Azure CLI - uses: azure/login@f5d393ae46f8fde4be8b75f32e3fc50e654ad0ca # v3 + uses: azure/login@d54469830ea7d513b7371e02a077c3ee5cb7b112 # v3 with: client-id: ${{ env.AZURE_CLIENT_ID }} tenant-id: ${{ env.AZURE_TENANT_ID }} @@ -135,7 +135,7 @@ jobs: echo "✅ Bicep linting completed" - name: Install azd - uses: Azure/setup-azd@0b7e3a35ab00f2eee7080c845eb39c3f0ebfa553 # v2 + uses: Azure/setup-azd@17a3d5f2aa75a53c7f6fa815c18ef1445c73257f # v2 - name: Azure Developer CLI login shell: bash @@ -225,14 +225,14 @@ jobs: uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Azure CLI login - uses: azure/login@f5d393ae46f8fde4be8b75f32e3fc50e654ad0ca # v3 + uses: azure/login@d54469830ea7d513b7371e02a077c3ee5cb7b112 # v3 with: client-id: ${{ env.AZURE_CLIENT_ID }} tenant-id: ${{ env.AZURE_TENANT_ID }} subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} - name: Install azd - uses: Azure/setup-azd@0b7e3a35ab00f2eee7080c845eb39c3f0ebfa553 # v2 + uses: Azure/setup-azd@17a3d5f2aa75a53c7f6fa815c18ef1445c73257f # v2 - name: Azure Developer CLI login shell: bash @@ -393,14 +393,14 @@ jobs: uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - name: Azure CLI login - uses: azure/login@f5d393ae46f8fde4be8b75f32e3fc50e654ad0ca # v3 + uses: azure/login@d54469830ea7d513b7371e02a077c3ee5cb7b112 # v3 with: client-id: ${{ env.AZURE_CLIENT_ID }} tenant-id: ${{ env.AZURE_TENANT_ID }} subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} - name: Install azd - uses: Azure/setup-azd@0b7e3a35ab00f2eee7080c845eb39c3f0ebfa553 # v2 + uses: Azure/setup-azd@17a3d5f2aa75a53c7f6fa815c18ef1445c73257f # v2 - name: Azure Developer CLI login shell: bash From 5aa5a204607f60ee037906a6058c1f0b6a5cab85 Mon Sep 17 00:00:00 2001 From: VishalSh-Microsoft Date: Wed, 19 Aug 2026 13:28:37 +0530 Subject: [PATCH 03/13] fix: Update actions/checkout version in workflows --- .github/workflows/azure-dev.yml | 18 +++++++++--------- .github/workflows/template-validation.yml | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/azure-dev.yml b/.github/workflows/azure-dev.yml index ae28503e..d0122557 100644 --- a/.github/workflows/azure-dev.yml +++ b/.github/workflows/azure-dev.yml @@ -109,10 +109,10 @@ jobs: resource_group_name: ${{ steps.check_create_rg.outputs.RESOURCE_GROUP_NAME }} steps: - name: Checkout - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - name: Setup Azure CLI - uses: azure/login@d54469830ea7d513b7371e02a077c3ee5cb7b112 # v3 + uses: azure/login@f5d393ae46f8fde4be8b75f32e3fc50e654ad0ca # v3.0.1 with: client-id: ${{ env.AZURE_CLIENT_ID }} tenant-id: ${{ env.AZURE_TENANT_ID }} @@ -135,7 +135,7 @@ jobs: echo "✅ Bicep linting completed" - name: Install azd - uses: Azure/setup-azd@17a3d5f2aa75a53c7f6fa815c18ef1445c73257f # v2 + uses: Azure/setup-azd@0b7e3a35ab00f2eee7080c845eb39c3f0ebfa553 # v2.4.0 - name: Azure Developer CLI login shell: bash @@ -222,17 +222,17 @@ jobs: solution_suffix: ${{ steps.get_output_linux.outputs.solution_suffix }} steps: - name: Checkout - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - name: Azure CLI login - uses: azure/login@d54469830ea7d513b7371e02a077c3ee5cb7b112 # v3 + uses: azure/login@f5d393ae46f8fde4be8b75f32e3fc50e654ad0ca # v3.0.1 with: client-id: ${{ env.AZURE_CLIENT_ID }} tenant-id: ${{ env.AZURE_TENANT_ID }} subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} - name: Install azd - uses: Azure/setup-azd@17a3d5f2aa75a53c7f6fa815c18ef1445c73257f # v2 + uses: Azure/setup-azd@0b7e3a35ab00f2eee7080c845eb39c3f0ebfa553 # v2.4.0 - name: Azure Developer CLI login shell: bash @@ -390,17 +390,17 @@ jobs: SOLUTION_SUFFIX: ${{ needs.deploy.outputs.solution_suffix }} steps: - name: Checkout - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - name: Azure CLI login - uses: azure/login@d54469830ea7d513b7371e02a077c3ee5cb7b112 # v3 + uses: azure/login@f5d393ae46f8fde4be8b75f32e3fc50e654ad0ca # v3.0.1 with: client-id: ${{ env.AZURE_CLIENT_ID }} tenant-id: ${{ env.AZURE_TENANT_ID }} subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }} - name: Install azd - uses: Azure/setup-azd@17a3d5f2aa75a53c7f6fa815c18ef1445c73257f # v2 + uses: Azure/setup-azd@0b7e3a35ab00f2eee7080c845eb39c3f0ebfa553 # v2.4.0 - name: Azure Developer CLI login shell: bash diff --git a/.github/workflows/template-validation.yml b/.github/workflows/template-validation.yml index 9de65720..4fdcba61 100644 --- a/.github/workflows/template-validation.yml +++ b/.github/workflows/template-validation.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Validate Azure Template uses: microsoft/template-validation-action@9f56864b1ddbfb56dd27f3cbc71a00750cdb9a18 # v0.4.3 From 70b9beda20e8521c7ca1a541f850333f04f3d457 Mon Sep 17 00:00:00 2001 From: NirajC3-Microsoft Date: Thu, 20 Aug 2026 09:38:22 +0530 Subject: [PATCH 04/13] fix: preserve ontology ID and wait for graph readiness --- .../deploy/fabric_solution_installer.ipynb | 82 +++++++++++++++++-- 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/infra/fabric/deploy/fabric_solution_installer.ipynb b/infra/fabric/deploy/fabric_solution_installer.ipynb index 4950a385..a09724c2 100644 --- a/infra/fabric/deploy/fabric_solution_installer.ipynb +++ b/infra/fabric/deploy/fabric_solution_installer.ipynb @@ -202,7 +202,7 @@ "\n", " # Resolve ontology directory and detect folder structure\n", " ontology_folder = f\"{ontology_name}.Ontology\"\n", - " \n", + "\n", " # Search for the ontology directory in repository root and all subfolders\n", " ontology_dir = None\n", " folder_path = None\n", @@ -213,7 +213,7 @@ " # Extract folder path - look for workspace, fabric_workspace, or definitions patterns\n", " # to determine the target folder structure in Fabric\n", " rel_path = os.path.relpath(root, repository_directory)\n", - " \n", + "\n", " # Check if this is in a recognized workspace structure\n", " path_parts = rel_path.split(os.sep)\n", " if 'workspace' in path_parts:\n", @@ -248,27 +248,27 @@ " print(\"2. Building lakehouse item ID mapping...\")\n", " lakehouse_id_map = {}\n", " list_url = f\"v1/workspaces/{workspace_id}/lakehouses\"\n", - " \n", + "\n", " while list_url:\n", " list_response = client.get(list_url)\n", " if list_response.status_code == 200:\n", " response_data = list_response.json()\n", " lakehouses = response_data.get(\"value\", [])\n", - " \n", + "\n", " for lakehouse in lakehouses:\n", " if lakehouse.get(\"displayName\") == LAKEHOUSE_NAME:\n", " lakehouse_id_map[LAKEHOUSE_NAME] = lakehouse.get(\"id\")\n", " print(f\" Found lakehouse '{LAKEHOUSE_NAME}': {lakehouse.get('id')}\")\n", " list_url = None # Stop pagination once found\n", " break\n", - " \n", + "\n", " # Get continuation token for next page (if lakehouse not found yet)\n", " if list_url and not lakehouse_id_map:\n", " list_url = response_data.get(\"continuationUri\")\n", " else:\n", " print(f\" ⚠️ Failed to list lakehouses: HTTP {list_response.status_code}\")\n", " break\n", - " \n", + "\n", " if not lakehouse_id_map:\n", " print(f\" ⚠️ Warning: Could not find lakehouse '{LAKEHOUSE_NAME}' in workspace\")\n", "\n", @@ -373,6 +373,12 @@ " print(f\" Status: {status}, retrying in {retry_after}s...\")\n", " else:\n", " raise Exception(f\"Failed to poll operation: {poll_response.status_code} {poll_response.text}\")\n", + "\n", + " # A 202 create doesn't return the item body directly - fetch it from the operation result\n", + " if not existing_ontology_id:\n", + " result_response = client.get(f\"v1/operations/{operation_id}/result\")\n", + " if result_response.status_code == 200:\n", + " existing_ontology_id = result_response.json().get(\"id\")\n", " elif response.status_code in (200, 201):\n", " if not existing_ontology_id:\n", " existing_ontology_id = response.json().get(\"id\")\n", @@ -380,6 +386,15 @@ " else:\n", " raise Exception(f\"API call failed: {response.status_code} {response.text}\")\n", "\n", + " # Fallback: look up the item by name if the id still wasn't resolved above\n", + " if not existing_ontology_id:\n", + " lookup_response = client.get(f\"v1/workspaces/{workspace_id}/items?type=Ontology\")\n", + " if lookup_response.status_code == 200:\n", + " for item in lookup_response.json().get(\"value\", []):\n", + " if item[\"displayName\"] == ontology_name:\n", + " existing_ontology_id = item[\"id\"]\n", + " break\n", + "\n", " # Step 7: Move ontology to the appropriate folder if needed\n", " if folder_path:\n", " print(f\"7. Moving ontology to folder '{folder_path}'...\")\n", @@ -433,7 +448,7 @@ "\n", "def _replace_sql_endpoints(obj, lakehouse_sql_endpoint_map):\n", " \"\"\"Recursively replace SQL endpoint values based on the sibling itemId → lakehouse endpoint mapping.\n", - " \n", + "\n", " This function can replace various endpoint-related fields in the ontology definition:\n", " - sqlEndpoint: SQL endpoint connection string\n", " - connectionString: Alternative field name for SQL endpoints\n", @@ -449,7 +464,7 @@ " # Replace connectionString if present\n", " if \"connectionString\" in obj:\n", " obj[\"connectionString\"] = lakehouse_sql_endpoint_map[item_id]\n", - " \n", + "\n", " # Recurse into nested dictionaries\n", " for value in obj.values():\n", " _replace_sql_endpoints(value, lakehouse_sql_endpoint_map)\n", @@ -469,7 +484,56 @@ " deployed_ontology_ids.append((ontology_name, ontology_id))\n", " print(f\"✅ Ontology '{ontology_name}' deployed successfully (ID: {ontology_id})\")\n", "\n", - "print(f\"\\n✅ All {len(deployed_ontology_ids)} ontologies deployed successfully\")" + "print(f\"\\n✅ All {len(deployed_ontology_ids)} ontologies deployed successfully\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69e7e7b2", + "metadata": {}, + "outputs": [], + "source": [ + "def check_graph_model_ready(ontology_name,\n", + " client=client,\n", + " workspace_id=workspace_id,\n", + " initial_wait_seconds=360,\n", + " attempts=3,\n", + " wait_seconds=30):\n", + " \"\"\"Check whether the ontology's graph model has finished its automatic initial load.\n", + "\n", + " Fabric loads graph data via its own background job after ontology deployment;\n", + " querying it does not start or speed up that job, so wait upfront for the\n", + " typical load time (plus a buffer for slower runs) before checking.\n", + " \"\"\"\n", + " graph_models = client.get(f\"v1/workspaces/{workspace_id}/graphModels\").json().get(\"value\", [])\n", + " match = next((g for g in graph_models if g.get(\"displayName\", \"\").startswith(f\"{ontology_name}_graph_\")), None)\n", + "\n", + " if not match:\n", + " print(f\" ⚠️ Could not find a graph model for '{ontology_name}'; skipping readiness check\")\n", + " return\n", + "\n", + " print(f\"⏳ Waiting {initial_wait_seconds}s for '{ontology_name}' graph model to finish loading...\")\n", + " time.sleep(initial_wait_seconds)\n", + "\n", + " for attempt in range(1, attempts + 1):\n", + " try:\n", + " client.post(\n", + " f\"v1/workspaces/{workspace_id}/graphModels/{match['id']}/executeQuery?beta=true\",\n", + " json={\"query\": \"MATCH (n) RETURN COUNT(n) AS cnt LIMIT 1;\"},\n", + " )\n", + " print(f\" ✅ Graph model is ready (attempt {attempt})\")\n", + " return\n", + " except Exception as e:\n", + " print(f\" Attempt {attempt} not ready yet: {e}\")\n", + " if attempt < attempts:\n", + " time.sleep(wait_seconds)\n", + "\n", + " print(f\" ⚠️ Graph model still loading after the wait; it finishes automatically - check the portal later\")\n", + "\n", + "\n", + "for ontology_name, _ in deployed_ontology_ids:\n", + " check_graph_model_ready(ontology_name)\n" ] }, { From 480a3fae0da5b48566a445a598a57cf1b20342d7 Mon Sep 17 00:00:00 2001 From: NirajC3-Microsoft Date: Thu, 20 Aug 2026 09:43:40 +0530 Subject: [PATCH 05/13] fix: preserve ontology ID and wait for graph readiness --- infra/fabric/deploy/fabric_solution_installer.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infra/fabric/deploy/fabric_solution_installer.ipynb b/infra/fabric/deploy/fabric_solution_installer.ipynb index a09724c2..ed89ac93 100644 --- a/infra/fabric/deploy/fabric_solution_installer.ipynb +++ b/infra/fabric/deploy/fabric_solution_installer.ipynb @@ -206,7 +206,7 @@ " # Search for the ontology directory in repository root and all subfolders\n", " ontology_dir = None\n", " folder_path = None\n", - " \n", + "\n", " for root, dirs, files in os.walk(repository_directory):\n", " if ontology_folder in dirs:\n", " ontology_dir = os.path.join(root, ontology_folder)\n", @@ -227,10 +227,10 @@ " if len(path_parts) > workspace_idx + 1:\n", " folder_path = '/'.join(path_parts[workspace_idx + 1:])\n", " break\n", - " \n", + "\n", " if not ontology_dir:\n", " raise FileNotFoundError(f\"Ontology directory '{ontology_folder}' not found in repository\")\n", - " \n", + "\n", " print(f\" Ontology directory: {ontology_dir}\")\n", " if folder_path:\n", " print(f\" Target folder: {folder_path}\")\n", From 859259bf6bfc0ee4597da78f161e61cc7192e172 Mon Sep 17 00:00:00 2001 From: NirajC3-Microsoft Date: Thu, 20 Aug 2026 09:51:40 +0530 Subject: [PATCH 06/13] fix: preserve ontology ID and wait for graph readiness - 2 --- infra/fabric/deploy/fabric_solution_installer.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infra/fabric/deploy/fabric_solution_installer.ipynb b/infra/fabric/deploy/fabric_solution_installer.ipynb index ed89ac93..d8b049e8 100644 --- a/infra/fabric/deploy/fabric_solution_installer.ipynb +++ b/infra/fabric/deploy/fabric_solution_installer.ipynb @@ -202,7 +202,7 @@ "\n", " # Resolve ontology directory and detect folder structure\n", " ontology_folder = f\"{ontology_name}.Ontology\"\n", - "\n", + " \n", " # Search for the ontology directory in repository root and all subfolders\n", " ontology_dir = None\n", " folder_path = None\n", @@ -268,7 +268,7 @@ " else:\n", " print(f\" ⚠️ Failed to list lakehouses: HTTP {list_response.status_code}\")\n", " break\n", - "\n", + " \n", " if not lakehouse_id_map:\n", " print(f\" ⚠️ Warning: Could not find lakehouse '{LAKEHOUSE_NAME}' in workspace\")\n", "\n", @@ -464,7 +464,7 @@ " # Replace connectionString if present\n", " if \"connectionString\" in obj:\n", " obj[\"connectionString\"] = lakehouse_sql_endpoint_map[item_id]\n", - "\n", + " \n", " # Recurse into nested dictionaries\n", " for value in obj.values():\n", " _replace_sql_endpoints(value, lakehouse_sql_endpoint_map)\n", From 0b29baaf76ef905f9a964e0e65ce5db29cfc3d66 Mon Sep 17 00:00:00 2001 From: NirajC3-Microsoft Date: Thu, 20 Aug 2026 10:06:19 +0530 Subject: [PATCH 07/13] fix: preserve ontology ID and wait for graph readiness - 3 --- infra/fabric/deploy/fabric_solution_installer.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infra/fabric/deploy/fabric_solution_installer.ipynb b/infra/fabric/deploy/fabric_solution_installer.ipynb index d8b049e8..ed89ac93 100644 --- a/infra/fabric/deploy/fabric_solution_installer.ipynb +++ b/infra/fabric/deploy/fabric_solution_installer.ipynb @@ -202,7 +202,7 @@ "\n", " # Resolve ontology directory and detect folder structure\n", " ontology_folder = f\"{ontology_name}.Ontology\"\n", - " \n", + "\n", " # Search for the ontology directory in repository root and all subfolders\n", " ontology_dir = None\n", " folder_path = None\n", @@ -268,7 +268,7 @@ " else:\n", " print(f\" ⚠️ Failed to list lakehouses: HTTP {list_response.status_code}\")\n", " break\n", - " \n", + "\n", " if not lakehouse_id_map:\n", " print(f\" ⚠️ Warning: Could not find lakehouse '{LAKEHOUSE_NAME}' in workspace\")\n", "\n", @@ -464,7 +464,7 @@ " # Replace connectionString if present\n", " if \"connectionString\" in obj:\n", " obj[\"connectionString\"] = lakehouse_sql_endpoint_map[item_id]\n", - " \n", + "\n", " # Recurse into nested dictionaries\n", " for value in obj.values():\n", " _replace_sql_endpoints(value, lakehouse_sql_endpoint_map)\n", From dc7a4cfafcde829e142575f7c4fb379f7cc0c3f2 Mon Sep 17 00:00:00 2001 From: NirajC3-Microsoft Date: Thu, 20 Aug 2026 13:28:53 +0530 Subject: [PATCH 08/13] Add code to publish data agent --- .../deploy/fabric_solution_installer.ipynb | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/infra/fabric/deploy/fabric_solution_installer.ipynb b/infra/fabric/deploy/fabric_solution_installer.ipynb index ed89ac93..b4522dd0 100644 --- a/infra/fabric/deploy/fabric_solution_installer.ipynb +++ b/infra/fabric/deploy/fabric_solution_installer.ipynb @@ -557,6 +557,30 @@ "print(\"✅ Data Agent items deployed successfully\")" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "0c6ad359", + "metadata": {}, + "outputs": [], + "source": [ + "# Publish the deployed Data Agent so it's live and usable outside the workspace\n", + "\n", + "print(\"📣 Publishing Data Agent...\")\n", + "\n", + "data_agents = client.get(f\"v1/workspaces/{workspace_id}/dataAgents\").json().get(\"value\", [])\n", + "agent = next((a for a in data_agents if a.get(\"displayName\") == \"RetailSC Ontology Agent\"), None)\n", + "\n", + "if not agent:\n", + " print(\" ⚠️ Could not find the Data Agent to publish; skipping\")\n", + "else:\n", + " response = client.post(\n", + " f\"v1/workspaces/{workspace_id}/dataAgents/{agent['id']}/staging/publish\",\n", + " json={\"publishedDescription\": \"Published by the Microsoft IQ solution installer\"},\n", + " )\n", + " print(f\" ✅ Data Agent '{agent['displayName']}' published successfully\")" + ] + }, { "cell_type": "code", "execution_count": null, From 52f2694e57dacbcd64321d69db6c83134eb37bff Mon Sep 17 00:00:00 2001 From: Yamini1-Microsoft Date: Thu, 20 Aug 2026 18:38:48 +0530 Subject: [PATCH 09/13] feat: configure Microsoft Package Feed Proxy for pip installs Routes all pip installs (devcontainer, azd hooks, dev tooling, GitHub Actions workflows) through the Microsoft Package Feed Proxy (https://packagefeedproxy.microsoft.io/pypi/simple/) instead of directly hitting pypi.org, so builds keep working once direct public registry access is blocked on Microsoft-managed devices/networks. PIP_INDEX_URL is overridable via devcontainer variable substitution / repo-env Variable / env var everywhere it is set. - devcontainer.json: set PIP_INDEX_URL via containerEnv with localEnv override support - post-create.sh: export PIP_INDEX_URL fallback; apply --index-url to all pip install calls (pip upgrade, root requirements.txt, datagen requirements.txt, fabric-launcher editable install, dev tooling) - Run-PythonScript.ps1: new -PipIndexUrl param (defaults to proxy, override via env var or flag, validated non-empty), applied to pip upgrade + requirements install - shared by azd postprovision/predown hooks - azure-dev.yml, template-validation.yml: set PIP_INDEX_URL in env block (vars.PIP_INDEX_URL override with proxy fallback) - .devcontainer/README.md: document the proxy default and how to override it back to public PyPI Note: this repo has no package.json/NuGet.config (pip-only), so the npm/NuGet acceptance criteria from the parent work item are not applicable here. Live connectivity to packagefeedproxy.microsoft.io can only be validated on a Microsoft-managed network/CI runner, not from a public sandbox, since package downloads redirect to vsblob.vsassets.io. AB#50845 --- .devcontainer/README.md | 3 +++ .devcontainer/devcontainer.json | 3 +++ .devcontainer/post-create.sh | 18 +++++++++++++----- .github/workflows/azure-dev.yml | 6 ++++++ .github/workflows/template-validation.yml | 1 + infra/scripts/utils/Run-PythonScript.ps1 | 22 +++++++++++++++++----- 6 files changed, 43 insertions(+), 10 deletions(-) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 81857919..e1744bc0 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -52,6 +52,9 @@ A development container (or dev container for short) lets you use a container as > Note: The Fabric installer notebook ([`fabric_solution_installer.ipynb`](../infra/fabric/deploy/fabric_solution_installer.ipynb)) runs **inside the Fabric workspace**, not in this container. It manages its own dependencies via `%pip install`; the container's Python packages do not affect it. +### Package Feed Proxy (Microsoft-Managed Devices) +All `pip install` commands above route through the Microsoft Package Feed Proxy (`https://packagefeedproxy.microsoft.io/pypi/simple/`) by default, via the `PIP_INDEX_URL` environment variable set in [`devcontainer.json`](./devcontainer.json)'s `containerEnv` (with a host `PIP_INDEX_URL` taking precedence if already set). This keeps `pip install` working once direct access to `pypi.org` is blocked on Microsoft-managed devices. To use public PyPI instead (e.g. outside a Microsoft-managed network), set `PIP_INDEX_URL=https://pypi.org/simple/` on the host before the container starts. + ## How to use this dev container This README is a **reference for the dev container itself** — what's installed, how it's configured, and how to customize it. For end-to-end deployment instructions (prerequisites, `azd up`, optional configuration variables, expected results, cleanup), see the single source of truth: [`docs/DeploymentGuide.md`](../docs/DeploymentGuide.md). diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 60ba2c71..da518556 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,6 +3,9 @@ "build": { "dockerfile": "Dockerfile" }, + "containerEnv": { + "PIP_INDEX_URL": "${localEnv:PIP_INDEX_URL:https://packagefeedproxy.microsoft.io/pypi/simple/}" + }, "features": { "ghcr.io/azure/azure-dev/azd:latest": { "version": "latest" diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh index 2226ae09..90cc67ba 100644 --- a/.devcontainer/post-create.sh +++ b/.devcontainer/post-create.sh @@ -7,6 +7,14 @@ set -e echo "🚀 Setting up Microsoft IQ Solution Accelerator development environment..." +# Microsoft Package Feed Proxy (CFS) configuration. +# All pip installs in this script route through the CFS-protected proxy instead of +# pypi.org / files.pythonhosted.org directly. PIP_INDEX_URL is normally already set via +# devcontainer.json's containerEnv, but is (re)exported here as a safety net for anyone +# invoking this script outside of the devcontainer. +export PIP_INDEX_URL="${PIP_INDEX_URL:-https://packagefeedproxy.microsoft.io/pypi/simple/}" +echo "📦 Using pip index URL: $PIP_INDEX_URL" + # Note: Core tools already provided by devcontainer.json: # - Python 3.x (base image) with pip and venv # - Azure CLI + Bicep (azure-cli feature) @@ -31,7 +39,7 @@ python3 -m pip --version # Upgrade pip echo "🐍 Upgrading pip..." -python3 -m pip install --upgrade pip +python3 -m pip install --index-url "$PIP_INDEX_URL" --upgrade pip # Install Python requirements for the project echo "📋 Installing Python dependencies globally..." @@ -40,7 +48,7 @@ echo "📋 Installing Python dependencies globally..." # This improves deployment script performance by avoiding repeated installations if [ -f "./requirements.txt" ]; then echo "📦 Installing main Fabric requirements globally..." - python3 -m pip install -r "./requirements.txt" + python3 -m pip install --index-url "$PIP_INDEX_URL" -r "./requirements.txt" echo "✅ Main Fabric requirements installed successfully" else echo "⚠️ Warning: ./requirements.txt not found" @@ -49,7 +57,7 @@ fi # Install data generation requirements (optional) if [ -f "./src/fabric/datagen/requirements.txt" ]; then echo "📦 Installing data generation requirements..." - python3 -m pip install -r "./src/fabric/datagen/requirements.txt" + python3 -m pip install --index-url "$PIP_INDEX_URL" -r "./src/fabric/datagen/requirements.txt" echo "✅ Data generation requirements installed successfully" else echo "ℹ️ Info: ./src/fabric/datagen/requirements.txt not found (optional)" @@ -58,7 +66,7 @@ fi # Install fabric-launcher if in multi-root workspace if [ -d "../fabric-launcher" ]; then echo "📦 Installing fabric-launcher in editable mode..." - python3 -m pip install -e "../fabric-launcher[dev]" + python3 -m pip install --index-url "$PIP_INDEX_URL" -e "../fabric-launcher[dev]" echo "✅ fabric-launcher installed successfully" else echo "ℹ️ Info: fabric-launcher not found in workspace (optional)" @@ -66,7 +74,7 @@ fi # Install additional development tools echo "🛠️ Installing development tools..." -if ! python3 -m pip install --user \ +if ! python3 -m pip install --index-url "$PIP_INDEX_URL" --user \ black \ flake8 \ pytest \ diff --git a/.github/workflows/azure-dev.yml b/.github/workflows/azure-dev.yml index d0122557..4415e75a 100644 --- a/.github/workflows/azure-dev.yml +++ b/.github/workflows/azure-dev.yml @@ -66,6 +66,12 @@ env: # --- Azure region --- AZURE_LOCATION: ${{ vars.AZURE_LOCATION || 'eastus' }} + # --- Package feed proxy --- + # Routes pip installs through the Microsoft Package Feed Proxy so CI keeps working + # once direct access to pypi.org is blocked on Microsoft-managed devices/runners. + # Override via a repo/environment Variable if a different index is needed. + PIP_INDEX_URL: ${{ vars.PIP_INDEX_URL || 'https://packagefeedproxy.microsoft.io/pypi/simple/' }} + # --- Common --- # ENABLE_TELEMETRY: ${{ vars.ENABLE_TELEMETRY }} DEPLOYING_USER_PRINCIPAL_TYPE: ${{ vars.DEPLOYING_USER_PRINCIPAL_TYPE || 'ServicePrincipal' }} diff --git a/.github/workflows/template-validation.yml b/.github/workflows/template-validation.yml index 4fdcba61..064b352d 100644 --- a/.github/workflows/template-validation.yml +++ b/.github/workflows/template-validation.yml @@ -38,6 +38,7 @@ jobs: AZURE_LOCATION: ${{ secrets.AZURE_LOCATION }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} DEPLOYING_USER_PRINCIPAL_TYPE: ServicePrincipal + PIP_INDEX_URL: ${{ vars.PIP_INDEX_URL || 'https://packagefeedproxy.microsoft.io/pypi/simple/' }} - name: Print result run: cat ${{ steps.validation.outputs.resultFile }} diff --git a/infra/scripts/utils/Run-PythonScript.ps1 b/infra/scripts/utils/Run-PythonScript.ps1 index cc84495a..523655fc 100644 --- a/infra/scripts/utils/Run-PythonScript.ps1 +++ b/infra/scripts/utils/Run-PythonScript.ps1 @@ -28,6 +28,12 @@ .PARAMETER RequirementsPath Path to requirements.txt file. Defaults to repository root requirements.txt. +.PARAMETER PipIndexUrl + PyPI index URL used for pip installs. Defaults to the Microsoft Package Feed Proxy + (https://packagefeedproxy.microsoft.io/pypi/simple/), or the PIP_INDEX_URL environment + variable when set. Override with -PipIndexUrl to target a different index (e.g. for + local development without corporate network access). + .EXAMPLE .\Run-PythonScript.ps1 -ScriptPath "infra/scripts/fabric/deploy_fabric_rti.py" @@ -58,7 +64,11 @@ param( [switch]$SkipPipUpgrade, [Parameter(Mandatory = $false, HelpMessage = "Path to requirements.txt file")] - [string]$RequirementsPath + [string]$RequirementsPath, + + [Parameter(Mandatory = $false, HelpMessage = "PyPI index URL to install packages from. Defaults to the Microsoft Package Feed Proxy (CFS) so builds keep working once direct access to pypi.org is blocked on Microsoft-managed devices. Override via -PipIndexUrl or the PIP_INDEX_URL environment variable.")] + [ValidateNotNullOrEmpty()] + [string]$PipIndexUrl = $(if ($env:PIP_INDEX_URL) { $env:PIP_INDEX_URL } else { "https://packagefeedproxy.microsoft.io/pypi/simple/" }) ) $ErrorActionPreference = "Stop" @@ -89,7 +99,8 @@ function Initialize-PythonEnvironment { [bool]$SkipVirtualEnv, [bool]$SkipDependencies, [bool]$SkipPipUpgrade, - [string]$RequirementsPath + [string]$RequirementsPath, + [string]$PipIndexUrl ) $pythonCmd = Get-PythonCommand @@ -130,7 +141,7 @@ function Initialize-PythonEnvironment { # Upgrade pip if not skipped if (-not $SkipPipUpgrade) { Write-Warning "Upgrading pip..." - & $pythonExec -m pip install --upgrade pip --quiet + & $pythonExec -m pip install --index-url $PipIndexUrl --upgrade pip --quiet if ($LASTEXITCODE -ne 0) { Write-Warning "Warning: Failed to upgrade pip, continuing with existing version..." } @@ -145,7 +156,8 @@ function Initialize-PythonEnvironment { if (-not (Test-Path $RequirementsPath)) { throw "requirements.txt not found at: $RequirementsPath" } - & $pythonExec -m pip install -r "$RequirementsPath" --quiet + Write-Info "Using pip index URL: $PipIndexUrl" + & $pythonExec -m pip install --index-url $PipIndexUrl -r "$RequirementsPath" --quiet if ($LASTEXITCODE -ne 0) { throw "Failed to install Python dependencies." } } else { @@ -181,7 +193,7 @@ try { Write-Success "Working directory: $TargetScriptDir" # Initialize Python environment - $pythonExec = Initialize-PythonEnvironment -RepoRoot $RepoRoot -SkipVirtualEnv:$SkipPythonVirtualEnvironment -SkipDependencies:$SkipPythonDependencies -SkipPipUpgrade:$SkipPipUpgrade -RequirementsPath $RequirementsPath + $pythonExec = Initialize-PythonEnvironment -RepoRoot $RepoRoot -SkipVirtualEnv:$SkipPythonVirtualEnvironment -SkipDependencies:$SkipPythonDependencies -SkipPipUpgrade:$SkipPipUpgrade -RequirementsPath $RequirementsPath -PipIndexUrl $PipIndexUrl # Change to the target script directory and execute Push-Location $TargetScriptDir From 06e656bf874b7ca561bd77a256da9098c8665ba8 Mon Sep 17 00:00:00 2001 From: Yamini1-Microsoft Date: Thu, 20 Aug 2026 19:30:41 +0530 Subject: [PATCH 10/13] fix(fabric): retry RefreshGraph job when ontology graph model fails to load --- .../deploy/fabric_solution_installer.ipynb | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/infra/fabric/deploy/fabric_solution_installer.ipynb b/infra/fabric/deploy/fabric_solution_installer.ipynb index b4522dd0..ae5952f9 100644 --- a/infra/fabric/deploy/fabric_solution_installer.ipynb +++ b/infra/fabric/deploy/fabric_solution_installer.ipynb @@ -500,11 +500,10 @@ " initial_wait_seconds=360,\n", " attempts=3,\n", " wait_seconds=30):\n", - " \"\"\"Check whether the ontology's graph model has finished its automatic initial load.\n", - "\n", - " Fabric loads graph data via its own background job after ontology deployment;\n", - " querying it does not start or speed up that job, so wait upfront for the\n", - " typical load time (plus a buffer for slower runs) before checking.\n", + " \"\"\"Check whether the ontology's graph model has finished its automatic initial load,\n", + " and actively retry the RefreshGraph job if Fabric's own background refresh failed\n", + " (e.g. error code GraphNotRefreshable, which often clears once the lakehouse SQL\n", + " endpoint has finished syncing).\n", " \"\"\"\n", " graph_models = client.get(f\"v1/workspaces/{workspace_id}/graphModels\").json().get(\"value\", [])\n", " match = next((g for g in graph_models if g.get(\"displayName\", \"\").startswith(f\"{ontology_name}_graph_\")), None)\n", @@ -513,23 +512,34 @@ " print(f\" ⚠️ Could not find a graph model for '{ontology_name}'; skipping readiness check\")\n", " return\n", "\n", - " print(f\"⏳ Waiting {initial_wait_seconds}s for '{ontology_name}' graph model to finish loading...\")\n", + " print(f\"⏳ Waiting {initial_wait_seconds}s for '{ontology_name}' graph model to finish its initial load...\")\n", " time.sleep(initial_wait_seconds)\n", "\n", " for attempt in range(1, attempts + 1):\n", - " try:\n", - " client.post(\n", - " f\"v1/workspaces/{workspace_id}/graphModels/{match['id']}/executeQuery?beta=true\",\n", - " json={\"query\": \"MATCH (n) RETURN COUNT(n) AS cnt LIMIT 1;\"},\n", - " )\n", - " print(f\" ✅ Graph model is ready (attempt {attempt})\")\n", - " return\n", - " except Exception as e:\n", - " print(f\" Attempt {attempt} not ready yet: {e}\")\n", - " if attempt < attempts:\n", + " response = client.post(f\"v1/workspaces/{workspace_id}/graphModels/{match['id']}/jobs/instances?jobType=RefreshGraph\")\n", + "\n", + " job_status = None\n", + " if response.status_code == 202:\n", + " job_url = response.headers.get(\"Location\")\n", + " elapsed = 0\n", + " job_status = \"NotStarted\"\n", + " while job_status in (\"NotStarted\", \"InProgress\") and elapsed < 300:\n", " time.sleep(wait_seconds)\n", + " elapsed += wait_seconds\n", + " job_status = client.get(job_url).json().get(\"status\")\n", + " elif response.status_code == 200:\n", + " job_status = \"Completed\"\n", + "\n", + " if job_status == \"Completed\":\n", + " print(f\" ✅ Graph model refreshed successfully (attempt {attempt})\")\n", + " return\n", + "\n", + " print(f\" Attempt {attempt} refresh not successful yet (status: {job_status or response.status_code})\")\n", + " if attempt < attempts:\n", + " time.sleep(wait_seconds)\n", "\n", - " print(f\" ⚠️ Graph model still loading after the wait; it finishes automatically - check the portal later\")\n", + " print(f\" ⚠️ Graph model refresh did not succeed after {attempts} attempts.\")\n", + " print(f\" Verify the ontology's mapped lakehouse tables contain data, then retry the refresh from the portal.\")\n", "\n", "\n", "for ontology_name, _ in deployed_ontology_ids:\n", From b6e00bbe552a3b762930f25e715a851a050b98c5 Mon Sep 17 00:00:00 2001 From: Yamini1-Microsoft Date: Thu, 20 Aug 2026 19:55:31 +0530 Subject: [PATCH 11/13] feat: add --index-url proxy directive directly to requirements.txt files Per the Configure Package Feeds via the Microsoft Package Feed Proxy guidance, add '--index-url https://packagefeedproxy.microsoft.io/pypi/simple/' as the first directive in both requirements.txt files in this repo, so pip honors the proxy even when a requirements file is installed directly (e.g. 'pip install -r requirements.txt') without relying solely on PIP_INDEX_URL/ --index-url from the calling script. Updated files: - requirements.txt - src/fabric/datagen/requirements.txt AB#50845 --- requirements.txt | 4 ++++ src/fabric/datagen/requirements.txt | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/requirements.txt b/requirements.txt index caa70fdf..25eabc13 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,9 @@ # Requirements for infra/scripts (Fabric + Foundry deployment) +# Microsoft Package Feed Proxy: routes pip installs through the CFS-protected proxy +# instead of pypi.org directly (required on Microsoft-managed devices/networks). +--index-url https://packagefeedproxy.microsoft.io/pypi/simple/ + # Azure authentication azure-identity==1.23.0 diff --git a/src/fabric/datagen/requirements.txt b/src/fabric/datagen/requirements.txt index d12e78e8..f6c281f5 100644 --- a/src/fabric/datagen/requirements.txt +++ b/src/fabric/datagen/requirements.txt @@ -3,6 +3,10 @@ # Core dependencies for generating realistic sales, finance & supply chain data # Supports both sales/finance generators and supply chain generators +# Microsoft Package Feed Proxy: routes pip installs through the CFS-protected proxy +# instead of pypi.org directly (required on Microsoft-managed devices/networks). +--index-url https://packagefeedproxy.microsoft.io/pypi/simple/ + # Essential for data generation and processing pandas>=2.0.0 numpy>=1.24.0 From 64d55414d8f3d8474eb06c415448f0c03088106b Mon Sep 17 00:00:00 2001 From: NirajC3-Microsoft Date: Fri, 21 Aug 2026 11:36:26 +0530 Subject: [PATCH 12/13] Resolve copilot comments --- infra/fabric/deploy/fabric_solution_installer.ipynb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/infra/fabric/deploy/fabric_solution_installer.ipynb b/infra/fabric/deploy/fabric_solution_installer.ipynb index ae5952f9..54e3c91c 100644 --- a/infra/fabric/deploy/fabric_solution_installer.ipynb +++ b/infra/fabric/deploy/fabric_solution_installer.ipynb @@ -505,7 +505,11 @@ " (e.g. error code GraphNotRefreshable, which often clears once the lakehouse SQL\n", " endpoint has finished syncing).\n", " \"\"\"\n", - " graph_models = client.get(f\"v1/workspaces/{workspace_id}/graphModels\").json().get(\"value\", [])\n", + " graph_models_response = client.get(f\"v1/workspaces/{workspace_id}/graphModels\")\n", + " if graph_models_response.status_code != 200:\n", + " print(f\" ⚠️ Failed to list graph models for '{ontology_name}': HTTP {graph_models_response.status_code} {graph_models_response.text}\")\n", + " return\n", + " graph_models = graph_models_response.json().get(\"value\", [])\n", " match = next((g for g in graph_models if g.get(\"displayName\", \"\").startswith(f\"{ontology_name}_graph_\")), None)\n", "\n", " if not match:\n", @@ -543,7 +547,7 @@ "\n", "\n", "for ontology_name, _ in deployed_ontology_ids:\n", - " check_graph_model_ready(ontology_name)\n" + " check_graph_model_ready(ontology_name)" ] }, { From 7132814b301fbd90f383a4ead2d456b0f56675a6 Mon Sep 17 00:00:00 2001 From: Yamini1-Microsoft <295898935+Yamini1-Microsoft@users.noreply.github.com> Date: Mon, 24 Aug 2026 10:55:55 +0530 Subject: [PATCH 13/13] fix: validate PIP_INDEX_URL and fall back to proxy on malformed values The PipIndexUrl default only fell back to the Microsoft Package Feed Proxy when the host PIP_INDEX_URL env var was empty/unset. Any non-empty but malformed value (e.g. a bare scheme like 'https', or whitespace) was passed straight through to pip, producing 'index url seems invalid' / 'Location is ignored' warnings and broken installs. Now validate that PIP_INDEX_URL looks like a real http(s) URL with a host; otherwise fall back to the proxy default and warn. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- infra/scripts/utils/Run-PythonScript.ps1 | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/infra/scripts/utils/Run-PythonScript.ps1 b/infra/scripts/utils/Run-PythonScript.ps1 index 523655fc..b673e7b7 100644 --- a/infra/scripts/utils/Run-PythonScript.ps1 +++ b/infra/scripts/utils/Run-PythonScript.ps1 @@ -67,12 +67,26 @@ param( [string]$RequirementsPath, [Parameter(Mandatory = $false, HelpMessage = "PyPI index URL to install packages from. Defaults to the Microsoft Package Feed Proxy (CFS) so builds keep working once direct access to pypi.org is blocked on Microsoft-managed devices. Override via -PipIndexUrl or the PIP_INDEX_URL environment variable.")] - [ValidateNotNullOrEmpty()] [string]$PipIndexUrl = $(if ($env:PIP_INDEX_URL) { $env:PIP_INDEX_URL } else { "https://packagefeedproxy.microsoft.io/pypi/simple/" }) ) $ErrorActionPreference = "Stop" +# Default pip index URL (Microsoft Package Feed Proxy). Used both as the parameter default +# above and as the fallback when a supplied/host PIP_INDEX_URL turns out to be malformed. +$DefaultPipIndexUrl = "https://packagefeedproxy.microsoft.io/pypi/simple/" + +# Guard against malformed PIP_INDEX_URL values (e.g. whitespace-only, or a bare scheme like +# "https" with no host/path) that would otherwise be silently passed through to pip and +# produce confusing "index url seems invalid" / "Location is ignored" warnings. +if ([string]::IsNullOrWhiteSpace($PipIndexUrl) -or $PipIndexUrl.Trim() -notmatch '^https?://[^/\s]+') { + Write-Warning "PIP_INDEX_URL value '$PipIndexUrl' is not a valid URL; falling back to $DefaultPipIndexUrl" + $PipIndexUrl = $DefaultPipIndexUrl +} +else { + $PipIndexUrl = $PipIndexUrl.Trim() +} + # Helper functions for colored output function Write-Info { param([string]$Message) Write-Host $Message -ForegroundColor Cyan } function Write-Success { param([string]$Message) Write-Host $Message -ForegroundColor Green }