Skip to content

Commit

Permalink
feat: [Slug] Auth Flow for SDK / Console Integration
Browse files Browse the repository at this point in the history
  • Loading branch information
jayanth-kumar-morem committed Feb 18, 2025
1 parent 5deebf1 commit 2213b08
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 16 deletions.
22 changes: 20 additions & 2 deletions preswald/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from preswald.deploy import deploy as deploy_app
from preswald.deploy import stop as stop_app
from preswald.main import start_server
from preswald.utils import configure_logging, read_port_from_config, read_template
from preswald.utils import configure_logging, read_template, get_project_slug, generate_slug, read_port_from_config


# Create a temporary directory for IPC
Expand Down Expand Up @@ -40,6 +40,9 @@ def init(name):
os.makedirs(os.path.join(name, "images"), exist_ok=True)
os.makedirs(os.path.join(name, "data"), exist_ok=True)

# Generate a unique slug for the project
project_slug = generate_slug(name)

# Copy default branding files from package resources
import shutil

Expand All @@ -62,10 +65,16 @@ def init(name):

for file_name, template_name in file_templates.items():
content = read_template(template_name)

# Replace the default slug in preswald.toml with the generated one
if file_name == "preswald.toml":
content = content.replace('slug = "preswald-project"', f'slug = "{project_slug}"')

with open(os.path.join(name, file_name), "w") as f:
f.write(content)

click.echo(f"Initialized a new Preswald project in '{name}/' 🎉!")
click.echo(f"Project slug: {project_slug}")
except Exception as e:
click.echo(f"Error initializing project: {e} ❌")

Expand Down Expand Up @@ -161,6 +170,14 @@ def deploy(script, target, port, log_level, github, api_key):
port = read_port_from_config(config_path=config_path, port=port)

if target == "structured":
# Validate project slug before deployment
try:
project_slug = get_project_slug(config_path)
click.echo(f"Using project slug: {project_slug}")
except Exception as e:
click.echo(click.style(f"Error: {str(e)} ❌", fg="red"))
return

