Skip to content

Commit 3ad0690

Browse files
Merge pull request #15 from microsoft/agh-auth-deployment-error
refactor: exit gracefully on 401 unauthorized
2 parents 4274af1 + b77882c commit 3ad0690

4 files changed

Lines changed: 68 additions & 78 deletions

File tree

azure.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ metadata:
55
template: real-time-intelligence-operations-solution-accelerator@1.0
66

77
requiredVersions:
8-
azd: ">1.19.0"
8+
azd: ">= 1.19.0"
99

1010
hooks:
1111
postprovision:

docs/DeploymentGuide.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ Before starting, ensure your deployment identity has the following requirements.
7070
- [ ] **`Microsoft.EventHub` Resource Provider Access**: Verify your Azure Subscription has Event Hub resource provider enabled
7171

7272
### 🔗 API Permissions
73-
- [ ] **Service principals and managed identities support on Fabric REST API**: To use service principals and managed identities with Fabric REST APIs (GitHub actions require it), [enable the `Service principals can use Fabric` APIs tenant setting](https://learn.microsoft.com/rest/api/fabric/articles/identity-support)
73+
- [ ] **Service principals and managed identities support on Fabric REST API**: To use service principals and managed identities with Fabric REST APIs (GitHub actions require it), [enable the `Service principals can use Fabric` APIs tenant setting](https://learn.microsoft.com/rest/api/fabric/articles/identity-support). You must be a [Microsoft 365 administrator](https://learn.microsoft.com/microsoft-365/admin/add-users/assign-admin-roles) to enable this setting
7474
- [ ] **Fabric REST API - Workspace Management**: Access to create and manage Fabric workspaces ([see scopes](https://learn.microsoft.com/rest/api/fabric/articles/scopes))
7575
- [ ] **Fabric REST API - Item Creation**: Access to create Eventhouses, KQL databases, and dashboards ([see scopes](https://learn.microsoft.com/rest/api/fabric/articles/scopes))
7676
- [ ] **Azure Event Hubs API**: Access to create and manage Event Hub resources
@@ -270,7 +270,7 @@ Deploy using automated CI/CD pipeline with GitHub Actions and Azure federated id
270270

271271
**Setup**:
272272
1. Fork the repository to your GitHub account
273-
2. Configure [Azure service principal with federated identity credentials](https://learn.microsoft.com/en-us/azure/developer/github/connect-from-azure-openid-connect) for GitHub Actions (recommened)
273+
2. Configure [Azure service principal with federated identity credentials](https://learn.microsoft.com/azure/developer/github/connect-from-azure-openid-connect) for GitHub Actions (recommened)
274274
3. Set repository variables in GitHub:
275275
- `AZURE_CLIENT_ID`: Service principal client ID
276276
- `AZURE_TENANT_ID`: Azure tenant ID

infra/scripts/fabric/deploy_fabric_rti.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
from fabric_auth import authenticate, authenticate_workspace
5959
from fabric_workspace import setup_workspace
6060
from fabric_workspace_admins import setup_workspace_administrators
61+
from fabric_api import FabricApiError
6162
from fabric_eventhouse import setup_eventhouse
6263
from fabric_database import setup_fabric_database
6364
from fabric_data_ingester import load_data_to_fabric
@@ -122,17 +123,40 @@ def main():
122123
# Step 1: Setup workspace
123124
print_step(1, 11, "Setting up Fabric workspace and capacity assignment", capacity_name=capacity_name, workspace_name=workspace_name)
124125
try:
125-
workspace_result = setup_workspace(
126+
workspace_id = setup_workspace(
126127
fabric_client=fabric_client,
127128
capacity_name=capacity_name,
128129
workspace_name=workspace_name
129130
)
130-
if workspace_result is None:
131+
if workspace_id is None:
131132
print_steps_summary(solution_name, solution_suffix, executed_steps, ["setup_workspace"])
132133
sys.exit(1)
133134
print(f"✅ Successfully completed: setup_workspace")
134135
executed_steps.append("setup_workspace")
135-
workspace_id = workspace_result.get('id')
136+
except FabricApiError as e:
137+
if e.status_code == 401:
138+
print(f"\n⚠️ WARNING: Authentication failed (401 Unauthorized)")
139+
print(f"\n📋 AUTHENTICATION ISSUE DETECTED:")
140+
print(f" The current user does not have sufficient permissions to create workspaces")
141+
print(f" or assign capacities in Microsoft Fabric.")
142+
print(f"\n🔧 REQUIRED PERMISSIONS:")
143+
print(f" • Enable the 'Service principals can use Fabric APIs' tenant setting.")
144+
print(f" You must be a Microsoft 365 administrator to enable this setting.")
145+
print(f" (https://learn.microsoft.com/rest/api/fabric/articles/identity-support")
146+
print(f" • Fabric REST API - Workspace Management: Access to create and manage")
147+
print(f" Fabric workspaces (see scopes: https://learn.microsoft.com/rest/api/fabric/articles/scopes)")
148+
print(f" • Fabric REST API - Item Creation: Access to create Eventhouses, KQL")
149+
print(f" databases, and dashboards (see scopes: https://learn.microsoft.com/rest/api/fabric/articles/scopes)")
150+
print(f"\n💡 NEXT STEPS:")
151+
print(f" 1. Contact your Fabric Administrator to grant the necessary permissions")
152+
print(f" 2. Ensure you're logged in with the correct account (az login)")
153+
print(f"\n☑️ Exiting gracefully due to insufficient permissions.")
154+
print_steps_summary(solution_name, solution_suffix, executed_steps, [])
155+
sys.exit(0) # Exit gracefully for auth issues
156+
else:
157+
print(f"❌ FabricApiError while executing setup_workspace: ({e.status_code}) {e}")
158+
print_steps_summary(solution_name, solution_suffix, executed_steps, [])
159+
sys.exit(1)
136160
except Exception as e:
137161
print(f"❌ Exception while executing setup_workspace: {e}")
138162
print_steps_summary(solution_name, solution_suffix, executed_steps, [])

infra/scripts/fabric/fabric_workspace.py

Lines changed: 38 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import sys
2020
from fabric_api import FabricApiClient, FabricWorkspaceApiClient, FabricApiError
2121

22-
def setup_workspace(fabric_client: FabricApiClient, capacity_name: str, workspace_name: str) -> object:
22+
def setup_workspace(fabric_client: FabricApiClient, capacity_name: str, workspace_name: str) -> str:
2323
"""
2424
Create a workspace (if it doesn't exist) and assign it to the specified capacity.
2525
@@ -29,83 +29,49 @@ def setup_workspace(fabric_client: FabricApiClient, capacity_name: str, workspac
2929
workspace_name: Name of the workspace to create
3030
3131
Returns:
32-
True if successful, False otherwise
32+
str: Workspace ID if successful, or None if failed
3333
"""
34-
try:
35-
print(f"🔍 Searching for capacity: '{capacity_name}'")
36-
37-
capacity = fabric_client.get_capacity(capacity_name)
38-
if not capacity:
39-
print(f"❌ Capacity '{capacity_name}' not found. Exiting.")
40-
return None
41-
42-
capacity_id = capacity['id']
43-
print(f"✅ Capacity found: ID = {capacity_id}, Name = {capacity['displayName']}")
44-
45-
existing_workspace = fabric_client.get_workspace(workspace_name)
46-
47-
if existing_workspace:
48-
workspace_id = existing_workspace['id']
49-
print(f"ℹ️ Using existing workspace: {workspace_name}")
50-
else:
51-
print(f"📁 Creating new workspace: '{workspace_name}'")
52-
try:
53-
workspace_id = fabric_client.create_workspace(name=workspace_name)
54-
print(f"✅ Successfully created workspace: {workspace_name} (ID: {workspace_id})")
55-
except FabricApiError as e:
56-
if e.status_code == 409:
57-
print(f"ℹ️ Workspace '{workspace_name}' already exists")
58-
workspace_id = fabric_client.get_workspace(workspace_name)['id']
59-
else:
60-
print(f"❌ Failed to create workspace: {e}")
61-
return None
62-
63-
workspace_setup_response = {"id": workspace_id }
64-
65-
print(f"🔧 Initializing workspace-specific client...")
66-
workspace_client = FabricWorkspaceApiClient(workspace_id=workspace_id)
67-
68-
print(f"⚡ Assigning workspace '{workspace_name}' to capacity '{capacity_name}'...")
34+
# Step 1: Get or create workspace
35+
existing_workspace = fabric_client.get_workspace(workspace_name)
36+
37+
if existing_workspace:
38+
workspace_id = existing_workspace['id']
39+
print(f"ℹ️ Using existing workspace: {workspace_name} ({workspace_id})")
40+
else:
41+
print(f"📁 Creating new workspace: '{workspace_name}'")
6942
try:
70-
workspace_client.assign_to_capacity(capacity_id)
71-
print(f"✅ Successfully assigned workspace to capacity!")
43+
workspace_id = fabric_client.create_workspace(name=workspace_name)
44+
print(f"✅ Successfully created workspace: {workspace_name} ({workspace_id})")
7245
except FabricApiError as e:
73-
print(f"❌ FabricApiError ({e.status_code}): {e}")
74-
return None
75-
76-
print(f"🔍 Verifying workspace assignment...")
77-
try:
78-
workspace_info = workspace_client.get_workspace_info()
79-
assigned_capacity_id = workspace_info.get('capacityId')
80-
81-
if assigned_capacity_id == capacity_id:
82-
print(f"✅ Verification successful: Workspace is assigned to capacity {capacity_name}")
83-
84-
print(f"\n📊 Workspace Summary:")
85-
print(f" Name: {workspace_info.get('displayName', 'Unknown')}")
86-
print(f" ID: {workspace_info.get('id', 'Unknown')}")
87-
print(f" Capacity: {capacity['displayName']} ({capacity_id})")
88-
print(f" Type: {workspace_info.get('type', 'Unknown')}")
89-
90-
items = workspace_client.get_items()
91-
print(f" Items: {len(items)} total")
92-
93-
return workspace_setup_response
46+
if e.status_code == 409:
47+
# Handle race condition where workspace was created between check and create
48+
print(f"ℹ️ Workspace '{workspace_name}' already exists (created during operation)")
49+
existing_workspace = fabric_client.get_workspace(workspace_name)
50+
workspace_id = existing_workspace['id']
9451
else:
95-
print(f"⚠️ Warning: Workspace shows different capacity assignment: {assigned_capacity_id}")
96-
return None
97-
98-
except FabricApiError as e:
99-
print(f"⚠️ Could not verify assignment: {e}")
100-
print(f"✅ Workspace creation and assignment completed (verification failed)")
101-
return workspace_setup_response
102-
103-
except Exception as e:
104-
print(f"❌ Unexpected error: {e}")
52+
print(f"❌ Failed to create workspace: {e}")
53+
raise
54+
55+
# Step 2: Find the capacity
56+
print(f"🔍 Searching for capacity: '{capacity_name}'")
57+
capacity = fabric_client.get_capacity(capacity_name)
58+
if not capacity:
59+
print(f"❌ Capacity '{capacity_name}' not found.")
10560
return None
10661

62+
capacity_id = capacity['id']
63+
print(f"✅ Capacity found: {capacity['displayName']} ({capacity_id})")
64+
65+
# Step 3: Assign workspace to capacity
66+
print(f"⚡ Assigning workspace to capacity '{capacity_name}'...")
67+
workspace_client = FabricWorkspaceApiClient(workspace_id=workspace_id)
68+
workspace_client.assign_to_capacity(capacity_id)
69+
print(f"✅ Successfully assigned workspace to capacity")
70+
71+
return workspace_id
72+
10773
def main():
108-
"""Main function to handle command line arguments and execute the workspace creation."""
74+
"""Main function to handle command line arguments and execute workspace setup."""
10975
parser = argparse.ArgumentParser(
11076
description="Create a Fabric workspace and assign it to a capacity",
11177
formatter_class=argparse.RawDescriptionHelpFormatter,
@@ -139,7 +105,7 @@ def main():
139105
workspace_name=args.workspace_name
140106
)
141107

142-
print(f"\n✅ Workspace ID: {result.get('id') if result else 'Failed'}")
108+
print(f"\n✅ Workspace ID: {result if result else 'Failed'}")
143109
print(f"✅ Workspace Name: {args.workspace_name}")
144110

145111

0 commit comments

Comments
 (0)