|
1 | 1 | import json |
| 2 | +import subprocess |
2 | 3 |
|
3 | 4 | import typer |
4 | 5 | from rich.console import Console |
|
24 | 25 | context_settings={"help_option_names": ["-h", "--help"]}, |
25 | 26 | ) |
26 | 27 |
|
| 28 | +cloud_create = typer.Typer( |
| 29 | + cls=OrderCommands, |
| 30 | + help="Create cloud provider credentials", |
| 31 | + add_completion=False, |
| 32 | + no_args_is_help=True, |
| 33 | + rich_markup_mode="rich", |
| 34 | + context_settings={"help_option_names": ["-h", "--help"]}, |
| 35 | +) |
| 36 | + |
27 | 37 |
|
28 | 38 | cloud_app.add_typer(cloud_connect, name="connect") |
| 39 | +cloud_app.add_typer(cloud_create, name="create") |
29 | 40 |
|
30 | 41 |
|
31 | 42 | @cloud_connect.command() |
@@ -106,6 +117,119 @@ def oracle( |
106 | 117 | _connect_cloud(name="oracle", credentials=credentials) |
107 | 118 |
|
108 | 119 |
|
| 120 | +@cloud_create.command() |
| 121 | +def azure( |
| 122 | + name: str = typer.Option( |
| 123 | + None, |
| 124 | + "--name", |
| 125 | + help="Name for the service principal (optional, auto-generated if not provided)" |
| 126 | + ), |
| 127 | + auto_connect: bool = typer.Option( |
| 128 | + False, |
| 129 | + "--auto-connect", |
| 130 | + help="Automatically connect the created credentials to Cirun" |
| 131 | + ), |
| 132 | +): |
| 133 | + """Create Azure Service Principal credentials for Cirun""" |
| 134 | + import time |
| 135 | + |
| 136 | + console = Console() |
| 137 | + error_console = Console(stderr=True, style="bold red") |
| 138 | + |
| 139 | + # Check if Azure CLI is installed |
| 140 | + try: |
| 141 | + subprocess.run( |
| 142 | + ["az", "--version"], |
| 143 | + capture_output=True, |
| 144 | + check=True |
| 145 | + ) |
| 146 | + except (subprocess.CalledProcessError, FileNotFoundError): |
| 147 | + error_console.print("Error: Azure CLI is not installed or not found in PATH") |
| 148 | + error_console.print("Install it from: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli") |
| 149 | + raise typer.Exit(code=1) |
| 150 | + |
| 151 | + # Check if user is logged in and get account details |
| 152 | + console.print("[bold blue]Checking Azure CLI login status...[/bold blue]") |
| 153 | + try: |
| 154 | + result = subprocess.run( |
| 155 | + ["az", "account", "show", "--output", "json"], |
| 156 | + capture_output=True, |
| 157 | + check=True, |
| 158 | + text=True |
| 159 | + ) |
| 160 | + account_info = json.loads(result.stdout) |
| 161 | + except subprocess.CalledProcessError: |
| 162 | + error_console.print("Error: Not logged in to Azure CLI") |
| 163 | + error_console.print("Please run: az login") |
| 164 | + raise typer.Exit(code=1) |
| 165 | + |
| 166 | + # Display account details |
| 167 | + console.print("\n[bold green]Azure Account Details:[/bold green]") |
| 168 | + console.print(f" Account Name: [bold]{account_info.get('user', {}).get('name', 'N/A')}[/bold]") |
| 169 | + console.print(f" Subscription Name: [bold]{account_info.get('name', 'N/A')}[/bold]") |
| 170 | + console.print(f" Subscription ID: [bold]{account_info.get('id', 'N/A')}[/bold]") |
| 171 | + console.print(f" Tenant ID: [bold]{account_info.get('tenantId', 'N/A')}[/bold]") |
| 172 | + console.print(f" State: [bold]{account_info.get('state', 'N/A')}[/bold]") |
| 173 | + console.print("") |
| 174 | + |
| 175 | + subscription_id = account_info.get('id') |
| 176 | + |
| 177 | + # Generate service principal name if not provided |
| 178 | + if not name: |
| 179 | + name = f"cirun-{int(time.time())}" |
| 180 | + |
| 181 | + # Create service principal |
| 182 | + console.print(f"[bold blue]Creating service principal '[bold green]{name}[/bold green]'...[/bold blue]") |
| 183 | + try: |
| 184 | + result = subprocess.run( |
| 185 | + [ |
| 186 | + "az", "ad", "sp", "create-for-rbac", |
| 187 | + "--name", name, |
| 188 | + "--role", "contributor", |
| 189 | + "--scopes", f"/subscriptions/{subscription_id}", |
| 190 | + "--output", "json" |
| 191 | + ], |
| 192 | + capture_output=True, |
| 193 | + check=True, |
| 194 | + text=True |
| 195 | + ) |
| 196 | + except subprocess.CalledProcessError as e: |
| 197 | + error_console.print(f"Error creating service principal: {e.stderr}") |
| 198 | + raise typer.Exit(code=1) |
| 199 | + |
| 200 | + # Parse output |
| 201 | + sp_data = json.loads(result.stdout) |
| 202 | + client_id = sp_data.get("appId") |
| 203 | + client_secret = sp_data.get("password") |
| 204 | + tenant_id = sp_data.get("tenant") |
| 205 | + |
| 206 | + # Display credentials |
| 207 | + success_console = Console(style="bold green") |
| 208 | + success_console.rule("[bold green]") |
| 209 | + success_console.print("[bold green]✓[/bold green] Service principal created successfully!") |
| 210 | + success_console.print("") |
| 211 | + success_console.print("[bold yellow]Azure Credentials for Cirun:[/bold yellow]") |
| 212 | + success_console.print("") |
| 213 | + success_console.print(f"AZURE_SUBSCRIPTION_ID: [bold]{subscription_id}[/bold]") |
| 214 | + success_console.print(f"AZURE_CLIENT_ID: [bold]{client_id}[/bold]") |
| 215 | + success_console.print(f"AZURE_CLIENT_SECRET: [bold]{client_secret}[/bold]") |
| 216 | + success_console.print(f"AZURE_TENANT_ID: [bold]{tenant_id}[/bold]") |
| 217 | + success_console.print("") |
| 218 | + success_console.print("[bold red]⚠️ Save the CLIENT_SECRET - it won't be shown again![/bold red]") |
| 219 | + success_console.rule("[bold green]") |
| 220 | + |
| 221 | + # Auto-connect if requested |
| 222 | + if auto_connect: |
| 223 | + console.print("\n[bold blue]Connecting credentials to Cirun...[/bold blue]") |
| 224 | + credentials = { |
| 225 | + "subscription_id": subscription_id, |
| 226 | + "tenant_id": tenant_id, |
| 227 | + "client_id": client_id, |
| 228 | + "client_secret": client_secret, |
| 229 | + } |
| 230 | + _connect_cloud(name="azure", credentials=credentials) |
| 231 | + |
| 232 | + |
109 | 233 | def _connect_cloud(name, credentials): |
110 | 234 | cirun = Cirun() |
111 | 235 | response_json = cirun.cloud_connect( |
|
0 commit comments