click.echo("Starting production deployment... 🚀")
try:
for status_update in deploy_app(
Expand Down Expand Up @@ -203,7 +220,8 @@ def deploy(script, target, port, log_level, github, api_key):
click.echo(click.style(success_message, fg="green"))

except Exception as e:
click.echo(f"Error deploying app: {e} ❌")
click.echo(click.style(f"Deployment failed: {e!s} ❌", fg="red"))
sys.exit(1)


@cli.command()
Expand Down
44 changes: 31 additions & 13 deletions preswald/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from datetime import datetime
import io

from preswald.utils import read_template
from preswald.utils import read_template, get_project_slug

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -243,8 +243,20 @@ def deploy_to_prod(script_path: str, port: int = 8501, github_username: str = No
"""
script_path = os.path.abspath(script_path)
script_dir = Path(script_path).parent
config_path = script_dir / "preswald.toml"
env_file = script_dir / '.env.structured'

# Get project slug from preswald.toml
try:
project_slug = get_project_slug(config_path)
except Exception as e:
yield {
'status': 'error',
'message': f'Failed to get project slug: {str(e)}',
'timestamp': datetime.now().isoformat()
}
raise Exception(f"Failed to get project slug: {str(e)}")

if not env_file.exists():
# Use provided credentials or get from user input
if not github_username:
Expand All @@ -254,14 +266,10 @@ def deploy_to_prod(script_path: str, port: int = 8501, github_username: str = No
else:
structured_cloud_api_key = api_key

# Generate a unique app ID (using timestamp)
app_id = f"app_{int(datetime.now().timestamp())}"

# Create and populate .env.structured file
with open(env_file, 'w') as f:
f.write(f"GITHUB_USERNAME={github_username}\n")
f.write(f"STRUCTURED_CLOUD_API_KEY={structured_cloud_api_key}\n")
f.write(f"APP_ID={app_id}\n")
f.write(f"STRUCTURED_CLOUD_API_KEY={structured_cloud_api_key}\n")
else:
# Read credentials from existing env file if not provided via CLI
credentials = {}
Expand All @@ -272,7 +280,6 @@ def deploy_to_prod(script_path: str, port: int = 8501, github_username: str = No

github_username = github_username or credentials['GITHUB_USERNAME']
structured_cloud_api_key = api_key or credentials['STRUCTURED_CLOUD_API_KEY']
app_id = credentials['APP_ID']

# Create a temporary zip file
zip_buffer = io.BytesIO()
Expand Down Expand Up @@ -309,7 +316,7 @@ def deploy_to_prod(script_path: str, port: int = 8501, github_username: str = No
data={
'github_username': github_username,
'structured_cloud_api_key': structured_cloud_api_key,
'app_id': app_id,
'project_slug': project_slug,
'git_repo_name': git_repo_name,
},
stream=True
Expand All @@ -332,7 +339,6 @@ def deploy_to_prod(script_path: str, port: int = 8501, github_username: str = No
}
raise Exception(f"Production deployment failed: {str(e)}")


def deploy_to_gcp(script_path: str, port: int = 8501) -> str:
"""
Deploy a Preswald app to Google Cloud Run.
Expand Down Expand Up @@ -573,8 +579,15 @@ def stop_structured_deployment(script_path: str) -> dict:
dict: Status of the stop operation
"""
script_dir = Path(script_path).parent
config_path = script_dir / "preswald.toml"
env_file = script_dir / '.env.structured'

# Get project slug from preswald.toml
try:
project_slug = get_project_slug(config_path)
except Exception as e:
raise Exception(f"Failed to get project slug: {str(e)}")

if not env_file.exists():
raise Exception("No deployment found. The .env.structured file is missing.")

Expand All @@ -587,15 +600,14 @@ def stop_structured_deployment(script_path: str) -> dict:

github_username = credentials['GITHUB_USERNAME']
structured_cloud_api_key = credentials['STRUCTURED_CLOUD_API_KEY']
app_id = credentials['APP_ID']

try:
response = requests.post(
f"{STRUCTURED_CLOUD_SERVICE_URL}/stop",
json={
'github_username': github_username,
'structured_cloud_api_key': structured_cloud_api_key,
'app_id': app_id
'project_slug': project_slug
}
)
response.raise_for_status()
Expand All @@ -616,8 +628,15 @@ def get_structured_deployments(script_path: str) -> dict:
dict: Deployment information including user, organization, and deployments list
"""
script_dir = Path(script_path).parent
config_path = script_dir / "preswald.toml"
env_file = script_dir / '.env.structured'

# Get project slug from preswald.toml
try:
project_slug = get_project_slug(config_path)
except Exception as e:
raise Exception(f"Failed to get project slug: {str(e)}")

if not env_file.exists():
raise Exception("No deployment found. The .env.structured file is missing.")

Expand All @@ -630,15 +649,14 @@ def get_structured_deployments(script_path: str) -> dict:

github_username = credentials['GITHUB_USERNAME']
structured_cloud_api_key = credentials['STRUCTURED_CLOUD_API_KEY']
app_id = credentials['APP_ID']

try:
response = requests.post(
f"{STRUCTURED_CLOUD_SERVICE_URL}/deployments",
json={
'github_username': github_username,
'structured_cloud_api_key': structured_cloud_api_key,
'app_id': app_id
'project_slug': project_slug
}
)
response.raise_for_status()
Expand Down
1 change: 1 addition & 0 deletions preswald/templates/preswald.toml.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title = "Preswald Project"
version = "0.1.0"
port = 8501
slug = "preswald-project" # Required: Unique identifier for your project

[branding]
name = "Preswald Project"
Expand Down
44 changes: 43 additions & 1 deletion preswald/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

import pkg_resources
import toml

import logging
import re
import random

def read_template(template_name):
"""Read content from a template file."""
Expand Down Expand Up @@ -69,3 +71,43 @@ def configure_logging(config_path: Optional[str] = None, level: Optional[str] =
logger.debug(f"Logging configured with level {log_config['level']}")

return log_config["level"]

def validate_slug(slug: str) -> bool:
pattern = r'^[a-z0-9][a-z0-9-]*[a-z0-9]$'
return bool(re.match(pattern, slug)) and len(slug) >= 3 and len(slug) <= 63


def get_project_slug(config_path: str) -> str:
if not os.path.exists(config_path):
raise Exception(f"Config file not found at: {config_path}")

try:
config = toml.load(config_path)
if "project" not in config:
raise Exception("Missing [project] section in preswald.toml")

if "slug" not in config["project"]:
raise Exception("Missing required field 'slug' in [project] section")

slug = config["project"]["slug"]
if not validate_slug(slug):
raise Exception(
"Invalid slug format. Slug must be 3-63 characters long, "
"contain only lowercase letters, numbers, and hyphens, "
"and must start and end with a letter or number."
)

return slug

except Exception as e:
raise Exception(f"Error reading project slug: {str(e)}")


def generate_slug(base_name: str) -> str:
base_slug = re.sub(r'[^a-zA-Z0-9]+', '-', base_name.lower()).strip('-')
random_number = random.randint(100000, 999999)
slug = f"{base_slug}-{random_number}"
if not validate_slug(slug):
slug = f"preswald-{random_number}"

return slug

0 comments on commit 2213b08

Please sign in to comment.