Skip to content

Commit

Permalink
Merge pull request #2 from cased/rick/cas-1697-branch-deploy-from-cli…
Browse files Browse the repository at this point in the history
…-commands

Call to get actual data
  • Loading branch information
SirEntropy authored Aug 15, 2024
2 parents 14fd1c2 + 6afa88b commit 16cfe44
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 106 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# .gitignore
_pycache_/
__pycache__/
.egg-info/
env/
.env/
Expand Down
111 changes: 43 additions & 68 deletions cased/commands/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,57 @@
import click
from rich.console import Console
from rich.progress import Progress
import random
import questionary

from cased.utils.auth import get_token
from cased.utils.api import get_branches, deploy_branch
from cased.utils.progress import run_process_with_status_bar

console = Console()


def get_deployable_branches():
# Simulate fetching deployable branches with their available targets
branches = [
{"name": "feature-branch-1", "targets": ["dev", "staging"]},
{"name": "feature-branch-2", "targets": ["dev", "staging", "prod"]},
{"name": "feature-branch-3", "targets": ["dev"]},
{"name": "main", "targets": ["dev", "staging", "prod"]},
{"name": "hotfix-branch", "targets": ["staging", "prod"]},
def _build_questionary_choices():
data = run_process_with_status_bar(
get_branches, "Fetching branches...", timeout=10
)
branches = data.get("pull_requests", [])
deployable_branches = [
branch for branch in branches if branch["deployable"] == True
]
# Prepare choices for questionary
choices = [
f"{b['branch_name']} -> [{', '.join([target["name"] for target in b.get("targets", [])])}]" for b in deployable_branches
]
return branches

if not choices:
console.print("[red]No deployable branches available.[/red]")
return

def simulate_api_call(branch, target):
time.sleep(1) # Simulate network delay
return random.choice(["dispatch succeeded", "failed"])
selected = questionary.select(
"Select a branch to deploy:", choices=choices
).ask()

branch = selected.split(" -> ")[0]

def run_deployment(branch, target):
with Progress() as progress:
deploy_task = progress.add_task(
f"[green]Deploying {branch} to {target}...", total=100
# Find the selected branch in our data
selected_branch = next(
(b for b in deployable_branches if b["branch_name"] == branch), None
)
if not selected_branch:
console.print(f"[red]Error: Branch '{branch}' is not deployable.[/red]")
return

available_targets = selected_branch["targets"]
if not available_targets:
console.print(
f"[red]Error: No targets available for branch '{branch}'.[/red]"
)
while not progress.finished:
progress.update(deploy_task, advance=0.5)
time.sleep(0.1)
console.print(f"[green]Deployment of {branch} to {target} completed successfully!")
return
target = questionary.select(
"Select a target environment:", choices=available_targets
).ask()


return branch, target


@click.command()
Expand All @@ -60,57 +77,15 @@ def deploy(branch, target):
console.print("[red]Please log in first using 'cased login'[/red]")
return

deployable_branches = get_deployable_branches()

if not branch:
# Prepare choices for questionary
choices = [
f"{b['name']} -> [{', '.join(b['targets'])}]" for b in deployable_branches
]

if not choices:
console.print("[red]No deployable branches available.[/red]")
return

selected = questionary.select(
"Select a branch to deploy:", choices=choices
).ask()

branch = selected.split(" -> ")[0]

# Find the selected branch in our data
selected_branch = next(
(b for b in deployable_branches if b["name"] == branch), None
)
if not selected_branch:
console.print(f"[red]Error: Branch '{branch}' is not deployable.[/red]")
return

if not target:
available_targets = selected_branch["targets"]
if not available_targets:
console.print(
f"[red]Error: No targets available for branch '{branch}'.[/red]"
)
return
target = questionary.select(
"Select a target environment:", choices=available_targets
).ask()
elif target not in selected_branch["targets"]:
console.print(
f"[red]Error: Target '{target}' is not available for branch '{branch}'.[/red]"
)
return
if not branch and not target:
branch, target = _build_questionary_choices()

console.print(
f"Preparing to deploy [cyan]{branch}[/cyan] to [yellow]{target}[/yellow]"
)

# Simulate API call
result = simulate_api_call(branch, target)

if result == "dispatch succeeded":
if branch and target:
deploy_branch(branch, target)
console.print("[green]Dispatch succeeded. Starting deployment...[/green]")
run_deployment(branch, target)
else:
console.print("[red]Deployment dispatch failed. Please try again later.[/red]")
65 changes: 34 additions & 31 deletions cased/commands/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
from rich.console import Console
from rich.table import Table
from rich.text import Text
from datetime import datetime, timedelta
import random
from dateutil import parser


from cased.utils.auth import get_token
from cased.utils.api import get_branches, get_targets, get_deployments
from cased.utils.progress import run_process_with_status_bar

console = Console()

Expand Down Expand Up @@ -37,26 +38,31 @@ def deployments(limit):
table.add_column("Target", style="blue")
table.add_column("View", style="cyan")

data = get_deployments().get("deployments", [])

deployments_data = []
for _ in range(limit):
begin_time = datetime.now() - timedelta(hours=random.randint(1, 48))
for idx, deployment in enumerate(data):
if idx == limit:
break
begin_time = parser.parse(deployment.get("start_time"))
end_time = (
begin_time + timedelta(hours=random.randint(1, 4))
if random.choice([True, False])
else None
parser.parse(deployment.get("end_time"))
if deployment.get("end_time")
else ""
)
status = random.choice(["pending", "running", "success", "failed", "canceled"])
deployment_id = random.randint(1000, 9999)
status = deployment.get("status", "Unknown")
deployment_id = deployment.get("id")
view_url = f"https://cased.com/deployments/{deployment_id}"
deployer_full_name = f"{deployment.get("deployer").get("first_name")} {deployment.get("deployer").get("last_name")}" if deployment.get("deployer") else "Unknown"

deployments_data.append(
{
"begin_time": begin_time,
"end_time": end_time,
"deployer": f"user{random.randint(1, 100)}@example.com",
"deployer": deployer_full_name,
"status": status,
"branch": f"feature-branch-{random.randint(1, 100)}",
"target": f"env-{random.choice(['prod', 'staging', 'dev'])}",
"branch": deployment.get("ref").replace("refs/heads/", ""),
"target": deployment.get("target").get("name"),
"view": (deployment_id, view_url),
}
)
Expand Down Expand Up @@ -108,31 +114,28 @@ def branches(limit):
table.add_column("PR Number", style="yellow")
table.add_column("PR Title", style="green")
table.add_column("Deployable", style="blue")
table.add_column("Deploy Checks", style="cyan")
table.add_column("Mergeable", style="blue")
table.add_column("Merge Checks", style="cyan")

table.add_column("Checks", style="cyan")

data = run_process_with_status_bar(get_branches, "Fetching branches...", timeout=10)
branches = data.get("pull_requests", [])
# Generate fake data
for _ in range(limit):
pr_number = random.randint(100, 999) if random.choice([True, False]) else None
for idx, branch in enumerate(branches):
if idx == limit:
break

table.add_row(
f"feature-branch-{random.randint(1, 100)}",
f"user{random.randint(1, 100)}@example.com",
str(pr_number) if pr_number else "NULL",
f"Implement new feature #{random.randint(1, 100)}" if pr_number else "NULL",
random.choice(["Yes", "No"]),
", ".join(
[
f"{check}:{random.choice(['✓', '✗'])}"
for check in ["lint", "test", "build"]
]
),
random.choice(["Yes", "No"]),
branch.get("branch_name"),
branch.get("owner"),
str(branch.get("number")),
branch.get("title"),
str(branch.get("deployable")),
str(branch.get("mergeable")),
", ".join(
[
f"{check}:{random.choice(['✓', '✗'])}"
for check in ["conflicts", "approvals", "checks"]
f"approved: {branch.get("approved")}",
f"up-to-date: {branch.get("up_to_date")}",
f"checks-passed: {branch.get("checks_passing")}",
]
),
)
Expand Down
39 changes: 32 additions & 7 deletions cased/utils/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,23 @@
import click
import os

API_BASE_URL = os.getenv("CASED_API_BASE_URL", "https://api.cased.com")
API_BASE_URL = os.environ.get(
"CASED_API_BASE_URL", default="https://app.cased.com/api/v1"
)
REQUEST_HEADERS = {
"X-CASED-API-KEY": os.environ.get("CASED_API_AUTH_KEY"),
"X-CASED-ORG-ID": str(os.environ.get("CASED_API_ORG_ID")),
"Accept": "application/json",
}


def get_branches():
response = requests.get(f"{API_BASE_URL}/branches")
def get_branches(target_name: str = None):
query_params = {"target_name": target_name} if target_name else {}
response = requests.get(
f"{API_BASE_URL}/prs",
headers=REQUEST_HEADERS,
params=query_params,
)
if response.status_code == 200:
return response.json()
else:
Expand All @@ -15,20 +27,33 @@ def get_branches():


def get_targets():
response = requests.get(f"{API_BASE_URL}/targets")
response = requests.get(f"{API_BASE_URL}/targets", headers=REQUEST_HEADERS)
if response.status_code == 200:
return response.json()
else:
click.echo("Failed to fetch targets. Please try again.")
return []


def deploy_branch(branch, target):
def get_deployments():
response = requests.get(f"{API_BASE_URL}/deployments/", headers=REQUEST_HEADERS)
if response.status_code == 200:
return response.json()
else:
click.echo("Failed to fetch deployments. Please try again.")
return []


def deploy_branch(branch_name, target_name):
# Implement branch deployment logic here
response = requests.post(
f"{API_BASE_URL}/deploy", json={"target": target, "branch": branch}
f"{API_BASE_URL}/branch-deploys/",
json={"branch_name": branch_name, "target_name": target_name},
headers=REQUEST_HEADERS,
)
if response.status_code == 200:
click.echo(f"Successfully deployed branch '{branch}' to target '{target}'!")
click.echo(
f"Successfully deployed branch '{branch_name}' to target '{target_name}'!"
)
else:
click.echo("Deployment failed. Please check your input and try again.")
51 changes: 51 additions & 0 deletions cased/utils/progress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import time
from rich.console import Console
from rich.progress import Progress
from concurrent.futures import ThreadPoolExecutor, TimeoutError
from typing import Callable, Any


def run_process_with_status_bar(
process_func: Callable[[], Any],
description: str = "Processing...",
timeout: int = 10,
*args,
**kwargs,
) -> Any:
console = Console()
result = None

def update_progress(progress, task):
start_time = time.time()
while time.time() - start_time < timeout:
elapsed = int(time.time() - start_time)
progress.update(task, completed=min(elapsed, timeout))
time.sleep(1) # Update every second

with Progress() as progress:
task = progress.add_task(f"[green]{description}", total=timeout)

with ThreadPoolExecutor(max_workers=2) as executor:
# Submit the main process
future = executor.submit(process_func, *args, **kwargs)
start_time = time.time()
while not future.done() and time.time() - start_time < timeout:
elapsed = int(time.time() - start_time)
progress.update(task, completed=min(elapsed, timeout))
time.sleep(0.1) # Update frequently for responsiveness

try:
result = future.result(timeout=timeout)
progress.update(
task, completed=timeout, description="[bold green]Done!"
)
except TimeoutError:
progress.update(task, description="[bold red]Timeout!")
console.print(
f"\n[bold red]Process timed out after {timeout} seconds. Please try again later."
)
except Exception as e:
progress.update(task, description="[bold red]Error!")
console.print(f"\n[bold red]Error: {e}")

return result
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
click==8.1.3
rich==13.3.5
questionary==1.10.0
python-dateutil==2.9.0
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"requests",
"rich",
"questionary",
"python-dateutil",
],
entry_points={
"console_scripts": [
Expand Down

0 comments on commit 16cfe44

Please sign in to comment.