11import json
2+ import subprocess
23
34import typer
45from rich .console import Console
2425 context_settings = {"help_option_names" : ["-h" , "--help" ]},
2526)
2627
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+
2737
2838cloud_app .add_typer (cloud_connect , name = "connect" )
39+ cloud_app .add_typer (cloud_create , name = "create" )
2940
3041
3142@cloud_connect .command ()
@@ -41,8 +52,8 @@ def aws(
4152 _connect_cloud (name = "aws" , credentials = credentials )
4253
4354
44- @cloud_connect .command ()
45- def azure (
55+ @cloud_connect .command (name = "azure" )
56+ def connect_azure (
4657 subscription_id = option ("--subscription-id" , help = "Azure subscription_id" ),
4758 tenant_id = option ("--tenant-id" , help = "Azure tenant_id" ),
4859 client_id = option ("--client-id" , help = "Azure client_id" ),
@@ -106,6 +117,130 @@ def oracle(
106117 _connect_cloud (name = "oracle" , credentials = credentials )
107118
108119
120+ @cloud_create .command (name = "azure" )
121+ def create_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 os
135+
136+ console = Console ()
137+ error_console = Console (stderr = True , style = "bold red" )
138+
139+ if auto_connect and not os .environ .get ("CIRUN_API_KEY" ):
140+ error_console .print ("Error: CIRUN_API_KEY environment variable is required for --auto-connect" )
141+ raise typer .Exit (code = 1 )
142+
143+ # Check if Azure CLI is installed
144+ try :
145+ subprocess .run (
146+ ["az" , "--version" ],
147+ capture_output = True ,
148+ check = True
149+ )
150+ except (subprocess .CalledProcessError , FileNotFoundError ):
151+ error_console .print ("Error: Azure CLI is not installed or not found in PATH" )
152+ error_console .print ("Install it from: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli" )
153+ raise typer .Exit (code = 1 )
154+
155+ # Check if user is logged in and get account details
156+ console .print ("[bold blue]Checking Azure CLI login status...[/bold blue]" )
157+ try :
158+ result = subprocess .run (
159+ ["az" , "account" , "show" , "--output" , "json" ],
160+ capture_output = True ,
161+ check = True ,
162+ text = True
163+ )
164+ account_info = json .loads (result .stdout )
165+ except subprocess .CalledProcessError :
166+ error_console .print ("Error: Not logged in to Azure CLI" )
167+ error_console .print ("Please run: az login" )
168+ raise typer .Exit (code = 1 )
169+
170+ # Display account details
171+ console .print ("\n [bold green]Azure Account Details:[/bold green]" )
172+ console .print (f" Account Name: [bold]{ account_info .get ('user' , {}).get ('name' , 'N/A' )} [/bold]" )
173+ console .print (f" Subscription Name: [bold]{ account_info .get ('name' , 'N/A' )} [/bold]" )
174+ console .print (f" Subscription ID: [bold]{ account_info .get ('id' , 'N/A' )} [/bold]" )
175+ console .print (f" Tenant ID: [bold]{ account_info .get ('tenantId' , 'N/A' )} [/bold]" )
176+ console .print (f" State: [bold]{ account_info .get ('state' , 'N/A' )} [/bold]" )
177+ console .print ("" )
178+
179+ subscription_id = account_info .get ('id' )
180+
181+ # Generate service principal name if not provided
182+ if not name :
183+ from datetime import datetime , timezone
184+ name = f"cirun-{ datetime .now (timezone .utc ).strftime ('%Y%m%d-%H%M%S' )} "
185+
186+ # Confirm before creating
187+ typer .confirm (
188+ f"Create service principal '{ name } ' with contributor role on subscription '{ subscription_id } '?" ,
189+ abort = True ,
190+ )
191+
192+ # Create service principal
193+ console .print (f"[bold blue]Creating service principal '[bold green]{ name } [/bold green]'...[/bold blue]" )
194+ try :
195+ result = subprocess .run (
196+ [
197+ "az" , "ad" , "sp" , "create-for-rbac" ,
198+ "--name" , name ,
199+ "--role" , "contributor" ,
200+ "--scopes" , f"/subscriptions/{ subscription_id } " ,
201+ "--output" , "json"
202+ ],
203+ capture_output = True ,
204+ check = True ,
205+ text = True
206+ )
207+ except subprocess .CalledProcessError as e :
208+ error_console .print (f"Error creating service principal: { e .stderr } " )
209+ raise typer .Exit (code = 1 )
210+
211+ # Parse output
212+ sp_data = json .loads (result .stdout )
213+ client_id = sp_data .get ("appId" )
214+ client_secret = sp_data .get ("password" )
215+ tenant_id = sp_data .get ("tenant" )
216+
217+ # Display credentials
218+ success_console = Console (style = "bold green" )
219+ success_console .rule ("[bold green]" )
220+ success_console .print ("[bold green]✓[/bold green] Service principal created successfully!" )
221+ success_console .print ("" )
222+ success_console .print ("[bold yellow]Azure Credentials for Cirun:[/bold yellow]" )
223+ success_console .print ("" )
224+ success_console .print (f"AZURE_SUBSCRIPTION_ID: [bold]{ subscription_id } [/bold]" )
225+ success_console .print (f"AZURE_CLIENT_ID: [bold]{ client_id } [/bold]" )
226+ success_console .print (f"AZURE_CLIENT_SECRET: [bold]{ client_secret } [/bold]" )
227+ success_console .print (f"AZURE_TENANT_ID: [bold]{ tenant_id } [/bold]" )
228+ success_console .print ("" )
229+ success_console .print ("[bold red]⚠️ Save the CLIENT_SECRET - it won't be shown again![/bold red]" )
230+ success_console .rule ("[bold green]" )
231+
232+ # Auto-connect if requested
233+ if auto_connect :
234+ console .print ("\n [bold blue]Connecting credentials to Cirun...[/bold blue]" )
235+ credentials = {
236+ "subscription_id" : subscription_id ,
237+ "tenant_id" : tenant_id ,
238+ "client_id" : client_id ,
239+ "client_secret" : client_secret ,
240+ }
241+ _connect_cloud (name = "azure" , credentials = credentials )
242+
243+
109244def _connect_cloud (name , credentials ):
110245 cirun = Cirun ()
111246 response_json = cirun .cloud_connect (
0 commit comments