From baacf394c8e9a0d2ef6b22897d7be878cd97a11c Mon Sep 17 00:00:00 2001 From: Rick Chen Date: Sun, 25 Aug 2024 21:01:31 -0700 Subject: [PATCH 1/5] init and build --- cased/cli.py | 6 + cased/commands/build.py | 177 ++++++++++++++++++++ cased/commands/init.py | 347 ++++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 + setup.py | 2 + 5 files changed, 534 insertions(+) create mode 100644 cased/commands/build.py create mode 100644 cased/commands/init.py diff --git a/cased/cli.py b/cased/cli.py index ccba2e8..ec7c2b4 100644 --- a/cased/cli.py +++ b/cased/cli.py @@ -1,9 +1,13 @@ import click +from cased.commands.build import build from cased.commands.deploy import deploy +from cased.commands.init import init from cased.commands.login import login, logout from cased.commands.resources import branches, deployments +CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) + @click.group() def cli(): @@ -17,6 +21,8 @@ def cli(): cli.add_command(deploy) +cli.add_command(init) +cli.add_command(build) cli.add_command(login) cli.add_command(logout) cli.add_command(deployments) diff --git a/cased/commands/build.py b/cased/commands/build.py new file mode 100644 index 0000000..b3647d2 --- /dev/null +++ b/cased/commands/build.py @@ -0,0 +1,177 @@ +import os + +import click +import yaml +from rich.console import Console +from rich.panel import Panel + +console = Console() + + +@click.command() +def build(): + """Build GitHub Actions workflow based on configuration.""" + console.print(Panel.fit("Building GitHub Actions Workflow", style="bold magenta")) + + # Validate config and workflow files + config = validate_config_file() + workflow = validate_workflow_file() + + if config and workflow: + # Generate GitHub Actions workflow + github_workflow = generate_github_actions_workflow(config, workflow) + + # Write GitHub Actions workflow file + os.makedirs(".github/workflows", exist_ok=True) + with open(".github/workflows/cased_workflow.yml", "w") as f: + for key, value in github_workflow.items(): + yaml.dump({key: value}, f, default_flow_style=False) + f.write("\n") + + console.print( + Panel.fit( + "GitHub Actions workflow generated successfully!", style="bold green" + ) + ) + console.print( + "Workflow file: [bold].github/workflows/cased_workflow.yml[/bold]" + ) + else: + console.print( + Panel.fit( + "Failed to generate GitHub Actions workflow. Please check your configuration.", # noqa: E501 + style="bold red", + ) + ) + + +def validate_config_file(): + try: + with open(".cased/config.yaml", "r") as f: + config = yaml.safe_load(f) + + required_keys = ["project", "environment", "runtime", "testing"] + for key in required_keys: + if key not in config: + raise ValueError(f"Missing required section: {key}") + + # Add more specific validation rules here + if "<" in str(config) or ">" in str(config): + console.print( + "[bold yellow]Warning:[/bold yellow] Some placeholder values in config.yaml have not been replaced." # noqa: E501 + ) + + return config + except FileNotFoundError: + console.print( + "[bold red]Error:[/bold red] config.yaml not found. Run 'cased init' first." + ) + except yaml.YAMLError as e: + console.print(f"[bold red]Error:[/bold red] Invalid YAML in config.yaml: {e}") + except ValueError as e: + console.print(f"[bold red]Error:[/bold red] {str(e)}") + return None + + +def validate_workflow_file(): + try: + with open(".cased/workflow.yaml", "r") as f: + workflow = yaml.safe_load(f) + + required_keys = ["name", "on", "jobs"] + for key in required_keys: + if key not in workflow: + raise ValueError(f"Missing required key in workflow: {key}") + + # Add more specific validation rules here + + return workflow + except FileNotFoundError: + console.print( + "[bold red]Error:[/bold red] workflow.yaml not found. Run 'cased init' first." # noqa: E501 + ) + except yaml.YAMLError as e: + console.print(f"[bold red]Error:[/bold red] Invalid YAML in workflow.yaml: {e}") + except ValueError as e: + console.print(f"[bold red]Error:[/bold red] {str(e)}") + return None + + +def generate_github_actions_workflow(config, workflow): + github_workflow = {"name": workflow["name"], "on": workflow["on"], "jobs": {}} + + # Build and Test job + build_and_test_job = { + "runs-on": "ubuntu-latest", + "steps": [ + {"name": "Checkout code", "uses": "actions/checkout@v2"}, + { + "name": "Set up Python", + "uses": "actions/setup-python@v2", + "with": {"python-version": config["environment"]["python_version"]}, + }, + {"name": "Install dependencies", "run": "pip install -r requirements.txt"}, + {"name": "Run tests", "run": f"python -m {config['testing']['framework']}"}, + ], + } + + if config["testing"]["coverage"].get("enabled"): + build_and_test_job["steps"].append( + { + "name": "Run coverage", + "run": f"coverage run -m {config['testing']['framework']} && coverage {config['testing']['coverage']['report_type']}", # noqa: E501 + } + ) + + github_workflow["jobs"]["build_and_test"] = build_and_test_job + + # Deploy job + deploy_job = { + "needs": "build_and_test", + "runs-on": "ubuntu-latest", + "steps": [ + {"name": "Checkout code", "uses": "actions/checkout@v2"}, + ], + } + + if config["docker"]["enabled"]: + deploy_job["steps"].extend( + [ + { + "name": "Set up Docker Buildx", + "uses": "docker/setup-buildx-action@v1", + }, + { + "name": "Build and push Docker image", + "uses": "docker/build-push-action@v2", + "with": { + "context": ".", + "push": True, + "tags": f"{config['docker']['image_name']}:${{ github.sha }}", + }, + }, + ] + ) + + if config["cloud_deployment"]["provider"] == "AWS": + deploy_job["steps"].extend( + [ + { + "name": "Configure AWS credentials", + "uses": "aws-actions/configure-aws-credentials@v1", + "with": { + "aws-access-key-id": "${{ secrets.AWS_ACCESS_KEY_ID }}", + "aws-secret-access-key": "${{ secrets.AWS_SECRET_ACCESS_KEY }}", + "aws-region": config["cloud_deployment"]["region"], + }, + }, + { + "name": "Deploy to AWS", + "run": 'echo "Add your AWS deployment commands here"', + }, + ] + ) + + github_workflow["jobs"]["deploy"] = deploy_job + + return github_workflow diff --git a/cased/commands/init.py b/cased/commands/init.py new file mode 100644 index 0000000..d783c2b --- /dev/null +++ b/cased/commands/init.py @@ -0,0 +1,347 @@ +import os + +import click +import inquirer +import yaml +from rich.console import Console +from rich.panel import Panel + +console = Console() + + +@click.command() +def init(): + """Initialize a new project configuration.""" + console.print(Panel.fit("Welcome to Cased", style="bold magenta")) + + config = {} + config.update(get_project_info()) + config.update(get_environment_info()) + config.update(get_additional_services_info()) + config.update(get_deployment_info()) + + config = expand_config_with_placeholders(config) + config = rearrange_config_sections(config) + + generate_config_file(config) + generate_workflow_file(config) + + display_results(config) + + +def get_project_info(): + questions = [inquirer.Text("name", message="Enter your project name")] + answers = inquirer.prompt(questions) + + return {"project": {"name": answers["name"]}} + + +def get_environment_info(): + questions = [ + inquirer.List( + "language", + message="Select the primary language for your project", + choices=["Python", "JavaScript"], + ), + inquirer.List( + "framework", + message="Select the framework (optional)", + choices=["None", "Django", "Flask", "Node.js"], + ), + inquirer.List( + "test_framework", + message="Select your preferred testing framework", + choices=["pytest", "unittest", "jest", "mocha"], + ), + ] + + answers = inquirer.prompt(questions) + + environment = { + "environment": { + "language": answers["language"], + "framework": answers["framework"], + }, + "testing": { + "framework": answers["test_framework"], + }, + } + if answers["language"] == "Python": + dependency_manager = [ + inquirer.List( + "dependency_manager", + message="Select the dependency manager", + choices=["pip", "poetry"], + ) + ] + dependency_manager_answers = inquirer.prompt(dependency_manager) + environment["environment"]["dependency_manager"] = dependency_manager_answers[ + "dependency_manager" + ] + environment["environment"]["python_version"] = "[REQUIRED] " + + return environment + + +def get_additional_services_info(): + questions = [ + inquirer.Confirm( + "use_database", message="Do you want to use a database?", default=False + ), + inquirer.Confirm( + "use_message_broker", + message="Do you want to use a message broker?", + default=False, + ), + ] + + answers = inquirer.prompt(questions) + + services = {} + + if answers["use_database"]: + db_questions = [ + inquirer.List( + "db_type", + message="Select the database type", + choices=["PostgreSQL", "MySQL", "MongoDB"], + ), + ] + db_answers = inquirer.prompt(db_questions) + services["database"] = { + "type": db_answers["db_type"], + "enabled": True, + } + else: + services["database"] = {"enabled": False} + + if answers["use_message_broker"]: + mb_questions = [ + inquirer.List( + "mb_type", + message="Select the message broker type", + choices=["RabbitMQ", "Redis", "Apache Kafka"], + ), + ] + mb_answers = inquirer.prompt(mb_questions) + services["message_broker"] = { + "type": mb_answers["mb_type"], + "enabled": True, + } + else: + services["message_broker"] = {"enabled": False} + + return services + + +def get_deployment_info(): + questions = [ + inquirer.Confirm( + "use_docker", message="Do you want to use Docker?", default=False + ), + inquirer.List( + "deployment_target", + message="Select your deployment target", + choices=["AWS", "Custom"], + ), + ] + + answers = inquirer.prompt(questions) + + deployment_info = { + "docker": { + "enabled": answers["use_docker"], + }, + "cloud_deployment": { + "provider": answers["deployment_target"], + }, + } + + return deployment_info + + +def expand_config_with_placeholders(config): + config["project"]["version"] = "<[OPTIONAL] PROJECT_VERSION>" + config["project"]["description"] = "<[OPTIONAL] PROJECT_DESCRIPTION>" + + config["environment"]["dependency_files"] = [ + "<[OPTIONAL] Cased build will smart detect these files if not provided here>" + ] + + config["testing"]["test_files"] = ["[OPTIONAL]", "", ""] + config["testing"]["coverage"] = {"enabled": False, "report_type": "report"} + + config["runtime"] = { + "entry_point": "", + "env_variables": ["[OPTIONAL]", "", ""], + "flags": ["[OPTIONAL]", "", ""], + "commands": { + "start": "", + "stop": "", + "restart": "", + }, + } + + if config.get("docker", {}).get("enabled", False): + config["docker"].update( + { + "dockerfile": "[REQUIRED] ", + "build_args": [ + "[OPTIONAL]", + "=", + "=", + ], + "image_name": "[REQUIRED] ", + "environment": [ + "[OPTIONAL]", + "=", + "=", + ], + "ports": ["[REQUIRED] :"], + } + ) + + if config.get("database", {}).get("enabled", False): + config["database"].update( + { + "version": "", + "host": "", + "port": "", + "dbname": "", + "migration_files": ["", ""], + } + ) + + if config.get("message_broker", {}).get("enabled", False): + config["message_broker"].update( + {"host": "", "port": ""} + ) + + config["cloud_deployment"] = config.get("cloud_deployment", {}) + config["cloud_deployment"].update( + { + "region": "", + "instance_type": "", + "autoscaling": { + "enabled": True, + "min_instances": "", + "max_instances": "", + }, + "load_balancer": {"enabled": True, "type": ""}, + } + ) + + config["post_deployment"] = { + "health_check": { + "url": "", + "interval": "", + }, + "notifications": { + "enabled": True, + "channels": ["", ""], + "slack": {"webhook_url": ""}, + "email": {"recipients": ["", ""]}, + }, + } + + return config + + +def rearrange_config_sections(config): + return { + "project": config["project"], + "environment": config["environment"], + "runtime": config["runtime"], + "testing": config["testing"], + "docker": config.get("docker", {"enabled": False}), + "kubernetes": config.get("kubernetes", {"enabled": False}), + "database": config.get("database", {"enabled": False}), + "message_broker": config.get("message_broker", {"enabled": False}), + "cloud_deployment": config["cloud_deployment"], + "post_deployment": config["post_deployment"], + } + + +def generate_config_file(config): + comments = """# CASED Configuration File +# +# This file contains the configuration for your project's DevOps processes. +# Please read the following instructions carefully before editing this file. +# +# Instructions: +# 1. Required fields are marked with [REQUIRED]. These must be filled out for the tool to function properly. +# 2. Optional fields are marked with [OPTIONAL]. Fill these out if they apply to your project. +# 3. Fields with default values are pre-filled. Modify them as needed. +# 4. Do not change the structure of this file (i.e., don't remove or rename sections). +# 5. Use quotes around string values, especially if they contain special characters. +# 6. For boolean values, use true or false (lowercase, no quotes). +# 7. For lists, maintain the dash (-) format for each item. +# +# Sections: +# - Project Metadata: Basic information about your project. All fields are required. +# - Environment Configuration: Specify your project's runtime environment. Python or Node version is required if applicable. +# - Testing Configuration: Set up your testing framework and files. Required if you want to run tests. +# - Application Runtime Configuration: Define how your application runs. The entry_point is required. +# - Docker Configuration: Required if you're using Docker. Set enabled to false if not using Docker. +# - Kubernetes Configuration: Optional. Set enabled to true if you're using Kubernetes. +# - Database Configuration: Optional. Fill out if your project uses a database. +# - Message Broker Configuration: Optional. Fill out if your project uses a message broker. +# - Cloud Deployment Configuration: Required if deploying to a cloud provider. +# - Post-Deployment Actions: Optional. Configure health checks and notifications. +# +# After editing this file, run 'cased build' to generate your GitHub Actions workflow. +# If you need help, refer to the documentation or run 'cased --help'. + + """ # noqa: E501 + os.makedirs(".cased", exist_ok=True) + with open(".cased/config.yaml", "w") as f: + f.write(f"{comments}\n") + for section, content in config.items(): + yaml.dump({section: content}, f, default_flow_style=False) + f.write("\n") # Add a blank line between sections + + +def generate_workflow_file(config): + workflow = generate_workflow_template() + with open(".cased/workflow.yaml", "w") as f: + for section, content in workflow.items(): + yaml.dump({section: content}, f, default_flow_style=False) + + +def generate_workflow_template(): + return { + "name": "CI/CD Workflow", + "on": {"push": "main"}, + "jobs": { + "build-and-test": { + "runs-on": "ubuntu-latest", + "steps": [ + {"name": "Checkout code", "uses": "actions/checkout@v2"}, + {"name": "Set up environment", "run": ""}, + {"name": "Run tests", "run": ""}, + ], + }, + "deploy": { + "needs": "build-and-test", + "runs-on": "ubuntu-latest", + "steps": [{"name": "Deploy", "run": ""}], + }, + }, + } + + +def display_results(config): + console.print( + Panel.fit("Configuration files created successfully!", style="bold green") + ) + console.print("Configuration file: [bold].cased/config.yaml[/bold]") + console.print("Workflow file: [bold].cased/workflow.yaml[/bold]") + + console.print("\n[bold yellow]Next steps:[/bold yellow]") + console.print("1. Review and edit the configuration files in the .cased directory.") + console.print( + "2. Replace all placeholder values (enclosed in < >) with your actual configuration." # noqa: E501 + ) + console.print( + "3. Once you've updated the config, run [bold]'cased build'[/bold] to generate your GitHub Actions workflow." # noqa: E501 + ) diff --git a/requirements.txt b/requirements.txt index 9cad651..3691fc4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,5 @@ click==8.1.3 rich==13.3.5 questionary==1.10.0 python-dateutil==2.9.0 +inquirer==2.7.0 +PyYAML==6.0 diff --git a/setup.py b/setup.py index 8d477ff..1d14995 100644 --- a/setup.py +++ b/setup.py @@ -20,6 +20,8 @@ "rich", "questionary", "python-dateutil", + "inquirer", + "PyYAML", ], entry_points={ "console_scripts": [ From c4490fa0574b3ce59d142f2b569605b38683386e Mon Sep 17 00:00:00 2001 From: Rick Chen Date: Sun, 8 Sep 2024 20:52:59 -0700 Subject: [PATCH 2/5] remove --- cased.egg-info/PKG-INFO | 82 ----------------------------- cased.egg-info/SOURCES.txt | 16 ------ cased.egg-info/dependency_links.txt | 0 cased.egg-info/entry_points.txt | 2 - cased.egg-info/requires.txt | 7 --- cased.egg-info/top_level.txt | 1 - 6 files changed, 108 deletions(-) delete mode 100644 cased.egg-info/PKG-INFO delete mode 100644 cased.egg-info/SOURCES.txt delete mode 100644 cased.egg-info/dependency_links.txt delete mode 100644 cased.egg-info/entry_points.txt delete mode 100644 cased.egg-info/requires.txt delete mode 100644 cased.egg-info/top_level.txt diff --git a/cased.egg-info/PKG-INFO b/cased.egg-info/PKG-INFO deleted file mode 100644 index 98761c1..0000000 --- a/cased.egg-info/PKG-INFO +++ /dev/null @@ -1,82 +0,0 @@ -Metadata-Version: 2.1 -Name: cased -Version: 0.1.0 -Summary: A CLI tool for managing deployments and branches -Home-page: https://github.com/cased/csd -Author: Cased -Author-email: cli@cased.com -Classifier: Programming Language :: Python :: 3 -Classifier: License :: OSI Approved :: MIT License -Classifier: Operating System :: OS Independent -Requires-Python: >=3.7 -Description-Content-Type: text/markdown -Requires-Dist: click -Requires-Dist: requests -Requires-Dist: rich -Requires-Dist: questionary -Requires-Dist: python-dateutil -Requires-Dist: inquirer -Requires-Dist: PyYAML - -# Cased CLI - -## Overview - -Cased CLI is a powerful command-line interface tool designed to streamline deployment processes and manage branches efficiently. It provides an intuitive way to interact with the Cased system, offering functionalities such as user authentication, deployment management, and branch oversight. - -## Features - -- User authentication (login/logout) -- View recent deployments -- Display active branches -- Interactive deployment process with branch and target selection -- Comprehensive help system - -## Installation - -You can install the Cased CLI tool using pip: - -``` -pip install cased -``` - -This will install the latest version of the tool from PyPI. - -For development installation: - -1. Clone the repository: - ``` - git clone https://github.com/cased/csd.git - cd csd - ``` - -2. Install in editable mode: - ``` - pip install -e . - ``` - -## Usage - -After installation, you can use the CLI tool by running `cased` followed by a command: - -``` -cased --help -``` - -### Available Commands: - -- `cased login`: Log in to the Cased system -- `cased logout`: Log out from the Cased system -- `cased deployments`: View recent deployments -- `cased branches`: View active branches -- `cased deploy`: Deploy a branch to a target environment - -For more details on each command, use: - -``` -cased COMMAND --help -``` - -## Contact - -For any questions or support, please contact cli@cased.com diff --git a/cased.egg-info/SOURCES.txt b/cased.egg-info/SOURCES.txt deleted file mode 100644 index 9e77cce..0000000 --- a/cased.egg-info/SOURCES.txt +++ /dev/null @@ -1,16 +0,0 @@ -README.md -setup.py -cased/__init__.py -cased/cli.py -cased.egg-info/PKG-INFO -cased.egg-info/SOURCES.txt -cased.egg-info/dependency_links.txt -cased.egg-info/entry_points.txt -cased.egg-info/requires.txt -cased.egg-info/top_level.txt -cased/commands/__init__.py -cased/commands/build.py -cased/commands/deploy.py -cased/commands/init.py -cased/commands/login.py -cased/commands/resources.py diff --git a/cased.egg-info/dependency_links.txt b/cased.egg-info/dependency_links.txt deleted file mode 100644 index e69de29..0000000 diff --git a/cased.egg-info/entry_points.txt b/cased.egg-info/entry_points.txt deleted file mode 100644 index f3a47a1..0000000 --- a/cased.egg-info/entry_points.txt +++ /dev/null @@ -1,2 +0,0 @@ -[console_scripts] -cased = cased.cli:cli diff --git a/cased.egg-info/requires.txt b/cased.egg-info/requires.txt deleted file mode 100644 index df245c7..0000000 --- a/cased.egg-info/requires.txt +++ /dev/null @@ -1,7 +0,0 @@ -click -requests -rich -questionary -python-dateutil -inquirer -PyYAML diff --git a/cased.egg-info/top_level.txt b/cased.egg-info/top_level.txt deleted file mode 100644 index 7408388..0000000 --- a/cased.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -cased From 42c17a436cb86d875ffbe14c2605da1d92bebcdd Mon Sep 17 00:00:00 2001 From: Rick Chen Date: Tue, 10 Sep 2024 16:26:01 -0700 Subject: [PATCH 3/5] build command --- .cased/config.yaml | 63 +++++++ .ci-pre-commit-config.yaml | 1 - cased/cli.py | 2 + cased/commands/build.py | 176 +++++++++++++++++++ cased/templates/docker_ec2_template.yaml | 83 +++++++++ cased/templates/non_docker_ec2_template.yaml | 52 ++++++ cased/utils/api.py | 29 +++ cased/utils/git.py | 22 +++ poetry.lock | 88 +++++++++- pyproject.toml | 1 + 10 files changed, 515 insertions(+), 2 deletions(-) create mode 100644 .cased/config.yaml create mode 100644 cased/commands/build.py create mode 100644 cased/templates/docker_ec2_template.yaml create mode 100644 cased/templates/non_docker_ec2_template.yaml create mode 100644 cased/utils/git.py diff --git a/.cased/config.yaml b/.cased/config.yaml new file mode 100644 index 0000000..127c333 --- /dev/null +++ b/.cased/config.yaml @@ -0,0 +1,63 @@ +# CASED Configuration File +# +# This file contains the configuration for your project's DevOps processes. +# Please read the following instructions carefully before editing this file. +# +# Instructions: +# 1. Required fields are marked with [REQUIRED]. These must be filled out for the tool to function properly. +# 2. Optional fields are marked with [OPTIONAL]. Fill these out if they apply to your project. +# 3. Fields with default values are pre-filled. Modify them as needed. +# 4. Do not change the structure of this file (i.e., don't remove or rename sections). +# 5. Use quotes around string values, especially if they contain special characters. +# 6. For boolean values, use true or false (lowercase, no quotes). +# 7. For lists, maintain the dash (-) format for each item. +# +# Sections: +# - Project Metadata: Basic information about your project. All fields are required. +# - Environment Configuration: Specify your project's runtime environment. Python or Node version is required if applicable. +# - Application Runtime Configuration: Define how your application runs. The entry_point is required. +# - Docker Configuration: Required if you're using Docker. Set enabled to false if not using Docker. +# - Cloud Deployment Configuration: Required if deploying to a cloud provider. +# +# After editing this file, run 'cased build' to generate your GitHub Actions workflow. +# If you need help, refer to the documentation or run 'cased --help'. + + +project: + description: <[OPTIONAL] PROJECT_DESCRIPTION> + name: bh + version: <[OPTIONAL] PROJECT_VERSION> + +environment: + dependency_files: + - <[OPTIONAL] Cased build will smart detect these files if not provided here> + dependency_manager: poetry + framework: Flask + language: Python + python_version: '[REQUIRED] ' + +runtime: + commands: + restart: + start: + stop: + entry_point: '[REQUIRED]' + flags: + - '[OPTIONAL]' + - + - + +docker: + enabled: false + +cloud_deployment: + autoscaling: + enabled: true + max_instances: + min_instances: + instance_type: + load_balancer: + enabled: true + type: + provider: AWS + region: diff --git a/.ci-pre-commit-config.yaml b/.ci-pre-commit-config.yaml index 7eb4e3f..9797595 100644 --- a/.ci-pre-commit-config.yaml +++ b/.ci-pre-commit-config.yaml @@ -2,7 +2,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.3.0 hooks: - - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace exclude: "^comet/core/migrations/.*" diff --git a/cased/cli.py b/cased/cli.py index 5e87a83..ec7c2b4 100644 --- a/cased/cli.py +++ b/cased/cli.py @@ -1,5 +1,6 @@ import click +from cased.commands.build import build from cased.commands.deploy import deploy from cased.commands.init import init from cased.commands.login import login, logout @@ -21,6 +22,7 @@ def cli(): cli.add_command(deploy) cli.add_command(init) +cli.add_command(build) cli.add_command(login) cli.add_command(logout) cli.add_command(deployments) diff --git a/cased/commands/build.py b/cased/commands/build.py new file mode 100644 index 0000000..fe0591c --- /dev/null +++ b/cased/commands/build.py @@ -0,0 +1,176 @@ +import os +import sys +from pathlib import Path +from typing import Any, Dict, List + +import click +import yaml +from jinja2 import Environment, FileSystemLoader +from rich.console import Console +from rich.panel import Panel +from rich.progress import Progress, SpinnerColumn, TextColumn + +from cased.utils.api import API_BASE_URL, create_secrets +from cased.utils.git import get_repo_name + +console = Console() +# Get the directory of the current script +CURRENT_DIR = Path(__file__).resolve().parent +# Go up one level to the 'cased' directory +CASED_DIR = CURRENT_DIR.parent +# Set the path to the templates directory +TEMPLATES_DIR = CASED_DIR / "templates" +CONFIG_PATH = ".cased/config.yaml" + + +@click.command() +def build() -> None: + """ + Generate a GitHub Actions workflow based on the configuration in .cased/config.yaml. + + This command reads the configuration file, validates it, generates a workflow file + in the .github/workflows directory, and sets up necessary secrets. + """ + if not os.path.exists(CONFIG_PATH): + console.print( + "[red]Error: Configuration file not found at .cased/config.yaml[/red]" + ) + console.print( + "Please run 'cased init' to generate the configuration file first." + ) + sys.exit(1) + + config = load_config(CONFIG_PATH) + + with Progress( + SpinnerColumn(), + TextColumn("[progress.description]{task.description}"), + console=console, + ) as progress: + validate_task = progress.add_task( + "[cyan]Validating configuration...", total=100 + ) + try: + validate_config(config) + progress.update( + validate_task, + completed=100, + description="[bold green]Configuration validated successfully!", + ) + except ValueError as e: + progress.update(validate_task, completed=100) + console.print(f"[red]Configuration validation failed: {str(e)}[/red]") + sys.exit(1) + + generate_task = progress.add_task("[cyan]Generating workflow...", total=100) + workflow_content = generate_workflow(config) + save_workflow(workflow_content) + progress.update( + generate_task, + completed=100, + description="[bold green]Workflow generated successfully!", + ) + + project_name = get_repo_name() + secrets = extract_secrets_from_workflow(workflow_content) + create_secrets(project_name, secrets) + + console.print( + Panel( + f""" + [bold green]GitHub Actions workflow generated successfully![/bold green] + [bold green]Please complete the following steps for the workflow to work correctly: [/bold green] + 1. Review the generated workflow file in .github/workflows/deploy.yaml + 2. Going to {API_BASE_URL}/secrets/{project_name} to update the secrets. + 3. Commit the changes to your repository, and the workflow will be triggered. + 4. Go to {API_BASE_URL}/deployments/ to monitor the deployment status. + """, # noqa: E501 + title="Success", + expand=False, + ) + ) + + +def load_config(file_path: str) -> Dict[str, Any]: + with open(file_path, "r") as file: + return yaml.safe_load(file) + + +def generate_workflow(config: Dict[str, Any]) -> str: + env = Environment(loader=FileSystemLoader(TEMPLATES_DIR)) + + if config["docker"]["enabled"]: + template = env.get_template("docker_ec2_template.yaml") + else: + template = env.get_template("non_docker_ec2_template.yaml") + + return template.render(config=config) + + +def save_workflow(content: str) -> None: + os.makedirs(".github/workflows", exist_ok=True) + with open(".github/workflows/deploy.yaml", "w") as file: + file.write(content) + + +def extract_secrets_from_workflow(workflow_content: str) -> List[str]: + secrets = [] + for line in workflow_content.split("\n"): + if "secrets." in line: + secret = line.split("secrets.")[1].split("}")[0].strip() + if secret not in secrets: + secrets.append(secret) + return secrets + + +def validate_config(config: Dict[str, Any]) -> None: + # Project validation + if "project" not in config: + raise ValueError("Missing 'project' section in config") + if "name" not in config["project"]: + raise ValueError("Missing 'name' in 'project' section") + + # Environment validation + if "environment" not in config: + raise ValueError("Missing 'environment' section in config") + required_env_fields = ["language", "python_version"] + for field in required_env_fields: + if field not in config["environment"]: + raise ValueError(f"Missing '{field}' in 'environment' section") + + # Docker validation + if "docker" not in config: + raise ValueError("Missing 'docker' section in config") + if "enabled" not in config["docker"]: + raise ValueError("Missing 'enabled' field in 'docker' section") + + if config["docker"]["enabled"]: + required_docker_fields = [ + "ECR Repository Name", + "dockerfile_path", + "image_name", + ] + for field in required_docker_fields: + if field not in config["docker"]: + raise ValueError(f"Missing '{field}' in 'docker' section") + + if not isinstance(config["docker"].get("ports", []), list): + raise ValueError("'ports' in 'docker' section must be a list") + + if "environment" in config["docker"] and not isinstance( + config["docker"]["environment"], list + ): + raise ValueError("'environment' in 'docker' section must be a list") + else: + # Non-docker validation + if "runtime" not in config: + raise ValueError("Missing 'runtime' section in config for non-docker setup") + required_runtime_fields = ["commands", "entry_point"] + for field in required_runtime_fields: + if field not in config["runtime"]: + raise ValueError(f"Missing '{field}' in 'runtime' section") + + required_commands = ["start", "stop", "restart"] + for command in required_commands: + if command not in config["runtime"]["commands"]: + raise ValueError(f"Missing '{command}' in 'runtime.commands' section") diff --git a/cased/templates/docker_ec2_template.yaml b/cased/templates/docker_ec2_template.yaml new file mode 100644 index 0000000..79aace3 --- /dev/null +++ b/cased/templates/docker_ec2_template.yaml @@ -0,0 +1,83 @@ +name: Deploy to EC2 + +on: + push: + branches: + - main + workflow_dispatch: + inputs: + branch: + description: 'Branch to deploy' + required: true + default: 'main' + target_name: + description: 'Target name' + required: true + default: 'prod' + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: {% raw %}${{ secrets.AWS_ACCESS_KEY_ID }}{% endraw %} + aws-secret-access-key: {% raw %}${{ secrets.AWS_SECRET_ACCESS_KEY }}{% endraw %} + aws-region: {% raw %}${{ secrets.AWS_REGION }}{% endraw %} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push image to Amazon ECR + env: + ECR_REGISTRY: {% raw %}${{ steps.login-ecr.outputs.registry }}{% endraw %} + ECR_REPOSITORY: {% raw %}${{ secrets.ECR_REPOSITORY }}{% endraw %} + IMAGE_TAG: {% raw %}${{ github.sha }}{% endraw %} + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG {{ config.docker.dockerfile_path }} + {% if config.docker.build_args %} + {% for arg in config.docker.build_args %} + --build-arg {{ arg }} \ + {% endfor %} + {% endif %} + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + + - name: Deploy to EC2 + env: + PRIVATE_KEY: {% raw %}${{ secrets.EC2_SSH_PRIVATE_KEY }}{% endraw %} + ECR_REGISTRY: {% raw %}${{ steps.login-ecr.outputs.registry }}{% endraw %} + EC2_PUBLIC_IP: {% raw %}${{ secrets.EC2_PUBLIC_IP }}{% endraw %} + ECR_REPOSITORY: {% raw %}${{ secrets.ECR_REPOSITORY }}{% endraw %} + IMAGE_TAG: {% raw %}${{ github.sha }}{% endraw %} + AWS_ACCOUNT_NUMBER: {% raw %}${{ secrets.AWS_ACCOUNT_NUMBER }}{% endraw %} + AWS_ACCESS_KEY_ID: {% raw %}${{ secrets.AWS_ACCESS_KEY_ID }}{% endraw %} + AWS_SECRET_ACCESS_KEY: {% raw %}${{ secrets.AWS_SECRET_ACCESS_KEY }}{% endraw %} + AWS_REGION: {% raw %}${{ secrets.AWS_REGION }}{% endraw %} + run: | + echo "$PRIVATE_KEY" > private_key && chmod 600 private_key + ssh -o StrictHostKeyChecking=no -i private_key $EC2_PUBLIC_IP " + export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID + export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY + aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_NUMBER.dkr.ecr.$AWS_REGION.amazonaws.com + docker pull $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + docker stop {{ config.docker.image_name }} || true + docker rm {{ config.docker.image_name }} || true + docker run -d --name {{ config.docker.image_name }} \ + {% if config.docker.ports %} + {% for port in config.docker.ports %} + -p {{ port }} \ + {% endfor %} + {% endif %} + {% if config.docker.environment %} + {% for env in config.docker.environment %} + -e {{ env }} \ + {% endfor %} + {% endif %} + $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + " diff --git a/cased/templates/non_docker_ec2_template.yaml b/cased/templates/non_docker_ec2_template.yaml new file mode 100644 index 0000000..10ebf33 --- /dev/null +++ b/cased/templates/non_docker_ec2_template.yaml @@ -0,0 +1,52 @@ +name: Deploy to EC2 + +on: + push: + branches: + - main + workflow_dispatch: + inputs: + branch: + description: 'Branch to deploy' + required: true + default: 'main' + target_name: + description: 'Target name' + required: true + default: 'prod' + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '{{ config.environment.python_version }}' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install {{ config.environment.dependency_manager }} + {{ config.environment.dependency_manager }} install + + - name: Run tests + run: | + # Add your test commands here + + - name: Deploy to EC2 + env: + PRIVATE_KEY: {% raw %}${{ secrets.EC2_SSH_PRIVATE_KEY }}{% endraw %} + EC2_PUBLIC_IP: {% raw %}${{ secrets.EC2_PUBLIC_IP }}{% endraw %} + run: | + echo "$PRIVATE_KEY" > private_key && chmod 600 private_key + scp -i private_key -o StrictHostKeyChecking=no -r ./* ec2-user@$EC2_PUBLIC_IP:~/app + ssh -i private_key -o StrictHostKeyChecking=no ec2-user@$EC2_PUBLIC_IP ' + cd ~/app + {{ config.runtime.commands.stop }} + {{ config.runtime.commands.start }} + ' diff --git a/cased/utils/api.py b/cased/utils/api.py index c63aaa8..15d3178 100644 --- a/cased/utils/api.py +++ b/cased/utils/api.py @@ -2,6 +2,9 @@ import click import requests +from rich.console import Console + +console = Console() API_BASE_URL = os.environ.get( "CASED_API_BASE_URL", default="https://app.cased.com/api/v1" @@ -58,3 +61,29 @@ def deploy_branch(branch_name, target_name): ) else: click.echo("Deployment failed. Please check your input and try again.") + + +def create_secrets(project_name: str, secrets: list): + payload = { + "storage_destination": "github_repository", + "keys": [{"name": secret, "type": "credentials"} for secret in secrets], + } + response = requests.post( + f"{API_BASE_URL}/api/v1/secrets/{project_name}/setup", + json=payload, + headers=REQUEST_HEADERS, + ) + if response.status_code == 201: + console.print("[green]Secrets setup successful![/green]") + console.print( + f"Please go to {API_BASE_URL}/secrets/{project_name} to update these secrets." # noqa: E501 + ) + else: + console.print( + f"[yellow]Secrets setup returned status code {response.status_code}.[/yellow]" # noqa: E501 + ) + console.print( + "Please go to your GitHub repository settings to manually set up the following secrets:" # noqa: E501 + ) + for secret in secrets: + console.print(f"- {secret}") diff --git a/cased/utils/git.py b/cased/utils/git.py new file mode 100644 index 0000000..2dcaed4 --- /dev/null +++ b/cased/utils/git.py @@ -0,0 +1,22 @@ +import subprocess + +from rich.console import Console + +console = Console() + + +def get_repo_name() -> str: + try: + result = subprocess.run( + ["git", "config", "--get", "remote.origin.url"], + capture_output=True, + text=True, + check=True, + ) + repo_url = result.stdout.strip() + return repo_url.split("/")[-1].replace(".git", "") + except subprocess.CalledProcessError: + console.print( + "[bold yellow]Warning: Unable to get repository name from git. Using 'unknown' as project name.[/bold yellow]" # noqa: E501 + ) + return "unknown" diff --git a/poetry.lock b/poetry.lock index bded471..7fb981f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -270,6 +270,23 @@ files = [ [package.extras] colors = ["colorama (>=0.4.6)"] +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + [[package]] name = "jinxed" version = "1.3.0" @@ -308,6 +325,75 @@ profiling = ["gprof2dot"] rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -650,4 +736,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "aa0bcf96414d78990f61be05e64394c7fdac45f4aee1d3572bf5415e20cff92b" +content-hash = "c2f24afce3179803aa334bbc58e86bdd374a3490ce83bc1eccaa4b2f13a6756c" diff --git a/pyproject.toml b/pyproject.toml index 9b6e0d8..2a3e987 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ questionary = "*" python-dateutil = "*" pyyaml = "^6.0.2" inquirer = "^3.4.0" +jinja2 = "^3.1.4" [tool.poetry.scripts] cased = "cased.cli:cli" From d098b62a92a3cc0b6ee1725273447869b3227679 Mon Sep 17 00:00:00 2001 From: Rick Chen Date: Tue, 10 Sep 2024 16:27:55 -0700 Subject: [PATCH 4/5] remove --- .cased/config.yaml | 63 ---------------------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 .cased/config.yaml diff --git a/.cased/config.yaml b/.cased/config.yaml deleted file mode 100644 index 127c333..0000000 --- a/.cased/config.yaml +++ /dev/null @@ -1,63 +0,0 @@ -# CASED Configuration File -# -# This file contains the configuration for your project's DevOps processes. -# Please read the following instructions carefully before editing this file. -# -# Instructions: -# 1. Required fields are marked with [REQUIRED]. These must be filled out for the tool to function properly. -# 2. Optional fields are marked with [OPTIONAL]. Fill these out if they apply to your project. -# 3. Fields with default values are pre-filled. Modify them as needed. -# 4. Do not change the structure of this file (i.e., don't remove or rename sections). -# 5. Use quotes around string values, especially if they contain special characters. -# 6. For boolean values, use true or false (lowercase, no quotes). -# 7. For lists, maintain the dash (-) format for each item. -# -# Sections: -# - Project Metadata: Basic information about your project. All fields are required. -# - Environment Configuration: Specify your project's runtime environment. Python or Node version is required if applicable. -# - Application Runtime Configuration: Define how your application runs. The entry_point is required. -# - Docker Configuration: Required if you're using Docker. Set enabled to false if not using Docker. -# - Cloud Deployment Configuration: Required if deploying to a cloud provider. -# -# After editing this file, run 'cased build' to generate your GitHub Actions workflow. -# If you need help, refer to the documentation or run 'cased --help'. - - -project: - description: <[OPTIONAL] PROJECT_DESCRIPTION> - name: bh - version: <[OPTIONAL] PROJECT_VERSION> - -environment: - dependency_files: - - <[OPTIONAL] Cased build will smart detect these files if not provided here> - dependency_manager: poetry - framework: Flask - language: Python - python_version: '[REQUIRED] ' - -runtime: - commands: - restart: - start: - stop: - entry_point: '[REQUIRED]' - flags: - - '[OPTIONAL]' - - - - - -docker: - enabled: false - -cloud_deployment: - autoscaling: - enabled: true - max_instances: - min_instances: - instance_type: - load_balancer: - enabled: true - type: - provider: AWS - region: From 554ebbad438af2a0fc728ef0a5b85d2eb44c67f1 Mon Sep 17 00:00:00 2001 From: Rick Chen Date: Tue, 10 Sep 2024 16:31:59 -0700 Subject: [PATCH 5/5] go --- cased/commands/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cased/commands/build.py b/cased/commands/build.py index fe0591c..9adf411 100644 --- a/cased/commands/build.py +++ b/cased/commands/build.py @@ -81,7 +81,7 @@ def build() -> None: [bold green]GitHub Actions workflow generated successfully![/bold green] [bold green]Please complete the following steps for the workflow to work correctly: [/bold green] 1. Review the generated workflow file in .github/workflows/deploy.yaml - 2. Going to {API_BASE_URL}/secrets/{project_name} to update the secrets. + 2. Go to {API_BASE_URL}/secrets/{project_name} to update the secrets. 3. Commit the changes to your repository, and the workflow will be triggered. 4. Go to {API_BASE_URL}/deployments/ to monitor the deployment status. """, # noqa: E501