-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 2b1ce68
Showing
24 changed files
with
2,402 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
repos: | ||
- repo: https://github.com/pre-commit/pre-commit-hooks | ||
rev: v2.3.0 | ||
hooks: | ||
- id: end-of-file-fixer | ||
- id: trailing-whitespace | ||
|
||
- repo: https://github.com/astral-sh/ruff-pre-commit | ||
rev: v0.0.291 | ||
hooks: | ||
- id: ruff | ||
args: [--line-length=100] | ||
|
||
- repo: https://github.com/pycqa/isort | ||
rev: 5.12.0 | ||
hooks: | ||
- id: isort |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
name: pre-commit | ||
|
||
on: | ||
pull_request: | ||
push: | ||
branches: [main] | ||
|
||
jobs: | ||
pre-commit: | ||
runs-on: blacksmith-4vcpu-ubuntu-2204 | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: useblacksmith/setup-python@v6 | ||
- uses: pre-commit/[email protected] | ||
with: | ||
extra_args: --all-files --config .ci-pre-commit-config.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# .gitignore | ||
_pycache_/ | ||
__pycache__/ | ||
.egg-info/ | ||
env/ | ||
.env/ | ||
venv/ | ||
.DS_STORE | ||
.pre-commit-config.yaml | ||
.pem |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# 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. | ||
|
||
## 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 | ||
``` | ||
|
||
## Development | ||
|
||
To set up the Cased CLI for development: | ||
|
||
1. Clone the repository: | ||
``` | ||
git clone https://github.com/cased/cli.git | ||
cd cli | ||
``` | ||
|
||
2. Install Poetry (if not already installed): | ||
``` | ||
pip install poetry | ||
``` | ||
|
||
3. Install dependencies: | ||
``` | ||
poetry install | ||
``` | ||
|
||
4. Activate the virtual environment: | ||
``` | ||
poetry shell | ||
``` | ||
|
||
5. Run the CLI in development mode: | ||
``` | ||
poetry run cased | ||
``` | ||
|
||
### Making Changes | ||
|
||
1. Make your changes to the codebase. | ||
2. Update tests if necessary. | ||
3. Run tests: | ||
``` | ||
poetry run pytest | ||
``` | ||
4. Build the package: | ||
``` | ||
poetry build | ||
``` | ||
|
||
### Submitting Changes | ||
|
||
1. Create a new branch for your changes: | ||
``` | ||
git checkout -b feature/your-feature-name | ||
``` | ||
2. Commit your changes: | ||
``` | ||
git commit -am "Add your commit message" | ||
``` | ||
3. Push to your branch: | ||
``` | ||
git push origin feature/your-feature-name | ||
``` | ||
4. Create a pull request on GitHub. | ||
|
||
## Contact | ||
|
||
For any questions or support, please contact [email protected] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"editor.formatOnSave": true, | ||
"editor.formatOnType": true, | ||
"editor.codeActionsOnSave": { | ||
"source.fixAll": "explicit" | ||
}, | ||
"[python]": { | ||
"editor.defaultFormatter": "ms-python.black-formatter", | ||
"editor.trimAutoWhitespace": true, | ||
"editor.formatOnSave": true, | ||
"editor.formatOnType": true | ||
}, | ||
"files.trimTrailingWhitespace": true, | ||
"[html][django-html][handlebars][hbs][mustache][jinja][jinja-html][nj][njk][nunjucks][twig]": { | ||
"editor.defaultFormatter": "monosans.djlint" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
__version__ = "0.1.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
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, projects, targets | ||
|
||
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) | ||
|
||
|
||
@click.group() | ||
def cli(): | ||
""" | ||
Cased CLI for authentication, target setup, and branch deployment. | ||
Use 'cased COMMAND --help' for more information on a specific command. | ||
""" | ||
|
||
pass | ||
|
||
|
||
cli.add_command(deploy) | ||
cli.add_command(init) | ||
cli.add_command(build) | ||
cli.add_command(login) | ||
cli.add_command(logout) | ||
cli.add_command(deployments) | ||
cli.add_command(branches) | ||
cli.add_command(projects) | ||
cli.add_command(targets) | ||
|
||
|
||
if __name__ == "__main__": | ||
try: | ||
cli() | ||
except Exception as _: | ||
click.echo("\nProcess interrupted. Exiting.") |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
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 CasedAPI | ||
from cased.utils.constants import CasedConstants | ||
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) | ||
CasedAPI().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. Go to {CasedConstants.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 {CasedConstants.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") |
Oops, something went wrong.