Skip to content

Commit 6fedb4a

Browse files
authored
Merge pull request #7 from AktechLabs/aktech/azure-auto-connect
Auto connect azure
2 parents ad333b8 + 831be0a commit 6fedb4a

File tree

3 files changed

+143
-4
lines changed

3 files changed

+143
-4
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ cirun cloud connect azure \
9292
--client-id 340d01fc-ba24-43ee-844e-d364899d29e7 \
9393
--client-secret KFCF3yi+df0cirunIsAwesomeIsntIt?n1DFGHJ
9494

95+
# Or create Azure credentials automatically and connect in one step
96+
# (requires Azure CLI to be installed and logged in)
97+
cirun cloud create azure --auto-connect
98+
9599
# Connect GCP
96100
cirun cloud connect gcp --key-file /path/to/service-account-key.json
97101
```
@@ -122,7 +126,7 @@ cirun_client.set_repo('username/repo-name', active=False)
122126
| Variable | Description | Default |
123127
|----------|-------------|---------|
124128
| `CIRUN_API_KEY` | API key for authentication | (Required) |
125-
| `CIRUN_API_URL` | Base URL for Cirun API | https://api.cirun.io/api/v1 |
129+
| `CIRUN_API_ENDPOINT` | Base URL for Cirun API | https://api.cirun.io/api/v1 |
126130

127131
## 📚 Documentation
128132

cirun/cloud.py

Lines changed: 137 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import subprocess
23

34
import typer
45
from rich.console import Console
@@ -24,8 +25,18 @@
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

2838
cloud_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+
109244
def _connect_cloud(name, credentials):
110245
cirun = Cirun()
111246
response_json = cirun.cloud_connect(

cirun/tests/test_integration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from cirun import Cirun
77

8-
MSG_401 = "This API Key is expired or non-existent."
8+
MSG_401 = "Invalid or inactive API token"
99

1010

1111
@pytest.fixture

0 commit comments

Comments
 (0)